diff --git a/QNAP/arr-stack/.env b/QNAP/arr-stack/.env new file mode 100644 index 0000000..e69de29 diff --git a/QNAP/arr-stack/docker-compose.yml b/QNAP/arr-stack/docker-compose.yml new file mode 100644 index 0000000..20a662c --- /dev/null +++ b/QNAP/arr-stack/docker-compose.yml @@ -0,0 +1,230 @@ +version: '3.8' +services: + + sabnzbd: + image: lscr.io/linuxserver/sabnzbd:latest + container_name: sabnzbd + restart: unless-stopped + ports: + - 8090:8080/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/MovieDL:/downloads-movies + - /share/Media/TVDL:/downloads-tv + - /share/Media/TempDL:/incomplete-downloads + - /share/Media/container-station-data/arr/sabnzbd:/config + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.sabnzbd.rule: Host(`sabnzbd.kaspers.us`) + traefik.http.services.sabnzbd.loadbalancer.server.port: '8080' + traefik.http.routers.sabnzbd.entrypoints: 'websecure' + traefik.http.routers.sabnzbd.tls.certresolver: 'letsencrypt' + traefik.http.routers.sabnzbd.tls: 'true' + tautulli: + image: lscr.io/linuxserver/tautulli:latest + container_name: tautulli + restart: unless-stopped + ports: + - 8181:8181/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/container-station-data/arr/tautulli:/config + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.tautulli.rule: Host(`tautulli.kaspers.us`) + traefik.http.services.tautulli.loadbalancer.server.port: '8181' + traefik.http.routers.tautulli.tls: 'true' + traefik.http.routers.tautulli.tls.certresolver: 'letsencrypt' + traefik.http.routers.tautulli.entrypoints: 'websecure' + radarr: + image: linuxserver/radarr:latest + container_name: radarr + restart: unless-stopped + ports: + - 7878:7878/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/MovieDL:/downloads-movies + - /share/Media/Movies:/bignasty-movies + - /share/Media/container-station-data/arr/radarr:/config + - /share/Media1/Media/Movies:/media1-movies + - /share/Media2/volume1/Media/Movies:/media2-movies + - /share/Media3-Movies:/media3-movies + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.radarr.rule: Host(`radarr.kaspers.us`) + traefik.http.services.radarr.loadbalancer.server.port: '7878' + traefik.http.routers.radarr.tls: 'true' + traefik.http.routers.radarr.tls.certresolver: 'letsencrypt' + traefik.http.routers.radarr.entrypoints: 'websecure' + homepage: + image: ghcr.io/gethomepage/homepage:latest + container_name: homepage + restart: unless-stopped + ports: + - 3001:3000/tcp + environment: + - HOMEPAGE_ALLOWED_HOSTS=dashboard.kaspers.us + - HOSTNAME=0.0.0.0 + - PORT=3000 + volumes: + - /share/Media/container-station-data/arr/dashboard/config:/app/config + - /var/run/docker.sock:/var/run/docker.sock + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.dashboard.rule: Host(`dashboard.kaspers.us`) + traefik.http.services.dashboard.loadbalancer.server.port: '3000' + traefik.http.routers.dashboard.tls: 'true' + traefik.http.routers.dashboard.tls.certresolver: 'letsencrypt' + traefik.http.routers.dashboard.entrypoints: 'websecure' + bazarr: + image: lscr.io/linuxserver/bazarr:latest + container_name: bazarr + restart: unless-stopped + ports: + - 6767:6767/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/Movies:/bignasty-movies + - /share/Media/TV:/bignasty-tv + - /share/Media/container-station-data/arr/bazarr:/config + - /share/Media1/Media/Movies:/media1-movies + - /share/Media1/Media/TV:/media1-tv + - /share/Media2/volume1/Media/Movies:/media2-movies + - /share/Media2/volume1/Media/TV:/media2-tv + - /share/Media3-Movies:/media3-movies + - /share/Media3-TV:/media3-tv + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.bazarr.rule: Host(`bazarr.kaspers.us`) + traefik.http.services.bazarr.loadbalancer.server.port: '6767' + traefik.http.routers.bazarr.tls: 'true' + traefik.http.routers.bazarr.tls.certresolver: 'letsencrypt' + traefik.http.routers.bazarr.entrypoints: 'websecure' + qbittorrent: + image: linuxserver/qbittorrent:latest + container_name: qbittorrent + restart: unless-stopped + ports: + - 8081:8081/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + - WEBUI_PORT=8081 + volumes: + - /share/Media/TorrentDL:/config + - /share/Media/container-station-data/arr/qbittorrent:/media/downloads + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.qbittorrent.rule: Host(`torrent.kaspers.us`) + traefik.http.services.qbittorrent.loadbalancer.server.port: '8081' + traefik.http.routers.qbittorrent.tls: 'true' + traefik.http.routers.qbittorrent.tls.certresolver: 'letsencrypt' + traefik.http.routers.qbittorrent.entrypoints: 'websecure' + overseerr: + image: lscr.io/linuxserver/overseerr:latest + container_name: overseerr + restart: unless-stopped + ports: + - 5055:5055/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/container-station-data/arr/overseerr:/config + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.overseerr.rule: Host(`requests.kaspers.us`) + traefik.http.services.overseerr.loadbalancer.server.port: '5055' + traefik.http.routers.overseerr.entrypoints: 'websecure' + traefik.http.routers.overseerr.tls.certresolver: 'letsencrypt' + traefik.http.routers.overseerr.tls: 'true' + traefik.constraint: 'proxy-public' + sonarr: + image: linuxserver/sonarr:latest + container_name: sonarr + restart: unless-stopped + ports: + - 8989:8989/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/TV:/bignasty-tv + - /share/Media/TVDL:/downloads-tv + - /share/Media/container-station-data/arr/sonarr:/config + - /share/external/.nd/1000/064c2ded3-99f3-4dfd-a65d-32095f668e9b/volume1/Media/TV:/media2-tv + - /share/external/.nd/1000/0d23da6e0-8ad7-4fe8-b9d3-657e4d1e8848:/media3-tv + - /share/external/.nd/1000/0dde4f8ca-ff7d-4011-8f3b-ceb12fcd6dbb/Media/TV:/media1-tv + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.sonarr.rule: Host(`sonarr.kaspers.us`) + traefik.http.services.sonarr.loadbalancer.server.port: '8989' + traefik.http.routers.sonarr.tls: 'true' + traefik.http.routers.sonarr.tls.certresolver: 'letsencrypt' + traefik.http.routers.sonarr.entrypoints: 'websecure' + nzbhydra2: + image: lscr.io/linuxserver/nzbhydra2:latest + container_name: nzbhydra2 + restart: unless-stopped + ports: + - 5076:5076/tcp + environment: + - PGID=1000 + - PUID=1000 + - TZ=America/New_York + volumes: + - /share/Media/container-station-data/arr/nzbhydra:/config + networks: + - proxy + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.nzbhydra.rule: Host(`nzbhydra.kaspers.us`) + traefik.http.services.nzbhydra.loadbalancer.server.port: '5076' + traefik.http.routers.nzbhydra.tls: 'true' + traefik.http.routers.nzbhydra.tls.certresolver: 'letsencrypt' + traefik.http.routers.nzbhydra.entrypoints: 'websecure' +networks: + proxy: + external: true + diff --git a/QNAP/arr-stack/environment-variables.json b/QNAP/arr-stack/environment-variables.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/QNAP/arr-stack/environment-variables.json @@ -0,0 +1 @@ +[] diff --git a/QNAP/arr-stack/metadata.txt b/QNAP/arr-stack/metadata.txt new file mode 100644 index 0000000..0df802b --- /dev/null +++ b/QNAP/arr-stack/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: arr-stack +Stack ID: 2 +Endpoint ID: 3 +Creation Date: 1753361662 +Update Date: 1754656702 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/arr-stack/stack-info.json b/QNAP/arr-stack/stack-info.json new file mode 100644 index 0000000..051402b --- /dev/null +++ b/QNAP/arr-stack/stack-info.json @@ -0,0 +1,32 @@ +{ + "Id": 2, + "Name": "arr-stack", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [], + "ResourceControl": { + "Id": 2, + "ResourceId": "3_arr-stack", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/2", + "CreationDate": 1753361662, + "CreatedBy": "admin", + "UpdateDate": 1754656702, + "UpdatedBy": "admin", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/QNAP/docuseal/.env b/QNAP/docuseal/.env new file mode 100644 index 0000000..e69de29 diff --git a/QNAP/docuseal.yaml b/QNAP/docuseal/docker-compose.yml similarity index 99% rename from QNAP/docuseal.yaml rename to QNAP/docuseal/docker-compose.yml index f8ab6dc..3d14582 100644 --- a/QNAP/docuseal.yaml +++ b/QNAP/docuseal/docker-compose.yml @@ -56,3 +56,4 @@ networks: driver: bridge proxy: external: true + diff --git a/QNAP/docuseal/environment-variables.json b/QNAP/docuseal/environment-variables.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/QNAP/docuseal/environment-variables.json @@ -0,0 +1 @@ +[] diff --git a/QNAP/docuseal/metadata.txt b/QNAP/docuseal/metadata.txt new file mode 100644 index 0000000..35171f6 --- /dev/null +++ b/QNAP/docuseal/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: docuseal +Stack ID: 4 +Endpoint ID: 3 +Creation Date: 1753362392 +Update Date: 1754657150 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/docuseal/stack-info.json b/QNAP/docuseal/stack-info.json new file mode 100644 index 0000000..27efeb9 --- /dev/null +++ b/QNAP/docuseal/stack-info.json @@ -0,0 +1,32 @@ +{ + "Id": 4, + "Name": "docuseal", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [], + "ResourceControl": { + "Id": 4, + "ResourceId": "3_docuseal", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/4", + "CreationDate": 1753362392, + "CreatedBy": "admin", + "UpdateDate": 1754657150, + "UpdatedBy": "admin", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/QNAP/gitea/.env b/QNAP/gitea/.env new file mode 100644 index 0000000..92afcc2 --- /dev/null +++ b/QNAP/gitea/.env @@ -0,0 +1 @@ +GITEA_DB_PASSWORD=adt.cfv*ZQE6azm0qvk diff --git a/QNAP/gitea/docker-compose.yml b/QNAP/gitea/docker-compose.yml new file mode 100644 index 0000000..8d8b3ff --- /dev/null +++ b/QNAP/gitea/docker-compose.yml @@ -0,0 +1,51 @@ +services: + + server: + image: gitea/gitea:1.23.7 + container_name: gitea-server + restart: always + environment: + - TZ=America/New_York + - GITEA__database__DB_TYPE=postgres + - GITEA__database__HOST=db:5432 + - GITEA__database__NAME=gitea + - GITEA__database__USER=gitea + - GITEA__database__PASSWD=${GITEA_DB_PASSWORD} + volumes: + - /share/Media/gitea/data:/data + labels: + - "traefik.enable=true" + - "traefik.http.routers.gitea.rule=Host(`gitea.kaspers.us`)" # ⚠️ UPDATE to your domain + - "traefik.http.routers.gitea.entrypoints=websecure" # Assumes your HTTP entrypoint is 'web' + - "traefik.http.services.gitea.loadbalancer.server.port=3000" + - "traefik.http.routers.gitea.tls=true" + - "traefik.http.routers.gitea.tls.certresolver=letsencrypt" + - "traefik.docker.network=proxy" + - "traefik.constraint=proxy-public" + networks: + - gitea-net + - proxy + ports: + - 3105:3000 + - 222:22 + depends_on: + - db + + db: + image: postgres:17.4 + container_name: gitea-db + restart: always + environment: + - TZ=America/New_York + - POSTGRES_DB=gitea + - POSTGRES_USER=gitea + - POSTGRES_PASSWORD=${GITEA_DB_PASSWORD} + volumes: + - /share/Media/gitea/db:/var/lib/postgresql/data + networks: + - gitea-net +networks: + gitea-net: + driver: bridge + proxy: + external: true diff --git a/QNAP/gitea/environment-variables.json b/QNAP/gitea/environment-variables.json new file mode 100644 index 0000000..9410d67 --- /dev/null +++ b/QNAP/gitea/environment-variables.json @@ -0,0 +1,6 @@ +[ + { + "name": "GITEA_DB_PASSWORD", + "value": "adt.cfv*ZQE6azm0qvk" + } +] diff --git a/QNAP/gitea/metadata.txt b/QNAP/gitea/metadata.txt new file mode 100644 index 0000000..a328436 --- /dev/null +++ b/QNAP/gitea/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: gitea +Stack ID: 17 +Endpoint ID: 3 +Creation Date: 1755699243 +Update Date: 0 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/gitea/stack-info.json b/QNAP/gitea/stack-info.json new file mode 100644 index 0000000..c20b625 --- /dev/null +++ b/QNAP/gitea/stack-info.json @@ -0,0 +1,37 @@ +{ + "Id": 17, + "Name": "gitea", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [ + { + "name": "GITEA_DB_PASSWORD", + "value": "adt.cfv*ZQE6azm0qvk" + } + ], + "ResourceControl": { + "Id": 11, + "ResourceId": "3_gitea", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/17", + "CreationDate": 1755699243, + "CreatedBy": "admin", + "UpdateDate": 0, + "UpdatedBy": "", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/QNAP/immich/.env b/QNAP/immich/.env new file mode 100644 index 0000000..d7abf8e --- /dev/null +++ b/QNAP/immich/.env @@ -0,0 +1,11 @@ +UPLOAD_LOCATION=/share/Media/Immich +DB_DATA_LOCATION=/share/Media/container-station-data/immich/db +IMMICH_VERSION=release +DB_PASSWORD=postgres +DB_USERNAME=postgres +DB_DATABASE_NAME=immich +TZ=America/New_York +OIDC_ENABLED=true +OIDC_ISSUER_URL=https://auth.kaspers.us/application/o/immich/ +OIDC_CLIENT_ID=hbzo7e5qAqIi4eZorZS9bOSjjKz9aBuVVZVHjIlZ +OIDC_CLIENT_SECRET=3NuNbX6GHXNC2DmhgOewMJUjoP5xboKPD3BkpTV02W7r2AEHfpNzCUq8DHYi5soJsVoahnbOxFpY24MF3mAaqfGlfbh4doZRWCAG1dUEUCyPE9h1082wcr2EFUEojFFs diff --git a/QNAP/immich/docker-compose.yml b/QNAP/immich/docker-compose.yml new file mode 100644 index 0000000..e622d1c --- /dev/null +++ b/QNAP/immich/docker-compose.yml @@ -0,0 +1,99 @@ +# +# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose +# +# Make sure to use the docker-compose.yml of the current release: +# +# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml +# +# The compose file on main may not be compatible with the latest release. + +name: immich +services: + immich-server: + container_name: immich_server + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + # extends: + # file: hwaccel.transcoding.yml + # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding + volumes: + # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file + - ${UPLOAD_LOCATION}:/data + - /share/Media/container-station-data/owncloud/data/files/kasperj@gmail.com/files/:/owncloud-immich:ro + - /etc/localtime:/etc/localtime:ro + ports: + - '2283:2283' + environment: + OIDC_ENABLED: true + OIDC_ISSUER_URL: https://auth.kaspers.us/application/o/immich/ + OIDC_CLIENT_ID: hbzo7e5qAqIi4eZorZS9bOSjjKz9aBuVVZVHjIlZ + OIDC_CLIENT_SECRET: 3NuNbX6GHXNC2DmhgOewMJUjoP5xboKPD3BkpTV02W7r2AEHfpNzCUq8DHYi5soJsVoahnbOxFpY24MF3mAaqfGlfbh4doZRWCAG1dUEUCyPE9h1082wcr2EFUEojFFs + #OIDC_BUTTON_TEXT: 'Login with Authentik' + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.immich.rule: Host(`photos.kaspers.us`) + traefik.http.services.immich.loadbalancer.server.port: '2283' + traefik.http.routers.immich.tls: 'true' + traefik.constraint: proxy-public + traefik.http.routers.immich.entrypoints: 'websecure' + traefik.http.routers.immich.tls.certresolver: 'letsencrypt' + depends_on: + - redis + - database + restart: always + networks: + proxy: {} + immich-internal: {} + healthcheck: + disable: false + + immich-machine-learning: + container_name: immich_machine_learning + # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. + # Example tag: ${IMMICH_VERSION:-release}-cuda + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration + # file: hwaccel.ml.yml + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable + volumes: + - model-cache:/cache + restart: always + healthcheck: + disable: false + networks: + immich-internal: {} + + redis: + container_name: immich_redis + image: docker.io/valkey/valkey:8-bookworm@sha256:facc1d2c3462975c34e10fccb167bfa92b0e0dbd992fc282c29a61c3243afb11 + healthcheck: + test: redis-cli ping || exit 1 + restart: always + networks: + immich-internal: {} + + database: + container_name: immich_postgres + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: ${DB_DATABASE_NAME} + POSTGRES_INITDB_ARGS: '--data-checksums' + # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs + # DB_STORAGE_TYPE: 'HDD' + volumes: + # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file + - ${DB_DATA_LOCATION}:/var/lib/postgresql/data + shm_size: 128mb + restart: always + networks: + immich-internal: {} + +volumes: + model-cache: +networks: + proxy: + external: true + immich-internal: + driver: bridge diff --git a/QNAP/immich/environment-variables.json b/QNAP/immich/environment-variables.json new file mode 100644 index 0000000..7ddf09e --- /dev/null +++ b/QNAP/immich/environment-variables.json @@ -0,0 +1,46 @@ +[ + { + "name": "UPLOAD_LOCATION", + "value": "/share/Media/Immich" + }, + { + "name": "DB_DATA_LOCATION", + "value": "/share/Media/container-station-data/immich/db" + }, + { + "name": "IMMICH_VERSION", + "value": "release" + }, + { + "name": "DB_PASSWORD", + "value": "postgres" + }, + { + "name": "DB_USERNAME", + "value": "postgres" + }, + { + "name": "DB_DATABASE_NAME", + "value": "immich" + }, + { + "name": "TZ", + "value": "America/New_York" + }, + { + "name": "OIDC_ENABLED", + "value": "true" + }, + { + "name": "OIDC_ISSUER_URL", + "value": "https://auth.kaspers.us/application/o/immich/" + }, + { + "name": "OIDC_CLIENT_ID", + "value": "hbzo7e5qAqIi4eZorZS9bOSjjKz9aBuVVZVHjIlZ" + }, + { + "name": "OIDC_CLIENT_SECRET", + "value": "3NuNbX6GHXNC2DmhgOewMJUjoP5xboKPD3BkpTV02W7r2AEHfpNzCUq8DHYi5soJsVoahnbOxFpY24MF3mAaqfGlfbh4doZRWCAG1dUEUCyPE9h1082wcr2EFUEojFFs" + } +] diff --git a/QNAP/immich/metadata.txt b/QNAP/immich/metadata.txt new file mode 100644 index 0000000..72511e7 --- /dev/null +++ b/QNAP/immich/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: immich +Stack ID: 8 +Endpoint ID: 3 +Creation Date: 1754055150 +Update Date: 1755274982 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/immich/stack-info.json b/QNAP/immich/stack-info.json new file mode 100644 index 0000000..d36f1ce --- /dev/null +++ b/QNAP/immich/stack-info.json @@ -0,0 +1,77 @@ +{ + "Id": 8, + "Name": "immich", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [ + { + "name": "UPLOAD_LOCATION", + "value": "/share/Media/Immich" + }, + { + "name": "DB_DATA_LOCATION", + "value": "/share/Media/container-station-data/immich/db" + }, + { + "name": "IMMICH_VERSION", + "value": "release" + }, + { + "name": "DB_PASSWORD", + "value": "postgres" + }, + { + "name": "DB_USERNAME", + "value": "postgres" + }, + { + "name": "DB_DATABASE_NAME", + "value": "immich" + }, + { + "name": "TZ", + "value": "America/New_York" + }, + { + "name": "OIDC_ENABLED", + "value": "true" + }, + { + "name": "OIDC_ISSUER_URL", + "value": "https://auth.kaspers.us/application/o/immich/" + }, + { + "name": "OIDC_CLIENT_ID", + "value": "hbzo7e5qAqIi4eZorZS9bOSjjKz9aBuVVZVHjIlZ" + }, + { + "name": "OIDC_CLIENT_SECRET", + "value": "3NuNbX6GHXNC2DmhgOewMJUjoP5xboKPD3BkpTV02W7r2AEHfpNzCUq8DHYi5soJsVoahnbOxFpY24MF3mAaqfGlfbh4doZRWCAG1dUEUCyPE9h1082wcr2EFUEojFFs" + } + ], + "ResourceControl": { + "Id": 7, + "ResourceId": "3_immich", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/8", + "CreationDate": 1754055150, + "CreatedBy": "admin", + "UpdateDate": 1755274982, + "UpdatedBy": "admin", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/QNAP/owncloud/.env b/QNAP/owncloud/.env new file mode 100644 index 0000000..e69de29 diff --git a/QNAP/owncloud/docker-compose.yml b/QNAP/owncloud/docker-compose.yml new file mode 100644 index 0000000..f5b233f --- /dev/null +++ b/QNAP/owncloud/docker-compose.yml @@ -0,0 +1,59 @@ +version: '3.8' + +services: + owncloud: + image: owncloud/server:latest + container_name: owncloud + restart: unless-stopped + networks: + - owncloud-net + - proxy # ⚠️ UPDATE this to your Traefik network name if different + depends_on: + - mariadb + volumes: + - /share/Media/container-station-data/owncloud/data:/mnt/data + environment: + - OWNCLOUD_DOMAIN=owncloud.kaspers.us # ⚠️ UPDATE to your domain + - OWNCLOUD_DB_TYPE=mysql + - OWNCLOUD_DB_HOST=mariadb + - OWNCLOUD_DB_NAME=ownclouddb + - OWNCLOUD_DB_USERNAME=oc_db_user + - OWNCLOUD_DB_PASSWORD=ybr7tey0pxn1CWA.wfd # ⚠️ SET a strong password + - OWNCLOUD_ADMIN_USERNAME=admin # ⚠️ UPDATE admin user (optional) + - OWNCLOUD_ADMIN_PASSWORD=johnwayne21 # ⚠️ SET a strong admin password + - TZ=America/New_York + - OWNCLOUD_MAX_UPLOAD=20G # sets upload_max_filesize & post_max_size + - OWNCLOUD_PHP_MEMORY_LIMIT=1G # optional; for big uploads/antivirus apps + - OWNCLOUD_MAX_INPUT_TIME=3600 + labels: + - "traefik.enable=true" + - "traefik.http.routers.owncloud.rule=Host(`owncloud.kaspers.us`)" # ⚠️ UPDATE to your domain + - "traefik.http.routers.owncloud.entrypoints=websecure" # Assumes your HTTP entrypoint is 'web' + - "traefik.http.services.owncloud.loadbalancer.server.port=8080" + - "traefik.docker.network=proxy" # ⚠️ UPDATE this to your Traefik network name if different + - "traefik.constraint=proxy-public" + - "traefik.http.routers.owncloud.tls=true" + - "traefik.http.routers.owncloud.tls.certresolver=letsencrypt" + - "traefik.http.routers.owncloud.middlewares=openid-rewrite@docker" + - "traefik.http.middlewares.openid-rewrite.replacepathregex.regex=^/.well-known/openid-configuration$$" + - "traefik.http.middlewares.openid-rewrite.replacepathregex.replacement=/index.php/apps/openidconnect/config" + mariadb: + image: mariadb:10.6 # Using a specific version is more stable + container_name: owncloud_db + restart: unless-stopped + networks: + - owncloud-net + volumes: + - /share/Media/container-station-data/owncloud/db:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=FrankAndBeans # ⚠️ SET a strong root password + - MYSQL_DATABASE=ownclouddb + - MYSQL_USER=oc_db_user + - MYSQL_PASSWORD=ybr7tey0pxn1CWA.wfd # ⚠️ MUST MATCH the password above + - TZ=America/New_York # ⚠️ UPDATE to your timezone + +networks: + owncloud-net: + driver: bridge + proxy: + external: true diff --git a/QNAP/owncloud/environment-variables.json b/QNAP/owncloud/environment-variables.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/QNAP/owncloud/environment-variables.json @@ -0,0 +1 @@ +[] diff --git a/QNAP/owncloud/metadata.txt b/QNAP/owncloud/metadata.txt new file mode 100644 index 0000000..eccc943 --- /dev/null +++ b/QNAP/owncloud/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: owncloud +Stack ID: 1 +Endpoint ID: 3 +Creation Date: 1753294011 +Update Date: 1755528476 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/owncloud/stack-info.json b/QNAP/owncloud/stack-info.json new file mode 100644 index 0000000..0ec22b7 --- /dev/null +++ b/QNAP/owncloud/stack-info.json @@ -0,0 +1,32 @@ +{ + "Id": 1, + "Name": "owncloud", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [], + "ResourceControl": { + "Id": 1, + "ResourceId": "3_owncloud", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/1", + "CreationDate": 1753294011, + "CreatedBy": "admin", + "UpdateDate": 1755528476, + "UpdatedBy": "admin", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/QNAP/paperless/.env b/QNAP/paperless/.env new file mode 100644 index 0000000..28ceef6 --- /dev/null +++ b/QNAP/paperless/.env @@ -0,0 +1,5 @@ +PAPERLESS_URL=https://paperless.kaspers.us +PAPERLESS_TIME_ZONE=America/New_York +PAPERLESS_OCR_LANGUAGE=eng +PAPERLESS_SECRET_KEY=1bU0TS6uOPiJhJxvC7xFrYSplRdsWtPtYXwEKRiB1rofwEYOrcSBeJZOhtNbd1Gs +COMPOSE_PROJECT_NAME=paperless diff --git a/QNAP/paperless/docker-compose.yml b/QNAP/paperless/docker-compose.yml new file mode 100644 index 0000000..c94f777 --- /dev/null +++ b/QNAP/paperless/docker-compose.yml @@ -0,0 +1,114 @@ +# docker-compose file for running paperless from the docker container registry. +# This file contains everything paperless needs to run. +# Paperless supports amd64, arm and arm64 hardware. +# +# All compose files of paperless configure paperless in the following way: +# +# - Paperless is (re)started on system boot, if it was running before shutdown. +# - Docker volumes for storing data are managed by Docker. +# - Folders for importing and exporting files are created in the same directory +# as this file and mounted to the correct folders inside the container. +# - Paperless listens on port 8000. +# +# In addition to that, this docker-compose file adds the following optional +# configurations: +# +# - Instead of SQLite (default), PostgreSQL is used as the database server. +# - Apache Tika and Gotenberg servers are started with paperless and paperless +# is configured to use these services. These provide support for consuming +# Office documents (Word, Excel, Power Point and their LibreOffice counter- +# parts. +# +# To install and update paperless with this file, do the following: +# +# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' +# and '.env' into a folder. +# - Run 'docker-compose pull'. +# - Run 'docker-compose run --rm webserver createsuperuser' to create a user. +# - Run 'docker-compose up -d'. +# +# For more extensive installation and update instructions, refer to the +# documentation. + +version: "3.4" +services: + broker: + image: docker.io/library/redis:7 + restart: unless-stopped + volumes: + - /share/Media/container-station-data/paperless/redis:/data + networks: + paperless-internal: {} + + db: + image: docker.io/library/postgres:13 + restart: unless-stopped + volumes: + - /share/Media/container-station-data/paperless/database:/var/lib/postgresql/data + environment: + POSTGRES_DB: paperless + POSTGRES_USER: paperless + POSTGRES_PASSWORD: paperless + networks: + paperless-internal: {} + + webserver: + image: ghcr.io/paperless-ngx/paperless-ngx:2.17.1 + restart: unless-stopped + depends_on: + - db + - broker + - gotenberg + - tika + ports: + - "8888:8000" + networks: + proxy: {} + paperless-internal: {} + labels: + traefik.docker.network: proxy + traefik.enable: 'true' + traefik.http.routers.paperless.rule: Host(`paperless.kaspers.us`) + traefik.http.services.paperless.loadbalancer.server.port: '8000' + healthcheck: + test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"] + interval: 30s + timeout: 10s + retries: 5 + volumes: + - /share/Media/container-station-data/paperless/data:/usr/src/paperless/data + - /share/Media/container-station-data/paperless/media:/usr/src/paperless/media + - /share/Media/container-station-data/paperless/export:/usr/src/paperless/export + - /share/Media/container-station-data/paperless/consume:/usr/src/paperless/consume + environment: + PAPERLESS_REDIS: redis://broker:6379 + PAPERLESS_DBHOST: db + PAPERLESS_TIKA_ENABLED: 1 + PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 + PAPERLESS_TIKA_ENDPOINT: http://tika:9998 + PAPERLESS_URL: https://paperless.kaspers.us + + gotenberg: + image: docker.io/gotenberg/gotenberg:7.8 + restart: unless-stopped + + # The gotenberg chromium route is used to convert .eml files. We do not + # want to allow external content like tracking pixels or even javascript. + command: + - "gotenberg" + - "--chromium-disable-javascript=true" + - "--chromium-allow-list=file:///tmp/.*" + networks: + paperless-internal: {} + + tika: + image: ghcr.io/paperless-ngx/tika:latest + restart: unless-stopped + networks: + paperless-internal: {} + +networks: + proxy: + external: true + paperless-internal: + driver: bridge diff --git a/QNAP/paperless/environment-variables.json b/QNAP/paperless/environment-variables.json new file mode 100644 index 0000000..f56d19c --- /dev/null +++ b/QNAP/paperless/environment-variables.json @@ -0,0 +1,22 @@ +[ + { + "name": "PAPERLESS_URL", + "value": "https://paperless.kaspers.us" + }, + { + "name": "PAPERLESS_TIME_ZONE", + "value": "America/New_York" + }, + { + "name": "PAPERLESS_OCR_LANGUAGE", + "value": "eng" + }, + { + "name": "PAPERLESS_SECRET_KEY", + "value": "1bU0TS6uOPiJhJxvC7xFrYSplRdsWtPtYXwEKRiB1rofwEYOrcSBeJZOhtNbd1Gs" + }, + { + "name": "COMPOSE_PROJECT_NAME", + "value": "paperless" + } +] diff --git a/QNAP/paperless/metadata.txt b/QNAP/paperless/metadata.txt new file mode 100644 index 0000000..0bef94c --- /dev/null +++ b/QNAP/paperless/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: paperless +Stack ID: 6 +Endpoint ID: 3 +Creation Date: 1753458813 +Update Date: 1755524126 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/paperless/stack-info.json b/QNAP/paperless/stack-info.json new file mode 100644 index 0000000..01b02ed --- /dev/null +++ b/QNAP/paperless/stack-info.json @@ -0,0 +1,53 @@ +{ + "Id": 6, + "Name": "paperless", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [ + { + "name": "PAPERLESS_URL", + "value": "https://paperless.kaspers.us" + }, + { + "name": "PAPERLESS_TIME_ZONE", + "value": "America/New_York" + }, + { + "name": "PAPERLESS_OCR_LANGUAGE", + "value": "eng" + }, + { + "name": "PAPERLESS_SECRET_KEY", + "value": "1bU0TS6uOPiJhJxvC7xFrYSplRdsWtPtYXwEKRiB1rofwEYOrcSBeJZOhtNbd1Gs" + }, + { + "name": "COMPOSE_PROJECT_NAME", + "value": "paperless" + } + ], + "ResourceControl": { + "Id": 6, + "ResourceId": "3_paperless", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/6", + "CreationDate": 1753458813, + "CreatedBy": "admin", + "UpdateDate": 1755524126, + "UpdatedBy": "admin", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/QNAP/traefik/.env b/QNAP/traefik/.env new file mode 100644 index 0000000..0005532 --- /dev/null +++ b/QNAP/traefik/.env @@ -0,0 +1,12 @@ +ROOT_DOMAIN=kaspers.us +HTTP_TIMEOUT=60 +POLLING_INTERVAL=10 +PROPAGATION_TIMEOUT=3600 +TTL=300 +PROVIDERS_GOOGLE_CLIENT_ID= +PROVIDERS_GOOGLE_CLIENT_SECRET= +SECRET=RandomTextGoesHere +WHITELIST= +LOG_LEVEL=INFO +ZONE_ID=7e2d1b9d7e0f7a5056bfaea28f070ba3 +TUNNEL_TOKEN=eyJhIjoiNmZkNGQyNGRhNDNiNTgyZDY3NjA4ZmZlZjU1NDljNGEiLCJ0IjoiOGI1MjBiYjUtNjA5My00YzE3LWE1YjEtZjhmYWNiMThkYjQ3IiwicyI6Ik9URTRNekZpWXpJdE1EVm1PUzAwTUROaUxXRTFNamt0WlRrMll6azVOVEV4TURJMyJ9 diff --git a/QNAP/traefik/docker-compose.yml b/QNAP/traefik/docker-compose.yml new file mode 100644 index 0000000..7dc5c6b --- /dev/null +++ b/QNAP/traefik/docker-compose.yml @@ -0,0 +1,181 @@ +version: '3.8' + +services: + reverse-proxy: + image: traefik:latest + command: + - "--log" + - "--log.level=debug" + - "--log.format=json" + - "--api.insecure=true" + - "--providers.docker" + - "--providers.docker.exposedbydefault=false" + - "--providers.file.directory=/config" + - "--providers.file.watch=true" + - "--serversTransport.insecureSkipVerify=true" # Allow self-signed certificates for target hosts - https://doc.traefik.io/traefik/routing/overview/#insecureskipverify + - "--metrics" + - "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0" + - "--entrypoints.web.address=:80" + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0s" + - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0s" + - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=5m" + - "--entrypoints.websecure.http.tls=true" + - "--entrypoints.websecure.http.tls.certresolver=letsencrypt" + - "--entrypoints.webinternal.address=:82" + - "--certificatesresolvers.letsencrypt.acme.email=kasperj@gmail.com" + - "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/letsencrypt.json" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=300" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=8.8.8.8:53" + + environment: + - CLOUDFLARE_DNS_API_TOKEN=n-iAlyJaGKcJwUcbxiIYA6kmxTVPBF_ez-g0fglW + - CLOUDFLARE_API_KEY=n-iAlyJaGKcJwUcbxiIYA6kmxTVPBF_ez-g0fglW + - CLOUDFLARE_HTTP_TIMEOUT=${HTTP_TIMEOUT} + - CLOUDFLARE_POLLING_INTERVAL=${POLLING_INTERVAL} + - CLOUDFLARE_PROPAGATION_TIMEOUT=${PROPAGATION_TIMEOUT} + - CLOUDFLARE_TTL=${TTL} + deploy: + labels: + - traefik.enable=true + - traefik.http.routers.api.rule=Host(`traefik-api.kaspers.us`) + - traefik.http.routers.api.service=api@internal + - traefik.http.routers.api.entrypoints=websecure + - traefik.http.routers.api.tls=true + - traefik.http.services.api.loadbalancer.server.port=8080 + - traefik.docker.network=proxy + - traefik.http.routers.api.tls.certresolver=letsencrypt + - traefik.http.routers.dashboard.rule=Host(`traefik.kaspers.us`) + - traefik.http.routers.dashboard.entrypoints=web + - traefik.http.routers.dashboard.service=api@internal + - traefik.http.routers.dashboard.tls=true + - traefik.http.routers.dashboard.tls.certresolver=myresolver + ports: + # HTTP + - target: 80 + published: 80 + # HTTPS + - target: 443 + published: 443 + # Web UI (enabled by --api.insecure=true) + - target: 8080 + published: 8182 + networks: + - proxy + - internal + volumes: + # So that Traefik can listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock + - /share/Media/container-station-data/traefik/acme:/etc/traefik/acme/ + - /share/Media/container-station-data/traefik/origcerts:/etc/traefik/certs/ + - /share/Media/container-station-data/traefik:/config + - /var/run/docker.sock:/var/run/docker.sock + - /share/Media/container-station-data/traefik/cloudflare:/cloudflare + +# traefik-forward-auth: +# image: thomseddon/traefik-forward-auth:2.1.0 +# networks: +# - traefik +# environment: +# - PROVIDERS_GOOGLE_CLIENT_ID=${PROVIDERS_GOOGLE_CLIENT_ID} +# - PROVIDERS_GOOGLE_CLIENT_SECRET=${PROVIDERS_GOOGLE_CLIENT_SECRET} +# - SECRET=${SECRET} +# - AUTH_HOST=auth.${ROOT_DOMAIN} +# - COOKIE_DOMAIN=${ROOT_DOMAIN} +# - WHITELIST=${WHITELIST} +# deploy: +# labels: +# - traefik.enable=true +# - traefik.docker.network=traefik +# +# - traefik.http.routers.auth.rule=Host(`auth.${ROOT_DOMAIN}`) +# - traefik.http.routers.auth.entrypoints=websecure +# - traefik.http.routers.auth.tls=true +# - traefik.http.routers.auth.tls.domains[0].main=${ROOT_DOMAIN} +# - traefik.http.routers.auth.tls.domains[0].sans=*.${ROOT_DOMAIN} +# - traefik.http.routers.auth.tls.certresolver=letsencrypt +# - traefik.http.routers.auth.service=auth@docker +# +# - traefik.http.services.auth.loadbalancer.server.port=4181 +# +# - traefik.http.middlewares.forward-auth.forwardauth.address=http://traefik-forward-auth:4181 +# - traefik.http.middlewares.forward-auth.forwardauth.trustForwardHeader=true +# - traefik.http.middlewares.forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User +# +# - traefik.http.routers.auth.middlewares=forward-auth +# +# - traefik.constraint=proxy-public + + tunnel: + container_name: cloudflared-tunnel + image: cloudflare/cloudflared + restart: unless-stopped + command: tunnel run + networks: + - proxy + environment: + - TUNNEL_TOKEN=${TUNNEL_TOKEN} + + error-pages: + image: tarampampam/error-pages:2.26.0 + environment: + TEMPLATE_NAME: l7-dark + networks: + - proxy + deploy: + labels: + - traefik.enable=true + - traefik.docker.network=traefik + + # use as "fallback" for any non-registered services (with priority below normal) + - traefik.http.routers.error-pages.rule=HostRegexp(`{host:.+}`) + - traefik.http.routers.error-pages.priority=10 + + # should say that all of your services work on https + - traefik.http.routers.error-pages.tls='true' + - traefik.http.routers.error-pages.entrypoints=websecure + - traefik.http.routers.error-pages.middlewares=error-pages + - traefik.http.services.error-pages.loadbalancer.server.port=8080 + + # "errors" middleware settings + - traefik.http.middlewares.error-pages.errors.status=400-599 + - traefik.http.middlewares.error-pages.errors.service=error-pages + - traefik.http.middlewares.error-pages.errors.query=/{status}.html + + cloudflare-companion: + image: ghcr.io/tiredofit/docker-traefik-cloudflare-companion:latest + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - TIMEZONE=America/New_York + - LOG_TYPE=CONSOLE + - LOG_LEVEL=INFO + - TRAEFIK_VERSION=2 + - RC_TYPE=CNAME + - TARGET_DOMAIN=8b520bb5-6093-4c17-a5b1-f8facb18db47.cfargotunnel.com + - DOMAIN1_TARGET_DOMAIN=8b520bb5-6093-4c17-a5b1-f8facb18db47.cfargotunnel.com + - REFRESH_ENTRIES=TRUE + - TRAEFIK_DEFAULT_RULE=FALSE + - TRAEFIK_ENABLE_LABEL_ONLY=TRUE + - DEFAULT_MODE=SKIP + - PURGE_ON_DELETE=TRUE + - ENABLE_TRAEFIK_POLL=TRUE + - TRAEFIK_POLL_URL=http://reverse-proxy:8080/ + - TRAEFIK_FILTER_LABEL=traefik.constraint + - TRAEFIK_FILTER=proxy-public + - DOMAIN1=${ROOT_DOMAIN} + - DOMAIN1_ZONE_ID=${ZONE_ID} + - DOMAIN1_PROXIED=TRUE + - TRAEFIK_EXCLUDED_HOST1=.* + - CF_TOKEN=n-iAlyJaGKcJwUcbxiIYA6kmxTVPBF_ez-g0fglW + restart: always + networks: + - internal + +networks: + proxy: + external: true + internal: diff --git a/QNAP/traefik/environment-variables.json b/QNAP/traefik/environment-variables.json new file mode 100644 index 0000000..18fef5e --- /dev/null +++ b/QNAP/traefik/environment-variables.json @@ -0,0 +1,50 @@ +[ + { + "name": "ROOT_DOMAIN", + "value": "kaspers.us" + }, + { + "name": "HTTP_TIMEOUT", + "value": "60" + }, + { + "name": "POLLING_INTERVAL", + "value": "10" + }, + { + "name": "PROPAGATION_TIMEOUT", + "value": "3600" + }, + { + "name": "TTL", + "value": "300" + }, + { + "name": "PROVIDERS_GOOGLE_CLIENT_ID", + "value": "" + }, + { + "name": "PROVIDERS_GOOGLE_CLIENT_SECRET", + "value": "" + }, + { + "name": "SECRET", + "value": "RandomTextGoesHere" + }, + { + "name": "WHITELIST", + "value": "" + }, + { + "name": "LOG_LEVEL", + "value": "INFO" + }, + { + "name": "ZONE_ID", + "value": "7e2d1b9d7e0f7a5056bfaea28f070ba3" + }, + { + "name": "TUNNEL_TOKEN", + "value": "eyJhIjoiNmZkNGQyNGRhNDNiNTgyZDY3NjA4ZmZlZjU1NDljNGEiLCJ0IjoiOGI1MjBiYjUtNjA5My00YzE3LWE1YjEtZjhmYWNiMThkYjQ3IiwicyI6Ik9URTRNekZpWXpJdE1EVm1PUzAwTUROaUxXRTFNamt0WlRrMll6azVOVEV4TURJMyJ9" + } +] diff --git a/QNAP/traefik/metadata.txt b/QNAP/traefik/metadata.txt new file mode 100644 index 0000000..e478101 --- /dev/null +++ b/QNAP/traefik/metadata.txt @@ -0,0 +1,8 @@ +Stack Name: traefik +Stack ID: 13 +Endpoint ID: 3 +Creation Date: 1754418005 +Update Date: 1755528537 +Status: 1 +Type: 2 +Entry Point: docker-compose.yml diff --git a/QNAP/traefik/stack-info.json b/QNAP/traefik/stack-info.json new file mode 100644 index 0000000..c17715e --- /dev/null +++ b/QNAP/traefik/stack-info.json @@ -0,0 +1,81 @@ +{ + "Id": 13, + "Name": "traefik", + "Type": 2, + "EndpointId": 3, + "SwarmId": "", + "EntryPoint": "docker-compose.yml", + "Env": [ + { + "name": "ROOT_DOMAIN", + "value": "kaspers.us" + }, + { + "name": "HTTP_TIMEOUT", + "value": "60" + }, + { + "name": "POLLING_INTERVAL", + "value": "10" + }, + { + "name": "PROPAGATION_TIMEOUT", + "value": "3600" + }, + { + "name": "TTL", + "value": "300" + }, + { + "name": "PROVIDERS_GOOGLE_CLIENT_ID", + "value": "" + }, + { + "name": "PROVIDERS_GOOGLE_CLIENT_SECRET", + "value": "" + }, + { + "name": "SECRET", + "value": "RandomTextGoesHere" + }, + { + "name": "WHITELIST", + "value": "" + }, + { + "name": "LOG_LEVEL", + "value": "INFO" + }, + { + "name": "ZONE_ID", + "value": "7e2d1b9d7e0f7a5056bfaea28f070ba3" + }, + { + "name": "TUNNEL_TOKEN", + "value": "eyJhIjoiNmZkNGQyNGRhNDNiNTgyZDY3NjA4ZmZlZjU1NDljNGEiLCJ0IjoiOGI1MjBiYjUtNjA5My00YzE3LWE1YjEtZjhmYWNiMThkYjQ3IiwicyI6Ik9URTRNekZpWXpJdE1EVm1PUzAwTUROaUxXRTFNamt0WlRrMll6azVOVEV4TURJMyJ9" + } + ], + "ResourceControl": { + "Id": 8, + "ResourceId": "3_traefik", + "SubResourceIds": [], + "Type": 6, + "UserAccesses": [], + "TeamAccesses": [], + "Public": false, + "AdministratorsOnly": true, + "System": false + }, + "Status": 1, + "ProjectPath": "/data/compose/13", + "CreationDate": 1754418005, + "CreatedBy": "admin", + "UpdateDate": 1755528537, + "UpdatedBy": "admin", + "AdditionalFiles": null, + "AutoUpdate": null, + "Option": null, + "GitConfig": null, + "FromAppTemplate": false, + "Namespace": "" +} diff --git a/README-portainer-backup.md b/README-portainer-backup.md new file mode 100644 index 0000000..7a9e248 --- /dev/null +++ b/README-portainer-backup.md @@ -0,0 +1,68 @@ +# Portainer Stack Backup Script + +This script connects to a Portainer instance and exports all stacks with their configurations, environment variables, and docker-compose files. + +## Prerequisites + +- `curl` (usually pre-installed on macOS) +- `jq` for JSON processing: `brew install jq` + +## Usage + +1. Make sure the script is executable: + ```bash + chmod +x portainer-backup.sh + ``` + +2. Run the script: + ```bash + ./portainer-backup.sh + ``` + +3. When prompted, enter: + - Portainer URL (e.g., `http://localhost:9000`) + - Username + - Password + +Alternatively, you can edit the script and set these variables at the top: +```bash +PORTAINER_URL="http://your-portainer-url:9000" +USERNAME="your-username" +PASSWORD="your-password" +``` + +## Output + +The script creates a `portainer-stacks-backup` directory containing: + +``` +portainer-stacks-backup/ +├── stack-name-1/ +│ ├── stack-info.json # Complete stack information +│ ├── environment-variables.json # Environment variables as JSON +│ ├── .env # Environment variables in .env format +│ ├── docker-compose.yml # Stack's compose file +│ └── metadata.txt # Human-readable metadata +├── stack-name-2/ +│ └── ... +``` + +## What gets backed up + +For each stack: +- Complete stack configuration (JSON format) +- Environment variables (both JSON and .env formats) +- Docker Compose file content +- Metadata (creation date, status, type, etc.) + +## Error Handling + +The script includes error handling for: +- Missing dependencies +- Authentication failures +- Network connectivity issues +- API errors + +## Security Note + +The script handles authentication via JWT tokens. Passwords are prompted securely (hidden input) if not set in the script. \ No newline at end of file diff --git a/portainer-backup.sh b/portainer-backup.sh new file mode 100755 index 0000000..c4984d6 --- /dev/null +++ b/portainer-backup.sh @@ -0,0 +1,232 @@ +#!/bin/bash + +# Portainer Stack Backup Script +# This script connects to a Portainer instance and exports all stacks with their configurations + +set -e + +# Configuration - Update these variables +PORTAINER_URL="http://192.168.11.12:9000" # Change this to your Portainer URL +USERNAME="admin" # Change this to your username +PASSWORD="JohnWayne#21" # Change this to your password or leave empty for prompt +OUTPUT_DIR="portainer-stacks-backup" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check if required tools are installed +check_dependencies() { + print_status "Checking dependencies..." + + if ! command -v curl &> /dev/null; then + print_error "curl is required but not installed." + exit 1 + fi + + if ! command -v jq &> /dev/null; then + print_error "jq is required but not installed. Please install it with: brew install jq" + exit 1 + fi + + print_status "All dependencies are available." +} + +# Function to prompt for configuration if not set +get_config() { + if [ -z "$PORTAINER_URL" ]; then + read -p "Enter Portainer URL (e.g., http://localhost:9000): " PORTAINER_URL + fi + + if [ -z "$USERNAME" ]; then + read -p "Enter Portainer username: " USERNAME + fi + + if [ -z "$PASSWORD" ]; then + read -s -p "Enter Portainer password: " PASSWORD + echo + fi + + # Remove trailing slash from URL if present + PORTAINER_URL=$(echo "$PORTAINER_URL" | sed 's/\/$//g') +} + +# Function to authenticate with Portainer +authenticate() { + print_status "Authenticating with Portainer..." + + AUTH_RESPONSE=$(curl -s -X POST \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \ + "$PORTAINER_URL/api/auth") + + if [ $? -ne 0 ]; then + print_error "Failed to connect to Portainer at $PORTAINER_URL" + exit 1 + fi + + JWT_TOKEN=$(echo "$AUTH_RESPONSE" | jq -r '.jwt // empty') + + if [ -z "$JWT_TOKEN" ] || [ "$JWT_TOKEN" = "null" ]; then + print_error "Authentication failed. Please check your credentials." + print_error "Response: $AUTH_RESPONSE" + exit 1 + fi + + print_status "Authentication successful." +} + +# Function to get all environments (endpoints) +get_environments() { + print_status "Getting environments..." + + ENVIRONMENTS_RESPONSE=$(curl -s -X GET \ + -H "Authorization: Bearer $JWT_TOKEN" \ + "$PORTAINER_URL/api/endpoints") + + if [ $? -ne 0 ]; then + print_error "Failed to get environments" + exit 1 + fi + + echo "$ENVIRONMENTS_RESPONSE" | jq -r '.[].Id' 2>/dev/null || echo "1" +} + +# Function to get all stacks +get_stacks() { + print_status "Getting list of stacks..." >&2 + + STACKS_RESPONSE=$(curl -s -X GET \ + -H "Authorization: Bearer $JWT_TOKEN" \ + "$PORTAINER_URL/api/stacks") + + if [ $? -ne 0 ]; then + print_error "Failed to get stacks" >&2 + exit 1 + fi + + echo "$STACKS_RESPONSE" +} + +# Function to get detailed stack information +get_stack_details() { + local stack_id=$1 + local endpoint_id=$2 + + curl -s -X GET \ + -H "Authorization: Bearer $JWT_TOKEN" \ + "$PORTAINER_URL/api/stacks/$stack_id?endpointId=$endpoint_id" +} + +# Function to get stack file content +get_stack_file() { + local stack_id=$1 + + curl -s -X GET \ + -H "Authorization: Bearer $JWT_TOKEN" \ + "$PORTAINER_URL/api/stacks/$stack_id/file" +} + +# Function to save stack data +save_stack_data() { + local stack_info=$1 + local stack_name=$(echo "$stack_info" | jq -r '.Name') + local stack_id=$(echo "$stack_info" | jq -r '.Id') + local endpoint_id=$(echo "$stack_info" | jq -r '.EndpointId') + + print_status "Processing stack: $stack_name (ID: $stack_id)" + + # Create directory for this stack + local stack_dir="$OUTPUT_DIR/$stack_name" + mkdir -p "$stack_dir" + + # Save basic stack information + echo "$stack_info" | jq '.' > "$stack_dir/stack-info.json" + + # Extract and save environment variables + echo "$stack_info" | jq '.Env // []' > "$stack_dir/environment-variables.json" + + # Save environment variables in .env format + echo "$stack_info" | jq -r '.Env[]? | select(.name != null and .value != null) | "\(.name)=\(.value)"' > "$stack_dir/.env" + + # Get and save stack file content (docker-compose.yml) + print_status "Getting stack file for: $stack_name" + STACK_FILE_RESPONSE=$(get_stack_file "$stack_id") + + if [ $? -eq 0 ] && [ "$STACK_FILE_RESPONSE" != "null" ]; then + echo "$STACK_FILE_RESPONSE" | jq -r '.StackFileContent // empty' > "$stack_dir/docker-compose.yml" + else + print_warning "Could not retrieve stack file for: $stack_name" + fi + + # Save additional metadata + cat > "$stack_dir/metadata.txt" << EOF +Stack Name: $stack_name +Stack ID: $stack_id +Endpoint ID: $endpoint_id +Creation Date: $(echo "$stack_info" | jq -r '.CreationDate // "N/A"') +Update Date: $(echo "$stack_info" | jq -r '.UpdateDate // "N/A"') +Status: $(echo "$stack_info" | jq -r '.Status // "N/A"') +Type: $(echo "$stack_info" | jq -r '.Type // "N/A"') +Entry Point: $(echo "$stack_info" | jq -r '.EntryPoint // "N/A"') +EOF + + print_status "Stack $stack_name saved to $stack_dir" +} + +# Main execution +main() { + print_status "Starting Portainer stack backup..." + + check_dependencies + get_config + authenticate + + # Create output directory + mkdir -p "$OUTPUT_DIR" + + # Get all stacks + STACKS_JSON=$(get_stacks) + + if [ -z "$STACKS_JSON" ] || [ "$STACKS_JSON" = "null" ]; then + print_warning "No stacks found or unable to retrieve stacks." + exit 1 + fi + + # Count stacks + STACK_COUNT=$(echo "$STACKS_JSON" | jq '. | length') + print_status "Found $STACK_COUNT stack(s)" + + if [ "$STACK_COUNT" -eq 0 ]; then + print_warning "No stacks to backup." + exit 0 + fi + + # Process each stack + echo "$STACKS_JSON" | jq -c '.[]' | while read -r stack; do + save_stack_data "$stack" + echo "---" + done + + print_status "Backup completed! All stacks saved to: $OUTPUT_DIR" + print_status "Summary:" + ls -la "$OUTPUT_DIR" +} + +# Run main function +main "$@" \ No newline at end of file