share: dockerize the file-share pipeline and document it

The Python half of the short-lived share links lived in the repo; the Apache
config and the cron entries that make it work were hand-placed on the host, so
the feature could not be rebuilt from a checkout. This adds the missing half.

Scripts (defaults unchanged, so existing bare-metal cron keeps working):
* scan_shares.py / revoke_shares.py take their paths from CONJURER_SHARE_*
  instead of hardcoding the Pi layout, and create their parent dirs;
* the revoke TTL is now CONJURER_SHARE_TTL_SECONDS. Its --help claimed "2min"
  while the code used a hardcoded 3600 - the help text now reports the real,
  configured value.

New share service:
* docker/Dockerfile.share - Apache + the two jobs, reusing the same scripts
  rather than forking copies;
* docker/share-vhost.conf.tpl - the previously undocumented Apache half.
  Three settings are load-bearing and commented as such: +FollowSymLinks (the
  shares ARE symlinks), -Indexes (a listing would expose every live token), and
  a deny rule for dotfiles (revoke_shares.py keeps .downloads.json - a map of
  every live token - inside the served directory);
* docker/entrypoint.share.sh - renders the vhost, seeds the index on first run,
  then runs scanner/revoker in sleep loops beside Apache (no cron, so their
  output shows up in docker logs);
* compose + env example, incl. the two volumes that MUST be shared with the
  musician (it creates the links and reads the index).

docs/deployment/FILE_SHARING.md documents the mechanism, both deployment routes
(docker and existing host Apache), the Discord command, why each Apache setting
matters, verification commands, and the known limitations - notably that a link
nobody ever downloads is never revoked, since the TTL starts at first download.

Verified: scanner and revoker exercised end-to-end against temp dirs (index
built; token recorded from a combined-format log line and the symlink unlinked).
Compose file not validated - no docker on this machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Michal Tuszowski
2026-07-19 22:16:49 +02:00
parent dc449dd74c
commit 33ec1c0e35
9 changed files with 437 additions and 10 deletions
+12 -5
View File
@@ -1,12 +1,18 @@
#!/usr/bin/env python3
"""Build the share index that the musician's /get_share_list endpoint searches.
Walks the media root and writes a JSON catalogue of every file/dir found.
Meant to run periodically (cron on bare metal, the scheduler loop in the share
container). Paths are environment-overridable; the defaults reproduce the
original Raspberry Pi layout, so existing cron entries keep working unchanged.
"""
import os
import json
import time
from pathlib import Path
# Configuration
SCAN_PATH = '/mnt/shares'
OUTPUT_FILE = '/var/log/share_scan.json'
# Configuration (env-overridable; defaults = original bare-metal Pi layout)
SCAN_PATH = os.getenv('CONJURER_SHARE_SCAN_PATH', '/mnt/shares')
OUTPUT_FILE = os.getenv('CONJURER_SHARE_DB', '/var/log/share_scan.json')
def scan_directory(root):
"""Recursively walk `root` and collect metadata."""
@@ -34,7 +40,8 @@ def main():
'root': SCAN_PATH,
'entries': scan_directory(SCAN_PATH),
}
# Write atomically
# Write atomically (create the target dir first so a fresh volume works)
os.makedirs(os.path.dirname(OUTPUT_FILE) or '.', exist_ok=True)
temp = OUTPUT_FILE + '.tmp'
with open(temp, 'w') as f:
json.dump(data, f, indent=2)