Skip to content

Commit 1ee4db7

Browse files
committed
fix(install): daemon systemd unit Restart=on-failure → Restart=always
SIGTERM sends an exit(0); on-failure would not restart the daemon after an updater-triggered restart. Restart=always matches the updater unit and ensures the daemon comes back after any clean shutdown. Also adds skill auto-injection status output at end of install and ignores the root spoof compiled binary.
1 parent f2560a6 commit 1ee4db7

4 files changed

Lines changed: 55 additions & 10 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,4 @@ tests/integration/local/logs.rerun*
168168
# Untracked sandboxes / personal scratch — never commit
169169
abpn/
170170
scripts/pilot-geo-exporter/
171+
/spoof

install.sh

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ ExecStart=${BIN_DIR}/pilot-daemon \\
320320
-identity ${PILOT_DIR}/identity.json \\
321321
-email ${EMAIL} \\
322322
-encrypt ${HOSTNAME_FLAG} ${PUBLIC_FLAG}
323-
Restart=on-failure
323+
Restart=always
324324
RestartSec=5
325325
326326
[Install]
@@ -499,3 +499,30 @@ echo ""
499499
echo " sudo ${BIN_DIR}/pilotctl gateway start --ports 80,3000 <pilot-addr>"
500500
echo " curl http://10.4.0.1:3000/status"
501501
echo ""
502+
echo "Agent skill auto-injection:"
503+
echo ""
504+
echo " The daemon scans every 15 minutes and injects the Pilot Protocol"
505+
echo " skill into installed agent tools. Triggering a first pass right now"
506+
echo " so your agents know about Pilot before the daemon is even started:"
507+
echo ""
508+
if "${BIN_DIR}/pilotctl" skills check 2>&1 | sed 's/^/ /'; then
509+
:
510+
else
511+
echo " (skills check failed — non-fatal; will re-attempt on daemon start)"
512+
fi
513+
echo ""
514+
echo " Per-tool target paths:"
515+
echo " Claude Code ~/.claude/skills/pilot-protocol/SKILL.md"
516+
echo " + heartbeat ref in ~/.claude/CLAUDE.md"
517+
echo " OpenClaw ~/.openclaw/skills/pilot-protocol/SKILL.md"
518+
echo " + heartbeat ref in ~/.openclaw/workspace/AGENTS.md"
519+
echo " PicoClaw ~/.picoclaw/workspace/skills/pilot-protocol/SKILL.md"
520+
echo " + heartbeat ref in ~/.picoclaw/workspace/AGENT.md"
521+
echo " OpenHands ~/.openhands/microagents/pilot-protocol.md (self-heartbeat)"
522+
echo " Hermes ~/.hermes/skills/pilot-protocol/SKILL.md"
523+
echo " + heartbeat ref in ~/.hermes/SOUL.md"
524+
echo ""
525+
echo " Inspect / force a refresh anytime:"
526+
echo " pilotctl skills # status of every install path"
527+
echo " pilotctl skills check # run one reconcile pass right now"
528+
echo ""

pkg/updater/updater.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type Updater struct {
5050
client *http.Client
5151
stopCh chan struct{}
5252
wg sync.WaitGroup
53+
exitFn func(int) // injectable for testing; defaults to os.Exit
5354
}
5455

5556
// GitHubRelease represents a subset of the GitHub release API response.
@@ -70,6 +71,7 @@ func New(cfg Config) *Updater {
7071
config: cfg,
7172
client: &http.Client{Timeout: 30 * time.Second},
7273
stopCh: make(chan struct{}),
74+
exitFn: os.Exit,
7375
}
7476
}
7577

@@ -294,7 +296,7 @@ func (u *Updater) applyUpdate(release *GitHubRelease) error {
294296
if updaterReplaced {
295297
os.RemoveAll(tmpDir)
296298
slog.Info("updater binary replaced — exiting for process manager to restart with new binary")
297-
os.Exit(0)
299+
u.exitFn(0)
298300
}
299301

300302
// Signal daemon to restart (SIGTERM for graceful shutdown).

pkg/updater/updater_test.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,11 @@ func TestApplyUpdate_SkipsServerBinaries(t *testing.T) {
286286
installDir := filepath.Join(tmpDir, "bin")
287287
os.MkdirAll(installDir, 0755)
288288

289-
// Seed existing binaries (client + server).
290-
os.WriteFile(filepath.Join(installDir, "daemon"), []byte("old-daemon"), 0755)
289+
// Seed existing binaries using installed names (install.sh adds pilot- prefix).
290+
os.WriteFile(filepath.Join(installDir, "pilot-daemon"), []byte("old-pilot-daemon"), 0755)
291+
os.WriteFile(filepath.Join(installDir, "pilot-gateway"), []byte("old-pilot-gateway"), 0755)
292+
os.WriteFile(filepath.Join(installDir, "pilot-updater"), []byte("old-pilot-updater"), 0755)
293+
os.WriteFile(filepath.Join(installDir, "pilotctl"), []byte("old-pilotctl"), 0755)
291294
os.WriteFile(filepath.Join(installDir, "registry"), []byte("old-registry"), 0755)
292295
os.WriteFile(filepath.Join(installDir, "beacon"), []byte("old-beacon"), 0755)
293296
os.WriteFile(filepath.Join(installDir, ".pilot-version"), []byte("v1.0.0\n"), 0644)
@@ -322,10 +325,12 @@ func TestApplyUpdate_SkipsServerBinaries(t *testing.T) {
322325
}))
323326
defer srv.Close()
324327

328+
exitCalled := false
325329
u := &Updater{
326330
config: Config{InstallDir: installDir},
327331
client: srv.Client(),
328332
stopCh: make(chan struct{}),
333+
exitFn: func(int) { exitCalled = true },
329334
}
330335

331336
release := &GitHubRelease{
@@ -340,14 +345,24 @@ func TestApplyUpdate_SkipsServerBinaries(t *testing.T) {
340345
t.Fatalf("applyUpdate: %v", err)
341346
}
342347

343-
// Client binaries should be updated.
344-
for _, name := range []string{"daemon", "pilotctl", "gateway", "updater"} {
345-
data, err := os.ReadFile(filepath.Join(installDir, name))
348+
// updater binary replaced → self-exit should have been triggered.
349+
if !exitCalled {
350+
t.Error("expected exitFn to be called after updater binary replacement")
351+
}
352+
353+
// Client binaries should be updated using installed names (pilot- prefix).
354+
for archiveName, installName := range map[string]string{
355+
"daemon": "pilot-daemon",
356+
"pilotctl": "pilotctl",
357+
"gateway": "pilot-gateway",
358+
"updater": "pilot-updater",
359+
} {
360+
data, err := os.ReadFile(filepath.Join(installDir, installName))
346361
if err != nil {
347-
t.Fatalf("read %s: %v", name, err)
362+
t.Fatalf("read %s (from archive %s): %v", installName, archiveName, err)
348363
}
349-
if string(data) != "new-"+name {
350-
t.Errorf("%s = %q, want %q", name, string(data), "new-"+name)
364+
if string(data) != "new-"+archiveName {
365+
t.Errorf("%s = %q, want %q", installName, string(data), "new-"+archiveName)
351366
}
352367
}
353368

0 commit comments

Comments
 (0)