Documentation
¶
Index ¶
- Constants
- Variables
- func ConfigureStaticNetwork(iface, ipCIDR, gateway string, dns []string) error
- func ConfiguredConsoles(cfg *Config) []string
- func CreateDevices() error
- func DHCPClient(ifname string) (*dhclient.Client, error)
- func ErrInvalidCfgFile(cfgFile string) error
- func ErrNotExists(fieldName string) error
- func ExitStatus(ws unix.WaitStatus) int
- func FormatUptime(d time.Duration) string
- func Gettys(ctx context.Context, ttys []string, persist bool)
- func InterfaceUp(ifname string) error
- func Mkdev(major, minor int) int
- func Mknod(path string, mode uint32, dev int) error
- func NewSSHServer(bind, keys string, opts ...option) (*server, error)
- func ParseServiceConfigs(dir string) (map[ServiceType][]*Service, error)
- func RunReaper()
- func ScanDevices(ctx context.Context) error
- func ServeControl(sup *Supervisor) error
- func SetupFirewall(sshPort uint16, containerBridge string) error
- func SetupNetworkInterfaces() error
- func StartSSHServer(ctx context.Context)
- func StartServices(providers map[ServiceType][]*Service)
- func WaitForCarrier(ifname string, timeout time.Duration) bool
- func WatchDevices(ctx context.Context) error
- func WithHostKeyFile(fn string) option
- type Command
- type Config
- type ControlRequest
- type ControlResponse
- type MountOption
- type Reaper
- type Service
- type ServiceName
- type ServiceStatus
- type ServiceType
- type Supervisor
- func (sup *Supervisor) Disable(name string) error
- func (sup *Supervisor) Enable(name string) error
- func (sup *Supervisor) List() []ServiceStatus
- func (sup *Supervisor) OnStart(fn func(name string, err error))
- func (sup *Supervisor) OnStop(fn func(name string, err error))
- func (sup *Supervisor) Reload(name string) error
- func (sup *Supervisor) Restart(name string) error
- func (sup *Supervisor) Start(name string) error
- func (sup *Supervisor) StartAll()
- func (sup *Supervisor) Status(name string) (ServiceStatus, error)
- func (sup *Supervisor) Stop(name string) error
- func (sup *Supervisor) StopAll()
- type UI
- func (u *UI) Bold(s string) string
- func (u *UI) Colored() bool
- func (u *UI) Dim(s string) string
- func (u *UI) Fail(format string, args ...any)
- func (u *UI) Green(s string) string
- func (u *UI) OK(format string, args ...any)
- func (u *UI) Plain(format string, args ...any)
- func (u *UI) Red(s string) string
- func (u *UI) ServiceState(state string, width int) (dot, word string)
- func (u *UI) Step(format string, args ...any)
- func (u *UI) Warn(format string, args ...any)
- func (u *UI) Yellow(s string) string
- type Unmounter
Constants ¶
const ControlSocket = "/run/sv.sock"
ControlSocket is the Unix socket PID 1 serves for the `sv` service-control CLI. It is root-only (0600).
Variables ¶
var DefaultReaper = NewReaper()
DefaultReaper is the process-wide reaper used by init's helpers (getty, ssh).
Functions ¶
func ConfigureStaticNetwork ¶
ConfigureStaticNetwork assigns a static address (CIDR, e.g. "192.0.2.10/24") to iface, optionally adds a default route via gateway, and writes the given DNS servers to /etc/resolv.conf. It is idempotent (replaces existing address and route), so it can be re-applied.
func ConfiguredConsoles ¶
ConfiguredConsoles returns the ttys to run a console getty on. If rc.conf sets `CONSOLES` (whitespace-separated, e.g. "tty0 ttyAMA0"), that list is used verbatim. Otherwise the default is tty0 (the virtual console) plus whatever serial console the kernel was told to use on its cmdline (`console=`) — so a graphical console and a headless/serial deployment both get a login, and we use the arch's real serial device (ttyAMA0 on arm64, ttyS0 on amd64) rather than guessing from a static list (the 8250 driver pre-creates phantom /dev/ttyS0..3 nodes on arm64 that aren't real ports, so device-node existence can't tell a usable serial port from a stub — the kernel's own console= can).
func CreateDevices ¶
func CreateDevices() error
func ErrInvalidCfgFile ¶
func ErrNotExists ¶
func ExitStatus ¶
func ExitStatus(ws unix.WaitStatus) int
ExitStatus maps a wait status to a conventional process exit code.
func FormatUptime ¶
FormatUptime renders a duration compactly for status output: "5s", "4m12s", "1h03m", "2d04h". A non-positive duration (a service that isn't running) shows as "-".
func Gettys ¶
Gettys spawns a getty on each named tty (e.g. "tty0", "ttyS0", "ttyAMA0") whose device node exists, so an explicitly-configured console with no node at all is skipped rather than left error-looping. If persist is true, each surviving getty is respawned when it exits.
func InterfaceUp ¶
func Mkdev ¶
Mkdev is used to build the value of linux devices (in /dev/) which specifies major and minor number of the newly created device special file. Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major, then the top 12 bits of the minor.
func Mknod ¶
Mknod creates a filesystem node (file, device special file or named pipe) named path with attributes specified by mode and dev.
func NewSSHServer ¶
func ParseServiceConfigs ¶
func ParseServiceConfigs(dir string) (map[ServiceType][]*Service, error)
Parses all the config in directory dir return a map of providers of ServiceTypes from that directory.
func RunReaper ¶
func RunReaper()
RunReaper runs the DefaultReaper. Call it once, in a goroutine, from PID 1.
func ScanDevices ¶
ScanDevices walks /sys/devices for the existing (cold-plugged) devices and populates /dev, the udev coldplug that has to finish before gettys start (they need their tty nodes). It dominated boot time as a single-threaded walk that read every `uevent` file in series, so this does it concurrently: collect the uevent paths (a cheap readdir-only pass), then read/parse them and mknod the nodes in a worker pool. The cost is the per-file sysfs reads, which are independent and syscall-bound, so overlapping them across CPUs roughly halves the phase. addDevice only ever touches distinct /dev paths and os.MkdirAll/ Mknod tolerate concurrency, so the parallelism is safe; the resulting set of nodes is identical to the serial scan.
func ServeControl ¶
func ServeControl(sup *Supervisor) error
ServeControl creates the control socket and serves it in the background, dispatching requests to the supervisor. It returns once the listener is up.
func SetupFirewall ¶
SetupFirewall installs a conservative baseline nftables policy: a single `inet gonix_filter` table with an input chain whose policy is drop, but which accepts loopback, established/related connections, SSH, ICMP/ICMPv6, and anything arriving on the container bridge (so box containers can still reach host services such as DNS). The forward and output paths are left untouched, so box's own NAT/forward rules keep working.
It is opt-in (init only calls it when configured) and the caller treats any error as non-fatal.
func SetupNetworkInterfaces ¶
func SetupNetworkInterfaces() error
func StartSSHServer ¶
func StartServices ¶
func StartServices(providers map[ServiceType][]*Service)
StartServices starts every service, gating each on its declared Needs and only marking a service's Provides available once it has started successfully.
func WaitForCarrier ¶
WaitForCarrier blocks until the named interface reports link carrier (the NIC has finished negotiating a link), or timeout elapses. Emulated NICs that model real hardware — notably VirtualBox's e1000 — take a couple of seconds to bring the link up after the interface is administratively up, so starting a DHCP client immediately would fire its first DISCOVER into a dead link and (depending on the client) never recover. QEMU's virtio-net reports carrier instantly, so there this returns right away. A timeout is NOT fatal: callers proceed and let DHCP retry, so a genuinely link-less box still boots.
func WatchDevices ¶
WatchDevices watches /sys for devices and populates /dev
func WithHostKeyFile ¶
func WithHostKeyFile(fn string) option
Types ¶
type Config ¶
func (*Config) WriteString ¶
type ControlRequest ¶
type ControlRequest struct {
Action string `json:"action"` // status | start | stop | restart
Service string `json:"service,omitempty"` // empty with status = all services
}
ControlRequest is one command from `sv` to the supervisor.
type ControlResponse ¶
type ControlResponse struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
Services []ServiceStatus `json:"services,omitempty"`
}
ControlResponse is the supervisor's reply.
type MountOption ¶
type Reaper ¶
type Reaper struct {
// contains filtered or unexported fields
}
As PID 1, init must reap every child that exits — both processes it started (gettys, ssh session commands, services) and orphans reparented to it. The naive approach (a goroutine looping on wait4(-1)) races os/exec's own cmd.Wait(): whichever calls wait4 first gets the status, so cmd.Wait() often returns ECHILD ("no child processes") and exit codes are lost.
The Reaper makes init the single owner of child reaping. Callers that spawn a child register its PID with Wait() and receive its exit status on the returned channel; orphans (no registered waiter) are reaped and discarded. Nothing else in init may call wait4/cmd.Wait().
func (*Reaper) Run ¶
func (r *Reaper) Run()
Run drains children on SIGCHLD. It is the only wait4 caller in the process.
func (*Reaper) Wait ¶
func (r *Reaper) Wait(pid int) <-chan unix.WaitStatus
Wait registers pid and returns a channel that receives its exit status exactly once. Call it immediately after starting the child. If the child was already reaped (it exited before this call), the status is delivered immediately.
type Service ¶
type Service struct {
Name ServiceName
Description string // optional human-readable summary for `sv`
Startup Command
Shutdown Command
Ready Command // optional readiness probe for daemons
User string // optional unprivileged user to run the service as
Provides []ServiceType
Needs []ServiceType
Type serviceType
RestartPolicy restartPolicy
// contains filtered or unexported fields
}
func ParseConfig ¶
Parses a single config file into the services it provides
func (*Service) Restart ¶
Restart stops the service (ignoring a "not running" error) and starts it again.
func (*Service) Start ¶
Start runs the service. A one-shot service runs to completion; a daemon is launched and then kept alive in the background by supervise() per its restart policy. Either way Start returns once the service is up (so StartServices can release its dependents), not when a daemon eventually exits.
type ServiceName ¶
type ServiceName string
type ServiceStatus ¶
type ServiceStatus struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
State string `json:"state"` // run | down | fail | starting | done
PID int `json:"pid,omitempty"`
Since time.Time `json:"since"` // start of the current run (zero if not running)
Restarts int `json:"restarts"`
Enabled bool `json:"enabled"` // starts at boot
}
ServiceStatus is a point-in-time snapshot of a service, reported to `sv` over the control socket.
type ServiceType ¶
type ServiceType string
type Supervisor ¶
type Supervisor struct {
// contains filtered or unexported fields
}
Supervisor owns the services parsed from /etc/rc.d and provides name-based control (start/stop/restart/status) on top of the dependency-ordered startup and per-service supervision in service.go. It backs the `sv` control socket.
func NewSupervisor ¶
func NewSupervisor(providers map[ServiceType][]*Service) *Supervisor
NewSupervisor indexes the parsed providers map by service name.
func (*Supervisor) Disable ¶
func (sup *Supervisor) Disable(name string) error
Disable marks a service not to start at boot (writing a disabled marker). It does not stop it now — use `sv stop` for that.
func (*Supervisor) Enable ¶
func (sup *Supervisor) Enable(name string) error
Enable marks a service to start at boot (removing its disabled marker). It does not start it now.
func (*Supervisor) List ¶
func (sup *Supervisor) List() []ServiceStatus
List returns every service's status, ordered by name.
func (*Supervisor) OnStart ¶
func (sup *Supervisor) OnStart(fn func(name string, err error))
OnStart registers a callback invoked by StartAll for each service as it starts (or fails to). It must be set before StartAll and is used by PID 1 to print per-service boot progress.
func (*Supervisor) OnStop ¶
func (sup *Supervisor) OnStop(fn func(name string, err error))
OnStop registers a callback invoked by StopAll for each running service as it stops. It must be set before StopAll and is the shutdown counterpart to OnStart, used by PID 1 to print per-service shutdown progress.
func (*Supervisor) Reload ¶
func (sup *Supervisor) Reload(name string) error
Reload re-reads the service's unit file from /etc/rc.d and restarts it with the updated definition. The supervisor otherwise holds the definition parsed at boot, so a changed Startup command (or any other unit field) is not picked up by Restart alone; Reload makes live reconfiguration possible without a reboot. Dependency wiring (Needs/Provides) for the boot ordering is not recomputed — only this service's own definition is refreshed.
If the unit wasn't known at boot, Reload registers it as a brand-new service and starts it (see register), so dropping a new /etc/rc.d/<name> file and running `sv reload <name>` brings it up live.
func (*Supervisor) Restart ¶
func (sup *Supervisor) Restart(name string) error
func (*Supervisor) Start ¶
func (sup *Supervisor) Start(name string) error
func (*Supervisor) StartAll ¶
func (sup *Supervisor) StartAll()
StartAll brings up every enabled service, honouring Needs/Provides ordering. Disabled services are excluded entirely (their capabilities aren't advertised, so a dependent of a disabled service won't start either); they can still be started by hand with `sv start`.
func (*Supervisor) Status ¶
func (sup *Supervisor) Status(name string) (ServiceStatus, error)
func (*Supervisor) Stop ¶
func (sup *Supervisor) Stop(name string) error
func (*Supervisor) StopAll ¶
func (sup *Supervisor) StopAll()
StopAll stops every running daemon, reporting each via onStop. It's the shutdown counterpart to StartAll. The daemons have no stop-order dependencies among them, so they're stopped concurrently — shutdown waits roughly the slowest service's stop grace rather than the sum. One-shot services have already run to completion, so there's nothing to stop.
type UI ¶
type UI struct {
// contains filtered or unexported fields
}
UI renders status lines to a writer, with colour and glyphs when that writer is a terminal. Its methods are safe for concurrent use, so service-start events reported from supervisor goroutines don't interleave on the console.
func (*UI) Bold ¶
Bold / Dim / Color expose the writer's colour scheme for inline use (table cells, names) so callers don't each re-implement the no-op-when-plain rule.
func (*UI) ServiceState ¶
ServiceState maps a service state ("run", "down", "fail", "starting", "done") to a coloured status dot and the state word padded to width and coloured to match — green for running/done, red for failed, yellow for starting, dim for down. Used by `sv` to render its status table.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cloudinit
Package cloudinit is a minimal cloud-init for GoNIX: at boot it discovers the instance's SSH keys, hostname and user-data from the platform and applies them, so a fresh image becomes a configured server with no baked-in secrets.
|
Package cloudinit is a minimal cloud-init for GoNIX: at boot it discovers the instance's SSH keys, hostname and user-data from the platform and applies them, so a fresh image becomes a configured server with no baked-in secrets. |
|
datadisk
Package datadisk mounts the optional persistent data disk as a supervised one-shot service (started from /etc/rc.d/datadisk, Provides: data).
|
Package datadisk mounts the optional persistent data disk as a supervised one-shot service (started from /etc/rc.d/datadisk, Provides: data). |
|
killall
Package killall implements killall: send a signal to all processes matching a name exactly (by comm, as in the traditional psmisc tool).
|
Package killall implements killall: send a signal to all processes matching a name exactly (by comm, as in the traditional psmisc tool). |
|
network
Package network configures eth0 as a supervised service (started from /etc/rc.d/network): a static address when /etc/rc.conf sets IP, otherwise a DHCP client.
|
Package network configures eth0 as a supervised service (started from /etc/rc.d/network): a static address when /etc/rc.conf sets IP, otherwise a DHCP client. |
|
nohup
Package nohup implements nohup: run a program immune to hangups, with output redirected to nohup.out when stdout is a terminal.
|
Package nohup implements nohup: run a program immune to hangups, with output redirected to nohup.out when stdout is a terminal. |
|
pgrep
Package pgrep implements pgrep and pkill: find (and optionally signal) processes by name.
|
Package pgrep implements pgrep and pkill: find (and optionally signal) processes by name. |
|
setsid
Package setsid implements setsid: run a program in a new session, detached from the controlling terminal.
|
Package setsid implements setsid: run a program in a new session, detached from the controlling terminal. |
|
sshd
Package sshd runs the GoNIX SSH server as a standalone, supervised service (started from /etc/rc.d/sshd), rather than a goroutine inside init.
|
Package sshd runs the GoNIX SSH server as a standalone, supervised service (started from /etc/rc.d/sshd), rather than a goroutine inside init. |
|
strace
go-strace only implements its syscall arg/ret decoding for amd64, so the real strace applet is amd64-only; strace_unsupported.go provides a stub elsewhere.
|
go-strace only implements its syscall arg/ret decoding for amd64, so the real strace applet is amd64-only; strace_unsupported.go provides a stub elsewhere. |
|
sv
Package sv implements the `sv` service-control client: a thin front-end to the supervisor PID 1 runs, reached over the /run/sv.sock control socket.
|
Package sv implements the `sv` service-control client: a thin front-end to the supervisor PID 1 runs, reached over the /run/sv.sock control socket. |
|
wg
Package wg is a small WireGuard control applet for GoNIX.
|
Package wg is a small WireGuard control applet for GoNIX. |
|
Package cefhost is the shared CEF (Chromium) offscreen-rendering host used by both browser host roles:
|
Package cefhost is the shared CEF (Chromium) offscreen-rendering host used by both browser host roles: |
|
Package cefipc is the shared transport contract between the GoNIX/macOS browser shell and its CEF rendering host: a memory-mapped frame buffer plus the constants both sides agree on.
|
Package cefipc is the shared transport contract between the GoNIX/macOS browser shell and its CEF rendering host: a memory-mapped frame buffer plus the constants both sides agree on. |
|
Package gui is a tiny, backend-agnostic windowing contract: a resizable software framebuffer (an *image.RGBA painted by the application) plus pointer, keyboard and scroll input and a run loop.
|
Package gui is a tiny, backend-agnostic windowing contract: a resizable software framebuffer (an *image.RGBA painted by the application) plus pointer, keyboard and scroll input and a run loop. |
|
Package wlui is a minimal, pure-Go, software-rendered Wayland window toolkit.
|
Package wlui is a minimal, pure-Go, software-rendered Wayland window toolkit. |
|
swizzle
Package swizzle provides functions for converting between RGBA pixel formats.
|
Package swizzle provides functions for converting between RGBA pixel formats. |
|
text
Package text is a minimal pure-Go text-rendering layer for wlui apps.
|
Package text is a minimal pure-Go text-rendering layer for wlui apps. |
|
ui
Package ui provides shared theming and drawing helpers for GoNIX GUI apps built on wlui — the small, home-grown beginnings of a widget layer.
|
Package ui provides shared theming and drawing helpers for GoNIX GUI apps built on wlui — the small, home-grown beginnings of a widget layer. |