From 35307e64cc7bcd230795bfa6c3dbcf6dce47f167 Mon Sep 17 00:00:00 2001 From: nexxo Date: Sun, 26 Jul 2026 17:42:26 +0200 Subject: [PATCH] fix(deploy): the installer could never finish, twice over MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by running it on a fresh Debian 12 with systemd, no docker, no git and no users — which is the first time it has ever been run anywhere. `optional_env` ended in `[[ -n "$2" ]] && set_env …`. With an empty value the function returns 1, and under `set -e` that ends the install, silently, with a half-written root-owned .env. Absent is the NORMAL case for those variables — they are the optional ones — so this killed every installation that did not happen to supply a Hetzner token. An `if` returns 0 and does not. Then it waited for `php -v` before migrating. PHP answers in seconds; the entrypoint is still running `composer install` on a fresh checkout, which takes minutes. So `artisan migrate` ran against a checkout with no vendor/ and died on a missing autoload.php. It now waits for vendor/autoload.php, exactly as the dependent containers already do, and says where to look if it never appears. After both: exit 0, 59 migrations applied, the plan catalogue seeded and consistent, the Owner account created with its role, and the portal answering 200 while the console redirects to login and the unconfigured Stripe webhook fails closed with 400. Co-Authored-By: Claude Opus 5 --- deploy/install.sh | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 3bdc169..7e2bc3e 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -275,7 +275,16 @@ else # inside the container belong to it and not to root. # Supplied through --env-file; skipped silently when absent, so a first # install can happen before the Stripe account exists. - optional_env() { [[ -n "${2:-}" ]] && set_env "$1" "$2"; } + # An `if`, not `[[ … ]] && …`: the && form makes the function return 1 for + # every value that is absent, and under `set -e` that ends the install. + # Absent is the NORMAL case here — these are the optional ones — so the + # script killed itself on the first installation that did not happen to + # supply a Hetzner token. + optional_env() { + if [[ -n "${2:-}" ]]; then + set_env "$1" "$2" + fi + } optional_env HETZNER_DNS_TOKEN "${HETZNER_DNS_TOKEN:-}" optional_env CLUPILOT_DNS_ZONE "${CLUPILOT_DNS_ZONE:-}" @@ -303,12 +312,23 @@ log "Building and starting the containers (this takes a few minutes)" compose build --quiet app compose up -d -log "Waiting for the application container" -for _ in $(seq 1 60); do - compose exec -T app php -v >/dev/null 2>&1 && break +# Not `php -v`: PHP answers within seconds, while the entrypoint is still +# running `composer install` on a fresh checkout — which takes minutes. The old +# condition let the migration below run against a checkout with no vendor/ at +# all, and artisan died on a missing autoload.php. Wait for what is actually +# needed, exactly as the dependent containers already do. +log "Waiting for the application container to install its dependencies" +deps_ready=0 +for _ in $(seq 1 180); do + if compose exec -T app test -f vendor/autoload.php 2>/dev/null; then + deps_ready=1 + break + fi sleep 5 done +[[ $deps_ready -eq 1 ]] || die "vendor/ never appeared. Look at: docker compose logs app" + log "Running migrations" compose exec -T app php artisan migrate --force