added backup script and backed up qnap

This commit is contained in:
Jake Kasper
2025-08-20 14:16:37 -04:00
parent 2deb975dae
commit 63fe21d3e9
37 changed files with 1591 additions and 0 deletions

0
QNAP/arr-stack/.env Normal file
View File

View File

@@ -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

View File

@@ -0,0 +1 @@
[]

View File

@@ -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

View File

@@ -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": ""
}

0
QNAP/docuseal/.env Normal file
View File

View File

@@ -56,3 +56,4 @@ networks:
driver: bridge
proxy:
external: true

View File

@@ -0,0 +1 @@
[]

View File

@@ -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

View File

@@ -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": ""
}

1
QNAP/gitea/.env Normal file
View File

@@ -0,0 +1 @@
GITEA_DB_PASSWORD=adt.cfv*ZQE6azm0qvk

View File

@@ -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

View File

@@ -0,0 +1,6 @@
[
{
"name": "GITEA_DB_PASSWORD",
"value": "adt.cfv*ZQE6azm0qvk"
}
]

8
QNAP/gitea/metadata.txt Normal file
View File

@@ -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

View File

@@ -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": ""
}

11
QNAP/immich/.env Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
]

8
QNAP/immich/metadata.txt Normal file
View File

@@ -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

View File

@@ -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": ""
}

0
QNAP/owncloud/.env Normal file
View File

View File

@@ -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

View File

@@ -0,0 +1 @@
[]

View File

@@ -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

View File

@@ -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": ""
}

5
QNAP/paperless/.env Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
]

View File

@@ -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

View File

@@ -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": ""
}

12
QNAP/traefik/.env Normal file
View File

@@ -0,0 +1,12 @@
ROOT_DOMAIN=kaspers.us
HTTP_TIMEOUT=60
POLLING_INTERVAL=10
PROPAGATION_TIMEOUT=3600
TTL=300
PROVIDERS_GOOGLE_CLIENT_ID=<GOOGLE CLIENT ID>
PROVIDERS_GOOGLE_CLIENT_SECRET=<GOOGLE CLIENT SECRET>
SECRET=RandomTextGoesHere
WHITELIST=<YOUR GOOGLE ACCOUNT EMAIL>
LOG_LEVEL=INFO
ZONE_ID=7e2d1b9d7e0f7a5056bfaea28f070ba3
TUNNEL_TOKEN=eyJhIjoiNmZkNGQyNGRhNDNiNTgyZDY3NjA4ZmZlZjU1NDljNGEiLCJ0IjoiOGI1MjBiYjUtNjA5My00YzE3LWE1YjEtZjhmYWNiMThkYjQ3IiwicyI6Ik9URTRNekZpWXpJdE1EVm1PUzAwTUROaUxXRTFNamt0WlRrMll6azVOVEV4TURJMyJ9

View File

@@ -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:

View File

@@ -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": "<GOOGLE CLIENT ID>"
},
{
"name": "PROVIDERS_GOOGLE_CLIENT_SECRET",
"value": "<GOOGLE CLIENT SECRET>"
},
{
"name": "SECRET",
"value": "RandomTextGoesHere"
},
{
"name": "WHITELIST",
"value": "<YOUR GOOGLE ACCOUNT EMAIL>"
},
{
"name": "LOG_LEVEL",
"value": "INFO"
},
{
"name": "ZONE_ID",
"value": "7e2d1b9d7e0f7a5056bfaea28f070ba3"
},
{
"name": "TUNNEL_TOKEN",
"value": "eyJhIjoiNmZkNGQyNGRhNDNiNTgyZDY3NjA4ZmZlZjU1NDljNGEiLCJ0IjoiOGI1MjBiYjUtNjA5My00YzE3LWE1YjEtZjhmYWNiMThkYjQ3IiwicyI6Ik9URTRNekZpWXpJdE1EVm1PUzAwTUROaUxXRTFNamt0WlRrMll6azVOVEV4TURJMyJ9"
}
]

View File

@@ -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

View File

@@ -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": "<GOOGLE CLIENT ID>"
},
{
"name": "PROVIDERS_GOOGLE_CLIENT_SECRET",
"value": "<GOOGLE CLIENT SECRET>"
},
{
"name": "SECRET",
"value": "RandomTextGoesHere"
},
{
"name": "WHITELIST",
"value": "<YOUR GOOGLE ACCOUNT EMAIL>"
},
{
"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": ""
}

View File

@@ -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.

232
portainer-backup.sh Executable file
View File

@@ -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 "$@"