netmove

package
v0.51.8 Latest Latest
Warning

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

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

Documentation

Overview

Package netmove holds the host-side logic for moving a standalone StackKits server to another network: observing the current address and wireless link, deciding whether the recorded site address is stale, pre-staging destination WiFi, and keeping a watcher installed that re-binds the stack by itself.

Everything that touches the host goes through Sys so the decisions stay unit-testable with fakes.

Index

Constants

View Source
const (

	// VerdictInPlace means every recorded address equals the current one.
	VerdictInPlace = "in-place"
	// VerdictMoved means a recorded address differs from the current one.
	VerdictMoved = "moved"
	// VerdictUnknown means there is no current or no recorded address to compare.
	VerdictUnknown = "unknown"
)
View Source
const (
	// WatchService and WatchTimer are the systemd units of the network watcher.
	WatchService = "kombify-stackkit-network-watch.service"
	WatchTimer   = "kombify-stackkit-network-watch.timer"

	// RetryBackoff is how long a failed automatic rebind waits before the
	// next attempt, so a broken apply does not loop every minute.
	RetryBackoff = 5 * time.Minute
)
View Source
const (
	// NetplanFile is the only netplan file this package writes.
	NetplanFile = "/etc/netplan/90-kombify-wifi.yaml"
	// NMConnectionDir holds NetworkManager keyfile connections.
	NMConnectionDir = "/etc/NetworkManager/system-connections"
)

Variables

View Source
var ErrNoBackend = errors.New("netmove: neither a running NetworkManager nor netplan was found; install one of them or configure the WiFi through the distribution's own tooling")

ErrNoBackend reports that neither NetworkManager nor netplan can stage WiFi.

View Source
var ErrNoWirelessInterface = errors.New("netmove: this host has no wireless interface; use --force to stage the network anyway")

ErrNoWirelessInterface reports that WiFi cannot be staged on this host.

Functions

func AcquireLock

func AcquireLock(workspace string, now time.Time) (release func(), ok bool, err error)

AcquireLock takes the watcher lock. ok is false when another run holds it. A lock older than staleLockAge belongs to a killed process and is replaced.

func NMKeyfile

func NMKeyfile(credential Credential) []byte

NMKeyfile renders the NetworkManager connection for a staged network. DHCP is requested for both families and the connection autoconnects on any wireless device, so it only ever takes effect where its SSID is in range.

func NetplanAdd

func NetplanAdd(existing []byte, credential Credential) ([]byte, error)

NetplanAdd merges one access point into an existing netplan document (nil for a new file) and keeps every other key, including ethernets.

func NetplanList

func NetplanList(existing []byte) ([]string, error)

NetplanList returns the staged SSIDs of a netplan document.

func NetplanRemove

func NetplanRemove(existing []byte, ssid string) (out []byte, found bool, err error)

NetplanRemove drops one access point. out is nil when nothing but the version header remains and the file can be deleted.

func ParseDefaultRoute

func ParseDefaultRoute(raw []byte) (iface, gateway string)

ParseDefaultRoute returns the interface and gateway of the lowest-metric default IPv4 route in the /proc/net/route format.

func ParseIWLinkSSID

func ParseIWLinkSSID(out []byte) string

ParseIWLinkSSID returns the SSID line of `iw dev <if> link`.

func ParseNMCLIActiveSSID

func ParseNMCLIActiveSSID(out []byte) string

ParseNMCLIActiveSSID returns the SSID of the active row of `nmcli -t -f active,ssid dev wifi` (terse mode escapes ':' as '\:').

func RenderWatchService

func RenderWatchService(cfg WatchConfig) string

RenderWatchService renders the one-shot service the timer starts.

func RenderWatchTimer

func RenderWatchTimer() string

RenderWatchTimer renders the timer: shortly after boot, then every minute.

func SaveState

func SaveState(workspace string, state State) error

SaveState persists the state with owner-only permissions.

func SetWatchOptOut

func SetWatchOptOut(workspace string, optedOut bool) error

SetWatchOptOut records or clears the owner's opt-out.

func Verdict

func Verdict(current string, recorded ...string) string

Verdict compares the current address with every recorded one. Empty recorded values are ignored. It is "moved" as soon as one recorded address differs, because every recorded copy must be re-issued.

func WatchOptedOut

func WatchOptedOut(workspace string) bool

WatchOptedOut reports whether the owner disabled the watcher for this stack, so a later Apply does not enable it again.

func WirelessInterfaces

func WirelessInterfaces(sys Sys) []string

WirelessInterfaces lists the host's wireless interface names.

Types

type Backend

type Backend interface {
	Name() string
	Add(ctx context.Context, credential Credential, applyNow bool) error
	List() ([]Network, error)
	Remove(ctx context.Context, ssid string, applyNow bool) (bool, error)
}

Backend stages WiFi networks through one host network manager.

func DetectBackend

func DetectBackend(ctx context.Context, sys Sys) (Backend, error)

DetectBackend prefers a running NetworkManager and falls back to netplan.

type Credential

type Credential struct {
	SSID     string
	Password string // empty means an open network
	Priority int
}

Credential is a network to stage.

func (Credential) Validate

func (c Credential) Validate() error

Validate checks the SSID and WPA passphrase shapes.

type NMBackend

type NMBackend struct {
	Dir string
	Sys Sys
}

NMBackend writes keyfile connections and asks NetworkManager to reload them. A keyfile keeps the passphrase out of any process argument list.

func (*NMBackend) Add

func (b *NMBackend) Add(ctx context.Context, credential Credential, _ bool) error

func (*NMBackend) List

func (b *NMBackend) List() ([]Network, error)

func (*NMBackend) Name

func (b *NMBackend) Name() string

func (*NMBackend) Remove

func (b *NMBackend) Remove(ctx context.Context, ssid string, _ bool) (bool, error)

type NetplanBackend

type NetplanBackend struct {
	Path string
	Sys  Sys
}

NetplanBackend owns one netplan file with a single wireless device that matches every wl* interface. It never writes an ethernet definition, and it sets the networkd renderer on that device only, so the global renderer and the host's existing wired DHCP stay as they are.

func (*NetplanBackend) Add

func (b *NetplanBackend) Add(ctx context.Context, credential Credential, applyNow bool) error

func (*NetplanBackend) List

func (b *NetplanBackend) List() ([]Network, error)

func (*NetplanBackend) Name

func (b *NetplanBackend) Name() string

func (*NetplanBackend) Remove

func (b *NetplanBackend) Remove(ctx context.Context, ssid string, applyNow bool) (bool, error)

type Network

type Network struct {
	SSID     string `json:"ssid"`
	Priority int    `json:"priority,omitempty"`
	Backend  string `json:"backend"`
}

Network is one staged WiFi network. The password is never part of it.

type Observation

type Observation struct {
	Address   string `json:"address,omitempty"`
	Interface string `json:"interface,omitempty"`
	Gateway   string `json:"gateway,omitempty"`
	Wireless  bool   `json:"wireless"`
	SSID      string `json:"ssid,omitempty"`
}

Observation is what the host reports about its current network position.

func Observe

func Observe(ctx context.Context, sys Sys) Observation

Observe reads the current site address, default route and wireless link. Each fact is independent: a missing one stays empty.

type State

type State struct {
	LastAttempt time.Time `json:"lastAttempt"`
	LastFailed  bool      `json:"lastFailed"`
	LastError   string    `json:"lastError,omitempty"`
	LastAddress string    `json:"lastAddress,omitempty"`
	// ApplyPending is set once the custody was re-issued and cleared when the
	// regenerate-and-apply that follows succeeds, so a failed apply is retried
	// even though the recorded addresses already match.
	ApplyPending bool `json:"applyPending,omitempty"`
}

State is the watcher's persisted attempt record under the stack's .stackkit.

func LoadState

func LoadState(workspace string) State

LoadState reads the state; a missing or unreadable file is the zero state.

func (State) ShouldBackOff

func (s State) ShouldBackOff(now time.Time) bool

ShouldBackOff reports whether the previous attempt failed too recently.

type Sys

type Sys struct {
	Discover func() (netip.Addr, error)
	ReadFile func(string) ([]byte, error)
	Exists   func(string) bool
	Glob     func(string) ([]string, error)
	LookPath func(string) (string, error)
	Run      func(ctx context.Context, name string, args ...string) ([]byte, error)
}

Sys is the narrow host boundary used by this package.

func DefaultSys

func DefaultSys() Sys

DefaultSys binds Sys to the running host.

type WatchConfig

type WatchConfig struct {
	Binary    string // absolute path of the stackkit executable
	Workspace string // absolute stack directory
	Spec      string // StackSpec path as the stack's commands take it
	// Home and Path carry the environment the stack was applied with. Apply
	// trust and the kit catalog live under the installing user's home, which
	// is not root's when the installer ran as a sudo-capable user.
	Home string
	Path string
}

WatchConfig describes the stack the watcher re-binds.

type WatchInstaller

type WatchInstaller struct {
	UnitDir string
	Sys     Sys
}

WatchInstaller installs and removes the watcher units.

func DefaultWatchInstaller

func DefaultWatchInstaller() WatchInstaller

DefaultWatchInstaller targets /etc/systemd/system on the running host.

func (WatchInstaller) Disable

func (w WatchInstaller) Disable(ctx context.Context) (removed bool, err error)

Disable stops the timer and removes both units.

func (WatchInstaller) Enable

func (w WatchInstaller) Enable(ctx context.Context, cfg WatchConfig) (changed bool, err error)

Enable writes both units and starts the timer. changed is false when the installed units already match and the timer is enabled.

func (WatchInstaller) Status

func (w WatchInstaller) Status(ctx context.Context) WatchStatus

Status reports the installed, enabled and active state of the timer.

func (WatchInstaller) WatchSupported

func (w WatchInstaller) WatchSupported() bool

WatchSupported reports whether systemd is the running init.

type WatchStatus

type WatchStatus struct {
	Installed bool   `json:"installed"`
	Enabled   bool   `json:"enabled"`
	Active    bool   `json:"active"`
	Unit      string `json:"unit"`
}

WatchStatus is the watcher's installed and running state.

Jump to

Keyboard shortcuts

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