Skip to content

Architecture

Griffen Fargo edited this page Apr 10, 2026 · 1 revision

Architecture

Two-Tree Design

strut operates across two filesystem trees that collaborate at runtime:

Strut_Home (~/.strut/)

The engine — installed once, shared across all projects:

~/.strut/
├── strut              # CLI entrypoint
├── VERSION            # Semver version
├── install.sh         # Installer script
├── lib/               # Shell library modules
│   ├── config.sh      # Project config discovery
│   ├── registry.sh    # Pluggable registry auth
│   ├── utils.sh       # Colors, logging, SSH helpers
│   ├── deploy.sh      # Deploy orchestration
│   ├── health.sh      # Dynamic health checks
│   ├── docker.sh      # Docker pull/prune helpers
│   ├── backup.sh      # Backup/restore orchestration
│   ├── volumes.sh     # Volume management
│   ├── keys.sh        # Key management
│   ├── schema.sh      # Database schema management
│   ├── audit.sh       # VPS audit system
│   ├── migrate.sh     # VPS migration wizard
│   ├── monitor.sh     # Monitoring stack management
│   ├── cmd_*.sh       # Command handlers
│   ├── backup/        # DB-specific backup implementations
│   ├── drift/         # Drift detection modules
│   ├── keys/          # Key management modules
│   └── migrate/       # Migration wizard phases
└── templates/         # Stack scaffolding templates

Project_Root (user's directory)

User-owned config and stack definitions:

my-project/
├── strut.conf              # Project-level config
├── .prod.env               # Production secrets (gitignored)
├── .staging.env            # Staging secrets (gitignored)
├── .gitignore
└── stacks/
    └── my-app/
        ├── docker-compose.yml
        ├── docker-compose.dev.yml
        ├── .env.template
        ├── services.conf
        ├── required_vars
        ├── volume.conf
        ├── repos.conf
        ├── backup.conf
        ├── .drift-ignore
        ├── nginx/
        └── sql/init/

Startup Sequence

  1. The strut entrypoint resolves STRUT_HOME by following symlinks back to the real script location
  2. lib/config.sh is sourced first
  3. find_project_root() walks up from $PWD looking for strut.conf
  4. load_strut_config() reads project-level defaults
  5. All other lib/*.sh modules are sourced
  6. Command dispatch routes to the appropriate cmd_* handler

Config Loading

All configuration flows through lib/config.sh:

  • find_project_root() — walks up from $PWD to find strut.conf
  • load_strut_config() — reads strut.conf and sets defaults
  • No hardcoded values in the engine — everything is config-driven

Registry System

lib/registry.sh provides pluggable container registry authentication:

Registry Config Value Auth Method
GitHub Container Registry ghcr GH_PAT token
Docker Hub dockerhub Docker login
Amazon ECR ecr AWS CLI
None none No auth

Health Check Engine

lib/health.sh dynamically discovers services from services.conf:

  • Reads *_PORT entries for HTTP health checks
  • Reads *_HEALTH_PATH for custom health endpoints
  • Reads DB_* flags for database probes (Postgres, Redis, Neo4j, etc.)
  • No hardcoded service names — everything is discovered at runtime

Command Dispatch

The strut entrypoint handles two categories:

Top-level commands (no stack required): init, list, scaffold, upgrade, --version, audit, migrate, monitoring, skills

Per-stack commands (require strut <stack> <command>): deploy, release, stop, health, logs, backup, drift, keys, domain, shell, exec, status, volumes, debug, etc.

Universal flags (--env, --services, --json, --dry-run) are parsed globally and passed to handlers.

Design Principles

  • No hardcoded values — no service names, ports, orgs, or paths in the engine
  • Config-driven behaviorstrut.conf, services.conf, and per-stack files drive everything
  • Dynamic discovery — health checks, services, and databases are discovered from config
  • Dry-run support — destructive operations can be previewed with --dry-run
  • SSH consistency — all remote commands use build_ssh_opts for uniform SSH option building
  • Compose consistency — all Docker Compose commands use resolve_compose_cmd for project naming

Clone this wiki locally