trigger rebuild after activities upload

This commit is contained in:
Davide Scaini
2026-04-11 08:47:27 +02:00
parent 82830222ba
commit ef5b06c5b3
3 changed files with 64 additions and 14 deletions
+7 -1
View File
@@ -20,9 +20,11 @@ console = Console()
@click.option("--strava-client-secret", default=None, envvar="STRAVA_CLIENT_SECRET", help="Strava OAuth client secret")
@click.option("--max-users", default=None, type=int, help="Override max users for this instance (0 = unlimited; updates the DB setting)")
@click.option("--public-url", default=None, envvar="PUBLIC_URL", help="Public base URL (e.g. https://yourdomain.com). Required for Strava OAuth to work behind a reverse proxy.")
@click.option("--webroot", default=None, type=click.Path(), help="Nginx webroot (e.g. /var/www/bincio). When set, uploads trigger a full Astro build + rsync so new activity pages are immediately accessible without a git push.")
def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
strava_client_id: Optional[str], strava_client_secret: Optional[str],
max_users: Optional[int], public_url: Optional[str]) -> None:
max_users: Optional[int], public_url: Optional[str],
webroot: Optional[str]) -> None:
"""Start the bincio multi-user application server.
Handles auth, user management, and write operations.
@@ -54,6 +56,8 @@ def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
srv.strava_client_secret = strava_client_secret
if public_url:
srv.public_url = public_url
if webroot and site_dir:
srv.webroot = Path(webroot).expanduser().resolve()
db = open_db(dd)
current_limit = get_setting(db, "max_users")
@@ -63,6 +67,8 @@ def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
console.print(f" Data: [cyan]{dd}[/cyan]")
if srv.site_dir:
console.print(f" Site: [cyan]{srv.site_dir}[/cyan]")
if srv.webroot:
console.print(f" Web: [cyan]{srv.webroot}[/cyan] (auto-rebuild on upload)")
console.print(f" URL: [cyan]http://{host}:{port}[/cyan]")
if current_limit and int(current_limit) > 0:
console.print(f" Users: [yellow]max {current_limit}[/yellow]")
+56 -13
View File
@@ -82,6 +82,7 @@ def _job_finish(job_id: str) -> None:
data_dir: Path | None = None
site_dir: Path | None = None # for post-write rebuild trigger
webroot: Path | None = None # nginx webroot — when set, trigger full rebuild + rsync
strava_client_id: str = ""
strava_client_secret: str = ""
public_url: str = "" # e.g. "https://yourdomain.com" — used for OAuth redirect URIs
@@ -201,25 +202,67 @@ def _unique_image_name(directory: Path, filename: str) -> str:
# ── Post-write rebuild ────────────────────────────────────────────────────────
# Serialises concurrent rebuilds — only one full build runs at a time.
# A second upload that arrives while a build is in progress will queue and
# run after the first finishes, picking up all data written in between.
_rebuild_lock = threading.Lock()
def _trigger_rebuild(handle: str) -> None:
"""Asynchronously re-merge one user's shard and rewrite the root manifest."""
"""Asynchronously re-merge and optionally rebuild + rsync the site.
- Without --webroot: fast path — merges sidecars + rewrites root manifest
(~1 s). New activity pages require the nginx try_files fallback to work.
- With --webroot: full Astro build + rsync to the nginx webroot (~3060 s,
serialised). New activity pages are immediately accessible.
"""
if site_dir is None:
return
if not _VALID_HANDLE.match(handle):
return # safety: never pass untrusted strings to subprocess
uv = shutil.which("uv") or str(Path.home() / ".local" / "bin" / "uv")
try:
subprocess.Popen(
[uv, "run", "bincio", "render",
"--data-dir", str(data_dir),
"--site-dir", str(site_dir),
"--handle", handle,
"--no-build"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except Exception:
pass # rebuild failure must never 500 the calling endpoint
_data_dir = str(data_dir)
_site_dir = str(site_dir)
_webroot = str(webroot) if webroot else None
_handle = handle
def _run() -> None:
try:
if _webroot is None:
# Fast: only update data, skip Astro build
subprocess.run(
[uv, "run", "bincio", "render",
"--data-dir", _data_dir,
"--site-dir", _site_dir,
"--handle", _handle,
"--no-build"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
else:
# Full build + rsync — serialised so concurrent uploads don't race
with _rebuild_lock:
result = subprocess.run(
[uv, "run", "bincio", "render",
"--data-dir", _data_dir,
"--site-dir", _site_dir,
"--handle", _handle],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if result.returncode == 0:
# Rsync built site to nginx webroot
subprocess.run(
["rsync", "-a", "--delete",
f"{_site_dir}/dist/", _webroot + "/"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except Exception:
pass # rebuild failure must never affect the calling request
threading.Thread(target=_run, daemon=True).start()
# ── Auth endpoints ────────────────────────────────────────────────────────────