diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..de6b335 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,31 @@ +name: Deploy Stack to Swarm + +on: + push: + branches: + - main + workflow_dispatch: + # Allows manual triggering + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Validate compose file + run: | + docker compose -f stack.yml config > /dev/null + echo "✅ Compose file is valid" + + - name: Deploy to swarm + run: | + echo "🚀 Deploying stack..." + docker stack deploy -c stack.yml ${{ github.event.repository.name }} --prune --with-registry-auth + echo "✅ Stack deployed successfully" + + - name: Verify deployment + run: | + sleep 5 + docker stack services ${{ github.event.repository.name }} diff --git a/.vscode/setting.json b/.vscode/setting.json deleted file mode 100644 index 5f88636..0000000 --- a/.vscode/setting.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "files.associations": { - "*.yml": "yaml", - "*.yaml": "yaml", - "docker-compose*.yml": "yaml", - "stack.yml": "yaml" - }, - "yaml.schemas": { - "https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json": [ - "docker-compose*.yml", - "**/stacks/**/stack.yml" - ] - }, - "yaml.format.enable": true, - "yaml.validate": true, - "editor.formatOnSave": true, - "editor.rulers": [80, 120], - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, - "git.autofetch": true, - "git.confirmSync": false, - "terminal.integrated.defaultProfile.windows": "PowerShell", - "[yaml]": { - "editor.insertSpaces": true, - "editor.tabSize": 2, - "editor.autoIndent": "advanced", - "editor.defaultFormatter": "redhat.vscode-yaml" - }, - "[markdown]": { - "editor.defaultFormatter": "yzhang.markdown-all-in-one" - } -} \ No newline at end of file diff --git a/stacks/startup/gitea-runner/stack.yml b/stacks/startup/gitea-runner/stack.yml new file mode 100644 index 0000000..a07f49b --- /dev/null +++ b/stacks/startup/gitea-runner/stack.yml @@ -0,0 +1,27 @@ +services: + gitea-runner: + image: gitea/act_runner:latest + hostname: "{{.Node.Hostname}}-runner" + environment: + - GITEA_INSTANCE_URL=https://git.frostlabs.me + - GITEA_RUNNER_REGISTRATION_TOKEN=hF9V6IIV4lj1cZVgNaZAXuXOcdVBiAQuoZdTU5Pp + - GITEA_RUNNER_NAME=swarm-runner-{{.Node.Hostname}} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - gitea-runner-data:/data + networks: + - homelab # Adjust to match your Gitea network + deploy: + replicas: 1 + placement: + constraints: + - node.role == manager + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 +volumes: + gitea-runner-data: +networks: + homelab: + external: true diff --git a/stacks/startup/portainer/stack.yml b/stacks/startup/portainer/stack.yml deleted file mode 100644 index f1e54be..0000000 --- a/stacks/startup/portainer/stack.yml +++ /dev/null @@ -1,36 +0,0 @@ -services: - portainer: - image: portainer/portainer-ce:latest - command: -H tcp://tasks.agent:9001 --tlsskipverify - volumes: - - /home/doc/projects/swarm-data/appdata/portainer:/data - networks: - - homelab - ports: - - 9001:9000 - deploy: - mode: replicated - replicas: 1 - labels: - - "traefik.enable=true" - - "traefik.swarm.network=homelab" - # Public-facing domain with Let's Encrypt certificate - - "traefik.http.routers.portainer.rule=Host(`portainer.frostlabs.me`)" - - "traefik.http.routers.portainer.entrypoints=websecure" - - "traefik.http.routers.portainer.tls=true" - - "traefik.http.routers.portainer.tls.certresolver=cloudflare" - - "traefik.http.services.portainer.loadbalancer.server.port=9000" - - agent: - image: portainer/agent:latest - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /var/lib/docker/volumes:/var/lib/docker/volumes - networks: - - homelab - deploy: - mode: global - -networks: - homelab: - external: true diff --git a/stacks/startup/stack.yml b/stacks/startup/stack.yml deleted file mode 100644 index 2c70bf7..0000000 --- a/stacks/startup/stack.yml +++ /dev/null @@ -1,223 +0,0 @@ -#--------------------------- -# AUTHENTIK -#--------------------------- -services: - redis: - image: redis:alpine - command: --save 60 1 --loglevel warning - volumes: - - /home/doc/projects/swarm-data/appdata/authentik/redis:/data - networks: - - homelab - healthcheck: - test: [ "CMD", "redis-cli", "ping" ] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - deploy: - replicas: 1 - resources: - limits: - memory: 512M - reservations: - memory: 128M - - authentik_server: - image: ghcr.io/goauthentik/server:2025.10.0 - command: server - environment: - AUTHENTIK_SECRET_KEY: "file:///run/secrets/auth-key" - AUTHENTIK_REDIS__HOST: "redis" - AUTHENTIK_POSTGRESQL__HOST: "10.0.4.10" - AUTHENTIK_POSTGRESQL__PORT: "5432" - AUTHENTIK_POSTGRESQL__USER: "admin" - AUTHENTIK_POSTGRESQL__NAME: "authentik" - AUTHENTIK_POSTGRESQL__PASSWORD: "file:///run/secrets/postgres-master" - # Optional: Set error reporting (set to false for privacy) - AUTHENTIK_ERROR_REPORTING__ENABLED: "false" - secrets: - - auth-key - - postgres-master - volumes: - - /home/doc/projects/swarm-data/appdata/authentik/media:/media - - /home/doc/projects/swarm-data/appdata/authentik/templates:/templates - - /var/run/docker.sock:/var/run/docker.sock - networks: - - homelab - healthcheck: - test: [ "CMD-SHELL", "ak healthcheck" ] - interval: 30s - timeout: 10s - retries: 3 - start_period: 90s - deploy: - replicas: 1 - resources: - limits: - memory: 1G - cpus: '1.0' - reservations: - memory: 512M - labels: - - "traefik.enable=true" - - "traefik.swarm.network=homelab" - # Public-facing domain with Let's Encrypt certificate - - "traefik.http.routers.authentik.rule=Host(`auth.frostlabs.me`)" - - "traefik.http.routers.authentik.entrypoints=websecure" - - "traefik.http.routers.authentik.tls=true" - - "traefik.http.routers.authentik.tls.certresolver=cloudflare" - - "traefik.http.services.authentik.loadbalancer.server.port=9000" - - depends_on: - - redis - - authentik_worker: - image: ghcr.io/goauthentik/server:2025.10.0 - command: worker - environment: - AUTHENTIK_SECRET_KEY: "file:///run/secrets/auth-key" - AUTHENTIK_REDIS__HOST: "redis" - AUTHENTIK_POSTGRESQL__HOST: "10.0.4.10" - AUTHENTIK_POSTGRESQL__PORT: "5432" - AUTHENTIK_POSTGRESQL__USER: "admin" - AUTHENTIK_POSTGRESQL__NAME: "authentik" - AUTHENTIK_POSTGRESQL__PASSWORD: "file:///run/secrets/postgres-master" - # Optional: Set error reporting (set to false for privacy) - AUTHENTIK_ERROR_REPORTING__ENABLED: "false" - secrets: - - auth-key - - postgres-master - volumes: - - /home/doc/projects/swarm-data/appdata/authentik/media:/media - - /home/doc/projects/swarm-data/appdata/authentik/templates:/templates - - /var/run/docker.sock:/var/run/docker.sock - networks: - - homelab - deploy: - replicas: 1 - resources: - limits: - memory: 1G - cpus: '1.0' - reservations: - memory: 512M - depends_on: - - redis - - #--------------------------- - # TRAEFIK - #--------------------------- - traefik: - image: traefik:v3.5.4 - # Remove all command arguments - using static config file instead - ports: - - 80:80 - - 443:443 - - 8082:8080 - environment: - - CF_DNS_API_TOKEN_FILE=/run/secrets/cloudflare_api_token - volumes: - - type: bind - source: /var/run/docker.sock - target: /var/run/docker.sock - read_only: true - - type: bind - source: /home/doc/projects/swarm-data/swarm-production/conf/traefik-conf/static.yml - target: /etc/traefik/traefik.yml - read_only: true - - type: bind - source: /home/doc/projects/swarm-data/swarm-production/conf/traefik-conf/dynamic.yml - target: /etc/traefik/dynamic/dynamic.yml - - type: bind - source: /home/doc/projects/swarm-data/appdata/traefik/certificates/acme.json - target: /certificates/acme.json - secrets: - - cloudflare_api_token - networks: - - homelab - healthcheck: - test: [ "CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/ping" ] - interval: 30s - timeout: 5s - retries: 3 - start_period: 30s - deploy: - mode: replicated - replicas: 1 - placement: - constraints: - - node.hostname == p0 - - #--------------------------- - # PORTAINER+AGENT - #--------------------------- - portainer: - image: portainer/portainer-ce:latest - command: -H tcp://tasks.agent:9001 --tlsskipverify - volumes: - - /home/doc/projects/swarm-data/appdata/portainer:/data - networks: - - homelab - ports: - - 9001:9000 - deploy: - mode: replicated - replicas: 1 - labels: - - "traefik.enable=true" - - "traefik.swarm.network=homelab" - # Public-facing domain with Let's Encrypt certificate - - "traefik.http.routers.portainer.rule=Host(`portainer.frostlabs.me`)" - - "traefik.http.routers.portainer.entrypoints=websecure" - - "traefik.http.routers.portainer.tls=true" - - "traefik.http.routers.portainer.tls.certresolver=cloudflare" - - "traefik.http.services.portainer.loadbalancer.server.port=9000" - - agent: - image: portainer/agent:latest - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /var/lib/docker/volumes:/var/lib/docker/volumes - networks: - - homelab - deploy: - mode: global - - #--------------------------- - # GITEA-RUNNER - #--------------------------- - gitea-runner: - image: gitea/act_runner:latest - hostname: "{{.Node.Hostname}}-runner" - environment: - - GITEA_INSTANCE_URL=https://git.frostlabs.me - - GITEA_RUNNER_REGISTRATION_TOKEN=hF9V6IIV4lj1cZVgNaZAXuXOcdVBiAQuoZdTU5Pp - - GITEA_RUNNER_NAME=swarm-runner-{{.Node.Hostname}} - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - gitea-runner-data:/data - networks: - - gitea_network # Adjust to match your Gitea network - deploy: - replicas: 1 - placement: - constraints: - - node.role == manager - restart_policy: - condition: on-failure - delay: 5s - max_attempts: 3 - -networks: - homelab: - external: true -secrets: - postgres-master: - external: true - auth-key: - external: true - cloudflare_api_token: - external: true -volumes: - gitea-runner-data: