fix(deploy): the installer could never finish, twice over
tests / pest (push) Successful in 6m55s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Successful in 5s Details

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 <noreply@anthropic.com>
feat/portal-design tested-20260726-1638-35307e6
nexxo 2026-07-26 17:42:26 +02:00
parent e9240c1324
commit 35307e64cc
1 changed files with 24 additions and 4 deletions

View File

@ -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