packaging

package
v0.1.7 Latest Latest
Warning

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

Go to latest
Published: Jun 27, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package packaging renders service-manager unit files (launchd plist on darwin, systemd user unit on linux) and installs them where the user's service manager expects to find them. Called from `bridge init` so a single operator command leaves behind a running, auto-restarting service.

Index

Constants

View Source
const ServiceLabel = "com.acoseac.1-bit-bridge"

ServiceLabel is the launchd Label / systemd unit name. Chosen to match the iOS bundle id ("com.acoseac.onebit") so `launchctl list` groups it sensibly alongside the app's user defaults.

Variables

View Source
var ErrServiceInstallUnsupported = errors.New("Windows Service install only supported on windows")

ErrServiceInstallUnsupported is returned when InstallWindowsService is called on a non-Windows host. Callers check the error and fall back to launchd/systemd (unix) or the Startup-folder launcher (windows).

View Source
var ErrSystemInstallNeedsRoot = errors.New("system-level install detected; re-run as root or convert to a user-context install")

ErrSystemInstallNeedsRoot is returned by Stop / Restart when the detected install is a system-level launchd LaunchDaemon or systemd system unit and the current process can't drive it. The menu surfaces this as a friendly "Re-launch as root or convert to a user-context install" hint rather than calling user-domain commands that would silently no-op against the wrong namespace.

Functions

func CmdEscape added in v0.1.1

func CmdEscape(s string) string

CmdEscape prepares a string for use inside a double-quoted argument on a Windows `cmd.exe` command line. The only thing that can tear a double-quoted argument is a literal `"` — cmd.exe's escape for a quote inside quotes is `""`, NOT backslash-anything. Backslashes are fine inside quoted arguments; they're literal path separators. CR / LF would end the line, which would break the script. NUL can't appear in a Windows path, but stripping it is cheap insurance.

Shared by the `cmdEscape` template func (startup.cmd.tmpl) and the runtime `SpawnDetached` helper so the init-time spawn and the logon-time Startup launcher produce byte-identical command lines.

func DefaultConfigDir

func DefaultConfigDir() (string, error)

DefaultConfigDir returns the standard config location for the current OS. macOS uses ~/Library/Application Support/1-bit-bridge so it's backed up by Time Machine and survives reinstalls; Linux follows XDG; Windows uses %LOCALAPPDATA%\1-bit-bridge (roaming is overkill for a per-machine SQLite DB + TLS cert).

func DefaultLogPath

func DefaultLogPath() (string, error)

DefaultLogPath returns a per-OS log file location.

func Install

func Install(p Params) (unitPath string, err error)

Install writes the service unit for the current OS and asks the service manager to load it. Returns the path of the unit file that was written. Non-darwin / non-linux: returns an empty path and (nil) error — the caller should advise the user to run `bridge serve` manually.

**Windows two-tier semantics**: tries the SCM service first; falls through to the Startup-folder launcher when SCM access is denied. Callers that need to *force* the Startup-folder path (because the operator explicitly chose option 1 in the wizard, even though they might be running elevated) MUST use `InstallStartup` instead — otherwise an Administrator-shell wizard run silently auto-elevates to SCM regardless of the operator's pick.

func InstallStartup added in v0.1.1

func InstallStartup(p Params) (unitPath string, err error)

InstallStartup installs the Startup-folder launcher only on Windows; never touches SCM. Used by the wizard when the operator explicitly picks "Launch when I log in" (option 1) — without this strict path, `Install`'s SCM-first auto-elevation would silently install as a Windows Service when the wizard was run from an Administrator shell, and the post-install status / restart behaviour would mismatch the operator's stated choice.

On non-Windows: returns ("", nil). The launchd / systemd paths have only one mode each, so the operator never sees this distinction outside Windows.

func InstallWindowsService

func InstallWindowsService(_ Params) (string, error)

InstallWindowsService is a non-Windows stub. Always returns ErrServiceInstallUnsupported. The signature matches the windows build so callers don't need build-constrained dispatch code.

func IsAdmin added in v0.1.1

func IsAdmin() bool

IsAdmin is the Windows-only elevation probe; on POSIX it returns true so callers that gate UX on admin rights don't need build-tagged dispatch — the gate is irrelevant when the relevant install paths are user-context (launchd user agent, systemd --user).

func IsInitialized added in v0.1.1

func IsInitialized() (cfgPath string, ok bool)

IsInitialized reports whether bridge.yaml exists at the platform-default config path. Returns the resolved config-file path either way so the caller can re-use it (e.g. as the --config arg for a manual launch).

func IsInstalled added in v0.1.1

func IsInstalled() bool

IsInstalled is a convenience wrapper around InstalledKind. Returns true when any non-None kind is detected; swallows the error so callers that only need the boolean can stay terse.

func IsListening added in v0.1.1

func IsListening(host string, port int) bool

IsListening returns true if something accepts TCP on host:port within a short dial timeout. Used before `SpawnDetached` so a re-run of `bridge init` while an SCM-installed service (or a previously-spawned launcher) is already bound doesn't produce a port-bind error that ends up in the log as a permanent-looking failure.

func IsRoot added in v0.1.1

func IsRoot() bool

IsRoot reports whether the current process is running with euid 0. Used to warn against `sudo bridge` on POSIX, where running as root resolves $HOME to /root and silently breaks the bridge config dir resolution at next user-context launch.

func Restart added in v0.1.1

func Restart() error

Restart stops then re-starts the installed service. Implemented per platform because the launchd/systemd/SCM APIs differ — the dispatcher here just keeps the public surface tidy. Returns nil when nothing is installed; same system-install gate as Stop.

func SpawnDetached added in v0.1.1

func SpawnDetached(binary, configPath, logPath string) error

SpawnDetached is Windows-only. On macOS/Linux, init hands off to launchd / systemctl which start the daemon as part of the unit install — the shell doesn't need a detached child. This stub exists only so init.go compiles without a build-tag dance around the call site.

func Start added in v0.1.1

func Start() error

Start asks the service manager to boot the installed bridge service if it isn't already running. No-op when nothing is installed (returns nil so menu / CLI callers don't have to gate on `IsInstalled()` first). Same system-install + admin gates as Stop / Restart.

Distinct from Restart in semantics: Start is idempotent "service should be up". Restart unconditionally bounces. The CLI surface added in this PR exposes both to operators.

func Stop added in v0.1.1

func Stop() error

Stop asks the service manager to stop the running bridge service (launchd / systemd / SCM) but leaves the install in place. A follow-up Start (via the OS itself or via Restart below) brings it back. No-op when nothing is installed — returns nil so menu callers can call this without a prior IsInstalled() check.

On Windows, calling Stop without admin returns the platform's access-denied error wrapped — the menu surfaces this as a "Re-launch as Administrator" hint rather than a stack trace.

On POSIX, calling Stop against a system-level install returns ErrSystemInstallNeedsRoot — see the comment on that sentinel.

func Uninstall

func Uninstall() (string, error)

Uninstall reverses Install: stops the service and removes the unit file. Missing files are not an error so `bridge init` can rerun idempotently. Returns (unitPath, err).

func UninstallWindowsService

func UninstallWindowsService() error

UninstallWindowsService is a non-Windows stub. Always returns nil so a best-effort uninstall doesn't fail across platforms.

func WaitForListen added in v0.1.1

func WaitForListen(host string, port int, timeout time.Duration) bool

WaitForListen polls host:port until something accepts, or the timeout expires. Used after `SpawnDetached` so `openInBrowser` fires only after the admin server is ready to answer. Returns true if a connection landed within the budget.

Types

type Params

type Params struct {
	Label      string // e.g. com.acoseac.1-bit-bridge
	BinaryPath string // absolute path to the bridge binary
	ConfigPath string // absolute path to bridge.yaml
	WorkingDir string // absolute path; where stdout/stderr filenames resolve
	LogPath    string // absolute path to a log file
}

Params bundles the values the templates expand. All paths must be absolute — launchd won't expand $HOME inside a plist, and systemd user units on most distros inherit a minimal PATH.

type ServiceKind added in v0.1.1

type ServiceKind int

ServiceKind identifies which service-manager artifact is installed for the bridge on the current host. The user/system split matters because the install paths differ — a user agent lives under the user's home dir and runs at logon, a system daemon lives under shared OS dirs and runs at boot under root / LocalSystem.

const (
	// KindNone — no service-manager artifact found.
	KindNone ServiceKind = iota
	// KindLaunchdUser — ~/Library/LaunchAgents/<label>.plist.
	// The supported macOS install path; runs at logon as the user.
	KindLaunchdUser
	// KindLaunchdSystem — /Library/LaunchDaemons/<label>.plist.
	// Rare and only present from a sudo install, which the menu
	// warns against because the bridge config dir lives under
	// the user's home and won't resolve correctly under root.
	KindLaunchdSystem
	// KindSystemdUser — ~/.config/systemd/user/<label>.service.
	// The supported Linux install path.
	KindSystemdUser
	// KindSystemdSystem — /etc/systemd/system/<label>.service.
	// Rare and operator-installed; same caveat as KindLaunchdSystem.
	KindSystemdSystem
	// KindWindowsSCM — registered with Windows SCM.
	// Requires admin to install/uninstall; survives logout.
	KindWindowsSCM
	// KindWindowsStartup — Startup-folder .cmd launcher.
	// User-context install; runs at logon while user is logged in.
	KindWindowsStartup
)

func InstalledKind added in v0.1.1

func InstalledKind() (ServiceKind, error)

InstalledKind reports which service-manager artifact (if any) is installed on the current host. Best-effort: file-existence probes on darwin/linux, SCM-then-Startup probe on windows. Returns KindNone with nil error when nothing is found. Errors are surfaced only when a probe failed in a way that prevents detection (e.g. $HOME unresolvable) — a genuine "no service" state is not an error.

On Windows, SCM is probed first; if SCM access is denied (no admin) the Startup-folder file probe still runs. Both can coexist on the same machine across reinstalls; SCM wins precedence in this report.

func (ServiceKind) Description added in v0.1.1

func (k ServiceKind) Description() string

Description returns a short, human-readable label for the service kind, suitable for a CLI status line. Distinct strings per kind so a UI never collapses two install kinds into the same display.

Jump to

Keyboard shortcuts

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