migrate

package
v2.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package migrate handles workspace migration for alpamon.

Migration moves an in-place alpamon agent from one Alpacon workspace to another while preserving the only access channel (the WebSocket back to Alpacon). The flow is split into three pieces:

  1. The `alpamon migrate` subcommand (in cmd/alpamon/command/migrate) calls the target workspace's register API, atomically swaps /etc/alpamon/alpamon.conf, writes a marker file, and schedules a transient systemd timer to `systemctl restart alpamon` shortly after.

  2. On the next startup the agent (root.go) loads the marker file. If present, it arms a watchdog and registers a connect-success hook on the WebSocket client. Confirm() clears the marker on the first successful auth handshake.

  3. If the watchdog timer elapses without Confirm() having fired (i.e. the new workspace never accepted the agent), Rollback() restores the backup config, best-effort unregisters the orphan record on the target workspace, and schedules another self-restart. The agent comes back up on the original workspace.

All durable state is committed in a strict order so that a crash anywhere in the sequence is recoverable on the next startup:

BackupConf  →  WritePending  →  WriteConfAtomic  →  ScheduleSelfRestart

If we die after BackupConf but before WritePending: an orphan .bak file lingers, no behavior change. If we die after WritePending but before the conf swap: the marker points at a backup that matches the running conf; the next watchdog tick restores it (no-op) and clears the marker. If we die after the conf swap but before scheduling the restart: the agent continues running with the old conf in memory, but the next restart (manual or otherwise) picks up the new conf and the watchdog protects us either way.

Index

Constants

View Source
const DefaultRollbackTimeout = 5 * time.Minute

DefaultRollbackTimeout is the default deadline for the post-migration watchdog. Tuned long enough to absorb transient network hiccups during the first reconnect, short enough that a stuck migration recovers before an operator gives up and pages someone.

View Source
const MaxRollbackTimeout = 1 * time.Hour

MaxRollbackTimeout caps how long the marker file (which carries the new workspace's server key in plaintext) can sit on disk. Even though the same key is in /etc/alpamon/alpamon.conf during the same window, we don't want operators accidentally extending the on-disk lifetime to days via an inflated --rollback-timeout flag.

Variables

This section is empty.

Functions

func BackupConf

func BackupConf(srcPath string) (string, error)

BackupConf copies srcPath to a timestamped sibling and returns the backup path. The backup keeps the source's mode bits so a restore produces an identical file. Both the file contents and the parent directory entry are fsync'd so a power loss right after this call does not lose the backup.

func BestEffortUnregister

func BestEffortUnregister(targetURL, serverID, serverKey string, sslVerify bool, caCertPath string)

BestEffortUnregister calls the target workspace's unregister endpoint as the just-created server. Used by Rollback (to remove the orphan record on the workspace we abandoned) and by the migrate subcommand's own error paths. Failures (network, auth, missing systemd, etc.) are logged but not surfaced — the caller is already on an error path.

A short deadline is enforced so a hung B-side endpoint cannot stall rollback or block the operator's terminal.

func ClearPending

func ClearPending() error

ClearPending removes the marker file. Idempotent.

func Confirm

func Confirm(st *PendingState)

Confirm finalizes a successful migration: removes the marker and the backup conf. Safe to call with a nil state. Logged failures are not surfaced because by the time Confirm runs, the WebSocket to the new workspace is up and there is no rollback path left.

func MarkerPath

func MarkerPath() string

MarkerPath returns the absolute path of the in-flight-migration marker.

func RestoreBackup

func RestoreBackup(backupPath, destPath string) error

RestoreBackup copies backupPath over destPath via WriteConfAtomic so the destination is updated atomically. The backup file's mode bits are preserved (rather than forced to 0600) so a rollback yields an identical file when the operator had previously set tighter or different permissions on the original conf.

func Rollback

func Rollback(state *PendingState, confPath string, sslVerify bool, caCertPath string) error

Rollback executes the recovery sequence: restore the backup config, best-effort unregister the orphan record on the target workspace, schedule a self-restart, and only then drop the marker + backup files. Returns an error if restoring the backup OR scheduling the restart fails; in either case the marker is intentionally left in place so the next agent startup retries via the watchdog.

Ordering matters: the marker must outlive a failed ScheduleSelfRestart (otherwise the agent would be left running on the new conf with no retry handle). The backup is removed last so a partially-completed Rollback can re-run safely from the next startup.

The function is idempotent: a missing backup file is treated as "already restored on a previous attempt".

func ScheduleSelfRestart

func ScheduleSelfRestart(delay time.Duration) error

ScheduleSelfRestart asks systemd to run `systemctl restart alpamon` after the given delay via a transient timer unit. The transient unit is owned by systemd PID 1, so it survives the imminent restart of the alpamon.service cgroup (the unit that the migrate or rollback caller itself lives under).

systemd is a hard requirement here: there is no portable way to ask a process tree we're about to be killed in to restart us from outside.

func StartWatchdog

func StartWatchdog(ctx context.Context, state *PendingState, onTimeout func(*PendingState)) context.CancelFunc

StartWatchdog arms a goroutine that fires onTimeout when the marker's ExpiresAt elapses without the marker having been cleared. If the marker is already past its deadline (we crashed during a previous wait), the callback fires asynchronously right away.

The returned cancel function disarms the watchdog. Callers MUST invoke it from the WebSocket connect-success path so a successful migration does not race against the timer firing the rollback (which would call the unregister endpoint against a now-live B-side server record). The fire path also re-loads the marker before invoking onTimeout to guard against the case where Confirm() ran between the timer firing and the callback entering.

func WriteConfAtomic

func WriteConfAtomic(confPath string, content []byte, mode os.FileMode) error

WriteConfAtomic writes content to confPath via writeFileAtomic. The parent directory is created with 0700 to mirror configs/tmpfile.conf, keeping the alpamon config dir confidential (it holds secrets).

func WritePending

func WritePending(st *PendingState) error

WritePending persists the marker via temp + fsync + rename + dir-fsync so a power loss after rename never resurfaces a marker with partial contents.

Types

type PendingState

type PendingState struct {
	BackupConfPath string    `json:"backup_conf_path"`
	OldURL         string    `json:"old_url"`
	NewURL         string    `json:"new_url"`
	NewServerID    string    `json:"new_server_id"`
	NewServerKey   string    `json:"new_server_key"`
	StartedAt      time.Time `json:"started_at"`
	ExpiresAt      time.Time `json:"expires_at"`
}

PendingState describes an in-flight migration. It is persisted as JSON to MarkerPath() and read back on the next agent startup.

NewServerKey is included so Rollback() can authenticate to the target workspace as the just-created server and delete the orphan record. Leaving the key on disk is acceptable here: the marker is in /var/lib/alpamon (mode 0750, root-owned), and the same key already lives in /etc/alpamon/alpamon.conf during the same window.

func LoadPending

func LoadPending() (*PendingState, error)

LoadPending reads the marker file. Returns (nil, nil) when no migration is in flight.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL