nixos-module: run db migrations on every activation, not just once

The .initial-migration marker file prevented `migrate --force` from ever
running again after the first activation, so migrations shipped by a later
invoiceninja version upgrade would never apply to an already-deployed
instance. `migrate` is idempotent (Laravel tracks applied migrations in the
DB), so just run it unconditionally on every activation instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 13:15:00 -04:00
co-authored by Claude Sonnet 5
parent bd112541e5
commit 2c9915699d
3 changed files with 15 additions and 8 deletions
+6 -3
View File
@@ -47,7 +47,7 @@ This project uses `just` (see `justfile`) and Nix directly.
`proxyServer = "caddy"` — so both proxy backends get exercised. The shared
assertion suite (factored into a `check_invoiceninja(machine, proxy_unit)` function in the test script) runs
against each node: waits for mysql/redis/phpfpm/queue-worker/proxy to be active, checks the
`invoiceninja-data-setup` marker files (`.initial-migration`, `.db-seeded`, `.admin-created`) to confirm the
`invoiceninja-data-setup` marker files (`.db-seeded`, `.admin-created`) to confirm the
one-time setup actually completed (not just that the oneshot unit exited), curls `/` for the app shell, exercises
the `invoiceninja-manage` wrapper, logs in via `/api/v1/login` using the test admin credentials as an
end-to-end check that migrations/seeding/account creation all worked, checks the full language list is
@@ -99,8 +99,11 @@ expect Nix to report the correct hash on a failed build (or use `nix-prefetch`/`
contents directly on disk in `dataDir`.
- `invoiceninja-data-setup.service` (oneshot, runs on every activation) does all first-run and idempotent setup:
clears bootstrap/opcode cache, rebuilds `.env`, rsyncs static storage into `dataDir`, symlinks the runtime
`app.php`, and guards one-time actions (`migrate`, `db:seed`, admin account creation) behind marker files
(`.initial-migration`, `.db-seeded`, `.admin-created`) in `dataDir`. `phpfpm-invoiceninja.service` and
`app.php`, runs `migrate --force` unconditionally on every activation (safe/idempotent — Laravel tracks
applied migrations in the DB and only runs new ones; this must NOT be marker-gated, since a later invoiceninja
version upgrade can ship new migrations that need to run on an already-migrated install), and guards the
remaining one-time actions (`db:seed`, admin account creation) behind marker files (`.db-seeded`,
`.admin-created`) in `dataDir`. `phpfpm-invoiceninja.service` and
`invoiceninja-queue-worker.service` both depend on it. It also unconditionally runs
`invoiceninja-manage cache:forget languages` on every activation, working around an upstream bug: migration
`2025_08_31_224356_additional_languages_af_ca_id.php` runs during `migrate` (before `db:seed`'s
+5 -2
View File
@@ -702,8 +702,11 @@ in
# We cannot link the cache folder only because bootstrap folder needs to be writeable.
ln -sf ${invoiceninja}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php
# Perform the first migration
[[ ! -f ${cfg.dataDir}/.initial-migration ]] && invoiceninja-manage migrate --force && touch ${cfg.dataDir}/.initial-migration
# Run migrations on every activation, not just the first: `migrate` is idempotent
# (Laravel tracks applied migrations in the DB and only runs new ones), and gating this
# behind a marker file meant migrations shipped by a later invoiceninja version upgrade
# would never run.
invoiceninja-manage migrate --force
# Seed database with records
# Necessary for languages, currencies, countries, etc.
+4 -3
View File
@@ -39,9 +39,10 @@ pkgs.testers.nixosTest {
# phpfpm-invoiceninja/invoiceninja-queue-worker both Require= invoiceninja-data-setup,
# so systemd already waited for it to finish above. Confirm the one-time setup steps
# it guards actually completed, rather than just that the unit exited.
with subtest("one-time setup (migrate, seed, admin account) completed"):
machine.succeed("test -f /var/lib/invoiceninja/.initial-migration")
# it guards actually completed, rather than just that the unit exited. (Migrations run
# unconditionally on every activation rather than being marker-gated, so there's no
# marker file to check for them here.)
with subtest("one-time setup (seed, admin account) completed"):
machine.succeed("test -f /var/lib/invoiceninja/.db-seeded")
machine.succeed("test -f /var/lib/invoiceninja/.admin-created")