feat: OS-aware service restarts and cross-platform shell detection #2

Merged
kmendell merged 2 commits from kmendell-fnl-fuzzy-adventure into main 2026-08-05 15:56:48 -05:00
Owner

Fleet updates permanently took Alpine (OpenRC) and OpenBSD (rc.d) nodes
offline: the agent only sent itself SIGTERM, and neither
command_background="yes" nor rc_bg=YES respawns anything. SSH sessions also
silently dropped every supplementary group, because leaving
syscall.Credential.Groups nil makes Go call setgroups(0, nil).

Add internal/platform, which detects the OS, distribution, init system and
container runtime, and maps each to a restart strategy: a native restart for
systemd and launchd, a plain exit where something respawns the service, and an
in-place syscall.Exec (preserving the pid, so rc.d and OpenRC pidfiles stay
valid) where nothing does. Both self-update paths use it, and an in-place
re-exec now shuts the SSH server down first so live sessions are not orphaned.

Rewrite login shell resolution to match sshd on every platform: parse both the
7-field and 10-field passwd layouts, defer to getent/dscl whenever the file
cannot answer authoritatively (absent user, NIS inclusion markers), validate
that the shell is an executable regular file, and fall back per-OS (ksh on the
BSDs, ash on musl, zsh on macOS). PATH now comes from login.defs, login.conf or
path_helper instead of a hardcoded Linux list, a missing home falls back to /,
and supplementary groups are restored.

Teach the installer about sysvinit, runit and s6, switch OpenRC to
supervise-daemon so an updated node comes back, and write service.env recording
what detection cannot know: the operator's service name and whether the unit
actually respawns. Nodes report their platform to the control plane, which
surfaces it in the servers list.

Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

Fleet updates permanently took Alpine (OpenRC) and OpenBSD (rc.d) nodes offline: the agent only sent itself SIGTERM, and neither command_background="yes" nor rc_bg=YES respawns anything. SSH sessions also silently dropped every supplementary group, because leaving syscall.Credential.Groups nil makes Go call setgroups(0, nil). Add internal/platform, which detects the OS, distribution, init system and container runtime, and maps each to a restart strategy: a native restart for systemd and launchd, a plain exit where something respawns the service, and an in-place syscall.Exec (preserving the pid, so rc.d and OpenRC pidfiles stay valid) where nothing does. Both self-update paths use it, and an in-place re-exec now shuts the SSH server down first so live sessions are not orphaned. Rewrite login shell resolution to match sshd on every platform: parse both the 7-field and 10-field passwd layouts, defer to getent/dscl whenever the file cannot answer authoritatively (absent user, NIS inclusion markers), validate that the shell is an executable regular file, and fall back per-OS (ksh on the BSDs, ash on musl, zsh on macOS). PATH now comes from login.defs, login.conf or path_helper instead of a hardcoded Linux list, a missing home falls back to /, and supplementary groups are restored. Teach the installer about sysvinit, runit and s6, switch OpenRC to supervise-daemon so an updated node comes back, and write service.env recording what detection cannot know: the operator's service name and whether the unit actually respawns. Nodes report their platform to the control plane, which surfaces it in the servers list. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
feat: OS-aware service restarts and cross-platform shell detection
Some checks failed
CI / lint (pull_request) Failing after 46s
CI / test (pull_request) Successful in 1m31s
1fccadc980
Fleet updates permanently took Alpine (OpenRC) and OpenBSD (rc.d) nodes
offline: the agent only sent itself SIGTERM, and neither
command_background="yes" nor rc_bg=YES respawns anything. SSH sessions also
silently dropped every supplementary group, because leaving
syscall.Credential.Groups nil makes Go call setgroups(0, nil).

Add internal/platform, which detects the OS, distribution, init system and
container runtime, and maps each to a restart strategy: a native restart for
systemd and launchd, a plain exit where something respawns the service, and an
in-place syscall.Exec (preserving the pid, so rc.d and OpenRC pidfiles stay
valid) where nothing does. Both self-update paths use it, and an in-place
re-exec now shuts the SSH server down first so live sessions are not orphaned.

Rewrite login shell resolution to match sshd on every platform: parse both the
7-field and 10-field passwd layouts, defer to getent/dscl whenever the file
cannot answer authoritatively (absent user, NIS inclusion markers), validate
that the shell is an executable regular file, and fall back per-OS (ksh on the
BSDs, ash on musl, zsh on macOS). PATH now comes from login.defs, login.conf or
path_helper instead of a hardcoded Linux list, a missing home falls back to /,
and supplementary groups are restored.

Teach the installer about sysvinit, runit and s6, switch OpenRC to
supervise-daemon so an updated node comes back, and write service.env recording
what detection cannot know: the operator's service name and whether the unit
actually respawns. Nodes report their platform to the control plane, which
surfaces it in the servers list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
fix: satisfy golangci-lint on the platform restart path
All checks were successful
CI / lint (pull_request) Successful in 55s
CI / test (pull_request) Successful in 1m28s
ab344161e8
Thread a context through Restarter.Restart so the init-system call inherits the
caller's deadline. Both call sites pass context.WithoutCancel: the restart
outlives the update request, and a cancelled context would skip it and leave the
process on the old binary.

The rest is lint hygiene: name every Init case in the two switches, drop three
nolint directives gosec no longer needs, use errors.New and bytes.SplitSeq, and
split node version/platform reporting out of TestRolesAndNodes into its own test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kmendell deleted branch kmendell-fnl-fuzzy-adventure 2026-08-05 15:56:48 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ofkm/overpass!2
No description provided.