From 6bb6941287a3e2c1804496f4361fc4d3fabbf174 Mon Sep 17 00:00:00 2001 From: q Date: Fri, 17 Oct 2025 11:29:29 +0300 Subject: [PATCH] update, and allow non-mount data --- code/Dockerfile | 41 ++++++++++++++--------------------------- code/app.py | 37 +++++++++++++++++++++++++++---------- code/entrypoint.sh | 22 +++++++++++----------- code/init_db.sh | 11 +++++++++-- code/utils/files.py | 14 +++++++++++++- docker-compose.yaml | 14 +++++++++----- env.example | 1 + 7 files changed, 84 insertions(+), 56 deletions(-) diff --git a/code/Dockerfile b/code/Dockerfile index c5517a2..1adf325 100644 --- a/code/Dockerfile +++ b/code/Dockerfile @@ -1,36 +1,23 @@ -FROM ubuntu:24.04 +FROM debian:stable +COPY ./requirements.txt /requirements.txt ENV DEBIAN_FRONTEND=noninteractive - +ENV TMPDIR=/tmp RUN apt-get update -yqq \ && apt-get install -y --no-install-recommends \ - curl \ - sqlite3 \ - tzdata \ - git \ - make \ - python3-venv \ - python3-pip \ + curl \ + tzdata \ + sqlite3 \ + python3-venv \ && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /data \ + && python3 -m venv /opt/venv \ + && . /opt/venv/bin/activate \ + && pip3 install --no-cache -r /requirements.txt \ + && rm -rf /root/.cache -ARG UUID -ARG UGID -ARG TZ -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -RUN id ubuntu && userdel ubuntu -RUN groupadd -g $UGID user && \ - useradd -u $UUID -g $UGID -ms /bin/bash user && \ - mkdir -p /opt/venv && chown $UUID:$UGID /opt/venv -COPY ./requirements.txt /requirements.txt -COPY docker-builder.sh / -USER user - -RUN bash /docker-builder.sh COPY ./ /app -USER root -RUN chown -R $UUID:$UGID /app -USER user - WORKDIR /app +RUN chmod 777 /app /data CMD bash /app/entrypoint.sh diff --git a/code/app.py b/code/app.py index e3b9eb6..49ccb23 100644 --- a/code/app.py +++ b/code/app.py @@ -20,6 +20,7 @@ from utils.files import ( db_add_download, db_delete_file, db_get_file, + db_get_last_maintenance, db_get_name, db_maintenance, db_store_file, @@ -28,6 +29,7 @@ from utils.files import ( file_full_url, file_list, file_list_simple, + get_db, invalidate_upload_token, new_upload_token, validate_upload_token, @@ -43,16 +45,17 @@ from werkzeug.utils import secure_filename logging.basicConfig( level=logging.INFO, - format=f"[%(asctime)s] [%(levelname)s] %(message)s", + format="[%(asctime)s] [%(levelname)s] %(message)s", ) -__VERSION__ = "20250328.0" +__VERSION__ = "20251017.0" app = Flask(__name__) app.config.from_object(__name__) app.config.from_prefixed_env() app.debug = True app.secret_key = app.config["APP_SECRET_KEY"] app.wsgi_app = ReverseProxied(app.wsgi_app) +app.config["KEY_LENGTH"] = int(app.config["KEY_LENGTH"]) @app.before_request @@ -70,14 +73,24 @@ def log_the_status_code(response): @app.route("/") def index(): """Returns Index""" - return render_template( - "index.html" - ) + return render_template("index.html") -@app.route('/health.html', methods=["GET",]) # fmt: skip +@app.route('/health', methods=["GET",]) # fmt: skip def health(): - return f"OK {request.url}", 200 + try: + get_db() + except Exception: + return "DB Error", 500 + + try: + last_maintenance = db_get_last_maintenance() + if time.time() > last_maintenance + 86400: # Daily + return f"OK, {db_maintenance()}, {request.url}", 200 + except Exception: + return "DB Maintenance error", 500 + + return f"OK, {request.url}", 200 @app.route("/upload", methods=["PUT", "POST"]) @@ -144,7 +157,7 @@ def upload(): return "IP list contains unknown characters" while True: - token = random_token() + token = random_token(app.config["KEY_LENGTH"]) folder = os.path.join(app.config["DATAFOLDER"], token) if not os.path.exists(folder): break @@ -196,6 +209,7 @@ def upload_token(): token = new_upload_token(expires) return token, 200 + @app.route("/details/", methods=["GET"]) @app.route("/details//", methods=["GET"]) def details(token, name=None): @@ -211,7 +225,10 @@ def details(token, name=None): if secret != app.config["ACCESS_TOKEN"]: return "Error", 401 if name is None: - name = db_get_name(token)[0] + try: + name = db_get_name(token)[0] + except TypeError: + return "No such file", 404 details = file_details(token, name) if details["allowed_ip"] is not None: if not is_ip_allowed( @@ -223,7 +240,7 @@ def details(token, name=None): @app.route("/delete/", methods=["GET"]) @app.route("/delete//", methods=["GET"]) -def delete_file(token,name=None): +def delete_file(token, name=None): """ Delete a file from the system """ diff --git a/code/entrypoint.sh b/code/entrypoint.sh index 25ea9d5..736ff6a 100644 --- a/code/entrypoint.sh +++ b/code/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON=python3 -SQLITE=sqlite3 + +set -eu export FLASK_DATAFOLDER="/data" export FLASK_DB="/data/flees.db" @@ -9,21 +9,21 @@ export SERVER=gunicorn export PID="flees.pid" export WORKERS export TIMEOUT +export TZ -if [[ $( stat -c %u /data ) -ne $( id -u ) ]]; then - echo User id and /data folder owner do not match - printf 'UID: %s\nFolder: %s\n' $( id -u ) $( stat -c %u /data ) - exit 1 +if [[ ! -w "$FLASK_DATAFOLDER" ]]; then + echo Cannot write to $FLASK_DATAFOLDER + exit 1 fi -set -eu . /opt/venv/bin/activate sh ./init_db.sh "$FLASK_DB" - +echo "Dowload script: curl -H 'Secret: [FLASK_ACCESS_TOKEN]' http://${FLASK_PUBLIC_URL}/script/mfl" exec "$SERVER" \ - -w $WORKERS \ - --timeout $TIMEOUT \ + -w "$WORKERS" \ + --worker-tmp-dir "$TMPDIR" \ + --timeout "$TIMEOUT" \ --pid="$PID" \ - -b 0.0.0.0:$INTERNAL_PORT \ + -b 0.0.0.0:"$INTERNAL_PORT" \ 'app:app' \ 2>&1 | tee -a /data/flees.log diff --git a/code/init_db.sh b/code/init_db.sh index c204473..38930b1 100644 --- a/code/init_db.sh +++ b/code/init_db.sh @@ -2,6 +2,7 @@ cat <<'EOF' | sqlite3 "$1" + CREATE TABLE IF NOT EXISTS files ( token text PRIMARY KEY, name text NOT NULL, @@ -13,12 +14,18 @@ CREATE TABLE IF NOT EXISTS files ( allowed_ip text, hidden boolean ); -EOF -cat <<'EOF' | sqlite3 "$1" CREATE TABLE IF NOT EXISTS upload_tokens ( token text PRIMARY KEY, added integer NOT NULL, expires integer NOT NULL ); + +CREATE TABLE IF NOT EXISTS tasks ( + key text PRIMARY KEY, + value integer NOT NULL +); + +INSERT OR IGNORE INTO tasks(key,value) VALUES('maintenance',0); + EOF diff --git a/code/utils/files.py b/code/utils/files.py index 5ce8e67..5312084 100644 --- a/code/utils/files.py +++ b/code/utils/files.py @@ -51,7 +51,6 @@ def db_get_name(token): ).fetchone() - def db_get_files(): db, c = get_db() return db.execute( @@ -92,6 +91,16 @@ def db_add_download(token, name): return +def db_get_last_maintenance(): + db, c = get_db() + return db.execute( + """ + SELECT value + FROM tasks WHERE key = 'maintenance' + """ + ).fetchone()[0] + + def db_maintenance(): messages = [] # === Delete DB entries where expiry or max DL is used up === @@ -174,6 +183,9 @@ def db_maintenance(): if c.rowcount > 0: db.commit() + db, c = get_db() + c.execute("UPDATE tasks SET value=? WHERE key='maintenance'", (int(time.time()),)) + db.commit() messages.append("Maintenance done.") return "\n".join(messages) diff --git a/docker-compose.yaml b/docker-compose.yaml index 0818cb1..741388f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,15 +3,18 @@ services: miniflees: build: context: code - args: - UUID: ${UUID} - UGID: ${UGID} - TZ: ${TZ} ports: - "${EXPOSE}:5000" volumes: - - ./data/:/data/ + - $HOME/tmp/data/:/data/ restart: unless-stopped + user: "${UUID}:${UGID}" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/health"] + interval: 3600s + timeout: 10s + retries: 3 + start_period: 40s environment: WORKERS: TIMEOUT: @@ -20,6 +23,7 @@ services: FLASK_PUBLIC_URL: FLASK_DEFAULT_EXPIRE: FLASK_DEFAULT_MAX_DL: + FLASK_KEY_LENGTH: TZ: diff --git a/env.example b/env.example index e0ad435..c5be2d1 100644 --- a/env.example +++ b/env.example @@ -9,3 +9,4 @@ FLASK_ACCESS_TOKEN=dff789f0bbe8183d3254258b33a147d580c1131f39a698c56d3f640ac8415 FLASK_PUBLIC_URL=http://localhost:8136 FLASK_DEFAULT_EXPIRE=2592000 FLASK_DEFAULT_MAX_DL=9999 +FLASK_KEY_LENGTH=8