provision

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

Documentation

Overview

Package provision orchestrates a deploy as discrete steps so the TUI can show progress: validate credentials, mint a Tailscale key, render cloud-init, create the server, and wait for it to join the tailnet.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AuthorizeKey

func AuthorizeKey(ctx context.Context, user, host, pubkey string) error

AuthorizeKey appends one pubkey to the box's authorized_keys over SSH, so a second device (your phone) can connect using a key it generated itself — the private half never leaves that device. The laptop's own (already-trusted) key is what logs in to do this. Idempotent: re-adding the same key is a no-op.

func AuthorizeKeys

func AuthorizeKeys(ctx context.Context, user, host string, keys []string) (added int, err error)

AuthorizeKeys appends every valid public key in keys to the box's authorized_keys in a single SSH round-trip, idempotently, and reports how many were newly added (already-present keys are skipped). Invalid lines are dropped.

func Destroy

func Destroy(ctx context.Context, cfg config.Config) error

Destroy deletes the server + firewall and revokes the tailnet node. A server pocketdev adopted (rather than created) is never deleted — only its tailnet node is revoked — since the user owned that machine first.

func FetchSSHIDKeys

func FetchSSHIDKeys(ctx context.Context, handle string) ([]string, error)

FetchSSHIDKeys pulls the public keys published for a Termius SSH ID handle from https://sshid.io/<handle> (the same list `curl` returns). Each device's private key stays on that device; only these public halves are ever fetched. Every line is validated before it can be installed.

func ParseAuthorizedKeys

func ParseAuthorizedKeys(body string) []string

ParseAuthorizedKeys keeps only the lines of body that are valid SSH public keys.

func ResolveFQDN

func ResolveFQDN(ctx context.Context, name string) (fqdn, ip string)

ResolveFQDN returns the box's full MagicDNS FQDN (no trailing dot) and 100.x IP from the local Tailscale CLI. Empty strings if it can't be found.

func ValidHandle

func ValidHandle(s string) bool

ValidHandle checks an SSH ID handle is safe to put in a URL path.

func ValidPubKey

func ValidPubKey(s string) bool

ValidPubKey does a cheap structural check on an SSH public key line and, since the value is interpolated into a remote shell command, rejects shell/line metacharacters so it can never break out of the single-quoted context.

Types

type Check

type Check struct {
	Name string
	OK   bool
}

Check is one verification result.

type Provisioner

type Provisioner struct {
	// contains filtered or unexported fields
}

Provisioner carries state across the deploy steps.

func New

func New(cfg config.Config) (*Provisioner, error)

New validates the config shape and builds the clients.

func (*Provisioner) AdoptServer

func (p *Provisioner) AdoptServer(ctx context.Context) error

AdoptServer bootstraps an existing server over SSH: it pipes a generated script (tailscale join + on-box files + agent install) to the box. Nothing is written to the box's disk except the intended files; the script arrives on stdin.

func (*Provisioner) Agents

func (p *Provisioner) Agents() []agents.Agent

Agents returns the resolved agent set (for the result screen).

func (*Provisioner) BootProgress

func (p *Provisioner) BootProgress(ctx context.Context) (status, detail string, err error)

BootProgress returns one snapshot of cloud-init's state: status is "done"/"running"/"error"/"" and detail is the latest activity (the last output-log line if readable, else cloud-init's own detail field). err is set only when the box can't be reached yet (e.g. sshd not up) — callers keep polling.

func (*Provisioner) Connect

func (p *Provisioner) Connect(ctx context.Context) error

Connect opens an interactive SSH session that runs `pocketdev setup` on the box (agent logins, gh auth, clone) and returns when the user exits it. It also reverse-forwards a local URL-opener so the box's browser-opens (gh/claude auth) pop in the LAPTOP browser instead of failing on the headless box.

func (*Provisioner) CreateServer

func (p *Provisioner) CreateServer(ctx context.Context) error

CreateServer creates the box (idempotent: skips if the name already exists).

func (*Provisioner) Node

func (p *Provisioner) Node() (name, ip string)

Node returns the box's display name (tailnet label, possibly name-N) and IP.

func (*Provisioner) PrepareTailscale

func (p *Provisioner) PrepareTailscale(ctx context.Context) error

PrepareTailscale mints a short-lived tagged key (OAuth path) and revokes any stale node with the same hostname so the new box keeps the clean MagicDNS name.

func (*Provisioner) Render

func (p *Provisioner) Render() error

Render builds the cloud-init document.

func (*Provisioner) ResolveHost

func (p *Provisioner) ResolveHost(ctx context.Context)

ResolveHost refreshes the box's online tailnet IP/name from the local CLI. Best-effort; used between boot-progress polls so we always target the live node.

func (*Provisioner) SSHUserHost

func (p *Provisioner) SSHUserHost() (user, host string)

SSHUserHost returns the login user and the best host (tailnet IP, else name).

func (*Provisioner) SyncLocal

func (p *Provisioner) SyncLocal(ctx context.Context) error

SyncLocal rsyncs the chosen local folder/file to the box over the tailnet, after it has joined. A directory is copied into ~/<name>/; a file into ~/.

func (*Provisioner) ValidateCredentials

func (p *Provisioner) ValidateCredentials(ctx context.Context) error

ValidateCredentials probes Hetzner and (if used) the Tailscale OAuth client.

func (*Provisioner) Verify

func (p *Provisioner) Verify(ctx context.Context) ([]Check, error)

Verify SSHes in and checks that each agent CLI is installed (and the rsync'd folder is present, if applicable).

func (*Provisioner) WaitForTailnet

func (p *Provisioner) WaitForTailnet(ctx context.Context) (string, error)

WaitForTailnet blocks until the box joins the tailnet. With an OAuth token it polls the Tailscale API; otherwise it watches the local `tailscale` CLI. Local detection is best-effort: if the CLI is absent or the node hasn't appeared, it returns ("", nil) rather than failing a deploy that is otherwise fine.

Jump to

Keyboard shortcuts

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