Documentation
¶
Overview ¶
Package server implements the daemon-side handler for the anovel.v1.CoreService connect-rpc API. The Server type holds the long-lived daemon state — registered stacks, the target supervisor, the env allocator and builder, and the log hub — and exposes each RPC as a method that reads or mutates that state.
Index ¶
- type Server
- func (s *Server) BackupVolume(ctx context.Context, req *connect.Request[anovelv1.BackupVolumeRequest]) (*connect.Response[anovelv1.BackupVolumeResponse], error)
- func (s *Server) ClearVolume(ctx context.Context, req *connect.Request[anovelv1.ClearVolumeRequest]) (*connect.Response[anovelv1.ClearVolumeResponse], error)
- func (s *Server) Debug(_ context.Context, req *connect.Request[anovelv1.DebugRequest]) (*connect.Response[anovelv1.DebugResponse], error)
- func (s *Server) DescribeService(_ context.Context, req *connect.Request[anovelv1.DescribeServiceRequest]) (*connect.Response[anovelv1.DescribeServiceResponse], error)
- func (s *Server) Exec(ctx context.Context, req *connect.Request[anovelv1.ExecRequest], ...) error
- func (s *Server) GetEnv(_ context.Context, req *connect.Request[anovelv1.GetEnvRequest]) (*connect.Response[anovelv1.GetEnvResponse], error)
- func (s *Server) GetTopology(_ context.Context, req *connect.Request[anovelv1.GetTopologyRequest]) (*connect.Response[anovelv1.GetTopologyResponse], error)
- func (s *Server) KillInfra(ctx context.Context, req *connect.Request[anovelv1.KillInfraRequest]) (*connect.Response[anovelv1.KillInfraResponse], error)
- func (s *Server) KillInfraContainer(ctx context.Context, req *connect.Request[anovelv1.KillInfraContainerRequest]) (*connect.Response[anovelv1.KillInfraContainerResponse], error)
- func (s *Server) KillTarget(ctx context.Context, req *connect.Request[anovelv1.KillTargetRequest]) (*connect.Response[anovelv1.KillTargetResponse], error)
- func (s *Server) ListRuns(_ context.Context, req *connect.Request[anovelv1.ListRunsRequest]) (*connect.Response[anovelv1.ListRunsResponse], error)
- func (s *Server) ListServices(_ context.Context, req *connect.Request[anovelv1.ListServicesRequest]) (*connect.Response[anovelv1.ListServicesResponse], error)
- func (s *Server) ListStacks(_ context.Context, _ *connect.Request[anovelv1.ListStacksRequest]) (*connect.Response[anovelv1.ListStacksResponse], error)
- func (s *Server) ListVolumes(_ context.Context, req *connect.Request[anovelv1.ListVolumesRequest]) (*connect.Response[anovelv1.ListVolumesResponse], error)
- func (s *Server) Ping(_ context.Context, _ *connect.Request[anovelv1.PingRequest]) (*connect.Response[anovelv1.PingResponse], error)
- func (s *Server) PrepareReinstall(_ context.Context, _ *connect.Request[anovelv1.PrepareReinstallRequest]) (*connect.Response[anovelv1.PrepareReinstallResponse], error)
- func (s *Server) RestartInfraContainer(ctx context.Context, ...) (*connect.Response[anovelv1.RestartInfraContainerResponse], error)
- func (s *Server) RestartTarget(ctx context.Context, req *connect.Request[anovelv1.RestartTargetRequest]) (*connect.Response[anovelv1.RestartTargetResponse], error)
- func (s *Server) RestoreVolume(ctx context.Context, req *connect.Request[anovelv1.RestoreVolumeRequest]) (*connect.Response[anovelv1.RestoreVolumeResponse], error)
- func (s *Server) Shutdown(ctx context.Context, req *connect.Request[anovelv1.ShutdownRequest]) (*connect.Response[anovelv1.ShutdownResponse], error)
- func (s *Server) ShutdownCh() <-chan struct{}
- func (s *Server) SignalShutdown()
- func (s *Server) StartInfra(ctx context.Context, req *connect.Request[anovelv1.StartInfraRequest]) (*connect.Response[anovelv1.StartInfraResponse], error)
- func (s *Server) StartTarget(ctx context.Context, req *connect.Request[anovelv1.StartTargetRequest]) (*connect.Response[anovelv1.StartTargetResponse], error)
- func (s *Server) Status(_ context.Context, _ *connect.Request[anovelv1.StatusRequest]) (*connect.Response[anovelv1.StatusResponse], error)
- func (s *Server) StreamLogs(ctx context.Context, req *connect.Request[anovelv1.StreamLogsRequest], ...) error
- func (s *Server) Watch(ctx context.Context, req *connect.Request[anovelv1.WatchRequest], ...) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the daemon's connect-rpc handler. It owns the daemon-side state that survives across RPC calls: the registered stacks, the per-target supervisor, the env allocator and builder, and the log hub.
func New ¶
func New(version, socketPath string, stk []stacks.Stack, disc []*discovery.Stack, run *runner.Runner, alloc *env.Allocator, builder *env.Builder, logStore *logs.Store) *Server
New constructs a Server with every daemon-side subsystem wired up.
func (*Server) BackupVolume ¶
func (s *Server) BackupVolume(ctx context.Context, req *connect.Request[anovelv1.BackupVolumeRequest]) (*connect.Response[anovelv1.BackupVolumeResponse], error)
BackupVolume writes a tar.zst snapshot per volume. Refuses if the service is up unless --force (which cascade-kills first).
func (*Server) ClearVolume ¶
func (s *Server) ClearVolume(ctx context.Context, req *connect.Request[anovelv1.ClearVolumeRequest]) (*connect.Response[anovelv1.ClearVolumeResponse], error)
ClearVolume destroys volumes (auto-backups first unless --no-backup). Refuses while service is up.
func (*Server) Debug ¶
func (s *Server) Debug(_ context.Context, req *connect.Request[anovelv1.DebugRequest]) (*connect.Response[anovelv1.DebugResponse], error)
Debug returns instructions for attaching Delve to a running go-exec target. It does not spawn dlv itself — the user runs the printed command in their own terminal so the dlv REPL is interactive and disconnects don't kill the target. (Auto-spawning dlv would require bidi streaming + raw-tty plumbing the daemon doesn't yet have.)
For container-mode targets, Debug is rejected — attaching to a containerised process needs a container-internal dlv install and a port-forward the daemon doesn't manage. Service developers can iterate in go-exec mode (the default) and graduate to container mode for integration tests.
DelvePort is a SUGGESTED port the user can pass to `dlv attach --listen=:<port>` — the daemon doesn't bind it. Chosen as `PID + 20000` (kept inside the high-but-not-privileged range) so a user debugging multiple targets gets a unique-per-PID port without allocation bookkeeping.
func (*Server) DescribeService ¶
func (s *Server) DescribeService(_ context.Context, req *connect.Request[anovelv1.DescribeServiceRequest]) (*connect.Response[anovelv1.DescribeServiceResponse], error)
DescribeService returns one service by (stack, service) lookup.
func (*Server) Exec ¶
func (s *Server) Exec(ctx context.Context, req *connect.Request[anovelv1.ExecRequest], stream *connect.ServerStream[anovelv1.ExecOutput]) error
Exec runs an arbitrary command "as if it were a sibling of the target":
- Container mode: `podman exec <container_id> <cmd...>` — runs inside the live container, so `a-novel exec rest sh` drops you into a shell next to the rest binary. Requires the target to be RUNNING.
- Go-exec mode: spawns the command in the target's working directory with the target's resolved env (POSTGRES_DSN, *_PORT etc.). Useful for `a-novel exec migrations psql` — psql gets the right DSN out of the box. Does not require the target to be running, just discoverable — the env builder synthesises a fresh allocation if none is current (so `a-novel exec migrations psql` works on a cold stack with infra up).
Stdout / stderr are forwarded as LOG_STREAM_STDOUT / _STDERR; the proto reuses LogStream so clients render with the same code path as logs.
func (*Server) GetEnv ¶
func (s *Server) GetEnv(_ context.Context, req *connect.Request[anovelv1.GetEnvRequest]) (*connect.Response[anovelv1.GetEnvResponse], error)
GetEnv assembles the env block for one service (or every service in scope). Read-only: never allocates new ports. Variables that haven't been allocated yet appear with empty string values (so the user can see the shape without side-effects).
func (*Server) GetTopology ¶
func (s *Server) GetTopology(_ context.Context, req *connect.Request[anovelv1.GetTopologyRequest]) (*connect.Response[anovelv1.GetTopologyResponse], error)
GetTopology renders the dependency graph as ASCII text. With Service == "", emits every service in the (default or specified) stack stacked vertically.
func (*Server) KillInfra ¶
func (s *Server) KillInfra(ctx context.Context, req *connect.Request[anovelv1.KillInfraRequest]) (*connect.Response[anovelv1.KillInfraResponse], error)
KillInfra refuses if any long-runner is still running for this service (unless --force, which cascade-kills targets first).
func (*Server) KillInfraContainer ¶
func (s *Server) KillInfraContainer(ctx context.Context, req *connect.Request[anovelv1.KillInfraContainerRequest]) (*connect.Response[anovelv1.KillInfraContainerResponse], error)
KillInfraContainer stops one infra container by (stack, service, name) — leaves the rest of the service's infra (and any running targets) untouched. Used by the TUI to manage infra entries like any other tab.
func (*Server) KillTarget ¶
func (s *Server) KillTarget(ctx context.Context, req *connect.Request[anovelv1.KillTargetRequest]) (*connect.Response[anovelv1.KillTargetResponse], error)
KillTarget stops the named instance with the requested SIGTERM grace (defaults to 10s; 0 means immediate SIGKILL). Idempotent.
func (*Server) ListRuns ¶
func (s *Server) ListRuns(_ context.Context, req *connect.Request[anovelv1.ListRunsRequest]) (*connect.Response[anovelv1.ListRunsResponse], error)
ListRuns returns the timestamps of archived runs for the target, newest first.
func (*Server) ListServices ¶
func (s *Server) ListServices(_ context.Context, req *connect.Request[anovelv1.ListServicesRequest]) (*connect.Response[anovelv1.ListServicesResponse], error)
ListServices returns every service in the requested stack. Request.Stack of "" → default stack; "*" → union across every registered stack (preserving stack name in each returned Service for disambiguation).
func (*Server) ListStacks ¶
func (s *Server) ListStacks(_ context.Context, _ *connect.Request[anovelv1.ListStacksRequest]) (*connect.Response[anovelv1.ListStacksResponse], error)
ListStacks returns every registered stack. Stacks are fixed at startup, so this reads them straight from state.
func (*Server) ListVolumes ¶
func (s *Server) ListVolumes(_ context.Context, req *connect.Request[anovelv1.ListVolumesRequest]) (*connect.Response[anovelv1.ListVolumesResponse], error)
ListVolumes returns one Volume row per compose-declared volume on the service, with size + backup count. Read-only.
func (*Server) Ping ¶
func (s *Server) Ping(_ context.Context, _ *connect.Request[anovelv1.PingRequest]) (*connect.Response[anovelv1.PingResponse], error)
Ping is the cheap handshake clients use to verify the daemon is alive. `core start` uses it to detect an already-running instance.
func (*Server) PrepareReinstall ¶
func (s *Server) PrepareReinstall(_ context.Context, _ *connect.Request[anovelv1.PrepareReinstallRequest]) (*connect.Response[anovelv1.PrepareReinstallResponse], error)
PrepareReinstall writes a checkpoint listing every running go-exec target (with its env, for relaunch), fsyncs it, then signals the daemon to shut down. Containers are not included — they survive the daemon's death independently.
Concurrency: rejects a second PrepareReinstall if one is already pending (the checkpoint file's existence is the guard).
func (*Server) RestartInfraContainer ¶
func (s *Server) RestartInfraContainer(ctx context.Context, req *connect.Request[anovelv1.RestartInfraContainerRequest]) (*connect.Response[anovelv1.RestartInfraContainerResponse], error)
RestartInfraContainer restarts one infra container in place (podman restart — stop+start without recreating, preserves volume bindings).
func (*Server) RestartTarget ¶
func (s *Server) RestartTarget(ctx context.Context, req *connect.Request[anovelv1.RestartTargetRequest]) (*connect.Response[anovelv1.RestartTargetResponse], error)
RestartTarget is Kill + Start in one RPC. Mutual-exclusion-safe: the kill completes before the start tries to take the slot.
func (*Server) RestoreVolume ¶
func (s *Server) RestoreVolume(ctx context.Context, req *connect.Request[anovelv1.RestoreVolumeRequest]) (*connect.Response[anovelv1.RestoreVolumeResponse], error)
RestoreVolume replaces each volume from the matching backup. Refuses while service is up.
func (*Server) Shutdown ¶
func (s *Server) Shutdown(ctx context.Context, req *connect.Request[anovelv1.ShutdownRequest]) (*connect.Response[anovelv1.ShutdownResponse], error)
Shutdown is the no-checkpoint daemon stop. With force=false it SIGTERMs every running go-exec target with a 10s grace each and leaves containers alone. With force=true it also calls KillInfra(force=true) on every service that has an active infra session — cascade-killing any remaining targets and tearing down infra containers.
Concurrency: just like PrepareReinstall, the shutdown signal is fired after the response is sent so the client gets a clean reply.
func (*Server) ShutdownCh ¶
func (s *Server) ShutdownCh() <-chan struct{}
ShutdownCh returns a channel the daemon's main loop waits on. Closed when SignalShutdown is called (e.g., by PrepareReinstall after the checkpoint is durable).
func (*Server) SignalShutdown ¶
func (s *Server) SignalShutdown()
SignalShutdown closes the shutdown channel. Idempotent.
func (*Server) StartInfra ¶
func (s *Server) StartInfra(ctx context.Context, req *connect.Request[anovelv1.StartInfraRequest]) (*connect.Response[anovelv1.StartInfraResponse], error)
StartInfra brings up a service's infrastructure containers and auto-runs every one-shot target whose successful completion the long-runners depend on. Idempotent.
func (*Server) StartTarget ¶
func (s *Server) StartTarget(ctx context.Context, req *connect.Request[anovelv1.StartTargetRequest]) (*connect.Response[anovelv1.StartTargetResponse], error)
StartTarget brings up the named target in the requested mode (defaults to go-exec), synthesizing the target's env before launch.
func (*Server) Status ¶
func (s *Server) Status(_ context.Context, _ *connect.Request[anovelv1.StatusRequest]) (*connect.Response[anovelv1.StatusResponse], error)
Status reports everything `a-novel core status` needs in one round-trip.
func (*Server) StreamLogs ¶
func (s *Server) StreamLogs(ctx context.Context, req *connect.Request[anovelv1.StreamLogsRequest], stream *connect.ServerStream[anovelv1.LogLine]) error
StreamLogs server-streams log lines for a target. Three modes:
- default: snapshot of current.log from start to end (one shot)
- --follow: snapshot + subscribe to new lines until proc terminates or client disconnects
- --run-id: archived run (no follow regardless)
Order: archived/snapshot lines come first, in file order; new lines (when --follow) interleave after. Each emitted LogLine carries the original timestamp + stream tag.
func (*Server) Watch ¶
func (s *Server) Watch(ctx context.Context, req *connect.Request[anovelv1.WatchRequest], stream *connect.ServerStream[anovelv1.StateEvent]) error
Watch streams every phase transition the runner emits, filtered by the caller's (stack, service, target_id) tuple. An empty filter field means "match any value" — clients usually pass (stack, service, "") to watch one service or all three empty to watch everything.
The stream stays open until the client cancels its side (or the daemon shuts down — the runner's emit fanout simply stops). No snapshot is emitted up-front; callers that need the initial state should pair Watch with a `ListServices` snapshot themselves (the CLI's `ps --watch` pattern).