added ftp support

This commit is contained in:
dm
2025-08-31 16:12:29 +03:00
parent 8c0bff24bd
commit 45ed8f9117
4 changed files with 54 additions and 24 deletions

3
.env
View File

@@ -2,3 +2,6 @@ FILES_DIR="./files"
API_KEY="aboba"
FILES_PADDING="5"
DATABASE_NAME="files.db"
FTP_URL="ftp"
FTP_LOGIN="sfs"
FTP_PASSWORD="MySecurePass123!"

View File

@@ -2,14 +2,6 @@
simple file server
### .env example
```
FILES_DIR="./files"
API_KEY="aboba"
FILES_PADDING="5"
DATABASE_NAME="files.db"
```
Working mpv
- [x] get/post files

View File

@@ -1,8 +1,10 @@
from fastapi import FastAPI, File, UploadFile, Depends, HTTPException, Security
from fastapi.responses import FileResponse, PlainTextResponse
from fastapi.responses import FileResponse, PlainTextResponse, StreamingResponse
from fastapi.security import APIKeyHeader
from sqlalchemy import exists
import hashlib
from ftplib import FTP
from io import BytesIO
from . import db
from dotenv import load_dotenv
import os
@@ -13,6 +15,10 @@ FILES_DIR = os.getenv("FILES_DIR")
API_KEY = os.getenv("API_KEY")
api_key_header = APIKeyHeader(name="X-API-Key")
FTP_URL = os.getenv("FTP_URL")
FTP_LOGIN = os.getenv("FTP_LOGIN")
FTP_PASSWORD = os.getenv("FTP_PASSWORD")
def verify_api_key(api_key: str = Security(api_key_header)):
if api_key != API_KEY:
raise HTTPException(status_code=403, detail="Forbidden")
@@ -42,8 +48,16 @@ async def save_file(file: UploadFile = File(...), api_key: str = Depends(verify_
file_url = db.add_file(file.filename, file.content_type, file.size, hash)
try:
with open(f"{FILES_DIR}/{file_url}", "wb") as f:
f.write(contents)
ftp = FTP(FTP_URL)
ftp.login(FTP_LOGIN, FTP_PASSWORD)
buffer = BytesIO(contents)
try:
ftp.mkd(FILES_DIR)
except:
pass
ftp.storbinary(f"STOR {FILES_DIR}/{file_url}", buffer)
ftp.quit()
return {"status": "saved", "filename": file_url}
except Exception as e:
db.remove_file(file_url)
@@ -53,25 +67,37 @@ async def save_file(file: UploadFile = File(...), api_key: str = Depends(verify_
@app.get("/file/{filename}")
async def get_file(filename: str, raw: bool = False, api_key: str = Depends(verify_api_key)):
file_path = os.path.join(FILES_DIR, filename)
try:
ftp = FTP(FTP_URL)
ftp.login(FTP_LOGIN, FTP_PASSWORD)
buffer = BytesIO()
ftp.retrbinary(f"RETR {FILES_DIR}/{filename}", buffer.write)
ftp.quit()
buffer.seek(0)
except Exception as e:
raise HTTPException(status_code=404, detail=f"File not found: {e}")
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="File not found")
if raw:
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
return PlainTextResponse(f.read())
return PlainTextResponse(buffer.getvalue().decode("utf-8", errors="ignore"))
return FileResponse(file_path, filename=filename)
return StreamingResponse(buffer, media_type="application/octet-stream", headers={"Content-Disposition": f'attachment; filename="{filename}"'})
from ftplib import FTP
@app.delete("/file/{filename}")
async def delete_file(filename: str, api_key: str = Depends(verify_api_key)):
if db.remove_file(filename):
file_path = f"{FILES_DIR}/{filename}"
if os.path.exists(file_path):
os.remove(file_path)
if not db.remove_file(filename):
return {"status": "error", "message": "no file like that"}
try:
ftp = FTP(FTP_URL)
ftp.login(FTP_LOGIN, FTP_PASSWORD)
ftp.delete(f"{FILES_DIR}/{filename}")
ftp.quit()
return {"status": "deleted"}
return {"status": "error", "message": "no file like that"}
except Exception as e:
return {"status": "error", "message": f"Could not delete file {filename}: {e}"}
@app.get("/files/")
async def get_list_of_files(api_key: str = Depends(verify_api_key)):

View File

@@ -1,13 +1,22 @@
services:
web:
build: .
container_name: sfs
container_name: sfs.api
command: uv run uvicorn app.api:app --host 0.0.0.0 --port 8000
ports:
- "8000:8000"
volumes:
- .:/app
- ./files:/app/files
env_file:
- .env
restart: unless-stopped
ftp:
image: delfer/alpine-ftp-server
container_name: sfs.ftp
ports: []
environment:
USERS: "sfs|MySecurePass123!|/home/sfs"
ADDRESS: "ftp"
volumes:
- /opt/sfs/files:/home/sfs
restart: unless-stopped