aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/daemon.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/daemon.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/daemon.py48
1 files changed, 48 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/daemon.py b/packages/meshbay-hub/src/meshbay_hub/daemon.py
index 024ca78..9609de6 100644
--- a/packages/meshbay-hub/src/meshbay_hub/daemon.py
+++ b/packages/meshbay-hub/src/meshbay_hub/daemon.py
@@ -35,6 +35,12 @@ def main() -> None:
prune.add_argument("--log-level", default=argparse.SUPPRESS,
choices=["DEBUG", "INFO", "WARNING", "ERROR"])
+ migrate = sub.add_parser(
+ "migrate", help="bring the database schema up to date")
+ migrate.add_argument("--config", type=Path, default=argparse.SUPPRESS)
+ migrate.add_argument("--log-level", default=argparse.SUPPRESS,
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"])
+
args = parser.parse_args()
logging.basicConfig(
@@ -47,6 +53,9 @@ def main() -> None:
if args.command == "prune-groups":
sys.exit(asyncio.run(_prune_groups(cfg, args.days, args.dry_run)))
+ if args.command == "migrate":
+ sys.exit(_migrate(cfg))
+
single_worker_or_exit(cfg.server.workers)
uvicorn.run(
@@ -59,6 +68,45 @@ def main() -> None:
)
+def migrations_dir() -> Path:
+ """Where this installation's migrations actually are.
+
+ Derived from the package rather than written down, because the one place a
+ path like this can be correct is next to the code it describes. The RPM
+ installs `meshbay_hub` into a shared venv and copies `alembic.ini` to
+ `/opt/meshbay-hub/migrations/`, where `%(here)s/src/meshbay_hub/db/…`
+ resolves to a directory that does not exist — so the packaged unit's
+ `ExecStartPre` could never have succeeded, and a hub installed from the
+ package would not start at all. The same `%(here)s` trap had already been
+ found once on the server, with a copy of alembic.ini pointing at a
+ month-old snapshot of the tree.
+ """
+ return Path(__file__).resolve().parent / "db" / "migrations"
+
+
+def _migrate(cfg) -> int:
+ """`alembic upgrade head`, with the paths resolved from the installation.
+
+ A command rather than a path in a unit file: it is correct for the RPM,
+ the DEB, a venv, and a checkout, and there is nothing to keep in step.
+ """
+ from alembic import command
+ from alembic.config import Config
+
+ scripts = migrations_dir()
+ if not (scripts / "versions").is_dir():
+ print(f"meshbay-hub: no migrations at {scripts}", file=sys.stderr)
+ return 1
+
+ alembic_cfg = Config()
+ alembic_cfg.set_main_option("script_location", str(scripts))
+ # `env.py` reads the URL from the environment the same way the server does,
+ # so the two cannot drift; this is set for the case where it does not.
+ alembic_cfg.set_main_option("sqlalchemy.url", cfg.db.url)
+ command.upgrade(alembic_cfg, "head")
+ return 0
+
+
def single_worker_or_exit(workers: int) -> None:
"""Refuse to start with more than one worker.