Retro Bulletin Board Systems on atproto. Web app and TUI.
atbbs.xyz
python
tui
atproto
bbs
1from importlib.metadata import version as pkg_version
2
3import rich_click as click
4from platformdirs import user_data_dir
5
6DEFAULT_DATA_DIR = user_data_dir("atbbs")
7
8
9@click.group(invoke_without_command=True)
10@click.version_option(version=pkg_version("atbbs"), prog_name="atbbs")
11@click.pass_context
12def cli(ctx: click.Context):
13 """Decentralized bulletin boards on atproto."""
14 if ctx.invoked_subcommand is None:
15 ctx.invoke(dial)
16
17
18@cli.command()
19@click.argument("handle", required=False)
20def dial(handle: str | None):
21 """Dial a BBS from the terminal."""
22 from tui.app import AtbbsApp
23
24 app = AtbbsApp(dial=handle)
25 app.run()
26
27
28@cli.command()
29@click.option("--host", default="127.0.0.1", show_default=True, help="Host to bind to.")
30@click.option(
31 "--port", "-p", default=8000, show_default=True, type=int, help="Port to bind to."
32)
33@click.option(
34 "--workers",
35 "-w",
36 default=1,
37 show_default=True,
38 type=int,
39 help="Number of worker processes.",
40)
41@click.option(
42 "--public-url",
43 default=None,
44 help="Public URL for OAuth callbacks. [default: http://{host}:{port}]",
45)
46@click.option(
47 "--data-dir",
48 default=DEFAULT_DATA_DIR,
49 show_default=True,
50 help="Directory for secrets and database.",
51)
52def serve(host: str, port: int, workers: int, public_url: str | None, data_dir: str):
53 """Start the web server."""
54 import os
55 import subprocess
56 import sys
57
58 if not public_url:
59 public_url = f"http://{host}:{port}"
60
61 os.environ.setdefault("ATBBS_DATA_DIR", data_dir)
62 os.environ.setdefault("PUBLIC_URL", public_url)
63
64 # Ensure data dir and secrets exist before spawning workers
65 os.makedirs(data_dir, exist_ok=True)
66 from core.auth.config import load_secrets
67
68 load_secrets(data_dir)
69
70 cmd = [
71 sys.executable,
72 "-m",
73 "hypercorn",
74 "web.app:create_app()",
75 "--bind",
76 f"{host}:{port}",
77 "--workers",
78 str(workers),
79 ]
80 raise SystemExit(subprocess.run(cmd).returncode)
81
82
83def main():
84 cli()
85
86
87if __name__ == "__main__":
88 main()