Skip to content

Latest commit

 

History

History
147 lines (120 loc) · 7.67 KB

File metadata and controls

147 lines (120 loc) · 7.67 KB

strut — Developer Context

Generic CLI tool for managing Docker stacks on VPS infrastructure. Bash + BATS test suite.

Architecture

Two filesystem trees collaborate at runtime:

  • Strut_Home (~/.strut/) — the engine: strut entrypoint, lib/*.sh modules, templates/
  • Project_Root (user's dir) — their config: strut.conf, stacks/, .env files

The strut entrypoint resolves Strut_Home via symlink resolution, then walks up from $PWD to find strut.conf (Project_Root). Config is loaded before any lib modules.

Key Files

File Purpose
strut CLI entrypoint — command dispatch, usage
lib/config.sh Project config discovery (find_project_root, load_strut_config)
lib/registry.sh Pluggable registry auth (ghcr/dockerhub/ecr/none)
lib/utils.sh Colors, logging, SSH helpers, compose builders
lib/deploy.sh Deploy orchestration, VPS release, repo sync
lib/deploy_blue_green.sh Blue-green deploy: dual-project, health-gated proxy swap, state-file rollback
lib/health.sh Dynamic health checks from services.conf
lib/cmd_*.sh Command handlers (deploy, stop, init, scaffold, tui, etc.)
lib/docker.sh Docker pull, prune helpers
lib/volumes.sh Dynamic volume management from volume.conf
lib/keys.sh + lib/keys/ Key management (SSH, API, env, db, GitHub)
lib/backup.sh + lib/backup/ Backup/restore for Postgres, Neo4j, MySQL, SQLite
lib/drift.sh + lib/drift/ Config drift detection and auto-fix
lib/migrate.sh + lib/migrate/ VPS migration wizard (8 phases)
templates/ Scaffold templates for new stacks
install.sh One-liner installer (clone + symlink)
VERSION Semver, read by strut --version

Commands

strut <stack> <command> [--env <name>] [--dry-run] [--services <profile>]
strut                                             # interactive TUI (fzf+select)
strut --no-tui | STRUT_NO_TUI=1                   # disable TUI

Top-level:  init, upgrade, --version, list, scaffold, audit, migrate, monitoring,
            secrets-filter (transparent git clean/smudge for at-rest secrets)
Per-stack:  deploy, stop, update, health, logs, status, shell, exec,
            backup, restore, db:pull, db:push, db:schema, drift, volumes, keys, domain
            release — alias for deploy (see below)

deploy resolves its target from the stack's topology, like status/health/ logs do (should_dispatch_remote, lib/utils.sh): a stack mapped to a VPS runs the full pipeline on that host (vps_release — sync → migrate → deploy → health → auto-rollback), everything else deploys against the local Docker daemon. --local (alias --force-local) forces local; --no-sync / --no-migrate skip pipeline steps.

release is a permanently-supported alias for deploy — it existed as a separate verb when deploy always meant "run here", which made wrong-target deploys a repeated mistake (strut#415). It differs only in refusing a stack that resolves to no VPS instead of falling back to local.

STRUT_REMOTE_EXEC=1 is stamped on every ./strut invocation sent over SSH. It is the authoritative "we are the target" signal — is_running_on_vps otherwise infers this from hostname/IP, which cannot match a Tailscale MagicDNS name or CNAME, and without the marker such a host would dispatch to itself unbounded. Any new remote ./strut call site must set it.

Config Files (per-stack, user-owned)

  • strut.conf — project-level: registry type, org, branch, banner
  • services.conf — service ports, health paths, DB flags (drives health engine)
  • required_vars — env vars validated before deploy
  • volume.conf — volume paths and ownership mappings
  • repos.conf — GitHub repos for key management
  • backup.conf — backup schedule and retention
  • env/hosts/<alias>.env — tracked, non-secret per-host env layer. Applied on every deploy where the stack resolves to a host alias (via [stacks][hosts] in strut.conf, or --host <alias>), last-wins over the base env file. Unlike the legacy stacks/<stack>/.<host>.env override (still supported, lower precedence), this path is not gitignored, so it survives a clean git-redeploy.
  • common.env (project root, optional) — shared, non-secret env values across every stack (load_common_env, lib/utils.sh). Lowest precedence: loaded before the stack/project env file and the per-host layer, both of which override it. Not swept by the .env/.*.env gitignore rules (no leading dot) — meant to be committed. See templates/common.env.template.
  • .<env>.enc.env (stack dir or project root) — committed, at-rest-encrypted env file for the secrets-filter git clean/smudge workflow (strut#178). Plaintext in the working tree, age ciphertext in the git blob. Preferred over .<env>.env by resolve_env_file at the same level, and explicitly un-ignored (!*.enc.env / !.*.enc.env) so it can be committed under stacks/<stack>/ where the plain .*.env rule would otherwise sweep it.
  • env/stack.gen.enc.env, env/hosts/<alias>.gen.enc.env — committed, age/gpg-encrypted values generated once by strut <stack> gen <VAR> (lib/cmd_gen.sh) and applied as the final layer of the env chain (env_apply_gen_layer, lib/utils.sh, strut#179), overriding the base env file (including a resolved .<env>.enc.env), env/hosts/<alias>.env, and common.env on conflicting keys — stack-scope applies first, host-scope last (most specific wins). Decrypted on demand via _secrets_unlock; decrypt failure (e.g. no age identity) warns and skips rather than failing the deploy, so read-only commands still work without the key.

Design Principles

  • No hardcoded service names, ports, orgs, or paths in the engine
  • All behavior driven by config files (strut.conf, services.conf, etc.)
  • lib/config.sh owns all config loading and defaults
  • lib/registry.sh owns all registry auth dispatch
  • Health checks dynamically discover services from services.conf
  • Required vars validation is optional (skip if no required_vars file)

Testing

bats tests/                    # Run all tests
bats tests/test_config.bats    # Run specific file

BATS test suite with property-based tests (100-iteration randomized loops). Key test files:

Test Properties
test_config.bats Config walk-up, parsing defaults, symlink resolution
test_registry.bats Registry dispatch routing, invalid type rejection
test_health_discovery.bats Service/DB/port discovery from services.conf
test_init.bats Init flag propagation to strut.conf
test_entrypoint.bats Version round-trip, upgrade guard
test_no_hardcodes.bats Static grep — no Climate-Hub references in engine
test_scaffold.bats Org substitution, required_vars consistency

Conventions

  • All lib/*.sh files start with set -euo pipefail
  • fail() exits with code 1 to stderr; warn() continues to stdout
  • log() prefix is [strut]; banner reads BANNER_TEXT from config
  • SSH commands use build_ssh_opts for consistent option building
  • Compose commands use resolve_compose_cmd for consistent project naming
  • DRY_RUN=true + run_cmd/run_cmd_eval for preview mode

Commit Messages

  • Do not add Co-Authored-By: Claude … (or any AI/tool attribution) trailers to commits or PR bodies in this repo. History was filter-repo'd to remove them; don't reintroduce.

Skills (.kiro/skills/)

9 procedural skills for operational workflows: vps-deployment, vps-debugging, database-backups, key-rotation, stack-validation, drift-detection, monitoring-setup, domain-ssl, vps-audit-migration.