diff --git a/s3-mount/install-rclone.sh b/s3-mount/install-rclone.sh new file mode 100644 index 0000000..7ad71d9 --- /dev/null +++ b/s3-mount/install-rclone.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl https://rclone.org/install.sh | sudo bash + +rclone config + + diff --git a/s3-mount/install-service.sh b/s3-mount/install-service.sh new file mode 100644 index 0000000..65a14ca --- /dev/null +++ b/s3-mount/install-service.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cp rclone-snikket.service /etc/systemd/system/rclone-snikket.service +systemctl daemon-reload +systemctl enable rclone-snikket +systemctl start rclone-snikket + diff --git a/s3-mount/rclone-snikket.service b/s3-mount/rclone-snikket.service new file mode 100644 index 0000000..aadd50f --- /dev/null +++ b/s3-mount/rclone-snikket.service @@ -0,0 +1,23 @@ +[Unit] +Description=rclone S3 mount for Snikket file share +After=network-online.target +Wants=network-online.target +Before=docker.service + +[Service] +Type=notify +ExecStartPre=/bin/mkdir -p /mnt/snikket-files +ExecStart=/usr/bin/rclone mount \ + jabogram-vk:jabogram/http_file_share \ + /mnt/snikket-files \ + --vfs-cache-mode writes \ + --vfs-cache-max-age 1h \ + --vfs-cache-max-size 512M \ + --allow-other +ExecStop=/bin/fusermount -uz /mnt/snikket-files +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target + diff --git a/s3-mount/uninstall-service.sh b/s3-mount/uninstall-service.sh new file mode 100644 index 0000000..ab99bb4 --- /dev/null +++ b/s3-mount/uninstall-service.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +systemctl stop rclone-snikket +systemctl disable rclone-snikket + diff --git a/server/docker-compose.yml b/server/docker-compose.yml index 1acb7c3..ba13d38 100644 --- a/server/docker-compose.yml +++ b/server/docker-compose.yml @@ -42,25 +42,12 @@ services: volumes: - snikket_data:/snikket - ./prosody.cfg.lua:/etc/prosody/conf.d/custom.cfg.lua:ro + - /mnt/snikket-files:/snikket/prosody/share%2ejbr%2ebvn13%2eme/http_file_share restart: "unless-stopped" depends_on: - postgres - s3_upload_handler - s3_upload_handler: - container_name: snikket-s3-upload - build: ./s3-upload-handler - network_mode: host - env_file: - - secrets.env - environment: - S3_BUCKET: "jabogram" - S3_REGION: "ru-msk" - # For MinIO or other S3-compatible storage, uncomment: - S3_ENDPOINT: "https://hb.vkcloud-storage.ru" - PRESIGN_EXPIRE: "3600" - restart: "unless-stopped" - postgres: container_name: snikket-postgres image: postgres:17 diff --git a/server/s3-upload-handler/Dockerfile b/server/s3-upload-handler/Dockerfile deleted file mode 100644 index 938f696..0000000 --- a/server/s3-upload-handler/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.12-slim - -WORKDIR /app -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY main.py . - -EXPOSE 5050 - -CMD ["gunicorn", "-b", "0.0.0.0:5050", "-w", "2", "--timeout", "120", "main:app"] diff --git a/server/s3-upload-handler/main.py b/server/s3-upload-handler/main.py deleted file mode 100644 index 707ea25..0000000 --- a/server/s3-upload-handler/main.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Prosody mod_http_upload_external -> S3 handler. - -Implements the same HMAC protocol as prosody-filer but stores files in S3. - -Flow: - 1. XMPP client asks Prosody for an upload slot - 2. Prosody generates a signed PUT URL pointing here - 3. Client PUTs the file; this handler verifies the HMAC and uploads to S3 - 4. On GET, handler generates a presigned S3 URL and redirects -""" - -import hashlib -import hmac as hmac_mod -import logging -import os - -import boto3 -from flask import Flask, Response, redirect, request - -app = Flask(__name__) -logging.basicConfig(level=logging.INFO) -log = logging.getLogger("s3-upload-handler") - -SECRET = os.environ["UPLOAD_SECRET"] -S3_BUCKET = os.environ["S3_BUCKET"] -S3_REGION = os.environ.get("S3_REGION", "us-east-1") -S3_ENDPOINT = os.environ.get("S3_ENDPOINT") -PRESIGN_EXPIRE = int(os.environ.get("PRESIGN_EXPIRE", "3600")) - -s3_kwargs = { - "region_name": S3_REGION, -} -if S3_ENDPOINT: - s3_kwargs["endpoint_url"] = S3_ENDPOINT - -s3 = boto3.client("s3", **s3_kwargs) - - -def verify_token(path: str, size: str, content_type: str, token: str) -> bool: - """Verify HMAC-SHA256 token from Prosody mod_http_upload_external.""" - message = f"{path} {size} {content_type}" - expected = hmac_mod.new( - SECRET.encode(), message.encode(), hashlib.sha256 - ).hexdigest() - return hmac_mod.compare_digest(expected, token) - - -@app.route("/upload/", methods=["PUT"]) -def upload(file_path): - token = request.args.get("v", "") - content_type = request.args.get("t", "application/octet-stream") - file_size = request.args.get("s", "0") - - hmac_path = f"upload/{file_path}" - - if not verify_token(hmac_path, file_size, content_type, token): - log.warning("HMAC verification failed for %s", hmac_path) - return Response("Forbidden", status=403) - - body = request.get_data() - - if len(body) != int(file_size): - return Response("Content length mismatch", status=400) - - s3.put_object( - Bucket=S3_BUCKET, - Key=file_path, - Body=body, - ContentType=content_type, - ) - - log.info("Uploaded %s (%s bytes) to s3://%s/%s", file_path, file_size, S3_BUCKET, file_path) - return Response(status=201) - - -@app.route("/upload/", methods=["GET", "HEAD"]) -def download(file_path): - try: - url = s3.generate_presigned_url( - "get_object", - Params={"Bucket": S3_BUCKET, "Key": file_path}, - ExpiresIn=PRESIGN_EXPIRE, - ) - except Exception: - log.exception("Failed to generate presigned URL for %s", file_path) - return Response("Not found", status=404) - - return redirect(url, code=302) - - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=5050) diff --git a/server/s3-upload-handler/requirements.txt b/server/s3-upload-handler/requirements.txt deleted file mode 100644 index 8b0c72e..0000000 --- a/server/s3-upload-handler/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flask==3.1.* -gunicorn==23.* -boto3==1.36.* diff --git a/server/scripts/invite-new-admin.sh b/server/scripts/invite-new-admin.sh index 70077b2..866a8a4 100644 --- a/server/scripts/invite-new-admin.sh +++ b/server/scripts/invite-new-admin.sh @@ -8,4 +8,4 @@ if [[ -z "$SNIKKET_DOMAIN" ]]; then fi #exec docker exec -it snikket prosodyctl shell invite create_account "bvn13@$SNIKKET_DOMAIN" -exec docker exec -it snikket create-invite --admin --group default "bvn13@$SNIKKET_DOMAIN" +exec docker exec -it snikket create-invite --admin --group default "$1@$SNIKKET_DOMAIN" diff --git a/server/scripts/invite-new.sh b/server/scripts/invite-new.sh index 7847f11..7ea51d5 100644 --- a/server/scripts/invite-new.sh +++ b/server/scripts/invite-new.sh @@ -7,4 +7,4 @@ if [[ -z "$SNIKKET_DOMAIN" ]]; then exit 1; fi -exec docker exec -it snikket prosodyctl shell invite create_account "bvn13@$SNIKKET_DOMAIN" +exec docker exec -it snikket prosodyctl shell invite create_account "$1@$SNIKKET_DOMAIN"