generate

package
v0.17.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package generate renders systemd units from dotdrift configuration.

Index

Constants

View Source
const (
	// KindVolume is a local block-device filesystem (e.g. btrfs, ntfs3).
	KindVolume = "volume"
	// KindNetwork is a remote export filesystem (e.g. nfs, cifs).
	KindNetwork = "network"
)

Filesystem kinds.

View Source
const (
	// LayerBase writes to <root>/modules/<id>.
	LayerBase = "base"
	// LayerHost writes to <root>/hosts/<hostname>/modules/<id>.
	LayerHost = "host"
	// LayerUser writes to <root>/users/<username>/modules/<id>.
	LayerUser = "user"
)

Layer names for Selection.Layer.

View Source
const OverrideEnvVar = "DOTDRIFT_GENERATE_REGISTRY"

OverrideEnvVar overrides the user registry path; used by tests.

Variables

View Source
var ErrNoRecommendation = errors.New("no recommended filesystem entry")

ErrNoRecommendation is returned by Recommend when no entry matches the requested family.

View Source
var KernelRelease = facts.KernelRelease

KernelRelease returns the running kernel release (`uname -r`). It aliases facts.KernelRelease (the one implementation); kept as a package-level var so existing tests can stub the lookup.

Functions

func EscapePath

func EscapePath(s string) string

EscapePath mirrors `systemd-escape --path` for mount-unit naming.

Behavior (verified against systemd 258):

  • The path is normalized: empty and "." components are dropped, ".." pops the previous component (or is dropped at the root).
  • The resulting components are joined with "-".
  • An empty result (e.g. "/" or "") yields "-".
  • Bytes outside [A-Za-z0-9:_.] are escaped as \xNN (UTF-8 bytes); a literal "-" becomes \x2d, and a "." as the very first character of the result is escaped as \x2e.

Unlike the real binary, EscapePath cannot fail: a non-normalized path such as "/a/../b" (which systemd-escape rejects) is normalized to "b".

func ModuleDir

func ModuleDir(root string, sel Selection) (string, error)

ModuleDir resolves the target directory for the selection without writing. Exported for the generate wizard (internal/tui), which locates the target module to mark already-managed volumes.

func RenderMountUnit

func RenderMountUnit(name string, spec profile.MountSpec, preset Entry, uid, gid int) string

RenderMountUnit renders the systemd .mount unit content for the mount named name. The unit's file name is derived by the caller as EscapePath(spec.Destination)+".mount" (systemd requires mount units to be named after their mount point).

A single "After=<deps>" line listing preset.After space-separated is emitted only when the preset declares dependencies (network presets; the reference script set AFTER only for nfs). Options resolve to spec.Options when non-empty, else preset.Options, with the bare tokens "uid"/"gid" expanded to uid=<uid>/gid=<gid> (see generate.go) and joined with commas.

func RenderServiceUnit

func RenderServiceUnit(unitName string) string

RenderServiceUnit renders the oneshot .service wrapper that starts and stops the mount unit unitName (e.g. "mnt-synology-syn01.mount") and remains active so the mount survives the one-shot start. The caller writes the content to the same stem with a .service suffix.

func RenderSharesConf

func RenderSharesConf(smb profile.SmbSpec) string

RenderSharesConf renders a Samba shares.conf with one [name] stanza per declared share, sorted by share name for byte-deterministic output and terminated by a single trailing newline. An empty Comment falls back to the share name (the reference script wrote "comment = $NAME"); an empty ValidUsers falls back to @<group> with the group defaulting to "smb" (the reference's SMB_DEFAULT_GROUP). An smb spec without shares renders empty.

func RenderTimerUnit

func RenderTimerUnit(name, startAt, unitName string) string

RenderTimerUnit renders the .timer unit that activates the service unitName (e.g. "mnt-synology-syn01.service") on the OnCalendar expression startAt for the mount named name. Render it only when the mount's StartAt is non-empty — the caller decides and writes the content to the same stem with a .timer suffix so systemd pairs it with the service unit.

The Description references unitName rather than "<name>.service": dotdrift names service units by escaped destination stem, so the reference template's $NAME slot corresponds to the activated service unit — using the mount name there would point at a unit that does not exist. name identifies the mount for the caller (file naming, logging); the unit content carries only the service reference.

func WriteModule

func WriteModule(root string, sel Selection, input Input) error

WriteModule materializes the module selected by sel under root from input. It validates the selection and every mount's filesystem type against the registry before writing anything, so an invalid input never leaves a half-written module behind.

Types

type Entry

type Entry struct {
	// Type is the filesystem type key (e.g. "nfs", "ntfs3").
	Type string `toml:"-"`
	// Kind is KindVolume or KindNetwork.
	Kind string `toml:"kind"`
	// Family groups alternative drivers (e.g. "ntfs" for ntfs3/ntfs-3g);
	// empty means the type is its own family. When non-empty it is also
	// the Type= the rendered .mount unit announces — the kernel/mount
	// helper selects the driver at mount time, so the unit names the
	// generic type instead of pinning a driver.
	Family string `toml:"family"`
	// Options is the mount-option preset. The bare tokens "uid" and "gid"
	// are placeholders substituted at render time.
	Options []string `toml:"options"`
	// After lists systemd unit dependencies for the generated mount unit.
	After []string `toml:"after"`
	// RecommendedIf gates the entry behind a kernel comparison
	// ("kernel <op> <version>"); empty means always eligible.
	RecommendedIf string `toml:"recommended_if"`
	// Packages are the distro packages the filesystem type needs.
	Packages []string `toml:"packages"`
	// Absent lists distro packages to remove when the type is used (e.g.
	// the ntfs3 preset removes ntfs-3g on kernel >= 7.2, mirroring the
	// legacy script's cleanup).
	Absent []string `toml:"absent"`
}

Entry is one filesystem preset from the registry.

type Input

type Input struct {
	// Mounts replaces the module's [mounts] section wholesale when
	// non-nil; nil means mounts are not managed by this run (existing
	// mounts, unit files, and their dotfile entries are left untouched).
	Mounts map[string]profile.MountSpec
	// Smb, when non-nil, replaces the module's [smb] section wholesale and
	// (re)generates shares.conf. Nil means "smb is not managed by this
	// run": an existing smb section, shares.conf, and smb.conf are all
	// left untouched.
	Smb *profile.SmbSpec
	// UID and GID expand the bare "uid"/"gid" option tokens.
	UID int
	GID int
}

Input carries the resolved specs and the uid/gid used for mount-option token expansion (the caller resolves them via os/user).

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the merged filesystem preset registry: the embedded presets plus any user override entries.

func Load

func Load(pathOverride ...string) (*Registry, error)

Load loads the embedded registry and merges the user override file whole-entry by filesystem type. pathOverride, when given, wins over the DOTDRIFT_GENERATE_REGISTRY env var, which wins over the default ~/.config/dotdrift/generate.toml. A missing override file is not an error; a malformed one is, and the error names its path.

func (*Registry) Entries

func (r *Registry) Entries() []Entry

Entries returns all presets, sorted by type for deterministic output.

func (*Registry) Entry

func (r *Registry) Entry(typ string) (Entry, bool)

Entry returns the preset for a filesystem type.

func (*Registry) Recommend

func (r *Registry) Recommend(family string) (Entry, error)

Recommend returns the recommended preset for a filesystem family, evaluating each candidate's recommended_if against KernelRelease. Candidates are considered in type-name order; an entry with an empty recommended_if is always eligible.

type Selection

type Selection struct {
	// Layer is LayerBase, LayerHost, or LayerUser.
	Layer string
	// Hostname is required for LayerHost (the hosts/<hostname> directory).
	Hostname string
	// Username is required for LayerUser (the users/<username> directory).
	Username string
	// ModuleID is the module directory name.
	ModuleID string
}

Selection names the module and the layer to write it at.

type Volume

type Volume struct {
	// UUID is the filesystem UUID, usable as a UUID= mount source.
	UUID string
	// FSType is the filesystem type as lsblk reports it (e.g. "btrfs",
	// "ntfs"), not necessarily a registry type key.
	FSType string
	// Label is the filesystem label; empty when unset.
	Label string
	// Size is lsblk's human-readable size (e.g. "949.9G").
	Size string
	// Mountpoints lists current mountpoints in lsblk order; empty when
	// the volume is not mounted.
	Mountpoints []string
	// Managed reports whether the volume's UUID already appears as a
	// UUID=<uuid> source in the existing mount sources passed to Volumes.
	Managed bool
}

Volume is one local block-device filesystem discovered via lsblk.

func Volumes

func Volumes(ctx context.Context, reg *Registry, sources []string) ([]Volume, error)

Volumes lists local volumes discovered via lsblk, filtered to partitions (TYPE=part) with a non-empty UUID whose filesystem type the registry classifies as KindVolume — by entry type (btrfs) or by entry family (lsblk "ntfs" matches the ntfs family of ntfs3/ntfs-3g). Network-kind filesystems (nfs, cifs) and unknown types are excluded. sources marks already-managed volumes: an entry of the form "UUID=<uuid>" sets Managed on the matching volume. The result is sorted by UUID for deterministic output.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL