server: Go skeleton — control plane, UDP data plane, Docker + systemd modes
Pure stdlib. Implements the spec's core: enrollment (single-use tokens), profile (SPKI pin, only real capabilities advertised), sessions with the §2.4 HKDF-SHA256 key schedule; UDP data plane with the 32-byte ELT1 header, 4-byte HMAC gate, 1024-wide anti-replay window, ECHO_RESP with observation block, TIMESYNC, and the §3.4 anti-amplification cap. Wire format has tests (roundtrip + silent-drop cases); enroll→profile→session smoke-tested live. Modes: container (autodetect /.dockerenv|/run/.containerenv|cgroup, or --docker/ECHOLOT_DOCKER=1; config via ECHOLOT_* env; distroless image; network_mode host required — Docker NAT would falsify observed sources) and native (--install-systemd/--uninstall-systemd with a hardened unit, opt-in --self-update from Gitea releases; refused in containers). CI: tests on any server/ push; server-v* tags build+push the image to the Gitea registry and attach linux amd64/arm64 binaries + SHA256SUMS to a release — the artifact self-update consumes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ee66648e3c
commit
8a80026d49
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Package system implements native-host lifecycle: systemd unit install /
|
||||
// uninstall. Linux-only by nature; on other OSes the commands fail with a
|
||||
// clear message rather than pretending.
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const unitPath = "/etc/systemd/system/echolot-server.service"
|
||||
|
||||
const unitTemplate = `[Unit]
|
||||
Description=Echolot probe server
|
||||
Documentation=https://echo-lot.app
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=%s
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StateDirectory=echolot-server
|
||||
Environment=ECHOLOT_STATE_DIR=/var/lib/echolot-server
|
||||
# Hardening — the server needs sockets and its state dir, nothing else.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/var/lib/echolot-server
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`
|
||||
|
||||
// InstallSystemd writes the unit for THIS binary (absolute path), reloads
|
||||
// systemd, and enables the service. Idempotent.
|
||||
func InstallSystemd(extraArgs []string) error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return fmt.Errorf("--install-systemd is Linux-only (this is %s)", runtime.GOOS)
|
||||
}
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self, err = filepath.EvalSymlinks(self)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
execStart := self
|
||||
for _, a := range extraArgs {
|
||||
execStart += " " + a
|
||||
}
|
||||
if err := os.WriteFile(unitPath, []byte(fmt.Sprintf(unitTemplate, execStart)), 0o644); err != nil {
|
||||
return fmt.Errorf("writing %s (need root?): %w", unitPath, err)
|
||||
}
|
||||
for _, cmd := range [][]string{
|
||||
{"systemctl", "daemon-reload"},
|
||||
{"systemctl", "enable", "--now", "echolot-server.service"},
|
||||
} {
|
||||
if out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("%v: %s: %w", cmd, out, err)
|
||||
}
|
||||
}
|
||||
fmt.Printf("installed + started echolot-server.service (ExecStart=%s)\n", execStart)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UninstallSystemd() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return fmt.Errorf("--uninstall-systemd is Linux-only (this is %s)", runtime.GOOS)
|
||||
}
|
||||
// Stop/disable first; ignore "not loaded" errors so uninstall is idempotent.
|
||||
_ = exec.Command("systemctl", "disable", "--now", "echolot-server.service").Run()
|
||||
if err := os.Remove(unitPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
_ = exec.Command("systemctl", "daemon-reload").Run()
|
||||
fmt.Println("removed echolot-server.service (state dir left in place)")
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user