Ctrl K
ComunidadGuías…

simple_nas/app/share_worker.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""The public share site as its own process.

Launched by app.py through share_jail.sh, which drops this to `nobody`, strips
the capabilities down to what uploads need, and hides everything outside the
shared folders and a short list of /data files. It then serves exactly the same
WSGI app the in-process fallback serves - the confinement is the only
difference.

Run standalone it simply serves without the sandbox (that is what the
in-process fallback does by importing serve_share).
"""
import json
import os
import sys
import threading
import time

HERE = os.path.dirname(os.path.abspath(__file__))
if HERE not in sys.path:
    sys.path.insert(0, HERE)

import accesslog
import ratelimit
import sharesandbox
import sharing_store
from ratelimit import LIMITER


def _load_json(path, default):
    try:
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except (OSError, ValueError):
        return default


def _save_json(path, data, mirror=True):
    tmp = f"{path}.tmp.{os.getpid()}"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(data, f)
    os.replace(tmp, path)


def _opt_reader(opts):
    def _opt(key, default=None):
        v = opts.get(key, default)
        return default if v is None else v
    return _opt


def _snapshot_loop(data_dir, opt, stop):
    """Publish the limiter state and apply queued unlocks, ~every 2 s."""
    link_ids = []
    last_scan = 0.0
    while not stop.is_set():
        try:
            for key in sharesandbox.drain_unlocks(data_dir):
                LIMITER.clear(key=key)
                print(f"[SHARE] Sperre aufgehoben: {key}", flush=True)
            now = time.time()
            if now - last_scan > 5:          # link ids change rarely; rescan cheaply
                link_ids = [l.get("id") for l in sharing_store.list_links() if l.get("id")]
                last_scan = now
            snap = LIMITER.snapshot(ratelimit.AUTHFAIL_IP[1])
            snap["link_locked"] = [lid for lid in link_ids
                                   if LIMITER.locked(f"authfail:link:{lid}", *ratelimit.AUTHFAIL_LINK)]
            sharesandbox.write_snapshot(data_dir, snap)
        except Exception as e:            # a transient error must not kill the loop
            print(f"[SHARE] Snapshot-Schleife: {type(e).__name__}: {e}", flush=True)
        stop.wait(2)


def serve_share(opt, load_shares, share_roots, data_dir, blocking=True, sandboxed=False):
    """Build the public app and serve it with waitress. Returns the waitress
    server when blocking is False (the in-process fallback runs it in a thread)."""
    import share_web
    from waitress import create_server

    share_app = share_web.create_share_app(opt, load_shares, share_roots, data_dir)
    share_web.assert_public_surface(share_app)

    import clamav
    share_web.SCAN_HOOK = clamav.make_hook(opt)

    host = str(opt("share_bind", "0.0.0.0") or "0.0.0.0")
    port = int(opt("share_port", 8101) or 8101)
    body_limit = int(opt("share_max_upload_mb", 1024) or 1024) * 1024 * 1024 + 8 * 1024 * 1024
    srv = create_server(share_app, host=host, port=port, threads=8, ident=None,
                        channel_timeout=600, max_request_body_size=body_limit,
                        asyncore_use_poll=True)
    share_web.RUNNING = True
    where = "abgeschottet als nobody" if sandboxed else "im Hauptprozess"
    print(f"[SHARE] oeffentliche Freigabe-Seite auf {host}:{port} ({where})", flush=True)
    if not blocking:
        return srv
    srv.run()
    return srv


def main():
    data_dir = os.environ.get("SHARE_DATA_DIR", "/data")
    opts = sharesandbox.read_options(data_dir)
    opt = _opt_reader(opts)

    sharing_store.init(data_dir, _load_json, _save_json)
    accesslog.init(os.path.join(data_dir, sharesandbox.LOG_FILE), opt("share_log_max_mb", 5))

    def load_shares():
        return _load_json(os.path.join(data_dir, "shares.json"), [])

    def share_roots():
        roots = opt("share_allowed_roots", None)
        if not isinstance(roots, list) or not roots:
            roots = list(sharing_store.DEFAULT_SHARE_ROOTS)
        return [str(r).strip() for r in roots if str(r).strip()]

    stop = threading.Event()
    threading.Thread(target=_snapshot_loop, args=(data_dir, opt, stop),
                     daemon=True, name="share-locks").start()

    uid = os.getuid() if hasattr(os, "getuid") else -1
    print(f"[SHARE] Worker gestartet (uid={uid})", flush=True)
    try:
        serve_share(opt, load_shares, share_roots, data_dir, blocking=True, sandboxed=True)
    finally:
        stop.set()


if __name__ == "__main__":
    main()