first commit
This commit is contained in:
commit
5de45a37bd
9 files changed
+499
No files matched your search
@@ -0,0 +1,3 @@
|
||||
.git
|
||||
.gitignore
|
||||
*.zip
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Local configuration and secrets
|
||||
.env
|
||||
.env.*
|
||||
env.sh
|
||||
secret
|
||||
|
||||
# Build artifacts and local tooling
|
||||
*.zip
|
||||
.idea/
|
||||
.vscode/
|
||||
*~
|
||||
.DS_Store
|
||||
@@ -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
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
+ )
|
||||
@@ -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
|
||||
@@ -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"
|
||||
Reference in new issue
Block a user