184afa20

Add 'bevel migrate' so bevel works on a fresh machine

## The bug

On a machine that has never had a `bevel` subcommand run on it, bevel does not
work. `bevel-gather` fails with `Failed to insert command: unable to open
database file` (or `no such table: command` if the directory happens to exist),
so nothing is ever recorded and there is nothing for `bevel sync` to sync. The
workaround was to run `bevel sync` once, let it fail, and rely on the
migrations it applied on the way in.

## Root cause

`bevel-gather/bevel-gather.c` only ever `INSERT`s and `UPDATE`s rows in the
`command` table. It never creates the schema, and it does not create the data
directory either.

The only place the schema was ever created was `bevelCLI`'s `runC` in
`bevel-cli/src/Bevel/CLI.hs`, which every subcommand goes through. So the
schema appeared as a side effect of running any `bevel` subcommand, and never
otherwise. The home-manager module installed the harness and, when
`sync.enable` was set, a `bevel-sync` service and daily timer, but nothing ran
the migrations at activation time.

## The test

`nix/nixos-module-test.nix` now records a command with `bevel-gather` right
after home-manager activation and asserts the row lands in the database. It is
placed before the existing `bevel register` / `login` / `sync` steps, because
those are exactly what used to hide the bug.

Committed separately in 0402393, where it fails with the error above.

There is also a cheap Haskell-level test in `bevel-cli/test/Bevel/CLISpec.hs`
covering the new subcommand on its own: `bevel migrate --database <nested path>`
followed by a `bevel-gather` run against that database.

## The fix

- New `bevel migrate` subcommand. `bevelCLI` is restructured so that taking the
  database lock and opening the pool (`withPool`) is separate from applying the
  migrations (`migrateDb`); `migrate` takes the exclusive lock and does only
  the latter. Every other subcommand still migrates first, as before.
- `nix/home-manager-module.nix` runs it in two places:
  - a `home.activation.bevelMigrate` script, and
  - a `bevel-migrate` user service wanted by `default.target`, which
    `bevel-sync` now `Wants` and is ordered `After`.

The activation script is the part that actually closes the hole, and this is a
deviation from the systemd-service-only approach we discussed. The user's
systemd instance is not necessarily running while home-manager activates: on
the fresh VM in the module test, activation logs `User systemd daemon not
running. Skipping reload.` and no user unit runs at all. Even with a live user
manager, `default.target` is reached concurrently with the first login shell, so
the service alone leaves a race for the first few commands after boot. The
service is still worth keeping for the reboot case and for the ordering
guarantee ahead of `bevel-sync`.

Note that `bevel-migrate` is now created whenever `programs.bevel.enable` is
set, not only when `sync.enable` is, since gathering needs the schema
regardless of whether syncing is on.

## Things to check manually

- Deploy to `tv` and confirm gathering works without ever running `bevel`
  by hand.
- `bevel migrate` failing now fails `home-manager switch`. I think a loud
  failure beats the silent one we had, but it is a behaviour change worth a
  second opinion: a corrupt history database would block switching the whole
  home configuration.
- `programs.bevel.config.database`, if set, is honoured by `bevel migrate`
  (via `BEVEL_CONFIG_FILE`) but not by `bevel-gather`, which only looks at
  `BEVEL_DATABASE` and `XDG_DATA_HOME`. That divergence predates this change,
  but it now means the migrated database and the gathered-into database can be
  different files. Same for a non-default `xdg.dataHome`.
- Whether `bevel-gather` should create the schema itself, so nothing outside it
  has to be arranged correctly first. Out of scope here.

## Base branch

Opened against `master`: `origin/development` is 8 commits behind `master` and
`origin/HEAD` points at `master`, so a PR onto `development` would have shown
those 8 commits as part of the diff.

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 0s 0s 0s 0s 0s
Eval 1s 14s 14s 15s 0s
Build 14s 0s 0s 14s 0s
Suite 0s 14s 15s 15s 0s

Timeline

0s10s