wsl

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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

View Source
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.

View Source
const VolumeName = "distro.wsl"

VolumeName is the filename the bootstrap's drive scan looks for.

Variables

View Source
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`.

View Source
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

func DirShareService(shares []sftpshare.DirShare) (s6.Service, error)

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

func IsFlakeRef(nixHome string) bool

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

func MountScript(share sftpshare.DirShare) (string, error)

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

func MountScriptMulti(shares []sftpshare.DirShare) (string, error)

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

func S6HealthcheckService() s6.Service

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

func SSHDBaseService() s6.Service

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

func SSHDNixService() s6.Service

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

func (d *Distro) AddService(svc s6.Service) error

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

func (d Distro) NeedsDocker() bool

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.

func (*Distro) PutService

func (d *Distro) PutService(svc s6.Service) error

PutService is AddService with override semantics; see Recipe.PutService.

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

func BaseRecipe(baseImage, user, distroName string) (Recipe, error)

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

func (r *Recipe) AddService(svc s6.Service) error

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

func (r *Recipe) PutService(svc s6.Service) error

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.

Jump to

Keyboard shortcuts

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