Documentation
¶
Overview ¶
Package apply orchestrates post-deployment bootstrap of identity resources.
The single entry point Run wires together the building blocks created by Tasks 5–10 (PocketID client, owner provisioner, break-glass generator, TinyAuth static-cred generator, recovery-bundle builder) into the post-terraform sequence the CLI calls once the homelab containers are healthy.
Idempotency: PocketID's STATIC_API_KEY-based bootstrap is itself a no-op when the static admin user already exists, so re-running the orchestrator against an already-provisioned PocketID surfaces ErrAlreadyBootstrapped (treated as success). Owner / break-glass / TinyAuth-static creation are not idempotent at the API level — re-running on a node that already has an owner will error in CreateUser. Phase 1 keeps that strict so a partial bootstrap is loud rather than silently double-provisioning. Per-step error wrapping makes it possible to identify exactly where a re-run would need to resume.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type OwnerBootstrapInput ¶
type OwnerBootstrapInput struct {
// NodeName is the homelab firstnode identifier (used for synthetic
// break-glass usernames and bundle filenames). Required.
NodeName string
// Hostname is the node's network hostname. Documentation-only field
// embedded into the bundle so the recovery operator can match the
// artifact to physical hardware. Defaults to NodeName when empty.
Hostname string
// PocketIDURL is the public origin of the PocketID instance, e.g.
// "https://id.example.com". No trailing slash. Required.
PocketIDURL string
// PocketIDStaticAPIKey is the value rendered into the PocketID
// container as STATIC_API_KEY (Task 12 will provision this via tfvars;
// for now Run reads it from the caller). Required.
PocketIDStaticAPIKey string
// Owner is the daily-admin owner spec. Required.
Owner identity.OwnerSpec
// RecoveryPassphraseHash is the argon2id-PHC hash the operator chose
// during init. Required. Run uses it to verify the operator types the
// matching plaintext at bundle-encryption time (third factor).
RecoveryPassphraseHash string
// RecoveryPassphrasePlain is the plaintext passphrase, optional. When
// set, Run skips the terminal prompt and verifies the value against
// the hash directly. Empty value triggers the prompt.
RecoveryPassphrasePlain string
// BundleDir is where the .age and .txt bundle files get written.
// Defaults to defaultBundleDir when empty.
BundleDir string
// TinyAuthEnvPath is the file the TinyAuth users env line is written
// to. Defaults to defaultTinyAuthEnvPath when empty.
TinyAuthEnvPath string
// ClusterRole is mirrored into the bundle so the recovery operator
// knows which role this node played. Defaults to "main" when empty.
ClusterRole string
// PocketIDClient is the optional injection point for tests. When nil,
// Run constructs a real *pocketid.Client from PocketIDURL +
// PocketIDStaticAPIKey.
PocketIDClient pocketIDClientForBootstrap
// TerminalPrompter overrides the third-factor passphrase prompt.
// Production sets this to nil (uses ttyPassphrasePrompter); tests inject
// a fake.
TerminalPrompter PassphrasePrompter
// Now is injectable for deterministic tests. Defaults to time.Now in
// the bundle builder.
Now func() time.Time
}
OwnerBootstrapInput is everything Run needs to provision the owner and break-glass artifacts. Validation rules: NodeName, PocketIDURL, PocketIDStaticAPIKey, and RecoveryPassphraseHash are required. Owner is validated by the underlying provisioner.
type OwnerBootstrapResult ¶
type OwnerBootstrapResult struct {
// OwnerUserID is the PocketID-assigned UUID for the owner record.
OwnerUserID string
// OwnerSetupURL is the one-time-access link the owner clicks to enroll
// a WebAuthn credential. Single-use; expires per identity.setupTokenTTL.
OwnerSetupURL string
// BreakGlass is the materialized Layer-1 recovery credential.
BreakGlass *identity.BreakGlassCredential
// TinyAuthStatic is the materialized Layer-2 recovery credential.
TinyAuthStatic *identity.TinyAuthStaticCredential
// BundlePaths are the on-disk locations of the encrypted (.age) and
// plaintext (.txt) recovery files.
BundlePaths *identity.BundlePaths
}
OwnerBootstrapResult is what Run returns on success — the persisted IDs the controller wants to push back to TechStack later (Task 13+) plus the on-disk paths the CLI prints to the operator.
func Run ¶
func Run(ctx context.Context, in OwnerBootstrapInput) (*OwnerBootstrapResult, error)
Run executes the full owner-and-break-glass bootstrap sequence:
- Wait for PocketID health (HTTP /healthz returns 2xx).
- BootstrapInitialAdmin — verifies STATIC_API_KEY is accepted. Treated as success when the instance is already bootstrapped.
- Ensure the "owners" and "admins" user-groups exist.
- Provision the daily-admin owner (CreateUser + group + setup URL).
- Generate the per-node break-glass admin (CreateUser + group + token).
- Generate the per-node TinyAuth static credential (random pwd + bcrypt).
- Write TINYAUTH_AUTH_USERS=username:bcrypt-hash into TinyAuthEnvPath.
- Confirm the recovery passphrase at the terminal (third factor).
- Build the recovery bundle and write the .age + .txt files.
The function is deliberately verbose with error wrapping — each step's error names which operation failed, so a partial-completion state is diagnosable from a single log line.
type PassphrasePrompter ¶
type PassphrasePrompter interface {
// PromptAndVerify reads the recovery passphrase from the operator,
// compares it against expectedHash via crypto.VerifyPassphrase, and
// returns the raw plaintext on success. It must give the operator at
// most attempts tries before erroring out — empty plaintext on success
// is not allowed.
PromptAndVerify(expectedHash string, attempts int) (plaintext string, err error)
}
PassphrasePrompter gates the third-factor passphrase confirmation. Production uses ttyPassphrasePrompter (reads /dev/tty with echo off); tests inject a fake.