hashgraph / hashgraph/solo-weaver

Story: Spawn self-upgrade worker as a root-created transient systemd unit (Option B; alternative to #525)

Open
#1,139 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.