Skip to content

Add shared setup wizard integration to deploy-core - #2

Merged
c-wri merged 48 commits into
mainfrom
feature/cli-wizard
Mar 10, 2026
Merged

Add shared setup wizard integration to deploy-core#2
c-wri merged 48 commits into
mainfrom
feature/cli-wizard

Conversation

@c-wri

@c-wri c-wri commented Mar 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • switch deploy-core to the extracted ansible-config-wizard package with a repo-local profile, builders, templates, and scripts/setup.sh
  • add the phase/stage-driven operator flow for SSH bootstrap, vault handling, backup destinations, Obsidian LiveSync access, preflight, deploy, backup, and lockdown handoffs
  • harden preflight validation for Tailscale planning, restic target naming, cache policy, host-key trust, and backup transport settings
  • generate Obsidian LiveSync setup URIs and stabilize wizard-generated restore artifacts

Dependencies

Testing

  • for t in tests/smoke/*.sh; do bash "$t"; done

c-wri added 30 commits March 9, 2026 13:43
Add a repo-local configure wrapper, declarative wizard profile, Jinja output templates, and a builder hook so crownops-deploy-core can use the extracted ansible-config-wizard package without carrying the generic engine in-tree.

Update the operator flow to prefer ./scripts/configure.sh, keep init-local-config.sh as a fallback scaffold, and document that the shared wizard implementation now lives outside this repo while local testing can use a sibling checkout via ANSIBLE_CONFIG_WIZARD_PROJECT.
Replace the hard requirement for preexisting authorized SSH keys with a bootstrap flow that generates an Ed25519 keypair, pauses with exact install/test commands, and resumes from a saved answers file.

Derive ssh_pubkeys from the generated bootstrap key, record the key paths in the details output, and update operator docs so the documented first-run flow matches the actual wizard behavior.
Point the bootstrap SSH key and optional details file at the shared wizard state directory so secret-bearing runtime artifacts are not written into the checkout by default.

Document the new state-home behavior in the operator docs and wrapper help to keep the first-run SSH flow aligned with the hardened shared wizard package.
Replace the repo's ad hoc ignore list with a consistent Ansible/operator-local baseline. Keep the existing local-only production inventory files and generated reports ignored, while adding standard coverage for Ansible runtime state, Python cache files from wizard support helpers, local build output, and vault password helper files.
Switch the first-run SSH path from an opaque bootstrap key in wizard state to a managed long-term key under ~/.ssh/ansible-config-wizard, wire ansible_ssh_private_key_file into generated inventory, and derive authorized_keys from that managed identity.

Update the operator guidance and resume messaging so the flow clearly pauses for ssh-copy-id, reuses existing managed keys on reruns, and treats resume-state files as temporary secure artifacts rather than persistent local credentials.
Add an explicit commands block for the first-run managed SSH action so the wizard writes a copy-friendly shell script and prints the exact ssh-copy-id, ssh, and resume commands as plain text during the pause step.
Switch the managed SSH pause step to the shared ssh_setup action so first-run access can either be installed automatically with the bootstrap password or handled manually with generated commands that already disable agent key offers.

Update the operator docs to describe the password-assisted install path and the client-side cause of Too many authentication failures so the wizard guidance matches the hardened shared package behavior.
Add explicit help text explaining that the ACME email field is the TLS certificate contact email for Let’s Encrypt or the configured ACME account, not an Obsidian user email.
Replace the half-step public-or-disabled Obsidian wizard branch with a proper feature plus access-mode model so operators can choose public_https or private_mesh without losing the existing public deployment path.

The builder now derives mode-specific values such as the effective base URL, default CouchDB bind host, and restic contributions. Generated all.yml output carries the shared obsidian_access_mode contract expected by the services collection.

Preflight now gates ACME and public 443 checks to public_https and rejects a public CouchDB firewall rule in private_mesh. The example inventory and operator docs were updated to match the new flow.
Reorder the core wizard so Tailscale inputs are collected before private mesh endpoint questions, keeping the prompts in dependency order instead of jumping from Obsidian to backups and back to mesh reachability.

Private mesh now supports a planned tailscale_magicdns URL strategy. Operators can derive the Obsidian base URL from tailscale_hostname plus a tailnet name before the host has joined the mesh, while explicit_url remains available for other private mesh endpoints.

The builder derives the planned MagicDNS URL, and preflight/docs keep the private mesh path aligned with the no-public-5984 requirement.
Replace the raw restic repository prompt with a guided SFTP target flow that asks for host, port, user, and path, then derives the repository URL and attempts ssh-keyscan when host pins are not supplied.

This change also updates preflight and the tracked examples to the generic restic_targets and restic_backup_jobs model, and records destination setup details so operators can prepare laptop and VPS targets without reverse-engineering the generated config.

The Obsidian vault account prompts now clarify that neutral aliases are the right choice when personal names should stay out of deployment records.
Adjust the core consumer profile so the updated shared presentation reads more naturally: warmer section descriptions, clearer SSH setup framing, and more human prompts around the host, feature, Tailscale, Obsidian, and backup steps.

This keeps the deployment contract unchanged while making the wizard feel more CrownOps than raw inventory editing.
Add concise help text to the host, domain, feature, mesh, Obsidian, and backup fields that still looked too much like raw variable names. The goal is to keep the wizard useful for an experienced engineer while still pointing a less familiar operator in the right direction at each decision.
Rename the Obsidian repeatable section and its fields so they read as part of the Obsidian LiveSync setup instead of unexplained CouchDB overrides. Database name and username fields now show derived defaults, and the password field makes clear that generation is automatic.
# Conflicts:
#	inventories/prod/group_vars/all.yml.example
#	inventories/prod/group_vars/vault.yml.example
#	roles/preflight_validate/tasks/main.yml
Replace the pasted restic backup private-key prompt with a local file-path prompt so operators do not echo key material into the terminal or resumable answer state.

Teach the core wizard builder to load the key from disk and update the repo docs to describe the safer workflow.
Update the CrownOps deployment profile to offer either the full deploy wrapper or a generated prerequisite setup script after config write.

Generate a backup-destination prerequisite script from the deployment data model, surface both handoff commands in the details output, and document the new flow in the repo guidance.

This keeps deployment-specific setup logic in the deployment repo while using the shared wizard's new multi-option handoff contract.
Resolve CrownOps collection sources from sibling workspace checkouts before falling back to the remote default branches, while still honoring explicit environment overrides.

This keeps prerequisite setup aligned with the local development branch state so new roles such as restic_sftp_target_bootstrap are available during bootstrap runs.

Also ignore wizard-generated inventory backup files to keep operator runs from leaving untracked .bak noise in the deployment repo.
Track the source fingerprint for public requirements and local CrownOps collections under .ansible/collection-state so unchanged installs are skipped instead of being forced on every run.

If refresh fails but a usable collection set already exists, continue with the existing local copy so offline operator runs can still proceed.

CROWNOPS_COLLECTIONS_REFRESH remains available to force a refresh when needed.
c-wri added 17 commits March 10, 2026 00:47
Run a direct SSH connectivity check before ansible-playbook when the bootstrap flow is using existing SSH access.

This preserves the normal host-key and key-passphrase prompt behavior in a real SSH session, which gives operators clearer diagnostics and avoids dropping straight into opaque Ansible connection failures.

The probe is skipped for explicit ask-pass flows to avoid prompting twice for SSH passwords.
Set the repo-local default vault password file to .vault_pass, teach the deploy and lockdown wrappers to honor that configured default or fall back to --ask-vault-pass for encrypted vaults, and document the operator-facing convention. Also fix the restic preflight checks so malformed target_names entries and unknown target references produce deterministic validation errors instead of Jinja evaluation failures, and narrow placeholder scanning to inventory-authored values to avoid self-generated false positives.
When vault.yml is encrypted and no password file has been selected yet, prompt the operator to choose between an interactive vault password prompt and a local vault password file instead of only collecting a path. This makes deploy.sh and lockdown.sh behave more like core Ansible workflows while still honoring the configured .vault_pass default when it exists.
Make setup.sh the interactive wrapper while keeping deploy.sh as the explicit deployment runner and renaming lockdown.sh to ssh-lockdown.sh.

Changes in this commit:

- add scripts/setup.sh as the canonical repo-local wizard entrypoint

- remove configure.sh and lockdown.sh from the command surface

- keep deploy.sh but strip out its interactive operator flow so it only runs explicit Ansible phases

- add shared vault-resolution helpers so deploy.sh and ssh-lockdown.sh honor .vault_pass, ansible.cfg, ANSIBLE_VAULT_PASSWORD_FILE, --vault-password-file, and --ask-vault-pass

- update wizard profile/template integration to stop using post-write handoff commands and to reference the new setup/ssh-lockdown names in generated artifacts

- update init-local-config guidance to point operators at setup.sh
Update operator-facing docs to describe setup.sh as the primary interactive flow, deploy.sh as the lower-level deployment runner, and ssh-lockdown.sh as the lower-level hardening runner.

Changes in this commit:

- rewrite quickstart and deployment sequencing around the wizard-owned stage model

- replace removed configure.sh and lockdown.sh references throughout the docs

- document the revised vault handling expectations and lower-level runner behavior

- align implementation/spec status docs with the new command surface
Rewrite the CrownOps deploy-core wizard profile around the new Configure, Prepare, and Deploy phases with explicit navigable stages for host access, platform, features, backups, review, vault handling, prerequisites, collections, deployment, verification, and hardening.

Update the operator and design docs so the repo now describes the wizard as a phase/stage workflow rather than a mixed section/stage setup.
- move tracked example inventory files into standard Ansible group_vars directories under inventories/prod/group_vars/all/ and inventories/prod/group_vars/core_hosts/

- update README, quickstart, deployment sequence, secrets model, wizard spec, scripts, gitignore, and wizard outputs to use the inventory-native all/main.yml, all/vault.yml, and core_hosts/main.yml paths

- make bootstrap play connect with bootstrap_ansible_user explicitly and remove the self-referential bootstrap_target_ubuntu_release role var override

- force generated SSH inventory entries to use IdentitiesOnly so bootstrap connections do not fail from agent key fan-out

- update preflight validation to read concrete inventory/config files for placeholder scanning and bind target host variables into localhost validation scope to avoid undefined-variable crashes

- add a smoke test and CI coverage for scaffolded preflight placeholder validation

- always render internal_network_name in the core wizard template so private_mesh deployments satisfy downstream compose templates
- load inventory group vars into localhost preflight via explicit include_vars files instead of rebinding dozens of host variables into ad hoc facts

- keep preflight placeholder scanning aligned with the standard inventory file set through dedicated defaults for loaded and scanned files

- promote invalid tailscale_tags values from a warning to a hard preflight error because non-prefixed tags break tailscale up

- document the required tag: prefix in the wizard help text and tracked example inventory

- add CI smoke coverage for the Tailscale tag validation path
- validate tailscale_hostname and tailscale_tailnet_name more strictly during preflight for private_mesh deployments that rely on Tailscale MagicDNS

- catch duplicated .ts.net suffixes in obsidian_base_url before deployment continues

- normalize the wizard's default Tailscale hostname generation to DNS-safe names and strip any accidental .ts.net suffix from the tailnet input when deriving the planned base URL

- persist private_mesh_url_strategy and tailscale_tailnet_name in generated all.yml output so future validation and troubleshooting keep the original planning inputs
- use the builder's sanitized restic target key for generated restic_targets entries and derived backup job target_names instead of preserving raw display names

- keep the user-facing display name alongside the normalized identifier so setup notes can stay readable without leaking invalid IDs into inventory

- add CI smoke coverage for restic target name normalization using a stubbed wizard generator interface
- fail preflight when restic target names are not lowercase normalized identifiers that match the backup role's documented pattern

- add CI smoke coverage for invalid restic target names so manual inventory edits are rejected before backup.yml runs
Carry configured non-default SFTP ports through the config wizard builder so generated restic targets keep transport settings instead of silently reverting to port 22.

Render sftp_port in the generated inventory template whenever the target defines one, keeping wizard output aligned with manual inventory expectations and the backup role contract.

Extend the builder smoke test to assert the normalized target output preserves sftp_port alongside the sanitized target identifiers.
Enable SSH pipelining in the core Ansible configuration so module execution avoids unnecessary remote file transfer round-trips during privilege escalation.

Disable fact gathering for the dedicated backup play because the restic backup role does not consume host facts, removing avoidable startup latency from every backup converge.
Expose restic_apt_cache_valid_time in the example inventory and wizard-generated all.yml so backup package-cache reuse is part of the supported configuration surface instead of an implicit role default.

Add a customize-mode wizard field for the apt cache reuse window and validate the value during preflight so manual edits cannot silently degrade or break backup convergence.

Add a preflight smoke test that fails on invalid restic_apt_cache_valid_time values and document the converge-vs-execution split in the architecture and ops runbook.
Refine the generated and example backup policy so application-data captures only local markdown workspaces and CouchDB data, while public HTTPS deployments retain only Traefik ACME state instead of broad service directories.

Add preflight guidance that warns on broad backup roots such as /srv/crownops, the full CouchDB directory, or the full Traefik directory, steering manual inventory edits toward precise durable paths.

Extend smoke coverage for the generated backup policy and add a warning-path smoke test so restore-scope regressions are caught in CI.

Update restore and architecture docs to describe the restore-first backup boundary explicitly.
Cache generated restic target keypairs per wizard run so repeated builder passes cannot diverge across vault output, setup notes, and backup destination bootstrap commands.

Add smoke coverage that fails when a generated backup target keypair changes between builder calls within the same run.
Add a dedicated Obsidian LiveSync helper that builds plugin-compatible setup URIs using the upstream AES-GCM and PBKDF2 format and secure bootstrap defaults.

Extend the crownops-deploy-core builder to generate per-vault bootstrap handoff records, persist bootstrap-only passphrases in the wizard vault output, and include fingerprint tracking for the new secrets.

Update the sensitive details template and handoff documentation so operators get per-vault setup URIs, setup-URI passphrases, bootstrap vault passphrases, and explicit rotation guidance.

Add smoke coverage for setup URI generation and round-trip decryption, and document the new bootstrap secret structure in the vault example.
@c-wri c-wri changed the title Adopt the shared setup wizard in deploy-core Add shared setup wizard integration to deploy-core Mar 10, 2026
Create a locked-down .vault_pass file in GitHub Actions after scaffolding local config so Ansible syntax checks can run under the repo's secure default ansible.cfg settings. Keep the change scoped to CI so local operators still manage real vault credentials through the wizard and lower-level runners.
@c-wri
c-wri merged commit 6ecefca into main Mar 10, 2026
1 check passed
@c-wri
c-wri deleted the feature/cli-wizard branch March 10, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant