diff --git a/arty.yml b/arty.yml index 14ba4d8..997c8b9 100644 --- a/arty.yml +++ b/arty.yml @@ -19,12 +19,6 @@ envs: CORE_REDIS_PORT: 6379 CORE_REDIS_IMAGE: redis:7-alpine CORE_POSTGRES_IMAGE: postgres:16-alpine - # Track - TRACK_TRAEFIK_ENABLED: true - TRACK_COMPOSE_PROJECT_NAME: track - TRACK_DOCKER_IMAGE: ghcr.io/umami-software/umami:postgresql-latest - TRACK_TRAEFIK_HOST: umami.pivoine.art - TRACK_DB_NAME: umami # Sexy SEXY_TRAEFIK_ENABLED: true SEXY_COMPOSE_PROJECT_NAME: sexy @@ -123,20 +117,23 @@ envs: DEV_ASCIINEMA_TRAEFIK_HOST: asciinema.dev.pivoine.art DEV_ASCIINEMA_DB_NAME: asciinema DEV_ASCIINEMA_SIGN_UP_DISABLED: true - # PairDrop - DROP_TRAEFIK_ENABLED: true - DROP_COMPOSE_PROJECT_NAME: drop - DROP_TRAEFIK_HOST: drop.pivoine.art + # Net (Traefik Proxy, Netdata, Watchtower, Umami) + NET_TRAEFIK_ENABLED: true + NET_COMPOSE_PROJECT_NAME: net + # Traefik Proxy + NET_PROXY_DOCKER_IMAGE: traefik:latest + NET_PROXY_TRAEFIK_HOST: proxy.pivoine.art + NET_PROXY_PORT_HTTP: 80 + NET_PROXY_PORT_HTTPS: 443 + NET_PROXY_LOG_LEVEL: INFO # Netdata - NETDATA_TRAEFIK_ENABLED: true - NETDATA_COMPOSE_PROJECT_NAME: netdata - NETDATA_IMAGE: netdata/netdata:latest - NETDATA_TRAEFIK_HOST: netdata.pivoine.art - NETDATA_HOSTNAME: netdata.pivoine.art - # Proxy - PROXY_COMPOSE_PROJECT_NAME: proxy - PROXY_DOCKER_IMAGE: traefik:latest - PROXY_TRAEFIK_HOST: proxy.pivoine.art + NET_NETDATA_IMAGE: netdata/netdata:latest + NET_NETDATA_TRAEFIK_HOST: netdata.pivoine.art + NET_NETDATA_HOSTNAME: netdata.pivoine.art + # Umami Analytics + NET_TRACK_DOCKER_IMAGE: ghcr.io/umami-software/umami:postgresql-latest + NET_TRACK_TRAEFIK_HOST: umami.pivoine.art + NET_TRACK_DB_NAME: umami # AI Stack AI_TRAEFIK_ENABLED: true AI_COMPOSE_PROJECT_NAME: ai diff --git a/compose.yaml b/compose.yaml index 099f88b..6346718 100644 --- a/compose.yaml +++ b/compose.yaml @@ -5,10 +5,7 @@ include: - util/compose.yaml - ai/compose.yaml - restic/compose.yaml - - netdata/compose.yaml - - umami/compose.yaml - - proxy/compose.yaml - - watch/compose.yaml + - net/compose.yaml - media/compose.yaml - dev/compose.yaml diff --git a/net/Dockerfile b/net/Dockerfile new file mode 100644 index 0000000..6daf85a --- /dev/null +++ b/net/Dockerfile @@ -0,0 +1,11 @@ +# Dockerfile for Netdata with msmtp support for email alerts +FROM netdata/netdata:latest + +# Install msmtp for sending emails +RUN apt-get update && \ + apt-get install -y msmtp msmtp-mta ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Set proper permissions for msmtp config +RUN chmod 600 /etc/msmtprc || true diff --git a/net/compose.yaml b/net/compose.yaml new file mode 100644 index 0000000..07f5db6 --- /dev/null +++ b/net/compose.yaml @@ -0,0 +1,238 @@ +services: + # Traefik - Reverse proxy and load balancer + traefik: + image: ${NET_PROXY_DOCKER_IMAGE} + container_name: ${NET_COMPOSE_PROJECT_NAME}_traefik + restart: unless-stopped + command: + # API & Dashboard + - '--api.dashboard=true' + - '--api.insecure=false' + + # Ping endpoint for healthcheck + - '--ping=true' + + # Experimental plugins + - '--experimental.plugins.sablier.modulename=github.com/acouvreur/sablier' + - '--experimental.plugins.sablier.version=v1.8.0' + + # Logging + - '--log.level=${NET_PROXY_LOG_LEVEL:-INFO}' + - '--accesslog=true' + + # Global + - '--global.sendAnonymousUsage=false' + - '--global.checkNewVersion=true' + + # Docker Provider + - '--providers.docker=true' + - '--providers.docker.exposedbydefault=false' + - '--providers.docker.network=${NETWORK_NAME}' + + # File Provider for dynamic configuration + - '--providers.file.directory=/etc/traefik/dynamic' + - '--providers.file.watch=true' + + # Entrypoints + - '--entrypoints.web.address=:${NET_PROXY_PORT_HTTP:-80}' + - '--entrypoints.web-secure.address=:${NET_PROXY_PORT_HTTPS:-443}' + + # Global HTTP to HTTPS redirect + - '--entrypoints.web.http.redirections.entryPoint.to=web-secure' + - '--entrypoints.web.http.redirections.entryPoint.scheme=https' + - '--entrypoints.web.http.redirections.entryPoint.permanent=true' + + # Security Headers (applied globally) + - '--entrypoints.web-secure.http.middlewares=security-headers@file' + + # Let's Encrypt + - '--certificatesresolvers.resolver.acme.tlschallenge=true' + - '--certificatesresolvers.resolver.acme.email=${ADMIN_EMAIL}' + - '--certificatesresolvers.resolver.acme.storage=/letsencrypt/acme.json' + + healthcheck: + test: ["CMD", "traefik", "healthcheck", "--ping"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + environment: + AUTH_USERS: ${AUTH_USERS} + + networks: + - compose_network + + ports: + - "${NET_PROXY_PORT_HTTP:-80}:80" + - "${NET_PROXY_PORT_HTTPS:-443}:443" + + volumes: + - letsencrypt_data:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./dynamic:/etc/traefik/dynamic:ro + + labels: + - 'traefik.enable=true' + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.entrypoints=web' + # HTTPS router with auth + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.entrypoints=web-secure' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.service=api@internal' + - 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-traefik-auth.basicauth.users=${AUTH_USERS}' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-traefik-auth' + - 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.loadbalancer.server.port=8080' + - 'traefik.docker.network=${NETWORK_NAME}' + + # Netdata - Real-time monitoring + netdata: + build: + context: . + dockerfile: Dockerfile + image: ${NET_NETDATA_IMAGE:-netdata/netdata:latest} + container_name: ${NET_COMPOSE_PROJECT_NAME}_netdata + restart: unless-stopped + hostname: ${NET_NETDATA_HOSTNAME:-netdata.pivoine.art} + cap_add: + - SYS_PTRACE + - SYS_ADMIN + security_opt: + - apparmor:unconfined + volumes: + - netdata_config:/etc/netdata + - netdata_lib:/var/lib/netdata + - netdata_cache:/var/cache/netdata + - ./go.d/postgres.conf:/etc/netdata/go.d/postgres.conf:ro + - ./go.d/filecheck.conf:/etc/netdata/go.d/filecheck.conf:ro + - ./health_alarm_notify.conf:/etc/netdata/health_alarm_notify.conf:ro + - ./msmtprc:/etc/netdata/msmtprc:ro + - /mnt/hidrive/users/valknar/Backup:/mnt/hidrive/users/valknar/Backup:ro + - /etc/passwd:/host/etc/passwd:ro + - /etc/group:/host/etc/group:ro + - /etc/localtime:/etc/localtime:ro + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /etc/os-release:/host/etc/os-release:ro + - /var/log:/host/var/log:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - NETDATA_CLAIM_TOKEN=${NETDATA_CLAIM_TOKEN:-} + - NETDATA_CLAIM_URL=${NETDATA_CLAIM_URL:-} + - NETDATA_CLAIM_ROOMS=${NETDATA_CLAIM_ROOMS:-} + - MATTERMOST_WEBHOOK_URL=${MATTERMOST_WEBHOOK_URL:-} + networks: + - compose_network + labels: + - 'traefik.enable=${NET_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.entrypoints=web' + # HTTPS router + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-compress.compress=true' + - 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-auth.basicauth.users=${AUTH_USERS}' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-auth,${NET_COMPOSE_PROJECT_NAME}-netdata-compress,security-headers@file' + # Service + - 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-netdata.loadbalancer.server.port=19999' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + # Watchtower - Automatic container updates + watchtower: + image: containrrr/watchtower:latest + container_name: ${NET_COMPOSE_PROJECT_NAME}_watchtower + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + # Check for updates every 5 minutes (300 seconds) + WATCHTOWER_POLL_INTERVAL: ${WATCHTOWER_POLL_INTERVAL:-300} + # Only update containers with the watchtower label + WATCHTOWER_LABEL_ENABLE: ${WATCHTOWER_LABEL_ENABLE:-true} + # Clean up old images after update + WATCHTOWER_CLEANUP: ${WATCHTOWER_CLEANUP:-true} + # Include stopped containers + WATCHTOWER_INCLUDE_STOPPED: ${WATCHTOWER_INCLUDE_STOPPED:-false} + # Include restarting containers + WATCHTOWER_INCLUDE_RESTARTING: ${WATCHTOWER_INCLUDE_RESTARTING:-true} + # Run once and exit (set to false for continuous monitoring) + WATCHTOWER_RUN_ONCE: ${WATCHTOWER_RUN_ONCE:-false} + # Notifications via Shoutrrr + WATCHTOWER_NOTIFICATIONS: ${WATCHTOWER_NOTIFICATIONS:-} + WATCHTOWER_NOTIFICATION_URL: ${WATCHTOWER_NOTIFICATION_URL:-} + # Log level (trace, debug, info, warn, error, fatal, panic) + WATCHTOWER_LOG_LEVEL: ${WATCHTOWER_LOG_LEVEL:-info} + # Rolling restart (update one container at a time) + WATCHTOWER_ROLLING_RESTART: ${WATCHTOWER_ROLLING_RESTART:-false} + labels: + # Allow watchtower to update itself + - com.centurylinklabs.watchtower.enable=true + + # Umami - Web analytics + umami: + image: ${NET_TRACK_DOCKER_IMAGE} + container_name: ${NET_COMPOSE_PROJECT_NAME}_umami + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + + # Database Configuration + DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@${CORE_DB_HOST}:${CORE_DB_PORT}/${NET_TRACK_DB_NAME} + DATABASE_TYPE: postgresql + + # Application Secret + APP_SECRET: ${TRACK_APP_SECRET} + + # Redis Cache Integration + REDIS_URL: redis://${CORE_REDIS_HOST}:${CORE_REDIS_PORT} + CACHE_ENABLED: true + + networks: + - compose_network + + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 40s + + labels: + # Traefik Configuration + - 'traefik.enable=${NET_TRAEFIK_ENABLED}' + + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.entrypoints=web' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.entrypoints=web-secure' + - 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.loadbalancer.server.port=3000' + - 'traefik.docker.network=${NETWORK_NAME}' + +volumes: + letsencrypt_data: + name: ${NET_COMPOSE_PROJECT_NAME}_letsencrypt_data + netdata_config: + name: ${NET_COMPOSE_PROJECT_NAME}_netdata_config + netdata_lib: + name: ${NET_COMPOSE_PROJECT_NAME}_netdata_lib + netdata_cache: + name: ${NET_COMPOSE_PROJECT_NAME}_netdata_cache + +networks: + compose_network: + name: ${NETWORK_NAME} + external: true diff --git a/net/dynamic/security.yaml b/net/dynamic/security.yaml new file mode 100644 index 0000000..b6f925b --- /dev/null +++ b/net/dynamic/security.yaml @@ -0,0 +1,61 @@ +tls: + options: + default: + minVersion: VersionTLS12 + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + curvePreferences: + - CurveP521 + - CurveP384 + sniStrict: true + +http: + middlewares: + # Security Headers Middleware + security-headers: + headers: + # HSTS (HTTP Strict Transport Security) + stsSeconds: 31536000 + stsIncludeSubdomains: true + stsPreload: true + + # Force HTTPS + forceSTSHeader: true + + # Clickjacking protection + customFrameOptionsValue: "SAMEORIGIN" + + # XSS Protection + browserXssFilter: true + + # Content Type sniffing protection + contentTypeNosniff: true + + # Referrer Policy + referrerPolicy: "strict-origin-when-cross-origin" + + # Permissions Policy (formerly Feature Policy) + customResponseHeaders: + X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex" + Permissions-Policy: "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()" + X-Content-Type-Options: "nosniff" + X-Frame-Options: "SAMEORIGIN" + + # Rate Limiting Middleware (optional, can be applied per service) + rate-limit: + rateLimit: + average: 100 + burst: 50 + period: 1s + + # Rate Limiting for API endpoints (stricter) + api-rate-limit: + rateLimit: + average: 30 + burst: 15 + period: 1s diff --git a/net/go.d/filecheck.conf b/net/go.d/filecheck.conf new file mode 100644 index 0000000..ee87551 --- /dev/null +++ b/net/go.d/filecheck.conf @@ -0,0 +1,5 @@ +jobs: + - name: restic_repository + dirs: + include: + - '/mnt/hidrive/users/valknar/Backup' diff --git a/net/go.d/postgres.conf b/net/go.d/postgres.conf new file mode 100644 index 0000000..03e0dd5 --- /dev/null +++ b/net/go.d/postgres.conf @@ -0,0 +1,3 @@ +jobs: + - name: docker_core_postgres + dsn: 'postgres://netdata:netdata_monitor_password@172.18.0.5:5432/postgres' diff --git a/net/health_alarm_notify.conf b/net/health_alarm_notify.conf new file mode 100644 index 0000000..bf95fda --- /dev/null +++ b/net/health_alarm_notify.conf @@ -0,0 +1,47 @@ +# Netdata health alarm notification configuration +# This file configures where to send alarm notifications + +# Enable/disable sending email notifications +SEND_EMAIL="YES" + +# Recipient email address for all alarms +DEFAULT_RECIPIENT_EMAIL="${ADMIN_EMAIL}" + +# Email sender address +EMAIL_SENDER="${EMAIL_FROM}" + +# SMTP configuration +SENDMAIL="" +EMAIL_SENDER="${EMAIL_FROM}" + +# Custom send email command using msmtp +EMAIL_COMMAND="msmtp -t" + +# Enable specific notification types +role_recipients_email[sysadmin]="${ADMIN_EMAIL}" +role_recipients_email[domainadmin]="${ADMIN_EMAIL}" +role_recipients_email[dba]="${ADMIN_EMAIL}" +role_recipients_email[webmaster]="${ADMIN_EMAIL}" +role_recipients_email[proxyadmin]="${ADMIN_EMAIL}" +role_recipients_email[sitemgr]="${ADMIN_EMAIL}" + +############################################################################### +# Mattermost notifications via Slack-compatible webhook +############################################################################### + +# Enable Slack notifications (Mattermost supports Slack-compatible webhooks) +SEND_SLACK="YES" + +# Mattermost incoming webhook URL (Slack-compatible) +SLACK_WEBHOOK_URL="${MATTERMOST_WEBHOOK_URL}" + +# Slack channel (optional, webhook default channel will be used if empty) +DEFAULT_RECIPIENT_SLACK="" + +# Slack notification recipients per role +role_recipients_slack[sysadmin]="notifications" +role_recipients_slack[domainadmin]="notifications" +role_recipients_slack[dba]="notifications" +role_recipients_slack[webmaster]="notifications" +role_recipients_slack[proxyadmin]="notifications" +role_recipients_slack[sitemgr]="notifications" diff --git a/net/msmtprc b/net/msmtprc new file mode 100644 index 0000000..be22c16 --- /dev/null +++ b/net/msmtprc @@ -0,0 +1,20 @@ +# MSMTP configuration for Netdata email alerts + +# Set default values for all accounts +defaults +auth on +tls on +tls_trust_file /etc/ssl/certs/ca-certificates.crt +logfile /var/log/msmtp.log + +# IONOS SMTP account +account ionos +host smtp.ionos.de +port 465 +tls_starttls off +from hi@pivoine.art +user hi@pivoine.art +password jaquoment + +# Set default account +account default : ionos diff --git a/restic/compose.yaml b/restic/compose.yaml index 357ffba..50fa19e 100644 --- a/restic/compose.yaml +++ b/restic/compose.yaml @@ -119,7 +119,7 @@ volumes: name: util_linkwarden_meili_data external: true backup_letsencrypt_data: - name: proxy_letsencrypt_data + name: net_letsencrypt_data external: true backup_util_vaultwarden_data: name: util_vaultwarden_data @@ -132,7 +132,7 @@ volumes: name: jelly_config external: true backup_netdata_config: - name: netdata_config + name: net_netdata_config external: true backup_ai_postgres_data: name: ai_postgres_data