From 359159a4ba690105da35d4df4559298a57a66aa1 Mon Sep 17 00:00:00 2001 From: karmacoma Date: Sat, 27 Dec 2025 00:36:05 +0100 Subject: [PATCH] intial commit --- .env | 21 +++++++ README.md | 137 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 113 +++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 .env create mode 100644 docker-compose.yml diff --git a/.env b/.env new file mode 100644 index 0000000..cebe3e5 --- /dev/null +++ b/.env @@ -0,0 +1,21 @@ +# Core versions (can be overridden per deployment) +NEXTCLOUD_VERSION=32-apache +MARIADB_VERSION=11.4 +REDIS_VERSION=7-alpine +ONLYOFFICE_VERSION=8.0 + +# Networking +NEXTCLOUD_DOMAIN=cloud.example.com + +# Nextcloud defaults (non-secret) +NEXTCLOUD_ADMIN_USER=ncadmin +MARIADB_DATABASE=nextcloud +MARIADB_USER=nextcloud + +# Secrets (must be generated per deployment) +# Provide strong random values; leave empty in git and set in Coolify or local overrides. +MARIADB_PASSWORD= +MARIADB_ROOT_PASSWORD= +NEXTCLOUD_ADMIN_PASSWORD= +REDIS_PASSWORD= +ONLYOFFICE_JWT_SECRET= diff --git a/README.md b/README.md index 98b24dc..857a81e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,139 @@ # knet-cloud +Modern Nextcloud stack with MariaDB, Redis caching, and OnlyOffice Document Server. Auth is expected through your own authentik OIDC provider. + +## Stack + +- Nextcloud (Apache) + dedicated cron sidecar +- MariaDB 11 +- Redis 7 (locking/file cache) +- OnlyOffice Document Server + +## Quick start (local or generic Compose) + +1. Install Docker Engine + Docker Compose. +2. Copy and edit [.env](.env) for visibility; the compose file carries defaults for non-secrets, but you must set the secrets. +3. Generate secrets (do not commit them): + + ```bash + cp .env .env.local + # Fill these with strong randoms + openssl rand -hex 32 # set MARIADB_PASSWORD + openssl rand -hex 32 # set MARIADB_ROOT_PASSWORD + openssl rand -hex 32 # set NEXTCLOUD_ADMIN_PASSWORD + openssl rand -hex 32 # set REDIS_PASSWORD + openssl rand -hex 32 # set ONLYOFFICE_JWT_SECRET + ``` + +4. Bring the stack up: + + ```bash + docker compose --env-file .env.local up -d + ``` + +5. First login at `https://` with `NEXTCLOUD_ADMIN_USER` and `NEXTCLOUD_ADMIN_PASSWORD`. + +## Required post-deploy hardening + +Run these once after the first boot. Replace placeholders where noted. + +```bash +# Set Redis as the locking cache (pick the same password as REDIS_PASSWORD) +docker compose exec nextcloud php occ config:system:set memcache.local --value='\OC\Memcache\APCu' +docker compose exec nextcloud php occ config:system:set memcache.locking --value='\OC\Memcache\Redis' +docker compose exec nextcloud php occ config:system:set redis host --value=redis +docker compose exec nextcloud php occ config:system:set redis port --value=6379 --type=integer +docker compose exec nextcloud php occ config:system:set redis password --value='' + +# Force HTTPS and trusted domains +docker compose exec nextcloud php occ config:system:set overwriteprotocol --value=https +docker compose exec nextcloud php occ config:system:set trusted_domains 1 --value='' + +# Install core apps we'll need +docker compose exec nextcloud php occ app:install user_oidc +docker compose exec nextcloud php occ app:install onlyoffice +``` + +## Wire authentik (OIDC) + +1. In authentik create an OIDC Provider + Application: + + - Redirect URI: `https:///apps/user_oidc/callback` + - Post-logout redirect: `https:///logout` + - Scopes: `openid email profile offline_access` + - Algorithm: RS256, with discovery enabled. + +2. Save the client ID and client secret. +3. Configure the `user_oidc` app in Nextcloud (CLI example): + + ```bash + docker compose exec nextcloud php occ config:app:set user_oidc oidc_login_provider_url --value='https://auth.example.com/application/o//' + docker compose exec nextcloud php occ config:app:set user_oidc oidc_login_client_id --value='' + docker compose exec nextcloud php occ config:app:set user_oidc oidc_login_client_secret --value='' + docker compose exec nextcloud php occ config:app:set user_oidc oidc_login_scope --value='openid email profile offline_access' + docker compose exec nextcloud php occ config:app:set user_oidc oidc_login_button_text --value='Login with authentik' + docker compose exec nextcloud php occ config:app:set user_oidc oidc_login_end_session_redirect --value='https:///logout' + ``` + +4. Test SSO and disable local password login for regular users once confirmed. + +## Hook up OnlyOffice + +1. In Nextcloud, enable the `onlyoffice` app (already installed above). +2. Set URLs and JWT secret (internal URL keeps traffic inside the bridge network): + + ```bash + docker compose exec nextcloud php occ config:app:set onlyoffice DocumentServerUrl --value='https://' + docker compose exec nextcloud php occ config:app:set onlyoffice DocumentServerInternalUrl --value='http://onlyoffice/' + docker compose exec nextcloud php occ config:app:set onlyoffice StorageUrl --value='http://nextcloud/' + docker compose exec nextcloud php occ config:app:set onlyoffice jwt_secret --value='' + docker compose exec nextcloud php occ config:app:set onlyoffice jwt_header --value='Authorization' + ``` + + - `` should match `ONLYOFFICE_JWT_SECRET` in [.env](.env). + +## Operational notes + +- Run behind a TLS reverse proxy (Traefik, Caddy, or NGINX). In Coolify, Traefik is managed for you and no host ports need to be exposed in the compose file. +- Backups: snapshot `nextcloud_data`, `nextcloud_config`, and `db_data`; also export MariaDB dumps regularly. +- Updates: bump the version tags in [.env](.env) and `docker compose pull && docker compose up -d`. +- Logs: `docker compose logs -f nextcloud` and `onlyoffice` are your primary places to debug. + +## Deploy with Coolify (step-by-step) + +Coolify ignores `.env` files. Enter every variable in the UI. Use [.env](.env) as a reference for names and defaults. Secrets must be generated per deployment. + +1. Create or select a Project in Coolify. +2. Add Resource → Docker Compose → “Import from Git”; point to this repo/branch. +3. In the Compose Resource settings, add environment variables: + - Non-secret defaults (override as needed): + - `NEXTCLOUD_VERSION=32-apache` + - `MARIADB_VERSION=11.4` + - `REDIS_VERSION=7-alpine` + - `ONLYOFFICE_VERSION=8.0` + - `NEXTCLOUD_DOMAIN=cloud.example.com` (set to your actual domain) + - `NEXTCLOUD_ADMIN_USER=ncadmin` + - `MARIADB_DATABASE=nextcloud` + - `MARIADB_USER=nextcloud` + - Required secrets (must be strong randoms): + - `MARIADB_PASSWORD` + - `MARIADB_ROOT_PASSWORD` + - `NEXTCLOUD_ADMIN_PASSWORD` + - `REDIS_PASSWORD` + - `ONLYOFFICE_JWT_SECRET` +4. Volumes: ensure the named volumes (`nextcloud_data`, `nextcloud_apps`, `nextcloud_config`, `db_data`, `redis_data`, `onlyoffice_data`, `onlyoffice_logs`) are set as persistent in Coolify (they map to Docker named volumes and will persist across deploys). +5. Domains/ingress: no port mappings required. Assign your domain(s) to the service in Coolify; Traefik handles HTTPS. Keep `NEXTCLOUD_DOMAIN` matching the public hostname. +6. Deploy and monitor logs until DB init and healthchecks are healthy. +7. Run the hardening `occ` commands (Redis cache, trusted domains/HTTPS, app installs) via the Coolify Console against the `nextcloud` container. + +## Quality-of-life ideas + +- Offload primary storage to S3/MinIO using the Nextcloud object store config for simpler scaling. + +- Enable metrics/monitoring (Prometheus exporters for MariaDB/Redis, Traefik dashboard if used) plus alerting on failed healthchecks. + +- Configure TOTP/WebAuthn in authentik and enforce SSO-only login on Nextcloud for consistent access policy. + +- Use a dedicated backup tool (restic or Borg) with encryption and retention against a separate target. + +- Add a content-delivery rule (CDN or edge cache) for static assets if hosting for many remote users. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bc94dc5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,113 @@ +version: "3.9" + +services: + nextcloud: + image: nextcloud:${NEXTCLOUD_VERSION:-32-apache} + restart: unless-stopped + depends_on: + - db + - redis + - onlyoffice + environment: + MYSQL_HOST: db + MYSQL_DATABASE: ${MARIADB_DATABASE:-nextcloud} + MYSQL_USER: ${MARIADB_USER:-nextcloud} + MYSQL_PASSWORD: ${MARIADB_PASSWORD?set_mariadb_password} + NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER:-ncadmin} + NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD?set_nextcloud_admin_password} + NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_DOMAIN:-cloud.example.com} + OVERWRITEHOST: ${NEXTCLOUD_DOMAIN:-cloud.example.com} + OVERWRITEPROTOCOL: https + REDIS_HOST: redis + REDIS_HOST_PASSWORD: ${REDIS_PASSWORD?generate_redis_password} + PHP_MEMORY_LIMIT: 1G + PHP_UPLOAD_LIMIT: 2G + volumes: + - nextcloud_data:/var/www/html + - nextcloud_apps:/var/www/html/custom_apps + - nextcloud_config:/var/www/html/config + + nextcloud-cron: + image: nextcloud:${NEXTCLOUD_VERSION:-32-apache} + restart: unless-stopped + entrypoint: /cron.sh + depends_on: + - db + - redis + environment: + MYSQL_HOST: db + MYSQL_DATABASE: ${MARIADB_DATABASE:-nextcloud} + MYSQL_USER: ${MARIADB_USER:-nextcloud} + MYSQL_PASSWORD: ${MARIADB_PASSWORD?set_mariadb_password} + NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER:-ncadmin} + NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD?set_nextcloud_admin_password} + NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_DOMAIN:-cloud.example.com} + OVERWRITEHOST: ${NEXTCLOUD_DOMAIN:-cloud.example.com} + OVERWRITEPROTOCOL: https + REDIS_HOST: redis + REDIS_HOST_PASSWORD: ${REDIS_PASSWORD?generate_redis_password} + PHP_MEMORY_LIMIT: 1G + PHP_UPLOAD_LIMIT: 2G + volumes: + - nextcloud_data:/var/www/html + - nextcloud_apps:/var/www/html/custom_apps + - nextcloud_config:/var/www/html/config + + db: + image: mariadb:${MARIADB_VERSION:-11.4} + restart: unless-stopped + command: + - --transaction-isolation=READ-COMMITTED + - --binlog-format=ROW + - --innodb_read_only_compressed=OFF + environment: + MYSQL_DATABASE: ${MARIADB_DATABASE:-nextcloud} + MYSQL_USER: ${MARIADB_USER:-nextcloud} + MYSQL_PASSWORD: ${MARIADB_PASSWORD?set_mariadb_password} + MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD?set_mariadb_root_password} + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 30s + timeout: 5s + retries: 5 + + redis: + image: redis:${REDIS_VERSION:-7-alpine} + restart: unless-stopped + command: ["redis-server", "--requirepass", "${REDIS_PASSWORD?generate_redis_password}"] + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD?generate_redis_password}", "ping"] + interval: 30s + timeout: 5s + retries: 5 + + onlyoffice: + image: onlyoffice/documentserver:${ONLYOFFICE_VERSION:-8.0} + restart: unless-stopped + environment: + JWT_ENABLED: "true" + JWT_SECRET: ${ONLYOFFICE_JWT_SECRET?generate_onlyoffice_jwt_secret} + JWT_HEADER: Authorization + depends_on: + - redis + volumes: + - onlyoffice_data:/var/www/onlyoffice/Data + - onlyoffice_logs:/var/log/onlyoffice + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost/healthcheck || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + +volumes: + nextcloud_data: + nextcloud_apps: + nextcloud_config: + db_data: + redis_data: + onlyoffice_data: + onlyoffice_logs: