Documentation
¶
Overview ¶
Package wsl builds WSL 1-compatible rootfs tarballs (.wsl) from Docker images. A Recipe describes the distro (Dockerfile, build args, context files); BuildTarball turns it into a cached tarball that ships on the answer volume as distro.wsl and is imported by the first-logon bootstrap.
WSL 1 on purpose: it shares the Windows network stack and filesystem drivers (drvfs), needs no nested virtualization, and — after CELL-532 — can stat/read the host project share through the WinFsp fixed-disk mount.
Index ¶
- Constants
- Variables
- func BuildTarball(ctx context.Context, cacheDir string, r Recipe, noCache bool, ...) (string, error)
- func DirShareService(shares []sftpshare.DirShare) (s6.Service, error)
- func IsFlakeRef(nixHome string) bool
- func MountScript(share sftpshare.DirShare) (string, error)
- func MountScriptMulti(shares []sftpshare.DirShare) (string, error)
- func RcloneMountService(host string, port int, user, password, drive, volname string) (s6.Service, error)
- func S6HealthcheckService() s6.Service
- func SSHDBaseService() s6.Service
- func SSHDNixService() s6.Service
- type Distro
- type NixHomeOpts
- type Recipe
Constants ¶
const ServicePrefix = "s6/"
ServicePrefix is the reserved ContextFiles prefix for s6 service dirs; the Dockerfile templates COPY it to /etc/s6/services, the scan dir of the s6-svscan loop the Windows bootstrap starts via /bin/s6-init.
const VolumeName = "distro.wsl"
VolumeName is the filename the bootstrap's drive scan looks for.
Variables ¶
var DefaultFlakeNix string
DefaultFlakeNix is the embedded home-manager flake, written into the docker build context when no external nixhome is given. @USER@ is substituted with the distro user at context-write time (flake outputs are attribute sets, so the username cannot be a runtime variable) — custom placeholders, not Go template syntax, so the files stay valid nix for editors and `nix fmt`.
var DefaultHomeNix string
DefaultHomeNix is the embedded home-manager module: the shell UX that used to be imperative .bashrc appends (completion, PS1, nix on PATH for non-login shells), now owned by home-manager. @DISTRO@ becomes the distro name in the prompt.
Functions ¶
func BuildTarball ¶
func BuildTarball(ctx context.Context, cacheDir string, r Recipe, noCache bool, logf func(string, ...any)) (string, error)
BuildTarball produces the .wsl rootfs tarball via docker build + export, caching the result in cacheDir. Requires docker on PATH (callers should degrade gracefully when it is absent). Several minutes cold; free warm.
func DirShareService ¶
DirShareService generates a service that (re)applies the host dir-share symlinks every boot: the mount script runs, then the service parks so s6-supervise does not respawn it in a loop.
func IsFlakeRef ¶
IsFlakeRef reports whether a nixhome value is a remote flake reference (passed to nix as-is) rather than a local directory (copied into the docker build context).
func MountScript ¶
MountScript returns a shell script that creates parent directories and symlinks vmPath to the WinFsp drive's WSL1 mount point (/mnt/<drive>). The script is idempotent: re-running it replaces an existing symlink.
func MountScriptMulti ¶
MountScriptMulti returns a shell script that mounts all shares.
func RcloneMountService ¶
func RcloneMountService(host string, port int, user, password, drive, volname string) (s6.Service, error)
RcloneMountService supervises the Windows rclone.exe SFTP mount from the distro's s6 loop via WSL1 interop, replacing the winkit-rclone-mount scheduled task. The Windows side still owns the mount machinery (WinFsp, rclone.exe installed by the bootstrap); s6 owns process lifetime. The run script is the embedded templates/s6/common/rclone-mount module with the build-time share parameters substituted in.
func S6HealthcheckService ¶
S6HealthcheckService polls all sibling s6 services and the W: drive mount, emitting JSONL to stdout every 30 seconds. When the s6 supervisor runs under winkit-service with --log-serial, these events reach the host structured log (build.jsonl).
func SSHDBaseService ¶
SSHDBaseService is the in-distro sshd for arbitrary docker bases: the run script tolerates sshd living outside PATH (/usr/sbin on debian-family bases). Port 2223 gives real PTY sessions where Windows OpenSSH hands WSL pipes.
func SSHDNixService ¶
SSHDNixService is the sshd variant for the nix rootfs: sshd is on the nix profile PATH set by /bin/s6-init, and -f pins the config since nix's compiled-in default path differs.
Types ¶
type Distro ¶
type Distro struct {
// Image is the config value this resolved from ("nix", "alpine", the
// URL, or the path) — used in logs and errors.
Image string
Recipe *Recipe
URL string
Path string
// VerifyCommand / VerifyContains: see Recipe. External images get a
// generic liveness check — their content is the publisher's contract.
VerifyCommand string
VerifyContains string
}
Distro is a resolvable WSL rootfs source: a docker-built recipe (nix, alpine), a URL to a published image (e.g. Canonical's .wsl builds), or a local tarball path. Exactly one of Recipe/URL/Path is set.
func DistroFor ¶
func DistroFor(image, user, distroName, nixHome string, nixOpts *NixHomeOpts) (Distro, error)
DistroFor resolves a config image value to its source. nixHome is only meaningful for the nix image; other sources ignore it.
Accepted forms:
- "" or any docker ref — docker-built from that base with the universal plumbing template (alpine is the default; ubuntu:24.04, ghcr.io/org/img:tag, …)
- "nix" — docker-built Nix + home-manager rootfs (the one distro with its own template + recipe logic)
- "https://…/x.wsl" — published image, downloaded and cached; the publisher owns the WSL plumbing (wsl.conf, default user, …)
- "./x.wsl" — local tarball, shipped verbatim
func (*Distro) AddService ¶
AddService registers an s6 service on a docker-built distro. URL and local-tarball distros are prebuilt — baking services in is impossible — so it errors rather than silently dropping them.
func (Distro) Materialize ¶
func (d Distro) Materialize(ctx context.Context, cacheDir string, noCache bool, logf func(string, ...any)) (string, error)
Materialize produces the rootfs tarball and returns its path: a docker build for recipes, a cached download for URLs, the file itself for local paths.
func (Distro) NeedsDocker ¶
NeedsDocker reports whether materializing this distro runs docker. Callers degrade gracefully (skip the preload) when docker is absent — but only for docker-built sources; URLs and local files never need it.
type NixHomeOpts ¶
type NixHomeOpts struct {
// Attr is the homeConfigurations attribute name. Empty defaults to the
// WSL_USER build arg (the distro user), which is what the embedded
// flake uses. External flakes (community-home) use a naming scheme
// like "wsl-<stack>-<arch>".
Attr string
// User is the username the external flake's config was built for
// (e.g. "nixos"). Empty means it matches the WSL distro user and no
// path redirection is needed.
User string
}
NixHomeOpts controls external-flake attribute resolution for NixRecipe. When a non-embedded nixhome flake uses a different homeConfigurations attribute name (e.g. "wsl-ultimate-aarch64") or a different user than the WSL distro user, set these so the Dockerfile's activation finds the right attribute and redirects paths via symlinks.
type Recipe ¶
type Recipe struct {
// Image is the recipe selector name ("nix", "alpine"); it keys the
// cache filename and log lines.
Image string
// User is the distro's default user, matched to the Windows account.
User string
// DistroName is the WSL registration name.
DistroName string
// Dockerfile is the full rootfs recipe.
Dockerfile string
// BuildArgs are extra --build-arg values (WSL_USER is set from User
// automatically).
BuildArgs map[string]string
// ContextFiles are extra files written into the docker build context,
// keyed by relative path (subdirectories allowed).
ContextFiles map[string][]byte
// Fingerprint is extra cache-key material for inputs not captured
// above (e.g. a remote flake ref that cannot be content-hashed).
Fingerprint string
// VerifyCommand is a /bin/sh -lc command run inside the imported
// distro after the build; VerifyContains must appear in its output.
VerifyCommand string
VerifyContains string
}
Recipe describes a WSL rootfs built from a Dockerfile. Everything that shapes the produced tarball must be in here: the cache key is derived from the recipe content, so any change invalidates the cached build.
func BaseRecipe ¶
BaseRecipe builds a WSL rootfs from an arbitrary docker image ref (ubuntu:24.04, ghcr.io/org/img:tag, …) using the universal plumbing template (templates/Dockerfile.base.tmpl): wsl.conf, fstab, login-shell wrapper always; packages, user, sudo, and in-distro sshd best-effort per the base's package manager.
func NixRecipe ¶
func NixRecipe(user, distroName, nixHome string, opts *NixHomeOpts) (Recipe, error)
NixRecipe builds the nix image recipe. nixHome selects the home-manager configuration baked into the rootfs: empty for the embedded default flake, a local directory (must contain a flake exposing homeConfigurations.<user>), or a remote flake ref (git+https://…, github:owner/repo, …). The CLI feeds this from --nixhome / WINKIT_NIXHOME; library callers pass it directly. opts overrides the flake attribute name and user when they differ from the distro user; pass nil to use defaults.
func (*Recipe) AddService ¶
AddService registers an s6 service in the recipe's docker build context under s6/<name>/. It participates in the cache key like any context file, so adding or editing a service invalidates the cached tarball. It errors if anything already occupies s6/<name>/ (duplicate service or a hand-set ContextFiles entry); use PutService to override.
On bases without an s6 package (dnf family) registered services are still baked into /etc/s6/services but are not supervised: /bin/s6-init falls back to exec'ing sshd's run script directly.
func (*Recipe) PutService ¶
PutService is AddService with replace-or-add semantics: any existing files under s6/<name>/ are removed first, so a user-supplied service (e.g. sshd from a winkit.yaml services dir) cleanly overrides the built-in of the same name.