diff --git a/roles/backup/defaults/main.yml b/roles/backup/defaults/main.yml index 65e4b0b..6478b34 100644 --- a/roles/backup/defaults/main.yml +++ b/roles/backup/defaults/main.yml @@ -5,4 +5,4 @@ backup_user: deploy # Timeweb S3 offsite backups s3_endpoint: "https://s3.timeweb.cloud" -s3_bucket: "visual-backup" +s3_bucket: "walava-backup" diff --git a/roles/backup/templates/backup.sh.j2 b/roles/backup/templates/backup.sh.j2 index f06de13..ed8dc14 100644 --- a/roles/backup/templates/backup.sh.j2 +++ b/roles/backup/templates/backup.sh.j2 @@ -32,6 +32,12 @@ docker exec plane-db pg_dump -U plane plane \ | gzip > "${WORK_DIR}/data/databases/plane.sql.gz" log " → databases/plane.sql.gz ($(du -sh "${WORK_DIR}/data/databases/plane.sql.gz" | cut -f1))" +# ── PostgreSQL: Outline ────────────────────────────────────────────────────── +log "Dumping outline-db..." +docker exec outline-db pg_dump -U outline outline \ + | gzip > "${WORK_DIR}/data/databases/outline.sql.gz" +log " → databases/outline.sql.gz ($(du -sh "${WORK_DIR}/data/databases/outline.sql.gz" | cut -f1))" + # ── Forgejo data volume (repos, attachments, LFS) ─────────────────────────── log "Backing up Forgejo data..." docker run --rm \ @@ -50,6 +56,24 @@ docker run --rm \ tar czf /backup/uptime-kuma.tar.gz /app/data log " → volumes/uptime-kuma.tar.gz ($(du -sh "${WORK_DIR}/data/volumes/uptime-kuma.tar.gz" | cut -f1))" +# ── n8n workflows + credentials ────────────────────────────────────────────── +log "Backing up n8n..." +docker run --rm \ + --volumes-from n8n \ + -v "${WORK_DIR}/data/volumes:/backup" \ + alpine:3 \ + tar czf /backup/n8n.tar.gz /home/node/.n8n +log " → volumes/n8n.tar.gz ($(du -sh "${WORK_DIR}/data/volumes/n8n.tar.gz" | cut -f1))" + +# ── Plane MinIO (uploaded files / attachments) ─────────────────────────────── +log "Backing up Plane MinIO..." +docker run --rm \ + --volumes-from plane-minio \ + -v "${WORK_DIR}/data/volumes:/backup" \ + alpine:3 \ + tar czf /backup/plane-minio.tar.gz /data +log " → volumes/plane-minio.tar.gz ($(du -sh "${WORK_DIR}/data/volumes/plane-minio.tar.gz" | cut -f1))" + # ── Add restore instructions ───────────────────────────────────────────────── cat > "${WORK_DIR}/data/RESTORE.md" << 'RESTORE_EOF' # Restore Instructions @@ -72,6 +96,9 @@ zcat data/databases/forgejo.sql.gz | docker exec -i forgejo-db psql -U forgejo f # Plane DB zcat data/databases/plane.sql.gz | docker exec -i plane-db psql -U plane plane + +# Outline DB +zcat data/databases/outline.sql.gz | docker exec -i outline-db psql -U outline outline ``` ## Step 3 — Restore volume data @@ -80,9 +107,17 @@ zcat data/databases/plane.sql.gz | docker exec -i plane-db psql -U plane plane docker run --rm --volumes-from forgejo -v $(pwd)/data/volumes:/backup \ alpine:3 sh -c "cd / && tar xzf /backup/forgejo.tar.gz" -# Uptime Kuma — extracts /app/data/ into the container +# Uptime Kuma docker run --rm --volumes-from uptime-kuma -v $(pwd)/data/volumes:/backup \ alpine:3 sh -c "cd / && tar xzf /backup/uptime-kuma.tar.gz" + +# n8n +docker run --rm --volumes-from n8n -v $(pwd)/data/volumes:/backup \ + alpine:3 sh -c "cd / && tar xzf /backup/n8n.tar.gz" + +# Plane MinIO (uploaded files) +docker run --rm --volumes-from plane-minio -v $(pwd)/data/volumes:/backup \ + alpine:3 sh -c "cd / && tar xzf /backup/plane-minio.tar.gz" ``` ## Step 4 — Restart services diff --git a/roles/services/defaults/main.yml b/roles/services/defaults/main.yml index fb67020..873e756 100644 --- a/roles/services/defaults/main.yml +++ b/roles/services/defaults/main.yml @@ -28,3 +28,8 @@ promtail_image: "grafana/promtail:3.4.3" # https://hub crowdsec_image: "crowdsecurity/crowdsec:v1.6.8" # https://hub.docker.com/r/crowdsecurity/crowdsec/tags redis_image: "redis:7-alpine" uptime_kuma_image: "louislam/uptime-kuma:1" # https://hub.docker.com/r/louislam/uptime-kuma/tags +outline_image: "outlinewiki/outline:0.80.2" # https://hub.docker.com/r/outlinewiki/outline/tags +outline_db_image: "postgres:15-alpine" +outline_redis_image: "redis:7-alpine" +n8n_image: "n8nio/n8n:1.89.2" # https://hub.docker.com/r/n8nio/n8n/tags +outline_mcp_image: "git.{{ domain_base }}/jack/outline-mcp:latest" diff --git a/roles/services/tasks/configs.yml b/roles/services/tasks/configs.yml index fc80180..4b79a8a 100644 --- a/roles/services/tasks/configs.yml +++ b/roles/services/tasks/configs.yml @@ -8,6 +8,15 @@ mode: "0600" notify: Restart stack +- name: Deploy Outline .env file + ansible.builtin.template: + src: env.outline.j2 + dest: "{{ services_root }}/.env.outline" + owner: "{{ deploy_user }}" + group: "{{ deploy_group }}" + mode: "0600" + notify: Restart stack + - name: Deploy docker-compose.yml ansible.builtin.template: src: docker-compose.yml.j2 @@ -143,24 +152,6 @@ mode: "0644" notify: Restart stack -- name: Deploy Authelia configuration - ansible.builtin.template: - src: authelia/configuration.yml.j2 - dest: "{{ services_root }}/authelia/configuration.yml" - owner: "{{ deploy_user }}" - group: "{{ deploy_group }}" - mode: "0600" - notify: Restart stack - -- name: Deploy Authelia users database - ansible.builtin.template: - src: authelia/users.yml.j2 - dest: "{{ services_root }}/authelia/users.yml" - owner: "{{ deploy_user }}" - group: "{{ deploy_group }}" - mode: "0600" - notify: Restart stack - - name: Deploy Traefik logrotate config ansible.builtin.template: src: logrotate/traefik.j2 diff --git a/roles/services/templates/docker-compose.yml.j2 b/roles/services/templates/docker-compose.yml.j2 index 2a7979f..012a62b 100644 --- a/roles/services/templates/docker-compose.yml.j2 +++ b/roles/services/templates/docker-compose.yml.j2 @@ -26,6 +26,12 @@ networks: monitoring: driver: bridge internal: true + outline-internal: + driver: bridge + internal: true + n8n-internal: + driver: bridge + internal: true volumes: forgejo_data: forgejo_db_data: @@ -39,6 +45,9 @@ volumes: loki_data: crowdsec_data: uptime_kuma_data: + outline_db_data: + outline_redis_data: + n8n_data: services: @@ -595,3 +604,123 @@ services: options: max-size: "5m" max-file: "2" + + # ── Outline wiki ──────────────────────────────────────────────────────────── + outline: + image: {{ outline_image }} + container_name: outline + restart: unless-stopped + env_file: .env.outline + networks: + - outline-internal + - backend + depends_on: + outline-db: + condition: service_healthy + outline-redis: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/_health"] + interval: 30s + timeout: 5s + retries: 3 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + outline-db: + image: {{ outline_db_image }} + container_name: outline-db + restart: unless-stopped + environment: + POSTGRES_DB: outline + POSTGRES_USER: outline + POSTGRES_PASSWORD: {{ outline_db_password }} + networks: + - outline-internal + volumes: + - outline_db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U outline"] + interval: 10s + timeout: 5s + retries: 5 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + outline-redis: + image: {{ outline_redis_image }} + container_name: outline-redis + restart: unless-stopped + networks: + - outline-internal + volumes: + - outline_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + # ── n8n workflow automation ────────────────────────────────────────────────── + n8n: + image: {{ n8n_image }} + container_name: n8n + restart: unless-stopped + networks: + - n8n-internal + - backend + volumes: + - n8n_data:/home/node/.n8n + environment: + - N8N_HOST={{ domain_n8n }} + - N8N_PORT=5678 + - N8N_PROTOCOL=https + - WEBHOOK_URL=https://{{ domain_n8n }}/ + - N8N_ENCRYPTION_KEY={{ n8n_encryption_key }} + - N8N_USER_MANAGEMENT_JWT_SECRET={{ n8n_jwt_secret }} + - GENERIC_TIMEZONE=Europe/Moscow + - TZ=Europe/Moscow + - N8N_METRICS=false + - N8N_LOG_LEVEL=warn + - EXECUTIONS_DATA_PRUNE=true + - EXECUTIONS_DATA_MAX_AGE=336 + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5678/healthz"] + interval: 30s + timeout: 5s + retries: 3 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + # ── Outline MCP server ─────────────────────────────────────────────────────── + outline-mcp: + image: {{ outline_mcp_image }} + container_name: outline-mcp + restart: unless-stopped + networks: + - backend + environment: + - OUTLINE_URL=https://{{ domain_wiki }} + - OUTLINE_API_KEY={{ outline_mcp_api_key }} + - PORT=8765 + - HOST=0.0.0.0 + - LOG_LEVEL=INFO + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" diff --git a/roles/services/templates/env.outline.j2 b/roles/services/templates/env.outline.j2 new file mode 100644 index 0000000..19d0af5 --- /dev/null +++ b/roles/services/templates/env.outline.j2 @@ -0,0 +1,42 @@ +# Outline env — generated by Ansible +NODE_ENV=production +SECRET_KEY={{ outline_secret_key }} +UTILS_SECRET={{ outline_utils_secret }} + +# Database +DATABASE_URL=postgres://outline:{{ outline_db_password }}@outline-db:5432/outline +PGSSLMODE=disable + +# Redis +REDIS_URL=redis://outline-redis:6379 + +# App URL +URL=https://{{ domain_wiki }} +PORT=3000 + +# S3 file storage (Timeweb Object Storage) +AWS_ACCESS_KEY_ID={{ s3_access_key }} +AWS_SECRET_ACCESS_KEY={{ s3_secret_key }} +AWS_REGION=ru-1 +AWS_S3_UPLOAD_BUCKET_NAME=walava-outline +AWS_S3_UPLOAD_BUCKET_URL=https://s3.timeweb.cloud +AWS_S3_FORCE_PATH_STYLE=true +AWS_S3_ACL=private +FILE_STORAGE=s3 + +# Auth +AUTH_PROVIDERS=email + +# SMTP via Resend (direct — main server has outbound SMTP) +SMTP_HOST=smtp.resend.com +SMTP_PORT=587 +SMTP_USERNAME=resend +SMTP_PASSWORD={{ resend_api_key }} +SMTP_FROM_EMAIL=noreply@{{ domain_base }} +SMTP_FROM_NAME=Visual Wiki +SMTP_SECURE=false + +# Optional +DEFAULT_LANGUAGE=en_US +RATE_LIMITER_ENABLED=true +ENABLE_UPDATES=false diff --git a/roles/services/templates/traefik/dynamic/routes.yml.j2 b/roles/services/templates/traefik/dynamic/routes.yml.j2 index 72dc9d0..da8b793 100644 --- a/roles/services/templates/traefik/dynamic/routes.yml.j2 +++ b/roles/services/templates/traefik/dynamic/routes.yml.j2 @@ -89,7 +89,6 @@ http: service: walava-landing middlewares: [rate-limit-default] - # ── Cross-server: tools ({{ ip_tools }}) ───────────────────────────────── wiki: rule: "Host(`{{ domain_wiki }}`)" entrypoints: [websecure] @@ -147,16 +146,15 @@ http: servers: - url: "http://walava-web:80" - # ── Cross-server services ───────────────────────────────────────────────── wiki: loadBalancer: servers: - - url: "http://{{ ip_tools }}:3000" + - url: "http://outline:3000" n8n: loadBalancer: servers: - - url: "http://{{ ip_tools }}:5678" + - url: "http://n8n:5678" middlewares: # ── Security Headers (applied globally via entrypoint) ─────────────────