hashgraph / hashgraph/solo-weaver
Story: Spawn self-upgrade worker as a root-created transient systemd unit (Option B; alternative to #525)
- Dominant language
- Go
- Stars
- 3
- Forks
- 0
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 47
Description
Part of epic #500 — Self-Upgrade Protocol. **Alternative to #525** (see the Option A vs B decision record on #500).
### Problem
The self-upgrade worker must **outlive the daemon** — it stops the old daemon and starts the new one. #525 plans to detach it with `os/exec` + `Setsid+Setpgid+Release`, but that does **not** survive `systemctl stop`: systemd stop is **cgroup-based**, the daemon unit defaults to `KillMode=control-group`, and Setsid/Setpgid don't move the child out of the unit's cgroup — so the worker is SIGKILL'd with the cgroup before it can start the new daemon. The child also inherits the daemon's `ProtectSystem=strict`/`PrivateTmp` sandbox.
### Proposed mechanism
Spawn the worker as its **own transient systemd `.service` unit**, so it runs in its own cgroup (survives the daemon stop) and a fresh namespace (no daemon sandbox).
Because the daemon is unprivileged (`User=weaver`) and cannot create system units (polkit denies `manage-units`), the transient unit is created by the **root** CLI reached through the existing sudo seam (`internal/daemon/privexec`):
```
daemon (weaver)
└─ privexec: sudo solo-provisioner daemon apply-upgrade --state-file= [root, brief]
└─ root CLI: dbus.StartTransientUnit("solo-provisioner-self-upgrade.service", "fail", props)
props = PropDescription, PropType("oneshot"),
PropExecStart([cliPath, "self", "upgrade", "--state-file", path], true),
{CollectMode: "inactive-or-failed"} // == systemd-run --collect
root CLI exits ← handoff done
└─ transient unit (systemd-owned, own cgroup, root, unsandboxed):
the #523 `self upgrade` child logic (verify → archive .bak →
atomic install → stop/start daemon → health-check → recovery)
```
Use the Go-native systemd D-Bus API (`github.com/coreos/go-systemd/v22/dbus`, already vendored and used in `pkg/os/systemd.go`) — **not** `os/exec`+`Setsid` and **not** the `systemd-run` binary.
### Scope
- New `pkg/os` helper (sibling of `StartService`/`RestartService`): `StartTransientUnit`-based spawner that takes a unit name + argv and creates a `oneshot` transient service with `CollectMode=inactive-or-failed`; `mode="fail"` gives a free single-flight guard (errors if an upgrade unit already exists).
- Wire the daemon-side spawn (#716) to call this via the root sudo hop instead of #525's Setsid mechanics.
- The child command itself (#523) is unchanged; it simply runs inside the transient unit.
### Acceptance criteria
- Worker survives `systemctl stop`/`restart` of the daemon (own cgroup).
- Worker runs unsandboxed (can write `/opt/solo` **and** `/usr/local/bin`; real `/tmp`).
- No `KillMode` change to the daemon unit; the daemon keeps `control-group` cleanup semantics for its normal children.
- Concurrent trigger is rejected (single-flight via `mode="fail"` and/or the state file `operationId`).
- Transient unit is garbage-collected after it exits/fails (`CollectMode`).
### Relationship to #525
If the team adopts this (Option B), #525 (Setsid+Setpgid+Release) should be closed as superseded, and #716's "spawn via #525" reference updated to this story.
### Depends on / relates to
- #716 (daemon-side detect + spawn), #523 (the `self upgrade` child), #529 (state file), #698 (verified download).
Contributor guide
Research direction
Start by reading pkg/os/systemd.go and the existing internal/daemon/privexec sudo seam, then trace #716's daemon-side spawn and #523's self upgrade command. Define the new pkg/os transient-unit helper and root CLI handoff around the stated D-Bus properties; done means the worker survives daemon stop, runs unsandboxed, rejects concurrent triggers, and is collected after exit.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, linux
- Domain
- cli, devops, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100