Initial commit: Seafile single-container image for TrueNAS SCALE
This commit is contained in:
commit
ae8fe0026d
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
seafile-server/
|
||||||
|
seahub/
|
||||||
|
.seafile-data/
|
||||||
|
.DS_Store
|
||||||
88
Dockerfile
Normal file
88
Dockerfile
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Seafile single-container image for TrueNAS SCALE Dragonfish
|
||||||
|
# Base: Debian Bookworm Slim for stable apt packages (nginx, mariadb, redis)
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
LANG=C.UTF-8 \
|
||||||
|
LC_ALL=C.UTF-8 \
|
||||||
|
TZ=UTC \
|
||||||
|
SEAFILE_HOME=/opt/seafile \
|
||||||
|
SEAFILE_DATA_DIR=/data/seafile-data \
|
||||||
|
SEAFILE_CONF_DIR=/data/conf \
|
||||||
|
SEAHUB_MEDIA_DIR=/data/seahub-media \
|
||||||
|
LOG_DIR=/data/logs
|
||||||
|
|
||||||
|
# Optional build-time args (not used to download by default; runtime entrypoint handles artifacts)
|
||||||
|
ARG SEAFILE_VERSION=""
|
||||||
|
ARG SEAFILE_TGZ_URL=""
|
||||||
|
|
||||||
|
# OS packages
|
||||||
|
RUN set -eux; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
nginx \
|
||||||
|
supervisor \
|
||||||
|
mariadb-server \
|
||||||
|
redis-server \
|
||||||
|
python3 \
|
||||||
|
python3-venv \
|
||||||
|
python3-pip \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
procps \
|
||||||
|
gosu; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# System users/groups (many packages create their own, we ensure 'seafile' app user)
|
||||||
|
RUN set -eux; \
|
||||||
|
groupadd -r seafile; \
|
||||||
|
useradd -r -g seafile -d ${SEAFILE_HOME} -s /usr/sbin/nologin seafile || true; \
|
||||||
|
mkdir -p ${SEAFILE_HOME} ${SEAFILE_HOME}/docker \
|
||||||
|
/data/conf /data/seafile-data /data/db /data/redis /data/seahub-media /data/logs /data/ssl \
|
||||||
|
/var/log/nginx /var/run/nginx; \
|
||||||
|
chown -R seafile:seafile ${SEAFILE_HOME}; \
|
||||||
|
chown -R www-data:www-data /var/log/nginx /var/run/nginx; \
|
||||||
|
# MariaDB and Redis dirs will be owned by respective users at runtime init
|
||||||
|
true
|
||||||
|
|
||||||
|
# Copy runtime scripts and templates (will be rendered at container start)
|
||||||
|
# Expect these files to be created in repo under docker/
|
||||||
|
COPY docker/ ${SEAFILE_HOME}/docker/
|
||||||
|
|
||||||
|
# Make scripts executable
|
||||||
|
RUN set -eux; \
|
||||||
|
find ${SEAFILE_HOME}/docker -type f -name "*.sh" -exec chmod +x {} \;; \
|
||||||
|
chmod 0644 ${SEAFILE_HOME}/docker/supervisord.conf.template || true; \
|
||||||
|
chmod 0644 ${SEAFILE_HOME}/docker/nginx.conf.template || true; \
|
||||||
|
chmod 0644 ${SEAFILE_HOME}/docker/gunicorn.conf.py || true; \
|
||||||
|
chmod 0644 ${SEAFILE_HOME}/docker/seahub_settings.py.template || true
|
||||||
|
|
||||||
|
# Environment defaults (can be overridden by TrueNAS app env)
|
||||||
|
ENV SEAFILE_SERVER_HOSTNAME=localhost \
|
||||||
|
SEAFILE_SERVER_URL=http://localhost \
|
||||||
|
ADMIN_EMAIL= \
|
||||||
|
ADMIN_PASSWORD= \
|
||||||
|
DB_ROOT_PASSWORD= \
|
||||||
|
DB_NAME=seafile \
|
||||||
|
DB_USER=seafile \
|
||||||
|
DB_PASSWORD= \
|
||||||
|
DB_NAME_SEAHUB=seahub_db \
|
||||||
|
DB_NAME_SEAFILE=seafile_db \
|
||||||
|
DB_NAME_CCNET=ccnet_db \
|
||||||
|
REDIS_URL=redis://127.0.0.1:6379/0 \
|
||||||
|
TIMEZONE=UTC \
|
||||||
|
NGINX_MAX_BODY=200m \
|
||||||
|
SSL_ENABLE=false
|
||||||
|
|
||||||
|
# Ports
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
|
# Healthcheck
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=10 CMD [ -x "${SEAFILE_HOME}/docker/healthcheck.sh" ] && ${SEAFILE_HOME}/docker/healthcheck.sh || exit 1
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
# Entrypoint manages idempotent bootstrap then hands off to supervisord
|
||||||
|
ENTRYPOINT ["/opt/seafile/docker/entrypoint.sh"]
|
||||||
|
CMD ["start"]
|
||||||
162
README.md
Normal file
162
README.md
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
Seafile Single-Container Image for TrueNAS SCALE Dragonfish
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
This repository packages Seafile Server Core (seafile-server) and Seahub (Django web UI) into a single container suitable for TrueNAS SCALE Dragonfish Custom App deployment.
|
||||||
|
|
||||||
|
Components inside the container:
|
||||||
|
- Nginx reverse proxy (ports 80/443) → proxies to internal Gunicorn (Seahub) and the Go fileserver
|
||||||
|
- Gunicorn serving Seahub (Django) on 127.0.0.1:8000
|
||||||
|
- Seafile core (seaf-server) and fileserver (Go) via seafile.sh
|
||||||
|
- MariaDB (in-container)
|
||||||
|
- Redis (in-container)
|
||||||
|
- Single persistent volume at /data storing configuration, databases, media, and logs
|
||||||
|
|
||||||
|
Persistent layout under /data:
|
||||||
|
- /data/conf → central Seafile/Seahub config (seafile.conf, ccnet.conf, seahub_settings.py, seahub_secret_key.txt)
|
||||||
|
- /data/seafile-data → Seafile data store
|
||||||
|
- /data/db → MariaDB datadir
|
||||||
|
- /data/redis → Redis data
|
||||||
|
- /data/seahub-media → Seahub uploads/media
|
||||||
|
- /data/logs → logs (nginx, seahub, seafile, supervisord, mariadb, redis)
|
||||||
|
- /data/ssl → TLS certs (optional if terminating TLS in-container)
|
||||||
|
|
||||||
|
Files
|
||||||
|
-----
|
||||||
|
- Dockerfile → Debian Bookworm base image with nginx, supervisor, mariadb, redis, python venv
|
||||||
|
- docker/entrypoint.sh → Idempotent bootstrap and configuration renderer, then execs supervisord
|
||||||
|
- docker/supervisord.conf.template → supervisord programs for mariadb, redis, seafile core, seahub, nginx
|
||||||
|
- docker/nginx.conf.template → reverse proxy and upload limits
|
||||||
|
- docker/gunicorn.conf.py → Gunicorn settings for Seahub
|
||||||
|
- docker/seahub_settings.py.template → rendered to /data/conf/seahub_settings.py
|
||||||
|
- docker/seafile.conf.template → rendered to /data/conf/seafile.conf
|
||||||
|
- docker/ccnet.conf.template → rendered to /data/conf/ccnet.conf
|
||||||
|
- docker/init_seahub.sh → one-shot collectstatic, migrations, optional admin creation
|
||||||
|
- docker/healthcheck.sh → container healthcheck
|
||||||
|
|
||||||
|
Environment Variables
|
||||||
|
---------------------
|
||||||
|
Required at first run (recommended):
|
||||||
|
- SEAFILE_SERVER_HOSTNAME: your public hostname (e.g., files.example.com)
|
||||||
|
- SEAFILE_SERVER_URL: full base URL (e.g., https://files.example.com)
|
||||||
|
- DB_ROOT_PASSWORD: MariaDB root password for bootstrap (required on first run)
|
||||||
|
- DB_PASSWORD: password for application DB user (seafile)
|
||||||
|
|
||||||
|
Optional (defaults in Dockerfile):
|
||||||
|
- DB_USER=seafile
|
||||||
|
- DB_NAME (generic bootstrap DB used by the plan; typically not required by Seafile)
|
||||||
|
- DB_NAME_SEAHUB=seahub_db
|
||||||
|
- DB_NAME_SEAFILE=seafile_db
|
||||||
|
- DB_NAME_CCNET=ccnet_db
|
||||||
|
- REDIS_URL=redis://127.0.0.1:6379/0
|
||||||
|
- TIMEZONE=UTC
|
||||||
|
- NGINX_MAX_BODY=200m (upload size limit)
|
||||||
|
- SSL_ENABLE=false (if you want nginx to terminate TLS with /data/ssl/{fullchain.pem,privkey.pem}, adjust nginx template or provide a values override)
|
||||||
|
- ADMIN_EMAIL and ADMIN_PASSWORD (optional) → auto-create admin user on first run
|
||||||
|
- SEAFILE_TGZ_URL (optional) → URL to an official Seafile server release tarball. If provided, the entrypoint will download and extract it into /opt/seafile/seafile-server-latest
|
||||||
|
|
||||||
|
Notes on Releases
|
||||||
|
-----------------
|
||||||
|
By default, this image expects an official Seafile server release to be present at:
|
||||||
|
- /opt/seafile/seafile-server-latest
|
||||||
|
|
||||||
|
You can satisfy this in one of two ways:
|
||||||
|
1) Provide SEAFILE_TGZ_URL (preferred): The entrypoint will download and extract on first start.
|
||||||
|
2) Bake or mount a release: Place the extracted release at /opt/seafile/seafile-server-latest (e.g., by modifying the Dockerfile to ADD/COPY it, or by mounting in TrueNAS using an additional hostPath volume).
|
||||||
|
|
||||||
|
TrueNAS SCALE Custom App Configuration
|
||||||
|
--------------------------------------
|
||||||
|
- Image: build and push the built Docker image, then reference it in your custom app
|
||||||
|
- Ports:
|
||||||
|
- TCP 80 → host or ingress (required)
|
||||||
|
- TCP 443 → host or ingress (optional if terminating TLS externally)
|
||||||
|
- Storage:
|
||||||
|
- PVC mounted at /data (ReadWriteOnce)
|
||||||
|
- Environment:
|
||||||
|
- SEAFILE_SERVER_HOSTNAME=files.example.com
|
||||||
|
- SEAFILE_SERVER_URL=https://files.example.com
|
||||||
|
- DB_ROOT_PASSWORD=your-root-password
|
||||||
|
- DB_PASSWORD=your-app-password
|
||||||
|
- DB_NAME_SEAHUB=seahub_db
|
||||||
|
- DB_NAME_SEAFILE=seafile_db
|
||||||
|
- DB_NAME_CCNET=ccnet_db
|
||||||
|
- ADMIN_EMAIL=admin@example.com (optional)
|
||||||
|
- ADMIN_PASSWORD=change-me (optional)
|
||||||
|
- NGINX_MAX_BODY=200m (adjust as desired)
|
||||||
|
- TIMEZONE=UTC
|
||||||
|
- SEAFILE_TGZ_URL=https://example.com/path/to/seafile-server_X.Y.Z_x86-64.tar.gz (optional, recommended)
|
||||||
|
- Healthcheck:
|
||||||
|
- Container includes a HEALTHCHECK that probes Seahub and the fileserver.
|
||||||
|
|
||||||
|
First Run Flow
|
||||||
|
--------------
|
||||||
|
On container start, the entrypoint will:
|
||||||
|
1) Create /data subdirectories; write /etc/redis/redis.conf
|
||||||
|
2) Initialize MariaDB at /data/db if empty:
|
||||||
|
- Set root password (DB_ROOT_PASSWORD)
|
||||||
|
- Create DBs: DB_NAME_SEAHUB, DB_NAME_SEAFILE, DB_NAME_CCNET
|
||||||
|
- Create user DB_USER with DB_PASSWORD; grant privileges
|
||||||
|
3) Optionally download and extract Seafile server release if SEAFILE_TGZ_URL is provided
|
||||||
|
4) Render configs:
|
||||||
|
- /etc/nginx/nginx.conf from template
|
||||||
|
- /etc/supervisor/supervisord.conf from template
|
||||||
|
- /data/conf/seahub_settings.py from template (uses env and SECRET_KEY)
|
||||||
|
- /data/conf/seafile.conf and /data/conf/ccnet.conf from templates
|
||||||
|
5) Create Python venv and install Seahub requirements from the release
|
||||||
|
6) Supervisord starts:
|
||||||
|
- mariadbd
|
||||||
|
- redis-server
|
||||||
|
- seafile (seafile.sh start)
|
||||||
|
- seahub (runs docker/init_seahub.sh once, then gunicorn)
|
||||||
|
- nginx
|
||||||
|
|
||||||
|
Nginx Routing
|
||||||
|
-------------
|
||||||
|
- / → Gunicorn (Seahub) at 127.0.0.1:8000
|
||||||
|
- /seafhttp → fileserver at 127.0.0.1:8082
|
||||||
|
- /media → /data/seahub-media
|
||||||
|
|
||||||
|
Build & Run (Local)
|
||||||
|
-------------------
|
||||||
|
Build:
|
||||||
|
- docker build -t seafile-single:local .
|
||||||
|
|
||||||
|
Run (example):
|
||||||
|
- mkdir -p /host/seafile-data
|
||||||
|
- docker run -it --rm \
|
||||||
|
-e SEAFILE_SERVER_HOSTNAME=localhost \
|
||||||
|
-e SEAFILE_SERVER_URL=http://localhost \
|
||||||
|
-e DB_ROOT_PASSWORD=changeme \
|
||||||
|
-e DB_PASSWORD=changeme \
|
||||||
|
-e ADMIN_EMAIL=admin@example.com \
|
||||||
|
-e ADMIN_PASSWORD=changeme \
|
||||||
|
-p 80:80 \
|
||||||
|
-v /host/seafile-data:/data \
|
||||||
|
seafile-single:local
|
||||||
|
|
||||||
|
Then open http://localhost and log in with the admin credentials.
|
||||||
|
|
||||||
|
From-Source Variant (Optional)
|
||||||
|
------------------------------
|
||||||
|
If you need to build from the provided sources instead of using official releases:
|
||||||
|
- seafile-server (C, autotools) found under ./seafile-server
|
||||||
|
- fileserver (Go) under ./seafile-server/fileserver
|
||||||
|
- Seahub (Django) under ./seahub
|
||||||
|
|
||||||
|
You will need to:
|
||||||
|
- Install build dependencies (see seafile-server/configure.ac)
|
||||||
|
- Build and install seafile core and fileserver into /opt/seafile/seafile-server-latest
|
||||||
|
- Use seahub/requirements.txt for Python dependencies
|
||||||
|
- Ensure templates in docker/ still render configs into /data/conf
|
||||||
|
|
||||||
|
Testing Checklist
|
||||||
|
-----------------
|
||||||
|
- Healthcheck: container becomes healthy (Seahub login page, fileserver protocol-version endpoint)
|
||||||
|
- Admin creation: login with ADMIN_EMAIL/ADMIN_PASSWORD
|
||||||
|
- Upload flow: create library, upload small file; verify in /data/seafile-data
|
||||||
|
- Persistence: stop/start container; confirm data intact
|
||||||
|
- Logs: /data/logs/* should not contain critical errors
|
||||||
|
|
||||||
|
Known Notes
|
||||||
|
-----------
|
||||||
|
- The container expects a compatible Seafile server release and Seahub version.
|
||||||
|
- If terminating TLS at Nginx inside the container, copy certs to /data/ssl and adapt nginx.conf.template accordingly (or add a values override in TrueNAS).
|
||||||
9
docker/ccnet.conf.template
Normal file
9
docker/ccnet.conf.template
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Rendered by entrypoint.sh into /data/conf/ccnet.conf
|
||||||
|
|
||||||
|
[General]
|
||||||
|
SERVICE_URL = {{SERVER_URL}}
|
||||||
|
|
||||||
|
# Optional settings:
|
||||||
|
# USER_NAME = seafile
|
||||||
|
# NAME = Seafile
|
||||||
|
# PORT = 10001
|
||||||
381
docker/entrypoint.sh
Normal file
381
docker/entrypoint.sh
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Globals / defaults
|
||||||
|
SEAFILE_HOME=${SEAFILE_HOME:-/opt/seafile}
|
||||||
|
SEAF_RELEASE_DIR_DEFAULT="${SEAFILE_HOME}/seafile-server-latest"
|
||||||
|
SEAF_RELEASE_DIR="${SEAF_RELEASE_DIR:-$SEAF_RELEASE_DIR_DEFAULT}"
|
||||||
|
|
||||||
|
SEAFILE_CONF_DIR=${SEAFILE_CONF_DIR:-/data/conf}
|
||||||
|
SEAFILE_DATA_DIR=${SEAFILE_DATA_DIR:-/data/seafile-data}
|
||||||
|
SEAHUB_MEDIA_DIR=${SEAHUB_MEDIA_DIR:-/data/seahub-media}
|
||||||
|
LOG_DIR=${LOG_DIR:-/data/logs}
|
||||||
|
TIMEZONE=${TIMEZONE:-UTC}
|
||||||
|
SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME:-localhost}
|
||||||
|
SEAFILE_SERVER_URL=${SEAFILE_SERVER_URL:-http://localhost}
|
||||||
|
REDIS_URL=${REDIS_URL:-redis://127.0.0.1:6379/0}
|
||||||
|
DB_NAME=${DB_NAME:-seafile}
|
||||||
|
DB_USER=${DB_USER:-seafile}
|
||||||
|
DB_PASSWORD=${DB_PASSWORD:-}
|
||||||
|
DB_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-}
|
||||||
|
NGINX_MAX_BODY=${NGINX_MAX_BODY:-200m}
|
||||||
|
SSL_ENABLE=${SSL_ENABLE:-false}
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
MYSQLD_BIN="/usr/sbin/mariadbd"
|
||||||
|
MYSQL_BIN="/usr/bin/mysql"
|
||||||
|
MYSQLADMIN_BIN="/usr/bin/mysqladmin"
|
||||||
|
MARIADB_INSTALL_DB="/usr/bin/mariadb-install-db"
|
||||||
|
REDIS_SERVER_BIN="/usr/bin/redis-server"
|
||||||
|
NGINX_BIN="/usr/sbin/nginx"
|
||||||
|
SUPERVISORD_BIN="/usr/bin/supervisord"
|
||||||
|
|
||||||
|
SUPERVISORD_CONF="/etc/supervisor/supervisord.conf"
|
||||||
|
NGINX_CONF="/etc/nginx/nginx.conf"
|
||||||
|
REDIS_CONF="/etc/redis/redis.conf"
|
||||||
|
|
||||||
|
HEALTH_URL_SEAHUB="http://127.0.0.1:8000/accounts/login/"
|
||||||
|
HEALTH_URL_FILESERVER="http://127.0.0.1:8082/protocol-version"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_dirs() {
|
||||||
|
log "Ensuring directory layout under /data and runtime dirs"
|
||||||
|
mkdir -p \
|
||||||
|
"${SEAFILE_HOME}" \
|
||||||
|
"${SEAFILE_HOME}/releases" \
|
||||||
|
"${SEAFILE_CONF_DIR}" \
|
||||||
|
"${SEAFILE_DATA_DIR}" \
|
||||||
|
/data/db \
|
||||||
|
/data/redis \
|
||||||
|
"${SEAHUB_MEDIA_DIR}" \
|
||||||
|
"${LOG_DIR}/nginx" \
|
||||||
|
"${LOG_DIR}/seahub" \
|
||||||
|
"${LOG_DIR}/seafile" \
|
||||||
|
"${LOG_DIR}/supervisor" \
|
||||||
|
"${LOG_DIR}/mariadb" \
|
||||||
|
"${LOG_DIR}/redis" \
|
||||||
|
/data/ssl \
|
||||||
|
/run/mysqld \
|
||||||
|
/run/redis
|
||||||
|
|
||||||
|
chown -R seafile:seafile "${SEAFILE_HOME}" "${SEAFILE_CONF_DIR}" "${SEAFILE_DATA_DIR}" "${SEAHUB_MEDIA_DIR}" "${LOG_DIR}"
|
||||||
|
chown -R mysql:mysql /data/db /run/mysqld || true
|
||||||
|
chown -R redis:redis /data/redis /run/redis || true
|
||||||
|
chown -R www-data:www-data /var/log/nginx || true
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_timezone() {
|
||||||
|
log "Configuring timezone: ${TIMEZONE}"
|
||||||
|
echo "${TIMEZONE}" >/etc/timezone
|
||||||
|
ln -sf "/usr/share/zoneinfo/${TIMEZONE}" /etc/localtime || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Basic poor-man's template renderer: replace {{VAR}} tokens using sed
|
||||||
|
# Usage: render_file TEMPLATE DEST
|
||||||
|
render_file() {
|
||||||
|
local template="$1"
|
||||||
|
local dest="$2"
|
||||||
|
[[ -f "$template" ]] || die "Template not found: $template"
|
||||||
|
|
||||||
|
sed \
|
||||||
|
-e "s|{{SERVER_NAME}}|${SEAFILE_SERVER_HOSTNAME}|g" \
|
||||||
|
-e "s|{{SERVER_URL}}|${SEAFILE_SERVER_URL}|g" \
|
||||||
|
-e "s|{{NGINX_MAX_BODY}}|${NGINX_MAX_BODY}|g" \
|
||||||
|
-e "s|{{SEAF_RELEASE_DIR}}|${SEAF_RELEASE_DIR}|g" \
|
||||||
|
-e "s|{{SEAFILE_CONF_DIR}}|${SEAFILE_CONF_DIR}|g" \
|
||||||
|
-e "s|{{SEAFILE_DATA_DIR}}|${SEAFILE_DATA_DIR}|g" \
|
||||||
|
-e "s|{{SEAHUB_MEDIA_DIR}}|${SEAHUB_MEDIA_DIR}|g" \
|
||||||
|
-e "s|{{LOG_DIR}}|${LOG_DIR}|g" \
|
||||||
|
-e "s|{{DB_NAME}}|${DB_NAME}|g" \
|
||||||
|
-e "s|{{DB_USER}}|${DB_USER}|g" \
|
||||||
|
-e "s|{{DB_PASSWORD}}|${DB_PASSWORD}|g" \
|
||||||
|
-e "s|{{DB_NAME_SEAFILE}}|${DB_NAME_SEAFILE:-seafile_db}|g" \
|
||||||
|
-e "s|{{DB_NAME_CCNET}}|${DB_NAME_CCNET:-ccnet_db}|g" \
|
||||||
|
-e "s|{{REDIS_URL}}|${REDIS_URL}|g" \
|
||||||
|
<"$template" >"$dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
init_redis() {
|
||||||
|
log "Writing Redis config"
|
||||||
|
cat >"${REDIS_CONF}" <<'EOF'
|
||||||
|
bind 127.0.0.1 ::1
|
||||||
|
protected-mode yes
|
||||||
|
port 6379
|
||||||
|
daemonize no
|
||||||
|
supervised no
|
||||||
|
pidfile /run/redis/redis-server.pid
|
||||||
|
dir /data/redis
|
||||||
|
save 900 1
|
||||||
|
save 300 10
|
||||||
|
save 60 10000
|
||||||
|
appendonly yes
|
||||||
|
EOF
|
||||||
|
chown -R redis:redis /data/redis
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_mysql_socket() {
|
||||||
|
local timeout="${1:-60}"
|
||||||
|
local i=0
|
||||||
|
while ! "${MYSQLADMIN_BIN}" ping --silent --protocol=socket --socket=/run/mysqld/mysqld.sock >/dev/null 2>&1; do
|
||||||
|
sleep 1
|
||||||
|
i=$((i+1))
|
||||||
|
if [[ $i -ge $timeout ]]; then
|
||||||
|
die "MariaDB did not become ready in ${timeout}s"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
init_mariadb() {
|
||||||
|
if [[ -d /data/db/mysql ]]; then
|
||||||
|
log "MariaDB data directory already initialized"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
[[ -n "${DB_ROOT_PASSWORD}" ]] || die "DB_ROOT_PASSWORD must be set for MariaDB bootstrap"
|
||||||
|
log "Initializing MariaDB datadir at /data/db"
|
||||||
|
install -d -o mysql -g mysql /data/db /run/mysqld
|
||||||
|
"${MARIADB_INSTALL_DB}" --user=mysql --datadir=/data/db --skip-test-db
|
||||||
|
|
||||||
|
log "Starting temporary MariaDB to set root password and create DB/user"
|
||||||
|
"${MYSQLD_BIN}" \
|
||||||
|
--datadir=/data/db \
|
||||||
|
--socket=/run/mysqld/mysqld.sock \
|
||||||
|
--bind-address=127.0.0.1 \
|
||||||
|
--skip-networking=0 \
|
||||||
|
--skip-name-resolve \
|
||||||
|
--skip-symbolic-links \
|
||||||
|
--log-bin=OFF \
|
||||||
|
--pid-file=/run/mysqld/mysqld.pid \
|
||||||
|
--user=mysql >/dev/null 2>&1 &
|
||||||
|
local mysqld_pid=$!
|
||||||
|
|
||||||
|
wait_for_mysql_socket 60
|
||||||
|
|
||||||
|
log "Configuring root password and seafile database"
|
||||||
|
"${MYSQL_BIN}" --protocol=socket --socket=/run/mysqld/mysqld.sock -uroot <<-SQL
|
||||||
|
ALTER USER 'root'@'localhost' IDENTIFIED BY '${DB_ROOT_PASSWORD}';
|
||||||
|
FLUSH PRIVILEGES();
|
||||||
|
SQL
|
||||||
|
|
||||||
|
"${MYSQL_BIN}" --protocol=socket --socket=/run/mysqld/mysqld.sock -uroot -p"${DB_ROOT_PASSWORD}" <<-SQL
|
||||||
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME_SEAFILE:-seafile_db}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME_CCNET:-ccnet_db}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME_SEAHUB:-seahub_db}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
|
||||||
|
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost';
|
||||||
|
GRANT ALL PRIVILEGES ON \`${DB_NAME_SEAFILE:-seafile_db}\`.* TO '${DB_USER}'@'localhost';
|
||||||
|
GRANT ALL PRIVILEGES ON \`${DB_NAME_CCNET:-ccnet_db}\`.* TO '${DB_USER}'@'localhost';
|
||||||
|
GRANT ALL PRIVILEGES ON \`${DB_NAME_SEAHUB:-seahub_db}\`.* TO '${DB_USER}'@'localhost';
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES();
|
||||||
|
SQL
|
||||||
|
|
||||||
|
log "Shutting down temporary MariaDB"
|
||||||
|
"${MYSQLADMIN_BIN}" --protocol=socket --socket=/run/mysqld/mysqld.sock -uroot -p"${DB_ROOT_PASSWORD}" shutdown || true
|
||||||
|
wait $mysqld_pid || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional runtime download of official Seafile server release if not present.
|
||||||
|
# If SEAFILE_TGZ_URL or SEAFILE_VERSION provided, try to fetch.
|
||||||
|
fetch_seafile_release_if_needed() {
|
||||||
|
if [[ -d "${SEAF_RELEASE_DIR}" ]]; then
|
||||||
|
log "Seafile release directory present: ${SEAF_RELEASE_DIR}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local url="${SEAFILE_TGZ_URL:-}"
|
||||||
|
if [[ -z "${url}" && -n "${SEAFILE_VERSION:-}" ]]; then
|
||||||
|
# Fallback to official static host if only version is provided
|
||||||
|
url="https://download.seadrive.org/seafile-server_${SEAFILE_VERSION}_x86-64.tar.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${url}" ]]; then
|
||||||
|
log "No Seafile release present and no SEAFILE_TGZ_URL provided. Skipping download (expect release to be baked into image or mounted)."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Downloading Seafile release from ${url}"
|
||||||
|
mkdir -p "${SEAFILE_HOME}/releases"
|
||||||
|
local tgz="${SEAFILE_HOME}/releases/seafile-server.tgz"
|
||||||
|
curl -fsSL "${url}" -o "${tgz}"
|
||||||
|
tar -xzf "${tgz}" -C "${SEAFILE_HOME}/releases"
|
||||||
|
# Try to locate extracted dir
|
||||||
|
local extracted
|
||||||
|
extracted="$(tar -tzf "${tgz}" | head -1 | cut -d/ -f1)"
|
||||||
|
if [[ -n "${extracted}" && -d "${SEAFILE_HOME}/releases/${extracted}" ]]; then
|
||||||
|
ln -s "${SEAFILE_HOME}/releases/${extracted}" "${SEAF_RELEASE_DIR_DEFAULT}"
|
||||||
|
log "Linked ${SEAF_RELEASE_DIR_DEFAULT} -> ${SEAFILE_HOME}/releases/${extracted}"
|
||||||
|
else
|
||||||
|
log "Could not detect extracted directory; ensure ${SEAF_RELEASE_DIR} exists"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
init_seahub_secret_key() {
|
||||||
|
local keyfile="${SEAFILE_CONF_DIR}/seahub_secret_key.txt"
|
||||||
|
if [[ ! -s "${keyfile}" ]]; then
|
||||||
|
log "Generating Django SECRET_KEY"
|
||||||
|
python3 - <<'PY'
|
||||||
|
import secrets, string, os, sys
|
||||||
|
alphabet = string.ascii_letters + string.digits + string.punctuation
|
||||||
|
key = ''.join(secrets.choice(alphabet) for _ in range(64))
|
||||||
|
print(key)
|
||||||
|
PY
|
||||||
|
python3 - <<PY >"${keyfile}"
|
||||||
|
import secrets, string
|
||||||
|
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(-_=+)"
|
||||||
|
print(''.join(secrets.choice(alphabet) for _ in range(64)))
|
||||||
|
PY
|
||||||
|
chown seafile:seafile "${keyfile}"
|
||||||
|
chmod 600 "${keyfile}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
render_configs() {
|
||||||
|
log "Rendering Nginx and Supervisord configs from templates"
|
||||||
|
render_file "${SEAFILE_HOME}/docker/nginx.conf.template" "${NGINX_CONF}"
|
||||||
|
render_file "${SEAFILE_HOME}/docker/supervisord.conf.template" "${SUPERVISORD_CONF}"
|
||||||
|
}
|
||||||
|
|
||||||
|
init_python_and_requirements() {
|
||||||
|
log "Setting up Python virtual environment"
|
||||||
|
local venv="${SEAFILE_HOME}/venv"
|
||||||
|
if [[ ! -x "${venv}/bin/python" ]]; then
|
||||||
|
python3 -m venv "${venv}"
|
||||||
|
"${venv}/bin/pip" install --no-cache-dir --upgrade pip wheel setuptools
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Seahub requirements from release if available
|
||||||
|
local req="${SEAF_RELEASE_DIR}/seahub/requirements.txt"
|
||||||
|
if [[ -f "${req}" ]]; then
|
||||||
|
log "Installing Seahub Python requirements from ${req}"
|
||||||
|
"${venv}/bin/pip" install --no-cache-dir -r "${req}"
|
||||||
|
else
|
||||||
|
log "Seahub requirements.txt not found at ${req}. Ensure release is present or install separately."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
init_seahub_settings() {
|
||||||
|
init_seahub_secret_key
|
||||||
|
local settings_target="${SEAFILE_CONF_DIR}/seahub_settings.py"
|
||||||
|
if [[ ! -f "${settings_target}" ]]; then
|
||||||
|
log "Rendering seahub_settings.py"
|
||||||
|
render_file "${SEAFILE_HOME}/docker/seahub_settings.py.template" "${settings_target}"
|
||||||
|
chown seafile:seafile "${settings_target}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
init_seafile_confs() {
|
||||||
|
# Render seafile core config files if missing
|
||||||
|
local seafile_conf="${SEAFILE_CONF_DIR}/seafile.conf"
|
||||||
|
local ccnet_conf="${SEAFILE_CONF_DIR}/ccnet.conf"
|
||||||
|
|
||||||
|
if [[ ! -f "${seafile_conf}" ]]; then
|
||||||
|
log "Rendering seafile.conf"
|
||||||
|
render_file "${SEAFILE_HOME}/docker/seafile.conf.template" "${seafile_conf}"
|
||||||
|
chown seafile:seafile "${seafile_conf}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${ccnet_conf}" ]]; then
|
||||||
|
log "Rendering ccnet.conf"
|
||||||
|
render_file "${SEAFILE_HOME}/docker/ccnet.conf.template" "${ccnet_conf}"
|
||||||
|
chown seafile:seafile "${ccnet_conf}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
django_manage() {
|
||||||
|
# Run Django manage.py commands within release's seahub directory using venv python
|
||||||
|
local venv="${SEAFILE_HOME}/venv"
|
||||||
|
local seahub_dir="${SEAF_RELEASE_DIR}/seahub"
|
||||||
|
|
||||||
|
[[ -x "${venv}/bin/python" ]] || die "Python venv missing"
|
||||||
|
[[ -d "${seahub_dir}" ]] || { log "Seahub dir not found at ${seahub_dir}, skipping manage.py"; return; }
|
||||||
|
|
||||||
|
pushd "${seahub_dir}" >/dev/null
|
||||||
|
# Ensure env
|
||||||
|
export PYTHONPATH="${SEAF_RELEASE_DIR}:${PYTHONPATH:-}"
|
||||||
|
export DJANGO_SETTINGS_MODULE="seahub.settings"
|
||||||
|
export CCNET_CONF_DIR="${SEAFILE_CONF_DIR}"
|
||||||
|
export SEAFILE_CENTRAL_CONF_DIR="${SEAFILE_CONF_DIR}"
|
||||||
|
export SEAFILE_CONF_DIR="${SEAFILE_CONF_DIR}"
|
||||||
|
export SEAFILE_DATA_DIR="${SEAFILE_DATA_DIR}"
|
||||||
|
|
||||||
|
log "Running Django collectstatic (noinput)"
|
||||||
|
"${venv}/bin/python" manage.py collectstatic --noinput || true
|
||||||
|
|
||||||
|
log "Running Django migrate"
|
||||||
|
"${venv}/bin/python" manage.py migrate --noinput || true
|
||||||
|
|
||||||
|
if [[ -n "${ADMIN_EMAIL}" && -n "${ADMIN_PASSWORD}" ]]; then
|
||||||
|
log "Ensuring admin user ${ADMIN_EMAIL}"
|
||||||
|
"${venv}/bin/python" manage.py shell <<PY || true
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
User = get_user_model()
|
||||||
|
email="${ADMIN_EMAIL}"
|
||||||
|
password="${ADMIN_PASSWORD}"
|
||||||
|
if not User.objects.filter(email=email).exists():
|
||||||
|
User.objects.create_superuser(email=email, password=password)
|
||||||
|
else:
|
||||||
|
u = User.objects.get(email=email)
|
||||||
|
if not u.is_superuser:
|
||||||
|
u.is_superuser = True
|
||||||
|
u.save()
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_services() {
|
||||||
|
log "Waiting for MariaDB socket..."
|
||||||
|
wait_for_mysql_socket 60
|
||||||
|
|
||||||
|
log "Waiting for Redis..."
|
||||||
|
local i=0
|
||||||
|
while ! (echo >/dev/tcp/127.0.0.1/6379) 2>/dev/null; do
|
||||||
|
sleep 1
|
||||||
|
i=$((i+1))
|
||||||
|
if [[ $i -ge 60 ]]; then
|
||||||
|
die "Redis did not open port 6379 in 60s"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
start_supervisord() {
|
||||||
|
log "Starting supervisord"
|
||||||
|
exec "${SUPERVISORD_BIN}" -n -c "${SUPERVISORD_CONF}"
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap() {
|
||||||
|
ensure_dirs
|
||||||
|
configure_timezone
|
||||||
|
init_redis
|
||||||
|
init_mariadb
|
||||||
|
fetch_seafile_release_if_needed
|
||||||
|
render_configs
|
||||||
|
init_python_and_requirements
|
||||||
|
init_seahub_settings
|
||||||
|
init_seafile_confs
|
||||||
|
# Note: seafile core DB schema is created/managed by seafile services; Seahub migrations handled above
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-start}" in
|
||||||
|
start)
|
||||||
|
bootstrap
|
||||||
|
start_supervisord
|
||||||
|
;;
|
||||||
|
bash|sh)
|
||||||
|
exec "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Pass-through to allow running arbitrary commands
|
||||||
|
exec "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
36
docker/gunicorn.conf.py
Normal file
36
docker/gunicorn.conf.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Gunicorn configuration for Seahub (Django)
|
||||||
|
# Used by supervisord program `seahub`
|
||||||
|
# Note: Logs go to /data/logs/seahub; supervisord also captures stdout/stderr
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Bind to localhost; nginx proxies to this
|
||||||
|
bind = "127.0.0.1:8000"
|
||||||
|
|
||||||
|
# Workers/threads
|
||||||
|
workers = max(multiprocessing.cpu_count(), 2)
|
||||||
|
worker_class = "gthread"
|
||||||
|
threads = 4
|
||||||
|
preload_app = True
|
||||||
|
|
||||||
|
# Timeouts: uploads can be long-running
|
||||||
|
timeout = 1200
|
||||||
|
graceful_timeout = 60
|
||||||
|
keepalive = 5
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
accesslog = "/data/logs/seahub/gunicorn.access.log"
|
||||||
|
errorlog = "/data/logs/seahub/gunicorn.error.log"
|
||||||
|
loglevel = "info"
|
||||||
|
|
||||||
|
# Security / proxy
|
||||||
|
forwarded_allow_ips = "*"
|
||||||
|
proxy_allow_ips = "*"
|
||||||
|
|
||||||
|
# Env vars (supervisord sets DJANGO_SETTINGS_MODULE, PYTHONPATH, etc.)
|
||||||
|
raw_env = [
|
||||||
|
f"SEAFILE_CONF_DIR={os.environ.get('SEAFILE_CONF_DIR', '/data/conf')}",
|
||||||
|
f"SEAFILE_CENTRAL_CONF_DIR={os.environ.get('SEAFILE_CENTRAL_CONF_DIR', '/data/conf')}",
|
||||||
|
f"SEAFILE_DATA_DIR={os.environ.get('SEAFILE_DATA_DIR', '/data/seafile-data')}",
|
||||||
|
]
|
||||||
33
docker/healthcheck.sh
Normal file
33
docker/healthcheck.sh
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Container healthcheck for Seafile single-container stack
|
||||||
|
# Returns 0 if both Seahub (via nginx) and fileserver respond, non-zero otherwise.
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
curl_opts=(--silent --show-error --fail --location --connect-timeout 2 --max-time 5)
|
||||||
|
|
||||||
|
check_seahub() {
|
||||||
|
local code
|
||||||
|
code="$(curl "${curl_opts[@]}" -o /dev/null -w "%{http_code}" "http://127.0.0.1/accounts/login/")" || return 1
|
||||||
|
# Accept 2xx and 3xx
|
||||||
|
if [[ "${code}" =~ ^2[0-9]{2}$ || "${code}" =~ ^3[0-9]{2}$ ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_fileserver() {
|
||||||
|
# Try direct fileserver port first
|
||||||
|
if curl "${curl_opts[@]}" "http://127.0.0.1:8082/protocol-version" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Fallback through nginx route
|
||||||
|
curl "${curl_opts[@]}" "http://127.0.0.1/seafhttp/protocol-version" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
check_seahub || exit 1
|
||||||
|
check_fileserver || exit 1
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
100
docker/init_seahub.sh
Normal file
100
docker/init_seahub.sh
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# One-shot initializer for Seahub (collectstatic, migrate, optional admin creation)
|
||||||
|
# Run under supervisord before starting gunicorn
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
SEAFILE_HOME=${SEAFILE_HOME:-/opt/seafile}
|
||||||
|
SEAF_RELEASE_DIR_DEFAULT="${SEAFILE_HOME}/seafile-server-latest"
|
||||||
|
SEAF_RELEASE_DIR="${SEAF_RELEASE_DIR:-$SEAF_RELEASE_DIR_DEFAULT}"
|
||||||
|
SEAFILE_CONF_DIR=${SEAFILE_CONF_DIR:-/data/conf}
|
||||||
|
SEAFILE_DATA_DIR=${SEAFILE_DATA_DIR:-/data/seafile-data}
|
||||||
|
|
||||||
|
ADMIN_EMAIL=${ADMIN_EMAIL:-}
|
||||||
|
ADMIN_PASSWORD=${ADMIN_PASSWORD:-}
|
||||||
|
|
||||||
|
MYSQLADMIN_BIN=${MYSQLADMIN_BIN:-/usr/bin/mysqladmin}
|
||||||
|
MYSQL_SOCKET=${MYSQL_SOCKET:-/run/mysqld/mysqld.sock}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[seahub-init $(date +'%Y-%m-%dT%H:%M:%S%z')] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_mariadb() {
|
||||||
|
local timeout="${1:-120}"
|
||||||
|
local i=0
|
||||||
|
log "Waiting for MariaDB to become ready..."
|
||||||
|
while ! "${MYSQLADMIN_BIN}" ping --silent --protocol=socket --socket="${MYSQL_SOCKET}" >/dev/null 2>&1; do
|
||||||
|
sleep 1
|
||||||
|
i=$((i+1))
|
||||||
|
if [[ $i -ge $timeout ]]; then
|
||||||
|
log "MariaDB did not become ready in ${timeout}s"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
log "MariaDB is ready."
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# Avoid re-running if we've already initialized (basic sentinel)
|
||||||
|
local sentinel="${SEAFILE_CONF_DIR}/.seahub_initialized"
|
||||||
|
if [[ -f "${sentinel}" ]]; then
|
||||||
|
log "Seahub already initialized, nothing to do."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_mariadb || true
|
||||||
|
|
||||||
|
local venv="${SEAFILE_HOME}/venv"
|
||||||
|
local seahub_dir="${SEAF_RELEASE_DIR}/seahub"
|
||||||
|
|
||||||
|
if [[ ! -x "${venv}/bin/python" ]]; then
|
||||||
|
log "Python venv missing at ${venv}; skipping init."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ ! -d "${seahub_dir}" ]]; then
|
||||||
|
log "Seahub directory missing at ${seahub_dir}; skipping init."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd "${seahub_dir}" >/dev/null
|
||||||
|
export PYTHONPATH="${SEAF_RELEASE_DIR}:${PYTHONPATH:-}"
|
||||||
|
export DJANGO_SETTINGS_MODULE="seahub.settings"
|
||||||
|
export CCNET_CONF_DIR="${SEAFILE_CONF_DIR}"
|
||||||
|
export SEAFILE_CENTRAL_CONF_DIR="${SEAFILE_CONF_DIR}"
|
||||||
|
export SEAFILE_CONF_DIR="${SEAFILE_CONF_DIR}"
|
||||||
|
export SEAFILE_DATA_DIR="${SEAFILE_DATA_DIR}"
|
||||||
|
|
||||||
|
log "Running collectstatic --noinput"
|
||||||
|
if ! "${venv}/bin/python" manage.py collectstatic --noinput; then
|
||||||
|
log "collectstatic failed (continuing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Running migrate --noinput"
|
||||||
|
if ! "${venv}/bin/python" manage.py migrate --noinput; then
|
||||||
|
log "migrate failed (continuing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${ADMIN_EMAIL}" && -n "${ADMIN_PASSWORD}" ]]; then
|
||||||
|
log "Ensuring admin user ${ADMIN_EMAIL}"
|
||||||
|
"${venv}/bin/python" manage.py shell <<PY || true
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
User = get_user_model()
|
||||||
|
email="${ADMIN_EMAIL}"
|
||||||
|
password="${ADMIN_PASSWORD}"
|
||||||
|
if not User.objects.filter(email=email).exists():
|
||||||
|
User.objects.create_superuser(email=email, password=password)
|
||||||
|
else:
|
||||||
|
u = User.objects.get(email=email)
|
||||||
|
if not u.is_superuser:
|
||||||
|
u.is_superuser = True
|
||||||
|
u.save()
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
touch "${sentinel}"
|
||||||
|
log "Seahub initialization complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
100
docker/nginx.conf.template
Normal file
100
docker/nginx.conf.template
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Nginx config for Seafile single-container
|
||||||
|
# Rendered by entrypoint.sh -> /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
user www-data;
|
||||||
|
worker_processes auto;
|
||||||
|
pid /run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
client_max_body_size {{NGINX_MAX_BODY}};
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log {{LOG_DIR}}/nginx/access.log main;
|
||||||
|
error_log {{LOG_DIR}}/nginx/error.log warn;
|
||||||
|
|
||||||
|
upstream seahub {
|
||||||
|
server 127.0.0.1:8000;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream fileserver {
|
||||||
|
server 127.0.0.1:8082;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
server_name {{SERVER_NAME}};
|
||||||
|
|
||||||
|
# If you terminate TLS here, add:
|
||||||
|
# listen 443 ssl http2;
|
||||||
|
# ssl_certificate /data/ssl/fullchain.pem;
|
||||||
|
# ssl_certificate_key /data/ssl/privkey.pem;
|
||||||
|
|
||||||
|
# Seahub (Django) app
|
||||||
|
location / {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_read_timeout 1200;
|
||||||
|
proxy_connect_timeout 90;
|
||||||
|
proxy_pass http://seahub;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Seafile Go fileserver
|
||||||
|
location /seafhttp {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|
||||||
|
# Large uploads
|
||||||
|
client_max_body_size {{NGINX_MAX_BODY}};
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
proxy_read_timeout 1200;
|
||||||
|
proxy_connect_timeout 90;
|
||||||
|
|
||||||
|
proxy_pass http://fileserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Seahub media (user uploads, avatars) stored on /data
|
||||||
|
location /media {
|
||||||
|
alias {{SEAHUB_MEDIA_DIR}};
|
||||||
|
access_log off;
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional: serve collected static if needed (usually handled by Django collectstatic)
|
||||||
|
# location /assets {
|
||||||
|
# alias {{SEAF_RELEASE_DIR}}/seahub/static;
|
||||||
|
# access_log off;
|
||||||
|
# expires 7d;
|
||||||
|
# add_header Cache-Control "public";
|
||||||
|
# }
|
||||||
|
}
|
||||||
|
}
|
||||||
18
docker/seafile.conf.template
Normal file
18
docker/seafile.conf.template
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Rendered by entrypoint.sh into /data/conf/seafile.conf
|
||||||
|
|
||||||
|
[general]
|
||||||
|
seafile_data_dir = {{SEAFILE_DATA_DIR}}
|
||||||
|
|
||||||
|
[database]
|
||||||
|
type = mysql
|
||||||
|
host = 127.0.0.1
|
||||||
|
port = 3306
|
||||||
|
user = {{DB_USER}}
|
||||||
|
password = {{DB_PASSWORD}}
|
||||||
|
db_name = {{DB_NAME_SEAFILE}}
|
||||||
|
ccnet_db_name = {{DB_NAME_CCNET}}
|
||||||
|
connection_charset = utf8mb4
|
||||||
|
|
||||||
|
[fileserver]
|
||||||
|
host = 127.0.0.1
|
||||||
|
port = 8082
|
||||||
64
docker/seahub_settings.py.template
Normal file
64
docker/seahub_settings.py.template
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Generated by container entrypoint at runtime
|
||||||
|
# This file is rendered into /data/conf/seahub_settings.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# Secret key is stored in a file under /data/conf
|
||||||
|
SEAFILE_CONF_DIR = os.environ.get("SEAFILE_CONF_DIR", "/data/conf")
|
||||||
|
SEAHUB_MEDIA_DIR = os.environ.get("SEAHUB_MEDIA_DIR", "/data/seahub-media")
|
||||||
|
SECRET_KEY_FILE = os.path.join(SEAFILE_CONF_DIR, "seahub_secret_key.txt")
|
||||||
|
with open(SECRET_KEY_FILE, "r") as f:
|
||||||
|
SECRET_KEY = f.read().strip()
|
||||||
|
|
||||||
|
# External URL and hostnames
|
||||||
|
SEAFILE_SERVER_URL = os.environ.get("SEAFILE_SERVER_URL", "http://localhost")
|
||||||
|
SEAFILE_SERVER_HOSTNAME = os.environ.get("SEAFILE_SERVER_HOSTNAME", "localhost")
|
||||||
|
|
||||||
|
# Fileserver (Go) endpoint behind nginx at /seafhttp
|
||||||
|
FILE_SERVER_ROOT = f"{SEAFILE_SERVER_URL.rstrip('/')}/seafhttp"
|
||||||
|
|
||||||
|
SITE_BASE_URL = SEAFILE_SERVER_URL
|
||||||
|
ALLOWED_HOSTS = [SEAFILE_SERVER_HOSTNAME, "127.0.0.1", "localhost"]
|
||||||
|
|
||||||
|
# Database (Seahub Django DB)
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.mysql",
|
||||||
|
"NAME": os.environ.get("DB_NAME_SEAHUB", "seahub_db"),
|
||||||
|
"USER": os.environ.get("DB_USER", "seafile"),
|
||||||
|
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
|
||||||
|
"HOST": "127.0.0.1",
|
||||||
|
"PORT": "3306",
|
||||||
|
"OPTIONS": {"charset": "utf8mb4"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache (Redis)
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.redis.RedisCache",
|
||||||
|
"LOCATION": os.environ.get("REDIS_URL", "redis://127.0.0.1:6379/0"),
|
||||||
|
"TIMEOUT": 300,
|
||||||
|
"OPTIONS": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Timezone
|
||||||
|
TIME_ZONE = os.environ.get("TIMEZONE", "UTC")
|
||||||
|
|
||||||
|
# Media (avatars, uploads)
|
||||||
|
MEDIA_ROOT = SEAHUB_MEDIA_DIR
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
# Security/Proxy headers (nginx sits in front)
|
||||||
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
|
||||||
|
# Optional email configuration can be provided via environment variables if desired
|
||||||
|
# Example:
|
||||||
|
# EMAIL_USE_TLS = True
|
||||||
|
# EMAIL_HOST = os.environ.get("EMAIL_HOST", "")
|
||||||
|
# EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
|
||||||
|
# EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
|
||||||
|
# EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587"))
|
||||||
92
docker/supervisord.conf.template
Normal file
92
docker/supervisord.conf.template
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
; Supervisord configuration for single-container Seafile stack
|
||||||
|
; Rendered by entrypoint.sh -> /etc/supervisor/supervisord.conf
|
||||||
|
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile={{LOG_DIR}}/supervisor/supervisord.log
|
||||||
|
pidfile=/run/supervisord.pid
|
||||||
|
childlogdir={{LOG_DIR}}/supervisor
|
||||||
|
logfile_maxbytes=50MB
|
||||||
|
logfile_backups=5
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///run/supervisor.sock
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file=/run/supervisor.sock
|
||||||
|
chmod=0700
|
||||||
|
|
||||||
|
; ------------ Programs ------------
|
||||||
|
|
||||||
|
; 1) MariaDB
|
||||||
|
[program:mariadb]
|
||||||
|
command=/usr/sbin/mariadbd --datadir=/data/db --socket=/run/mysqld/mysqld.sock --bind-address=127.0.0.1 --skip-name-resolve --skip-symbolic-links --log-bin=OFF --user=mysql
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=10
|
||||||
|
startsecs=5
|
||||||
|
priority=10
|
||||||
|
stdout_logfile={{LOG_DIR}}/mariadb/stdout.log
|
||||||
|
stderr_logfile={{LOG_DIR}}/mariadb/stderr.log
|
||||||
|
stdout_logfile_maxbytes=20MB
|
||||||
|
stderr_logfile_maxbytes=20MB
|
||||||
|
environment=MYSQL_UNIX_PORT="/run/mysqld/mysqld.sock"
|
||||||
|
|
||||||
|
; 2) Redis
|
||||||
|
[program:redis]
|
||||||
|
command=/usr/bin/redis-server /etc/redis/redis.conf
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=10
|
||||||
|
startsecs=2
|
||||||
|
priority=20
|
||||||
|
stdout_logfile={{LOG_DIR}}/redis/stdout.log
|
||||||
|
stderr_logfile={{LOG_DIR}}/redis/stderr.log
|
||||||
|
stdout_logfile_maxbytes=20MB
|
||||||
|
stderr_logfile_maxbytes=20MB
|
||||||
|
|
||||||
|
; 3) Seafile Core + Fileserver (via seafile.sh -> seafile-controller)
|
||||||
|
[program:seafile]
|
||||||
|
command=/bin/bash -lc "for i in {1..60}; do /usr/bin/mysqladmin --protocol=socket --socket=/run/mysqld/mysqld.sock ping --silent && break || sleep 1; done; {{SEAF_RELEASE_DIR}}/seafile.sh start"
|
||||||
|
directory={{SEAF_RELEASE_DIR}}
|
||||||
|
user=seafile
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=10
|
||||||
|
startsecs=8
|
||||||
|
priority=30
|
||||||
|
stdout_logfile={{LOG_DIR}}/seafile/seafile.stdout.log
|
||||||
|
stderr_logfile={{LOG_DIR}}/seafile/seafile.stderr.log
|
||||||
|
stdout_logfile_maxbytes=50MB
|
||||||
|
stderr_logfile_maxbytes=50MB
|
||||||
|
environment=CCNET_CONF_DIR="{{SEAFILE_CONF_DIR}}",SEAFILE_CONF_DIR="{{SEAFILE_CONF_DIR}}",SEAFILE_CENTRAL_CONF_DIR="{{SEAFILE_CONF_DIR}}",SEAFILE_DATA_DIR="{{SEAFILE_DATA_DIR}}"
|
||||||
|
|
||||||
|
; 4) Seahub via Gunicorn
|
||||||
|
[program:seahub]
|
||||||
|
command=/bin/bash -lc "/opt/seafile/docker/init_seahub.sh && exec /opt/seafile/venv/bin/gunicorn -c /opt/seafile/docker/gunicorn.conf.py seahub.wsgi:application"
|
||||||
|
directory={{SEAF_RELEASE_DIR}}/seahub
|
||||||
|
user=seafile
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=10
|
||||||
|
startsecs=8
|
||||||
|
priority=40
|
||||||
|
stdout_logfile={{LOG_DIR}}/seahub/gunicorn.stdout.log
|
||||||
|
stderr_logfile={{LOG_DIR}}/seahub/gunicorn.stderr.log
|
||||||
|
stdout_logfile_maxbytes=50MB
|
||||||
|
stderr_logfile_maxbytes=50MB
|
||||||
|
environment=DJANGO_SETTINGS_MODULE="seahub.settings",PYTHONPATH="{{SEAF_RELEASE_DIR}}",CCNET_CONF_DIR="{{SEAFILE_CONF_DIR}}",SEAFILE_CONF_DIR="{{SEAFILE_CONF_DIR}}",SEAFILE_CENTRAL_CONF_DIR="{{SEAFILE_CONF_DIR}}",SEAFILE_DATA_DIR="{{SEAFILE_DATA_DIR}}"
|
||||||
|
|
||||||
|
; 5) Nginx
|
||||||
|
[program:nginx]
|
||||||
|
command=/usr/sbin/nginx -g "daemon off;"
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=10
|
||||||
|
startsecs=2
|
||||||
|
priority=50
|
||||||
|
stdout_logfile={{LOG_DIR}}/nginx/stdout.log
|
||||||
|
stderr_logfile={{LOG_DIR}}/nginx/stderr.log
|
||||||
|
stdout_logfile_maxbytes=20MB
|
||||||
|
stderr_logfile_maxbytes=20MB
|
||||||
187
implementation_plan.md
Normal file
187
implementation_plan.md
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# Implementation Plan
|
||||||
|
|
||||||
|
[Overview]
|
||||||
|
Package Seafile Server Core (seafile-server) and Seahub (Django web UI) into a single-container image suitable for TrueNAS SCALE Dragonfish Custom App deployment.
|
||||||
|
|
||||||
|
This implementation builds a single container that runs all required services for a functional Seafile deployment: MariaDB (in-container as per decision), Redis, Seafile file server (Go), Seafile core daemons, and Seahub behind an in-container Nginx reverse proxy. The container exposes HTTP/HTTPS and uses a single persistent volume at /data to store configuration, databases, media, and logs. The plan prioritizes reliability and repeatability on TrueNAS SCALE, using official Seafile release artifacts by default while documenting an alternative-from-source build path for the provided repositories.
|
||||||
|
|
||||||
|
Key runtime components:
|
||||||
|
- Nginx reverse proxy (ports 80/443) → proxies to internal gunicorn (Seahub) and fileserver
|
||||||
|
- gunicorn serving Seahub (Django) on 127.0.0.1:8000
|
||||||
|
- Seafile "fileserver" (Go) on 127.0.0.1:8082
|
||||||
|
- Seafile core services (seaf-server and related daemons)
|
||||||
|
- MariaDB (in-container) for metadata
|
||||||
|
- Redis (in-container) for caching/pubsub
|
||||||
|
- One persistent mount at /data containing conf, databases, media, and logs
|
||||||
|
|
||||||
|
Assumptions:
|
||||||
|
- Database: In-container MariaDB (user confirmed).
|
||||||
|
- Web: In-container Nginx reverse proxy (for TLS termination and path routing).
|
||||||
|
- Redis: In-container Redis (to keep single-container constraint).
|
||||||
|
- Persistent storage: Single mount /data (with subdirectories).
|
||||||
|
- Build source: Default to official Seafile release artifacts for server daemons; include an optional from-source path for the provided seafile-server and seahub repositories.
|
||||||
|
|
||||||
|
[Types]
|
||||||
|
Configuration and environment are expressed via environment variables and YAML/INI-like files written at container start.
|
||||||
|
|
||||||
|
Type definitions and configuration contracts:
|
||||||
|
- EnvConfig (environment variables):
|
||||||
|
- SEAFILE_SERVER_HOSTNAME: string (required), hostname (e.g., files.example.com)
|
||||||
|
- SEAFILE_SERVER_URL: string (required), public base URL (e.g., https://files.example.com)
|
||||||
|
- ADMIN_EMAIL: string (optional, used at first-run to create admin)
|
||||||
|
- ADMIN_PASSWORD: string (optional, used at first-run)
|
||||||
|
- DB_ROOT_PASSWORD: string (required for MariaDB bootstrap)
|
||||||
|
- DB_NAME: string, default "seafile"
|
||||||
|
- DB_USER: string, default "seafile"
|
||||||
|
- DB_PASSWORD: string, required
|
||||||
|
- REDIS_URL: string, default "redis://127.0.0.1:6379/0"
|
||||||
|
- TIMEZONE: string, default "UTC"
|
||||||
|
- NGINX_MAX_BODY: string, default "50m"
|
||||||
|
- SEAFILE_DATA_DIR: string, default "/data/seafile-data"
|
||||||
|
- SEAFILE_CONF_DIR: string, default "/data/conf"
|
||||||
|
- SEAHUB_MEDIA_DIR: string, default "/data/seahub-media"
|
||||||
|
- LOG_DIR: string, default "/data/logs"
|
||||||
|
- SSL_ENABLE: bool, default "false" (TrueNAS may handle TLS; if true, place certs at /data/ssl)
|
||||||
|
- FileLayout:
|
||||||
|
- /data/conf/: central Seafile/Seahub config (ccnet/seafile.conf, seahub_settings.py)
|
||||||
|
- /data/seafile-data/: seafile data store
|
||||||
|
- /data/db/: MariaDB datadir
|
||||||
|
- /data/redis/: Redis data (if persistence desired)
|
||||||
|
- /data/seahub-media/: uploads and media
|
||||||
|
- /data/logs/: logs (nginx, seahub, seafile, supervisord)
|
||||||
|
- /data/ssl/: TLS certs (optional)
|
||||||
|
- NginxConfig:
|
||||||
|
- server_name: from SEAFILE_SERVER_HOSTNAME
|
||||||
|
- upstreams:
|
||||||
|
- seahub: 127.0.0.1:8000
|
||||||
|
- fileserver: 127.0.0.1:8082
|
||||||
|
- routes:
|
||||||
|
- /: proxy to seahub, static caching rules
|
||||||
|
- /seafhttp: proxy_pass to fileserver, client_max_body_size NGINX_MAX_BODY
|
||||||
|
- /media: alias to /opt/seafile/seahub/media (static), or to collected static path
|
||||||
|
- DBConfig:
|
||||||
|
- Databases: seafile [and related], initialized via official seafile init scripts or seafile-admin tooling from release package
|
||||||
|
- User and grants as per Seafile docs
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Single Dockerfile-based image and runtime scripts; all persistent state under /data.
|
||||||
|
|
||||||
|
New files to be created:
|
||||||
|
- Dockerfile
|
||||||
|
- Multi-stage (optional) to fetch official Seafile release tarball and build Go fileserver if not bundled
|
||||||
|
- Installs: nginx, supervisor, mariadb-server, redis-server, python3, python3-venv, pip, required OS libs, curl, tzdata
|
||||||
|
- Sets up non-root user (e.g., seafile) and folders under /opt/seafile
|
||||||
|
- docker/entrypoint.sh
|
||||||
|
- Idempotent bootstrap: initialize /data layout, MariaDB, Redis, Seafile configs, Django SECRET_KEY, admin user
|
||||||
|
- Runs database migrations and static collection for Seahub
|
||||||
|
- Starts supervisord
|
||||||
|
- docker/supervisord.conf
|
||||||
|
- Programs:
|
||||||
|
- mariadbd
|
||||||
|
- redis-server
|
||||||
|
- seafile core (seaf-server and any required service supervisors)
|
||||||
|
- fileserver (Go binary)
|
||||||
|
- gunicorn (Seahub)
|
||||||
|
- nginx
|
||||||
|
- docker/nginx.conf
|
||||||
|
- Reverse proxy configuration and size limits
|
||||||
|
- docker/gunicorn.conf.py
|
||||||
|
- Workers, timeouts, bind address 127.0.0.1:8000
|
||||||
|
- docker/init_db.sh
|
||||||
|
- Initializes MariaDB root password, creates seafile DB/user
|
||||||
|
- docker/healthcheck.sh
|
||||||
|
- Checks HTTP 200 on /ping or /accounts/login/ and fileserver /ping if available
|
||||||
|
- docker/seahub_settings.py.template
|
||||||
|
- Generates seahub_settings.py under /data/conf
|
||||||
|
- docker/requirements-override.txt (optional)
|
||||||
|
- Pin/override Python deps if necessary for release compatibility
|
||||||
|
|
||||||
|
Existing files to be modified:
|
||||||
|
- None inside provided source trees; implementation uses release artifacts and runtime-generated config under /data.
|
||||||
|
|
||||||
|
Files to be deleted or moved:
|
||||||
|
- None.
|
||||||
|
|
||||||
|
Configuration file updates:
|
||||||
|
- /data/conf/seahub_settings.py generated with database, cache (Redis), ALLOWED_HOSTS, SITE_BASE_URL, media/static paths, SECRET_KEY
|
||||||
|
- /data/conf/seafile.conf generated with service URLs and seafile-data path
|
||||||
|
- Nginx server_name and upstreams from env vars
|
||||||
|
|
||||||
|
[Functions]
|
||||||
|
Shell-level entrypoint functions and service supervisors are introduced.
|
||||||
|
|
||||||
|
New functions (shell) with purpose:
|
||||||
|
- entrypoint.sh:init_layout()
|
||||||
|
- Create /data subdirectories; set permissions for seafile/nginx/mysql/redis users.
|
||||||
|
- entrypoint.sh:init_mariadb()
|
||||||
|
- Initialize mariadb datadir if empty; secure install; create seafile DB/user with grants.
|
||||||
|
- entrypoint.sh:init_redis()
|
||||||
|
- Write minimal redis.conf (dir /data/redis, protected-mode yes).
|
||||||
|
- entrypoint.sh:init_seafile()
|
||||||
|
- If /data/conf empty, unpack official seafile-server release, run init scripts to generate seafile.conf, seahub_settings.py template; place into /data/conf; create seafile-data dir.
|
||||||
|
- entrypoint.sh:init_seahub()
|
||||||
|
- Create Python venv, pip install -r requirements; generate SECRET_KEY if missing; run Django collectstatic; perform DB migrations; optionally create admin user if ADMIN_EMAIL and ADMIN_PASSWORD present.
|
||||||
|
- entrypoint.sh:configure_nginx()
|
||||||
|
- Render nginx.conf using env vars; enable gzip, client_max_body_size, proxy headers, X-Accel config if needed.
|
||||||
|
- entrypoint.sh:wait_for_services()
|
||||||
|
- Wait for MariaDB and Redis sockets ready.
|
||||||
|
- entrypoint.sh:run_supervisord()
|
||||||
|
- Start all services via supervisord.
|
||||||
|
|
||||||
|
Modified functions (N/A in existing codebase; these are new runtime scripts).
|
||||||
|
|
||||||
|
Removed functions:
|
||||||
|
- N/A.
|
||||||
|
|
||||||
|
[Classes]
|
||||||
|
No new application-level classes; infra relies on supervisord processes and Nginx. Django app (Seahub) remains unmodified.
|
||||||
|
|
||||||
|
New classes:
|
||||||
|
- N/A.
|
||||||
|
|
||||||
|
Modified classes:
|
||||||
|
- N/A.
|
||||||
|
|
||||||
|
Removed classes:
|
||||||
|
- N/A.
|
||||||
|
|
||||||
|
[Dependencies]
|
||||||
|
Container-level OS and language dependencies and Seafile release artifacts.
|
||||||
|
|
||||||
|
New packages (apt):
|
||||||
|
- nginx, supervisor, mariadb-server, redis-server
|
||||||
|
- python3, python3-venv, python3-pip
|
||||||
|
- curl, ca-certificates, tzdata
|
||||||
|
- build deps only if compiling from source variant: build-essential, autoconf, automake, libtool, pkg-config, libglib2.0-dev, libevent-dev, libjansson-dev, uuid-dev, libsqlite3-dev, libssl-dev, zlib1g-dev, libmysqlclient-dev, libarchive-dev, libcurl4-openssl-dev, libhiredis-dev, libjwt-dev, libargon2-dev, golang
|
||||||
|
|
||||||
|
Python packages (from seahub/requirements.txt, vetted for release compatibility):
|
||||||
|
- Django 5.2.*, DRF 3.16.*, gunicorn 23.*, mysqlclient 2.2.*, redis 6.2.*, and listed dependencies.
|
||||||
|
|
||||||
|
Integration requirements:
|
||||||
|
- Official Seafile server release tarball (server core + scripts) matching Seahub version
|
||||||
|
- For from-source variant, align seafile-server and seahub versions and run autogen/configure/make install flow plus Go fileserver build.
|
||||||
|
|
||||||
|
[Testing]
|
||||||
|
Container functional tests via healthchecks and basic web/API flow.
|
||||||
|
|
||||||
|
Test requirements and validation strategies:
|
||||||
|
- Healthcheck: curl http://127.0.0.1/accounts/login/ returns 200 after startup
|
||||||
|
- Fileserver: curl http://127.0.0.1/seafhttp/ returns expected status or ping endpoint
|
||||||
|
- Admin creation: login with ADMIN_EMAIL/ADMIN_PASSWORD succeeds
|
||||||
|
- Upload flow: create test library, upload a small file via UI; verify in seafile-data
|
||||||
|
- Persistence: stop/start container; verify data preserved at /data
|
||||||
|
- Logs: verify absence of critical errors in /data/logs/*
|
||||||
|
|
||||||
|
[Implementation Order]
|
||||||
|
Implement in layers to minimize complexity: image base, config generation, service orchestration, then polish and tests.
|
||||||
|
|
||||||
|
1) Dockerfile: base image, OS deps, users, directories, copy scripts/templates.
|
||||||
|
2) Download and lay down official Seafile release artifacts in /opt/seafile (or document from-source alternative).
|
||||||
|
3) Add Python venv creation and pip install for Seahub requirements.
|
||||||
|
4) Add Nginx, gunicorn, Redis, MariaDB, and supervisord configuration files.
|
||||||
|
5) Implement entrypoint.sh to initialize /data, DBs, configs, static, migrations, admin user.
|
||||||
|
6) Wire supervisord programs for services; ensure start order and restart policies.
|
||||||
|
7) Add healthcheck.sh and Docker HEALTHCHECK.
|
||||||
|
8) Expose ports 80/443; document TrueNAS mount /data and required env vars.
|
||||||
|
9) Smoke test locally with docker run and validate UI, uploads, persistence.
|
||||||
|
10) Document alternative from-source build path (autotools + Go) for provided repositories if explicit source-build is later required.
|
||||||
Loading…
x
Reference in New Issue
Block a user