# XAMXAM test environment — a fresh Debian box you can deploy to `test-env/` spins up a throwaway **Debian trixie** systemd container that behaves like a brand-new server with **nothing preinstalled**, then drives the project's **real** setup scripts and `just` deploy recipes against it over SSH — without ever touching the production host. This is how you verify the setup scripts actually work before trusting them on real hardware. --- ## How it works ``` HOST (you) podman network ┌─────────────────────────────┐ ┌──────────────────────────────┐ │ just test-env-run … │ ssh / │ server = Debian trixie │ │ uses test-env/bin/* shims │ rsync │ systemd + sshd :22022 │ │ (ssh/rsync route `xamxam` │───────▶│ "fresh box" │ │ → 127.0.0.1:22022) │ └──────────────────────────────┘ └─────────────────────────────┘ ``` - The **`just` recipes run unmodified on your host**. All they know is an SSH host named `xamxam`. Normally that alias points at production; with the test env it points at the container. - Two tiny shims, `test-env/bin/ssh` and `test-env/bin/rsync`, are put on `PATH` only inside `test-env-run`. They force the test SSH config (`test-env/ssh/config`), so every `ssh xamxam …` / `rsync … xamxam:/…` hits the container. Your real `~/.ssh/config` is never touched. - **Safety**: the shims always force the test transport. A caller-supplied `rsync -e` is replaced, and when none is given the shim injects its own so rsync can never fall back to the host's real config (which maps `xamxam` → PRODUCTION). - The systemd container runs nginx + php-fpm as **real services**, so `systemctl`, `nginx -t`, socket paths etc. all behave like a real box. ## What it tests | Step | File | Does | |------|------|------| | Stack install | `test-env/scripts/provision-server-packages.sh` | `apt-get` nginx, php8.4-fpm, php-cli/curl/sqlite3/mbstring/xml, composer, sqlite3, rsync, git, just | | Server setup | `scripts/setup-server.sh` (real) | `xamxam` group, deploy+www-data membership, `/var/www/xamxam`, 2775/664 perms, cache/log/backup dirs | | Deploy | `just deploy` (real) | build, `deploy-code`, `deploy-nginx` (→ `scripts/deploy-server.sh`), `deploy-deps`, `deploy-migrate`, `deploy-env`, `deploy-verify-permissions` | | Nginx config | `nginx/xamxam.conf` (real) | installed via `deploy-nginx`, validated with `nginx -t`, reloaded | | First provision | `scripts/provision-server-env.sh` (real) | server-side `.env` / `APP_KEY` | | Remote provisioning | `just provision-server` (real) | chains env + deploy + backup + logrotate | ## Usage ```bash # 1. Boot the fresh box (builds image, starts container, renders ssh config) just test-env-up # 2. Install the stack on the box (apt-get) just test-env-provision # 3. Run the real one-shot server setup (group/user/dir bootstrap) just test-env-setup-server # 4. Run the real deploy chain, pointed at the box just test-env-run # == just deploy # or individual recipes: just test-env-run recipe=deploy-nginx just test-env-run recipe=provision-server-env just test-env-run recipe=deploy-db # 5. Health check just test-env-status # 6. Interactive shell podman compose -f test-env/compose.yaml exec server bash # Tear down just test-env-teardown # + -- --keys to also delete the SSH keypair ``` ## SSH to the box directly ```bash ssh -p 22022 -i test-env/keys/xamxam-test_ed25519 deploy@127.0.0.1 ``` The `deploy` user has **passwordless sudo** so the recipes' `sudo …` calls run unattended (the realistic option, matching that `provision-server-env` uses a non-TTY `sudo tee`). To force sudo to prompt instead, remove `/etc/sudoers.d/deploy` inside the container: ```bash podman compose -f test-env/compose.yaml exec server rm /etc/sudoers.d/deploy ``` …but note some recipes (e.g. `provision-server-env`) rely on non-TTY sudo, so passwordless is the supported default. ## How the justfile is wired ```make test-env-run recipe='deploy': # run any recipe against the box @PATH="$(cd test-env && pwd)/bin:$PATH" just {{recipe}} ``` Only the `test-env-*` recipes prepend the shims; a plain `just deploy` still targets production as before. ## Caveat: recipes using `ssh -t` need a real terminal Recipes that call `ssh -t xamxam "sudo …"` (e.g. `deploy-nginx`) require a pseudo-terminal. When you run `just test-env-run` from a normal interactive terminal you SSH through the shim fine. But if `just` is driven from a non-TTY context (a script, CI, or agent), `ssh -t` cannot allocate a pty and `sudo` refuses with “a terminal is required”. Always drive these recipes from an interactive shell. ## Generated / ignored - `test-env/keys/` — throwaway SSH keypair (gitignored). - `test-env/ssh/config` — rendered from `config.template`, never committed. ## Finding: `deploy-code` lost `--chown=www-data:xamxam` First run of `just deploy` against a fresh box reproduces a **real regression** in the working-copy justfile (pre-existing uncommitted refactor that switched `deploy-code` to `rsync -az … --exclude-from=.rsync-exclude`): ``` rsync: [receiver] mkstemp "/var/www/xamxam/storage/.xamxam.sqlite…" failed: Permission denied rsync error: code 23 ``` Every deployed file/dir ends up `deploy:deploy` / `drwxr-xr-x` instead of `www-data:xamxam` / group-writable, so php-fpm (www-data) cannot write `storage/`. The recipe dropped `--chown="www-data:xamxam"` that the previous version had. Restore it to fix fresh deploys.