lvwerra's picture
lvwerra HF Staff
Deploy msgboard demo
1f31ca5 verified
Raw History Blame Contribute Delete
10.9 kB
"""python -m msgboard {init,serve,backup,rotate-operator-token,demo}"""
from __future__ import annotations
import argparse
import ipaddress
import os
import signal
import sys
import time
from pathlib import Path
from . import db as dbmod
from . import store
from .config import DB_NAME, DEFAULT_PORT, OPERATOR_TOKEN_FILE
NETWORK_FS = {"nfs", "nfs4", "cifs", "smb3", "fuse", "fuseblk", "9p", "sshfs"}
def _fs_type(path: Path) -> str | None:
"""Best-effort filesystem type of `path` from /proc/mounts (Linux only)."""
try:
mounts = Path("/proc/mounts").read_text().splitlines()
except OSError:
return None
best, fstype = "", None
target = str(path.resolve())
for line in mounts:
parts = line.split()
if len(parts) >= 3 and (target == parts[1] or target.startswith(parts[1].rstrip("/") + "/")):
if len(parts[1]) > len(best):
best, fstype = parts[1], parts[2]
return fstype
def _warn_fs(data_dir: Path) -> None:
fstype = _fs_type(data_dir)
if fstype and (fstype in NETWORK_FS or fstype.startswith("fuse")):
print(f"warning: {data_dir} is on a {fstype} filesystem. SQLite WAL needs a local disk; "
"do not keep a live board on network or bucket mounts.", file=sys.stderr)
def _write_secret_file(path: Path, secret: str) -> None:
"""Create a 0600 file that must not already exist, and fsync it."""
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
os.write(fd, (secret + "\n").encode())
os.fsync(fd)
finally:
os.close(fd)
def cmd_init(args) -> int:
data_dir = Path(args.data_dir).expanduser()
data_dir.mkdir(parents=True, exist_ok=True)
os.chmod(data_dir, 0o700)
_warn_fs(data_dir)
db_path = data_dir / DB_NAME
token_path = data_dir / OPERATOR_TOKEN_FILE
existed = db_path.exists()
conn = dbmod.connect(db_path)
try:
before = dbmod.schema_version(conn)
try:
version = dbmod.migrate(conn, db_path)
except dbmod.SchemaError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
if store.admin_exists(conn):
note = "migrated" if before != version else "unchanged"
print(f"Board already initialized in {data_dir} (schema v{version}, {note}).")
tmp_path = data_dir / (OPERATOR_TOKEN_FILE + ".tmp")
if not token_path.exists() and tmp_path.exists():
# An earlier init committed but stopped before moving the file into place.
try:
store.authenticate(conn, tmp_path.read_text().strip(), time.time())
os.link(tmp_path, token_path)
tmp_path.unlink()
except Exception:
pass
if token_path.exists():
print(f"Operator token file: {token_path}")
else:
print("Operator token file is missing; existing credentials were left untouched. "
"Run `python -m msgboard rotate-operator-token --data-dir ...` to issue a new one.")
return 0
if token_path.exists():
print(f"error: {token_path} exists but the database has no operator; refusing to overwrite it. "
"Move the file away if it is stale.", file=sys.stderr)
return 1
# Write the secret to a temp file first, then commit, then move it into place.
tmp_path = data_dir / (OPERATOR_TOKEN_FILE + ".tmp")
if tmp_path.exists():
tmp_path.unlink()
conn.execute("BEGIN IMMEDIATE")
try:
_, token = store.create_operator(conn, time.time())
_write_secret_file(tmp_path, token)
conn.execute("COMMIT")
except BaseException:
conn.execute("ROLLBACK")
if tmp_path.exists():
tmp_path.unlink()
raise
os.link(tmp_path, token_path) # fails rather than overwrite
tmp_path.unlink()
finally:
conn.close()
print(f"{'Initialized schema in existing' if existed else 'Created'} board in {data_dir} (schema v{version}).")
print(f"Operator token saved to {token_path} (mode 0600). Keep it secret; open the dashboard with it to create invite links.")
return 0
def cmd_rotate_operator(args) -> int:
data_dir = Path(args.data_dir).expanduser()
db_path = data_dir / DB_NAME
token_path = data_dir / OPERATOR_TOKEN_FILE
if not db_path.exists():
print(f"error: no board in {data_dir}", file=sys.stderr)
return 1
conn = dbmod.connect(db_path)
try:
if dbmod.schema_version(conn) != dbmod.SCHEMA_VERSION:
print("error: run init first to migrate the schema", file=sys.stderr)
return 1
new_path = data_dir / (OPERATOR_TOKEN_FILE + ".new")
if new_path.exists():
new_path.unlink()
conn.execute("BEGIN IMMEDIATE")
try:
token = store.rotate_operator(conn, time.time())
_write_secret_file(new_path, token)
conn.execute("COMMIT")
except BaseException:
conn.execute("ROLLBACK")
if new_path.exists():
new_path.unlink()
raise
os.replace(new_path, token_path)
finally:
conn.close()
print(f"Operator token rotated; the old one is revoked. New token saved to {token_path}.")
return 0
def cmd_backup(args) -> int:
db_path = Path(args.data_dir).expanduser() / DB_NAME
if not db_path.exists():
print(f"error: no board at {db_path}", file=sys.stderr)
return 1
conn = dbmod.connect(db_path)
try:
dbmod.backup(conn, args.out)
finally:
conn.close()
print(f"Backup written to {args.out}. Attachments live in {db_path.parent / 'blobs'}; copy them too.")
return 0
def _is_loopback(host: str) -> bool:
if host == "localhost":
return True
try:
return ipaddress.ip_address(host).is_loopback
except ValueError:
return False
def cmd_serve(args) -> int:
from .server import AlreadyRunning, Board, NotInitialized
data_dir = Path(args.data_dir).expanduser()
_warn_fs(data_dir)
replicator = None
if args.replica:
from .replicate import Replicator, restore, store_from_url
store = store_from_url(args.replica)
if not (data_dir / DB_NAME).exists():
manifest = restore(store, data_dir)
print(f"Restored {manifest['snapshot']} from replica." if manifest
else "Replica is empty; nothing to restore.", file=sys.stderr)
replicator = Replicator(store, data_dir, interval=args.snapshot_interval)
try:
board = Board(data_dir, replicator=replicator)
except (NotInitialized, AlreadyRunning, dbmod.SchemaError) as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
if not _is_loopback(args.host):
print(f"warning: listening on {args.host}. Tokens travel in plain HTTP; "
"put HTTPS in front of this outside isolated local development.", file=sys.stderr)
run_board(board, args.host, args.port, args.log_level)
return 0
def run_board(board, host: str, port: int, log_level: str = "info", app=None) -> None:
"""Serve `board` in the foreground until SIGINT/SIGTERM, then close it (final snapshot included)."""
import uvicorn
from .server import create_app
class BoardServer(uvicorn.Server):
def handle_exit(self, sig, frame):
board.begin_shutdown()
super().handle_exit(sig, frame)
config = uvicorn.Config(
app or create_app(board), host=host, port=port, log_level=log_level,
timeout_graceful_shutdown=5, server_header=False,
)
# uvicorn re-raises the signal it caught after shutting down. With the
# default SIGTERM action that kills the process before board.close() runs,
# so the final snapshot would be lost. Turn it into a normal exit instead.
def _exit(signum, frame):
raise SystemExit(0)
signal.signal(signal.SIGTERM, _exit)
signal.signal(signal.SIGINT, _exit)
try:
BoardServer(config).run()
finally:
board.close()
print("Board stopped cleanly.", file=sys.stderr)
def cmd_restore(args) -> int:
from .replicate import restore, store_from_url
manifest = restore(store_from_url(args.replica), Path(args.data_dir).expanduser())
if manifest is None:
print("error: replica has no snapshot", file=sys.stderr)
return 1
print(f"Restored {manifest['snapshot']} ({manifest['size']} bytes) into {args.data_dir}.")
return 0
def cmd_demo(args) -> int:
from .demo import run_demo
return run_demo(args.data_dir, args.port)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="python -m msgboard", description=__doc__)
sub = parser.add_subparsers(dest="command", required=True)
p = sub.add_parser("init", help="create or migrate a board; never overwrites existing credentials")
p.add_argument("--data-dir", required=True)
p.set_defaults(func=cmd_init)
p = sub.add_parser("serve", help="run the HTTP server in the foreground (Ctrl-C to stop)")
p.add_argument("--data-dir", required=True)
p.add_argument("--host", default="127.0.0.1")
p.add_argument("--port", type=int, default=DEFAULT_PORT)
p.add_argument("--log-level", default="info")
p.add_argument("--replica", default=None,
help="snapshot target: hf://buckets/<ns>/<bucket>[/<prefix>] or a directory; "
"an empty data dir is restored from it first")
p.add_argument("--snapshot-interval", type=float, default=10.0, help="seconds between snapshots")
p.set_defaults(func=cmd_serve)
p = sub.add_parser("restore", help="restore an empty data dir from a replica")
p.add_argument("--data-dir", required=True)
p.add_argument("--replica", required=True)
p.set_defaults(func=cmd_restore)
p = sub.add_parser("backup", help="write a consistent SQLite snapshot")
p.add_argument("--data-dir", required=True)
p.add_argument("--out", required=True)
p.set_defaults(func=cmd_backup)
p = sub.add_parser("rotate-operator-token", help="replace the operator token (local file access required)")
p.add_argument("--data-dir", required=True)
p.set_defaults(func=cmd_rotate_operator)
p = sub.add_parser("demo", help="start a throwaway board and run two agents against it")
p.add_argument("--data-dir", default=None, help="defaults to a new temporary directory")
p.add_argument("--port", type=int, default=0, help="0 picks a free port")
p.set_defaults(func=cmd_demo)
args = parser.parse_args(argv)
return args.func(args)