fix(felhomsshd): persist claimed port in agent StateDir (non-root can't write /etc)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,11 +1,6 @@
|
|||||||
package felhomsshd
|
package felhomsshd
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Candidates is the ordered OOB-port candidate list (spike §2). First free wins; NEVER :22 or a
|
// Candidates is the ordered OOB-port candidate list (spike §2). First free wins; NEVER :22 or a
|
||||||
// random port. Package-var (not const) so tests can shrink it.
|
// random port. Package-var (not const) so tests can shrink it.
|
||||||
@@ -42,24 +37,3 @@ func claimPort(candidates []int, isFree portProbe, readPersisted func() (int, bo
|
|||||||
}
|
}
|
||||||
return 0, ErrPortsExhausted
|
return 0, ErrPortsExhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPortFile parses PortFile → (port, ok). A missing/garbage file → (0,false).
|
|
||||||
func readPortFile() (int, bool) {
|
|
||||||
raw, err := os.ReadFile(PortFile)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
p, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
|
||||||
if err != nil || p < 1 || p > 65535 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return p, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// writePortFile persists the claimed port (0644 — a port is not a secret).
|
|
||||||
func writePortFile(port int) error {
|
|
||||||
if err := os.MkdirAll(ConfDir, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(PortFile, []byte(strconv.Itoa(port)+"\n"), 0o644)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -74,6 +74,28 @@ func (m *Manager) Port() int { return m.port }
|
|||||||
|
|
||||||
func (m *Manager) sshdDir() string { return filepath.Join(m.stateDir, "felhom-sshd") }
|
func (m *Manager) sshdDir() string { return filepath.Join(m.stateDir, "felhom-sshd") }
|
||||||
func (m *Manager) stagedConfPath() string { return filepath.Join(m.sshdDir(), stagedConfName) }
|
func (m *Manager) stagedConfPath() string { return filepath.Join(m.sshdDir(), stagedConfName) }
|
||||||
|
func (m *Manager) portFilePath() string { return filepath.Join(m.sshdDir(), "port") }
|
||||||
|
|
||||||
|
// readPort/writePort persist the claimed port in the AGENT-OWNED state dir (the agent is non-root and
|
||||||
|
// cannot write the root-owned /etc/felhom-sshd). A port is not a secret.
|
||||||
|
func (m *Manager) readPort() (int, bool) {
|
||||||
|
raw, err := os.ReadFile(m.portFilePath())
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
p, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
||||||
|
if err != nil || p < 1 || p > 65535 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) writePort(port int) error {
|
||||||
|
if err := os.MkdirAll(m.sshdDir(), 0o700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(m.portFilePath(), []byte(strconv.Itoa(port)+"\n"), 0o600)
|
||||||
|
}
|
||||||
|
|
||||||
// Apply claims the port, renders the config, reconciles the running unit, and (when the desired-state
|
// Apply claims the port, renders the config, reconciles the running unit, and (when the desired-state
|
||||||
// block carries it) installs the operator's authorized_keys. Idempotent: no change → at most an
|
// block carries it) installs the operator's authorized_keys. Idempotent: no change → at most an
|
||||||
@@ -81,7 +103,7 @@ func (m *Manager) stagedConfPath() string { return filepath.Join(m.sshdDir(), st
|
|||||||
// operator login/belt inputs are skipped. Returns the claimed port (0 on a claim/exhaustion error) —
|
// operator login/belt inputs are skipped. Returns the claimed port (0 on a claim/exhaustion error) —
|
||||||
// the caller (belt sync) needs it.
|
// the caller (belt sync) needs it.
|
||||||
func (m *Manager) Apply(ctx context.Context, block *hub.WireWireguard) (int, error) {
|
func (m *Manager) Apply(ctx context.Context, block *hub.WireWireguard) (int, error) {
|
||||||
port, err := claimPort(Candidates, m.isFree, readPortFile, writePortFile)
|
port, err := claimPort(Candidates, m.isFree, m.readPort, m.writePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("felhomsshd: port claim failed", "err", err)
|
m.logger.Error("felhomsshd: port claim failed", "err", err)
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
Reference in New Issue
Block a user