mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-21 09:12:09 +00:00
33ec1c0e35
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>
52 lines
1.8 KiB
Python
Executable File
52 lines
1.8 KiB
Python
Executable File
#!/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
|
|
|
|
# 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."""
|
|
entries = []
|
|
for dirpath, dirs, files in os.walk(root):
|
|
for name in dirs + files:
|
|
full = os.path.join(dirpath, name)
|
|
try:
|
|
stat = os.stat(full)
|
|
entries.append({
|
|
'path': full,
|
|
'is_dir': os.path.isdir(full),
|
|
'size_bytes': stat.st_size,
|
|
'mtime': time.strftime('%Y-%m-%dT%H:%M:%S%z',
|
|
time.localtime(stat.st_mtime)),
|
|
})
|
|
except Exception as e:
|
|
# skip items we can't stat
|
|
continue
|
|
return entries
|
|
|
|
def main():
|
|
data = {
|
|
'scanned_at': time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime()),
|
|
'root': SCAN_PATH,
|
|
'entries': scan_directory(SCAN_PATH),
|
|
}
|
|
# 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)
|
|
os.replace(temp, OUTPUT_FILE)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|