first commit

This commit is contained in:
hnzio committed 2026-09-07 12:26:43 +02:00
commit 5de45a37bd
9 files changed
+499

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
.git
.gitignore
*.zip
+12
View File
@@ -0,0 +1,12 @@
# Local configuration and secrets
.env
.env.*
env.sh
secret
# Build artifacts and local tooling
*.zip
.idea/
.vscode/
*~
.DS_Store
+17
View File
@@ -0,0 +1,17 @@
{
"id": "io.hnz.floppy",
"title": "Floppy",
"description": "Self-hosted media tracker and Trakt alternative.",
"tagline": "Self-hosted media tracking",
"version": "0.1.4",
"healthCheckPath": "/",
"httpPort": 8000,
"addons": {
"localstorage": {},
"postgresql": {},
"redis": {}
},
"memoryLimit": 2147483648,
"manifestVersion": 2
}
+45
View File
@@ -0,0 +1,45 @@
FROM ghcr.io/dannyvfilms/floppy:release
USER root
RUN python - <<'PY'
from pathlib import Path
import sys
path = Path("/floppy/app/media_list_views.py")
text = path.read_text()
old = ''' else:
# Default: most recently updated
show_trackers = show_trackers.order_by("-updated_at")
'''
new = ''' else:
# Default: most recently completed/listened podcast show
show_trackers = show_trackers.annotate(
last_completed=Max("show__podcast_entries__end_date")
).order_by(
F("last_completed").desc(nulls_last=True),
"show__title",
)
'''
if new in text:
print("Podcast sort patch already present upstream")
elif old in text:
path.write_text(text.replace(old, new, 1))
print("Applied podcast sort patch")
else:
print("ERROR: Podcast sort patch no longer matches upstream Floppy", file=sys.stderr)
sys.exit(1)
PY
COPY start.sh /app/code/start.sh
COPY supervisord-cloudron.conf /app/code/supervisord-cloudron.conf
COPY nginx-cloudron.conf /app/code/nginx-cloudron.conf
RUN chmod +x /app/code/start.sh
EXPOSE 8000
CMD ["/app/code/start.sh"]
+146
View File
@@ -0,0 +1,146 @@
# Floppy for Cloudron
Unofficial Cloudron package for [Floppy](https://github.com/dannyvfilms/Floppy), using Cloudron-managed PostgreSQL and Redis.
## Why this package is different from Floppy's Docker Compose
Floppy's upstream Docker image expects a normal writable Docker filesystem and its entrypoint changes ownership on several image paths. Cloudron intentionally runs app images read-only except for `/tmp`, `/run`, and `/app/data`. This package therefore keeps the upstream application image but replaces the runtime entrypoint, Supervisor configuration, and Nginx runtime paths.
The package uses:
- Cloudron PostgreSQL -> Floppy `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
- Cloudron Redis -> Floppy `REDIS_URL`, cache and Celery URLs
- `/app/data` -> Floppy secret, user configuration, logs and backups
- Cloudron HTTPS proxy -> Floppy `URLS`, `ALLOWED_HOSTS`, forwarded-proto handling
## Install
Install the Cloudron CLI on your workstation, not on the Cloudron server:
```bash
sudo npm install -g cloudron
cloudron login my.example.com
```
Unzip this package and enter the directory:
```bash
cd floppy-cloudron
cloudron install
```
When prompted, choose the desired location, for example `floppy` for `https://floppy.example.com`.
Cloudron can build custom app source directly on the server, so no local Docker registry is required for this workflow.
## First run
On first start the package creates:
```text
/app/data/secret
/app/data/env.sh
```
`secret` is generated automatically and persists across updates/backups.
The initial `env.sh` contains:
```bash
REGISTRATION=True
TMDB_API=
TZ=Europe/Berlin
```
1. Open Floppy and create your user account.
2. Add your TMDb API key to `/app/data/env.sh`.
3. Change `REGISTRATION=True` to `REGISTRATION=False`.
4. Restart the app from the Cloudron dashboard.
You can edit `/app/data/env.sh` with the Cloudron File Manager or via:
```bash
cloudron exec
vi /app/data/env.sh
```
For hnz.io, TMDb is the important metadata provider, so set at least:
```bash
TMDB_API=YOUR_KEY
```
Optional integration keys can be added to `env.sh`, including Trakt, TVDB, MAL, IGDB, Steam, BoardGameGeek, Hardcover, ComicVine and Last.fm.
## Importing your old Trakt data
If you still have working Trakt API credentials for a private-profile OAuth import, add:
```bash
TRAKT_API=...
TRAKT_API_SECRET=...
```
Floppy also supports other import paths from its UI. After migration, hnz.io can use Floppy as the personal-data source while continuing to use TMDb for metadata.
## Cloudron resources
The manifest requests 2 GiB RAM. Floppy automatically adapts worker counts to available resources. This package intentionally starts one combined Celery worker by default, which is the safer layout for a personal Cloudron instance.
If you later give the app more memory and want Floppy's split worker layout, adjust the Supervisor config/package rather than setting arbitrary worker variables in `env.sh`.
## Updating Floppy
This package currently uses:
```dockerfile
FROM ghcr.io/dannyvfilms/floppy:release
```
To refresh to the current stable upstream image, rebuild/update the Cloudron package:
```bash
cloudron update
```
For fully reproducible production deployments, replace `:release` in the Dockerfile with a specific upstream `:vX.Y.Z` tag after testing that release, then bump the Cloudron package `version`.
## Logs
Use:
```bash
cloudron logs -f
```
The application processes log primarily to stdout/stderr. Floppy's own file log/backups are directed to `/app/data/logs` and `/app/data/backups`.
## Troubleshooting
If the app does not become healthy:
```bash
cloudron logs -f
```
Verify that migrations completed and that Gunicorn is listening on `127.0.0.1:8001` behind Nginx.
For a shell:
```bash
cloudron exec
```
Useful checks inside the app:
```bash
env | grep '^CLOUDRON_\|^DB_\|^REDIS_'
cd /floppy
python manage.py check
```
Do not replace the Cloudron PostgreSQL variables with a `DATABASE_URL`: Floppy selects PostgreSQL only when `DB_HOST` is set.
## Status
This is an unofficial compatibility package assembled for a custom Cloudron installation. It follows the current Floppy `release` container contract and current Cloudron packaging/addon model, but it has not been executed against your actual Cloudron instance from this environment. Treat the first install as a deployment test and inspect the logs before moving production watch-history data into it.
+62
View File
@@ -0,0 +1,62 @@
user abc;
worker_processes 1;
pid /run/floppy/nginx.pid;
error_log /dev/stderr warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /dev/stdout combined;
client_body_temp_path /run/floppy/nginx/client_body;
proxy_temp_path /run/floppy/nginx/proxy;
fastcgi_temp_path /run/floppy/nginx/fastcgi;
uwsgi_temp_path /run/floppy/nginx/uwsgi;
scgi_temp_path /run/floppy/nginx/scgi;
sendfile on;
tcp_nodelay on;
keepalive_timeout 65;
client_max_body_size 20M;
client_body_buffer_size 256k;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
upstream app_server {
server 127.0.0.1:8001;
}
server {
listen 8000;
server_name _;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
location / {
proxy_pass http://app_server;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
location /health/ {
access_log off;
proxy_pass http://app_server;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
location /static/ {
alias /floppy/staticfiles/;
expires 30d;
add_header Cache-Control "public";
}
}
}
+14
View File
@@ -0,0 +1,14 @@
--- a/app/media_list_views.py
+++ b/app/media_list_views.py
@@
- else:
- # Default: most recently updated
- show_trackers = show_trackers.order_by("-updated_at")
+ else:
+ # Default: most recently completed/listened podcast show
+ show_trackers = show_trackers.annotate(
+ last_completed=Max("show__podcast_entries__end_date")
+ ).order_by(
+ F("last_completed").desc(nulls_last=True),
+ "show__title",
+ )
Executable
+150
View File
@@ -0,0 +1,150 @@
#!/bin/sh
set -eu
mkdir -p \
/app/data/logs \
/app/data/backups \
/run/floppy/nginx/client_body \
/run/floppy/nginx/proxy \
/run/floppy/nginx/fastcgi \
/run/floppy/nginx/uwsgi \
/run/floppy/nginx/scgi
if [ ! -f /app/data/secret ]; then
python - <<'PY' > /app/data/secret
import secrets
print(secrets.token_urlsafe(64))
PY
chmod 600 /app/data/secret
fi
if [ ! -f /app/data/env.sh ]; then
cat > /app/data/env.sh <<'ENVEOF'
# Floppy settings managed by you.
# Edit this file in Cloudron's File Manager and restart the app.
REGISTRATION=True
ADMIN_ENABLED=False
DEMO_ACCOUNT_ENABLED=False
TMDB_API=
TZ=Europe/Berlin
# Optional examples:
# TRAKT_API=
# TRAKT_API_SECRET=
# TVDB_API_KEY=
# TVDB_PIN=
# MAL_API=
# IGDB_ID=
# IGDB_SECRET=
# STEAM_API_KEY=
# BGG_API_TOKEN=
# HARDCOVER_API=
# COMICVINE_API=
# LASTFM_API_KEY=
ENVEOF
fi
# Export all variables defined in env.sh so Django, Celery and Supervisor
# receive them as real environment variables.
set -a
# shellcheck disable=SC1091
. /app/data/env.sh
set +a
# ----------------------------------------------------------------------
# Cloudron managed services
# ----------------------------------------------------------------------
export SECRET="$(cat /app/data/secret)"
export DB_HOST="$CLOUDRON_POSTGRESQL_HOST"
export DB_PORT="$CLOUDRON_POSTGRESQL_PORT"
export DB_NAME="$CLOUDRON_POSTGRESQL_DATABASE"
export DB_USER="$CLOUDRON_POSTGRESQL_USERNAME"
export DB_PASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD"
export REDIS_URL="$CLOUDRON_REDIS_URL"
export REDIS_CACHE_URL="$CLOUDRON_REDIS_URL"
export CELERY_BROKER_URL="$CLOUDRON_REDIS_URL"
export CELERY_RESULT_BACKEND="$CLOUDRON_REDIS_URL"
# Cloudron owns the Redis instance. Floppy must not try to manage
# Redis memory limits globally.
export REDIS_ADMIN_URL="$CLOUDRON_REDIS_URL"
export FLOPPY_REDIS_MAXMEMORY=0
# ----------------------------------------------------------------------
# Cloudron reverse proxy
# ----------------------------------------------------------------------
export URLS="$CLOUDRON_APP_ORIGIN"
export ALLOWED_HOSTS="$CLOUDRON_APP_DOMAIN"
export USE_X_FORWARDED=True
export USE_X_FORWARDED_PROTO=True
# ----------------------------------------------------------------------
# Floppy defaults
#
# Values from /app/data/env.sh take precedence.
# ----------------------------------------------------------------------
export DEBUG="${DEBUG:-False}"
export DEMO_ACCOUNT_ENABLED="${DEMO_ACCOUNT_ENABLED:-False}"
export ADMIN_ENABLED="${ADMIN_ENABLED:-False}"
export REGISTRATION="${REGISTRATION:-True}"
export TMDB_API="${TMDB_API:-}"
export TZ="${TZ:-Europe/Berlin}"
export LOG_DIR=/app/data/logs
export BACKUP_DIR=/app/data/backups
export FLOPPY_DATA_DIR=/app/data
# ----------------------------------------------------------------------
# Permissions
# ----------------------------------------------------------------------
chown -R abc:abc /app/data /run/floppy
cd /floppy
# ----------------------------------------------------------------------
# Database
# ----------------------------------------------------------------------
echo "[cloudron] Applying Floppy database migrations"
DB_POOL_ENABLED=false \
PGOPTIONS="-c lock_timeout=120s" \
python manage.py migrate --noinput
# ----------------------------------------------------------------------
# Runtime sizing
#
# Match Floppy's own resource sizing logic without invoking its
# Docker-specific entrypoint.
# ----------------------------------------------------------------------
if resource_env=$(python -c 'from config.runtime_profile import emit_env; emit_env()' 2>/dev/null); then
eval "$resource_env"
fi
export FLOPPY_RESOURCE_TIER="${FLOPPY_RESOURCE_TIER:-minimal}"
export FLOPPY_CELERY_QUEUES="${FLOPPY_CELERY_QUEUES:-celery,interactive,discover}"
export FLOPPY_CELERY_ROLE="${FLOPPY_CELERY_ROLE:-background}"
export FLOPPY_START_INTERACTIVE_WORKER="${FLOPPY_START_INTERACTIVE_WORKER:-false}"
export FLOPPY_START_DISCOVER_WORKER="${FLOPPY_START_DISCOVER_WORKER:-false}"
# ----------------------------------------------------------------------
# Start
# ----------------------------------------------------------------------
echo "[cloudron] Starting Floppy on ${CLOUDRON_APP_ORIGIN}"
echo "[cloudron] Registration enabled: ${REGISTRATION}"
echo "[cloudron] Admin enabled: ${ADMIN_ENABLED}"
echo "[cloudron] Timezone: ${TZ}"
exec supervisord -c /app/code/supervisord-cloudron.conf
+50
View File
@@ -0,0 +1,50 @@
[supervisord]
nodaemon=true
user=root
logfile=/tmp/supervisord.log
logfile_maxbytes=0
pidfile=/run/floppy/supervisord.pid
childlogdir=/tmp
[program:nginx]
command=nginx -c /app/code/nginx-cloudron.conf -g "daemon off;"
user=root
priority=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:gunicorn]
command=gunicorn --control-socket /tmp/gunicorn.ctl --config python:config.gunicorn config.wsgi:application
user=abc
priority=5
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:celery]
command=sh -c 'if [ "${DEBUG:-False}" = "True" ]; then LOGLEVEL=DEBUG; else LOGLEVEL=INFO; fi; exec celery --app config worker --beat --scheduler django --queues "${FLOPPY_CELERY_QUEUES:-celery,interactive,discover}" --hostname=celery@%%h --loglevel $LOGLEVEL --without-mingle --without-gossip'
environment=FLOPPY_PROCESS_ROLE="%(ENV_FLOPPY_CELERY_ROLE)s"
user=abc
stopasgroup=true
killasgroup=true
stopwaitsecs=60
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:podcast-autocomplete]
command=/floppy/.venv/bin/python /app/data/scripts/podcast-autocomplete.py
directory=/floppy
autostart=true
autorestart=true
startsecs=5
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
environment=PYTHONUNBUFFERED="1"