OXYGENHUB

Run the daemon on Earth: make install-server, a unit that waits for a Spectra (#25) aa72eb904932

make install copies the binary instead of linking into the build tree;
install-server does the same over SSH with the user unit; the unit waits
until a Spectra exists. Earth now serves, set up for Andy's KeyId and
waiting for his claim. The README describes the two-machine setup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 0e66123960d9
When
· 2026-11-03 13:48 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
aa72eb9049325388138d098a87899003a63a605676d8b5b5620b18cbc0623310
Depends on
01320eefd505 200875410937 258bb3824813 98a16830a3d3 d17cbc7069ce
Depended on by
862f0db224aa 98a0fb9f0948 813b6330f3ad
In bookmarks
main
In tags
v0.1.0
Edits
17 atoms, 1 tree operation, 6 files

Browse the tree at this change

added scripts/install-server.sh +35 −0
1 +
#!/bin/sh
2 +
# Install `spectra serve` on another machine as a systemd user service
3 +
# (spec §8.6, spectra#25): build a release here, copy a stripped binary and
4 +
# the unit over SSH, and enable it. The unit waits until a Spectra exists
5 +
# there (`spectra init --owner KEYID`), and starts from boot when the
6 +
# account has lingering on.
7 +
#
8 +
#   scripts/install-server.sh [USER@]HOST
9 +
set -eu
10 +
[ $# -eq 1 ] || { echo "usage: $0 [USER@]HOST" >&2; exit 2; }
11 +
target=$1
12 +
cd "$(dirname "$0")/.."
13 +
SPECTRA_BUILD="$(oxy --plain describe 2>/dev/null || true)" CARGO_INCREMENTAL=0 cargo build --release -p spectra
14 +
bin=${CARGO_TARGET_DIR:-target}/release/spectra
15 +
tmp=$(mktemp -d)
16 +
trap 'rm -rf "$tmp"' EXIT
17 +
strip -o "$tmp/spectra" "$bin"
18 +
ssh -o BatchMode=yes "$target" 'mkdir -p ~/.local/bin ~/.config/systemd/user'
19 +
scp -q -o BatchMode=yes "$tmp/spectra" "$target:.local/bin/spectra.new"
20 +
scp -q -o BatchMode=yes packaging/spectra.service "$target:.config/systemd/user/spectra.service"
21 +
ssh -o BatchMode=yes "$target" '
22 +
set -e
23 +
chmod 755 ~/.local/bin/spectra.new && mv ~/.local/bin/spectra.new ~/.local/bin/spectra
24 +
systemctl --user daemon-reload
25 +
systemctl --user enable spectra >/dev/null 2>&1
26 +
if systemctl --user is-active --quiet spectra; then systemctl --user restart spectra; fi
27 +
echo "installed: $(~/.local/bin/spectra --version)"
28 +
if [ -e ~/.local/share/spectra/spectra.db ]; then
29 +
  systemctl --user start spectra
30 +
  echo "service: $(systemctl --user is-active spectra)"
31 +
else
32 +
  echo "service: enabled; waiting for a Spectra: spectra init --owner KEYID, then systemctl --user start spectra"
33 +
fi
34 +
echo "linger: $(loginctl show-user "$(id -un)" -p Linger --value) (yes means it runs from boot)"
35 +
'
modified CHANGELOG.md +1 −0
…
1 unchanged lines
2 2
3 3
## Unreleased
4 4
5 +
- **Added: `make install-server HOST=user@host` (#25).** Builds a release, copies a stripped binary and the systemd user unit over SSH, and enables the service. The unit now waits until a Spectra exists instead of failing in a loop. `make install` copies the binary rather than linking into the build tree, so cleaning a build directory no longer breaks an installed service. The README describes setting up the daemon on another machine.
5 6
- **Added: Radar from another machine (#24).** With a server configured, `spectra open` serves Radar on `http://127.0.0.1:7350` (`--port` to change it) through a proxy to the pinned server, and signs you in through it; pages, forms and the live stream all pass through. The proxy answers only its own loopback host and origin, so no other site can reach the server through it. Ctrl-C stops it.
6 7
- **Changed: the command line works from another machine (#23).** With a server configured by `spectra connect`, every command goes through the JSON API and the workstation needs no ledger file: `identity show`, `agent add`, `list` and `disable`, `project add` and `list`, `say`, `inbox`, `search`, `api` and `admin verify`, which the server runs on its own ledger (`GET /api/v1/admin/verify`). `admin check` and `rebuild` say they run on the server. Locally, commands read through the daemon whenever it is serving, so the same path is exercised on one machine and on two. The API's agents now carry their delegation state and expiry, projects their agents, and inbox items and search hits their author's display name.
7 8
- **Added: `spectra connect https://HOST[:PORT]` (#21).** Uses a Spectra on another machine. It trusts the server only when your own identity delegates to its instance key and that key vouches for the certificate it presents; until you have delegated, it saves the instance's public identity and prints the `oxy identity delegate add` to run. Then it pins the certificate in `~/.config/spectra/client.toml` (or `SPECTRA_SERVER`), claims a server set up with `spectra init --owner`, and brings the server up to your current identity. A certificate that changes is refused until `connect` checks it again. The stdio shim uses the configured server (with `--home`).
…
17 unchanged lines
modified Makefile +7 −2
1 -
.PHONY: build check test fmt clippy changelog run ts install deps-check
1 +
.PHONY: install-server build check test fmt clippy changelog run ts install deps-check
2 2
3 3
# Spectra's own packages. `--workspace` and `--all` also reach the pinned
4 4
# Oxygen in deps/ (a path dependency), whose tests and lints are its own.
…
27 unchanged lines
32 32
# Build and install spectra with its build identity (oxy describe) in --version.
33 33
install:
34 34
	SPECTRA_BUILD="$$(oxy --plain describe 2>/dev/null)" CARGO_INCREMENTAL=0 cargo build --release
35 -
	ln -sf $${CARGO_TARGET_DIR:-$(CURDIR)/target}/release/spectra $(HOME)/.local/bin/spectra
35 +
	install -D -m 755 $${CARGO_TARGET_DIR:-$(CURDIR)/target}/release/spectra $(HOME)/.local/bin/spectra
36 36
	$(HOME)/.local/bin/spectra --version
37 37
38 +
# The daemon on another machine over SSH (spec §8.6): make install-server HOST=user@host
39 +
install-server:
40 +
	@test -n "$(HOST)" || { echo "make install-server HOST=[USER@]HOST" >&2; exit 2; }
41 +
	scripts/install-server.sh $(HOST)
42 +
38 43
# Every crate in Cargo.lock must be one the OxygenFlow build image vendors
39 44
# (oxygenflow images/rust: the crates of oxygen, oxygenhub and oxygenflow), or
40 45
# CI cannot build offline (spectra#2).
…
2 unchanged lines
modified README.md +33 −1
…
16 unchanged lines
17 17
```
18 18
cargo build --release
19 19
make check            # changelog, deps-check, fmt, clippy -D warnings, tests
20 -
make install          # release build with its build identity, linked into ~/.local/bin
20 +
make install          # release build with its build identity, copied into ~/.local/bin
21 +
make install-server HOST=user@host   # the same, over SSH, with the systemd user unit
21 22
```
22 23
23 24
`make deps-check` keeps every crate on a version the OxygenFlow build image vendors, so CI builds with no network; after adding a dependency, `scripts/deps-pin.sh` moves new crates onto vendored versions.
…
33 unchanged lines
57 58
58 59
Open Radar with `spectra open`. From a terminal: `spectra say "…"`, `spectra inbox --follow`, `spectra search "pin bump"`, `spectra agent list`, and `spectra admin verify`.
59 60
61 +
## The daemon on another machine
62 +
63 +
Spectra can run on a server while you, your keys and your agents stay on your workstation (spec §8.6). Nothing secret goes to the server: it holds only its own instance key, and every request is still signed where the key lives. Traffic is TLS with a certificate the server makes itself; your workstation trusts it because your own identity delegates to the server's instance key, which vouches for the certificate. No certificate authority is involved.
64 +
65 +
On the workstation, install the server and note your KeyId:
66 +
67 +
```
68 +
make install-server HOST=claude@earth
69 +
oxy identity show                              # the 64-hex key
70 +
```
71 +
72 +
On the server:
73 +
74 +
```
75 +
spectra init --owner KEYID --name earth        # no key of yours is needed there
76 +
# in ~/.local/share/spectra/spectra.toml, under [server]:
77 +
#   bind = "[::]:7350"
78 +
#   names = ["earth", "earth.example"]
79 +
systemctl --user start spectra
80 +
```
81 +
82 +
On the workstation:
83 +
84 +
```
85 +
spectra connect https://earth.example:7350     # prints the delegation to run, the first time
86 +
oxy identity delegate add ~/.config/spectra/earth-instance.identity --scope changes
87 +
spectra connect https://earth.example:7350     # trusted, pinned, and claimed as yours
88 +
```
89 +
90 +
From then on every command on the workstation works against the server, with no ledger file locally. Agents name their home, since it is on the workstation: `spectra mcp --agent claude-oxygen --home ~/.oxy-agents/claude-oxygen`. `spectra open` serves Radar on `http://127.0.0.1:7350` through a proxy to the server.
91 +
60 92
Output uses colour and glyphs on a UTF-8 terminal, like `oxy`'s, and falls back to plain ASCII when piped, with `--plain`, or with `NO_COLOR`.
61 93
62 94
## Layout
…
16 unchanged lines
modified docs/plan.md +5 −5
…
34 unchanged lines
35 35
- [x] #21 spectra connect — done 2026-11-03: trust through the owner's delegation to the instance key, pin, claim with the rotation delegating causes; tests/connect.rs, and the shim over TLS in tests/client.rs.
36 36
- [x] #23 The CLI without a ledger file — done 2026-11-03: every command through the API when a daemon serves, local or remote; admin verify via the server; tests/client.rs runs them all from a workstation with no data directory.
37 37
- [x] #24 Radar from a workstation — done 2026-11-03: spectra open serves a loopback proxy to the pinned server and signs in through it; tests/client.rs; checked by hand in Chrome on a demo server with a real oxy identity (sign-in, live update from the CLI, a post from the compose box, verify).
38 -
- [ ] #25 Run the daemon on Earth: install over SSH, a unit that waits for a Spectra, README.
38 +
- [x] #25 Run the daemon on Earth — done 2026-11-03: make install-server, a unit that waits for a Spectra, make install copies; Earth runs `spectra serve` on [::]:7350 as the claude account from boot, set up with `spectra init --owner d89664ced1fc…` (Andy's public KeyId) and waiting for his claim; /api/v1/instance answers from the workstation.
39 39
- [ ] #13 Acceptance (with Andy), across two machines. Ready when the rest is done:
40 -
  1. On Earth (the claude account, no key of Andy's): `spectra init --owner KEYID`; the service starts.
41 -
  2. On the workstation: `spectra connect https://earth.andymccall.xyz:7350`; run the printed `oxy identity delegate add … --scope changes`; `spectra connect` again, which claims Earth as Andy's.
40 +
  1. Done for Andy: Earth runs the daemon, set up for his KeyId and waiting for his claim. Before step 2, `spectra --version` here should be the release (`make install`).
41 +
  2. On the workstation: `spectra connect https://earth.andymccall.xyz:7350`; run the printed `oxy identity delegate add ~/.config/spectra/earth-instance.identity --scope changes`; `spectra connect` again, which claims Earth as Andy's.
42 42
  3. `spectra project add ~/development/oxygen` and `~/development/oxygenhub`.
43 43
  4. `spectra agent add claude-oxygen --kind claude-code --project oxygen --home ~/.oxy-agents/claude-oxygen`, then the printed delegation; the same for `antigravity-oxygenhub`.
44 -
  5. Claude Code in oxygen: `claude mcp add spectra -- spectra mcp --agent claude-oxygen`; Antigravity in oxygenhub: the README's `mcp_config.json` line.
44 +
  5. Claude Code in oxygen: `claude mcp add spectra -- spectra mcp --agent claude-oxygen --home ~/.oxy-agents/claude-oxygen`; Antigravity in oxygenhub: the README's `mcp_config.json` line with `--home` added.
45 45
  6. Both call `brief`, post in #general, and see each other's messages; watch with `spectra open`.
46 -
  7. `spectra admin verify`; on a copy of Earth's database, edit a row and see verify catch it.
46 +
  7. `spectra admin verify` (Earth verifies its own ledger); on Earth, on a copy of the database, edit a row and see `spectra --data COPY admin verify` catch it.
47 47
  8. Cut 0.1.0 in CHANGELOG.md and tag it.
48 48
49 49
## Log
…
7 unchanged lines
modified packaging/spectra.service +5 −1
…
1 unchanged lines
2 2
# they start, so it should outlive any one terminal.
3 3
#
4 4
#   cp packaging/spectra.service ~/.config/systemd/user/
5 +
#   (on another machine: make install-server HOST=user@host)
5 6
#   systemctl --user daemon-reload && systemctl --user enable --now spectra
6 7
#   journalctl --user -u spectra -f
7 8
[Unit]
8 9
Description=Spectra, the control plane for AI coding agents
9 -
After=default.target
10 +
After=default.target network-online.target
11 +
# Until `spectra init` (or `spectra init --owner KEYID` on a server) makes
12 +
# a Spectra, the enabled service waits instead of failing in a loop.
13 +
ConditionPathExists=%h/.local/share/spectra/spectra.db
10 14
11 15
[Service]
12 16
ExecStart=%h/.local/bin/spectra serve
…
9 unchanged lines