From 6279b005b2551edda884bbf4c822ad8953c7aab5 Mon Sep 17 00:00:00 2001 From: shenlan Date: Mon, 1 Dec 2025 13:35:47 +0800 Subject: [PATCH] Parameterize Zitadel deployment variables --- playbooks/deploy_zitadel_docker.yaml | 12 ++ playbooks/roles/docker/zitadel/README.md | 23 +++ .../roles/docker/zitadel/defaults/main.yml | 6 + .../zitadel/files/certbot/conf/.gitkeep | 0 .../docker/zitadel/files/certbot/www/.gitkeep | 0 .../docker/zitadel/files/nginx/nginx.conf | 5 + playbooks/roles/docker/zitadel/files/run.sh | 6 + playbooks/roles/docker/zitadel/tasks/main.yml | 40 +++++ .../zitadel/templates/docker-compose.yaml | 168 ++++++++++++++++++ .../templates/nginx/conf.d/default.conf | 42 +++++ 10 files changed, 302 insertions(+) create mode 100644 playbooks/deploy_zitadel_docker.yaml create mode 100644 playbooks/roles/docker/zitadel/README.md create mode 100644 playbooks/roles/docker/zitadel/defaults/main.yml create mode 100644 playbooks/roles/docker/zitadel/files/certbot/conf/.gitkeep create mode 100644 playbooks/roles/docker/zitadel/files/certbot/www/.gitkeep create mode 100644 playbooks/roles/docker/zitadel/files/nginx/nginx.conf create mode 100644 playbooks/roles/docker/zitadel/files/run.sh create mode 100644 playbooks/roles/docker/zitadel/tasks/main.yml create mode 100644 playbooks/roles/docker/zitadel/templates/docker-compose.yaml create mode 100644 playbooks/roles/docker/zitadel/templates/nginx/conf.d/default.conf diff --git a/playbooks/deploy_zitadel_docker.yaml b/playbooks/deploy_zitadel_docker.yaml new file mode 100644 index 0000000..dddde7c --- /dev/null +++ b/playbooks/deploy_zitadel_docker.yaml @@ -0,0 +1,12 @@ +- name: setup zitadel + hosts: "{{ zitadel_target_host }}" + become: true + vars: + zitadel_target_host: auth.svc.plus + zitadel_domain: "{{ zitadel_target_host }}" + zitadel_masterkey: MasterkeyNeedsToHave32Characters + zitadel_workspace: /opt/zitadel + roles: + - roles/vhosts/common/ + - roles/vhosts/docker/ + - roles/docker/zitadel/ diff --git a/playbooks/roles/docker/zitadel/README.md b/playbooks/roles/docker/zitadel/README.md new file mode 100644 index 0000000..99e7569 --- /dev/null +++ b/playbooks/roles/docker/zitadel/README.md @@ -0,0 +1,23 @@ +# Zitadel Docker role + +This role provisions a Zitadel stack with Postgres, optional TLS termination, login frontend, Nginx proxy, and Certbot assets. Templates from `templates/` and static assets from `files/` are rendered into `{{ zitadel_workspace }}` and the Docker Compose stack is started. + +## Layout +``` +files/ +├── certbot/ +│ ├── conf/ +│ └── www/ +├── docker-compose.yaml +├── nginx/ +│ ├── conf.d/ +│ │ └── default.conf +│ └── nginx.conf +└── run.sh +``` + +## Defaults +- `zitadel_deploy_dir`: `/opt/zitadel` +- `zitadel_workspace`: `{{ zitadel_deploy_dir }}` +- `zitadel_domain`: `auth.svc.plus` +- `zitadel_masterkey`: `MasterkeyNeedsToHave32Characters` diff --git a/playbooks/roles/docker/zitadel/defaults/main.yml b/playbooks/roles/docker/zitadel/defaults/main.yml new file mode 100644 index 0000000..f26d8cd --- /dev/null +++ b/playbooks/roles/docker/zitadel/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# Default deployment directory for Zitadel Docker stack +zitadel_deploy_dir: /opt/zitadel +zitadel_workspace: "{{ zitadel_deploy_dir }}" +zitadel_domain: auth.svc.plus +zitadel_masterkey: MasterkeyNeedsToHave32Characters diff --git a/playbooks/roles/docker/zitadel/files/certbot/conf/.gitkeep b/playbooks/roles/docker/zitadel/files/certbot/conf/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/docker/zitadel/files/certbot/www/.gitkeep b/playbooks/roles/docker/zitadel/files/certbot/www/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/docker/zitadel/files/nginx/nginx.conf b/playbooks/roles/docker/zitadel/files/nginx/nginx.conf new file mode 100644 index 0000000..9fe4ac3 --- /dev/null +++ b/playbooks/roles/docker/zitadel/files/nginx/nginx.conf @@ -0,0 +1,5 @@ +events {} + +http { + include /etc/nginx/conf.d/*.conf; +} diff --git a/playbooks/roles/docker/zitadel/files/run.sh b/playbooks/roles/docker/zitadel/files/run.sh new file mode 100644 index 0000000..77897d3 --- /dev/null +++ b/playbooks/roles/docker/zitadel/files/run.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Helper script to start the Zitadel docker compose stack +cd "$(dirname "$0")" +docker-compose -f docker-compose.yaml up -d diff --git a/playbooks/roles/docker/zitadel/tasks/main.yml b/playbooks/roles/docker/zitadel/tasks/main.yml new file mode 100644 index 0000000..e5b6c82 --- /dev/null +++ b/playbooks/roles/docker/zitadel/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Ensure Zitadel directories exist + become: true + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "{{ zitadel_workspace }}" + - "{{ zitadel_workspace }}/certbot" + - "{{ zitadel_workspace }}/certbot/conf" + - "{{ zitadel_workspace }}/certbot/www" + - "{{ zitadel_workspace }}/nginx" + - "{{ zitadel_workspace }}/nginx/conf.d" + +- name: Template Zitadel configuration files + become: true + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ zitadel_workspace }}/{{ item.dest }}" + mode: "{{ item.mode | default('0644') }}" + loop: + - { src: 'docker-compose.yaml', dest: 'docker-compose.yaml' } + - { src: 'nginx/conf.d/default.conf', dest: 'nginx/conf.d/default.conf' } + +- name: Copy Zitadel static files + become: true + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ zitadel_workspace }}/{{ item.dest }}" + mode: "{{ item.mode | default('0644') }}" + loop: + - { src: 'run.sh', dest: 'run.sh', mode: '0755' } + - { src: 'nginx/nginx.conf', dest: 'nginx/nginx.conf' } + +- name: Bring up Zitadel stack + become: true + ansible.builtin.command: docker-compose -f {{ zitadel_workspace }}/docker-compose.yaml up -d + args: + chdir: "{{ zitadel_workspace }}" diff --git a/playbooks/roles/docker/zitadel/templates/docker-compose.yaml b/playbooks/roles/docker/zitadel/templates/docker-compose.yaml new file mode 100644 index 0000000..0e87b41 --- /dev/null +++ b/playbooks/roles/docker/zitadel/templates/docker-compose.yaml @@ -0,0 +1,168 @@ +services: + + zitadel-external-tls: + extends: + service: zitadel-init + command: 'start-from-setup --masterkey "{{ zitadel_masterkey }}"' + environment: + ZITADEL_EXTERNALPORT: 443 + ZITADEL_EXTERNALSECURE: true + ZITADEL_TLS_ENABLED: false + networks: + - app + - db + depends_on: + db: + condition: 'service_healthy' + zitadel-init: + condition: 'service_completed_successfully' + + zitadel-enabled-tls: + extends: + service: zitadel-init + command: 'start-from-setup --masterkey "{{ zitadel_masterkey }}"' + environment: + ZITADEL_EXTERNALPORT: 443 + ZITADEL_EXTERNALSECURE: true + ZITADEL_TLS_ENABLED: true + ZITADEL_TLS_CERTPATH: /etc/letsencrypt/live/{{ zitadel_domain }}/fullchain.pem + ZITADEL_TLS_KEYPATH: /etc/letsencrypt/live/{{ zitadel_domain }}/privkey.pem + volumes: + - "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt" + networks: + - app + - db + depends_on: + zitadel-init: + condition: 'service_completed_successfully' + db: + condition: 'service_healthy' + + zitadel-init: + image: '${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}' + command: 'init' + depends_on: + db: + condition: 'service_healthy' + environment: + # Using an external domain other than localhost proofs, that the proxy configuration works. + # If Zitadel can't resolve a requests original host to this domain, + # it will return a 404 Instance not found error. + ZITADEL_EXTERNALDOMAIN: {{ zitadel_domain }} + # In case something doesn't work as expected, + # it can be handy to be able to read the access logs. + ZITADEL_LOGSTORE_ACCESS_STDOUT_ENABLED: true + # For convenience, ZITADEL should not ask to change the initial admin users password. + ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: false + # database configuration + ZITADEL_DATABASE_POSTGRES_HOST: db + ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw + # Set up a service account with IAM_LOGIN_CLIENT role and write the PAT to the file ./login-client.pat + ZITADEL_FIRSTINSTANCE_LOGINCLIENTPATPATH: /current-dir/login-client.pat + ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_USERNAME: login-client + ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_NAME: Automatically Initialized IAM Login Client + ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_PAT_EXPIRATIONDATE: '2029-01-01T00:00:00Z' + # The master key is used to + networks: + - db + healthcheck: + test: [ "CMD", "/app/zitadel", "ready" ] + interval: '10s' + timeout: '5s' + retries: 5 + start_period: '10s' + volumes: + - "{{ zitadel_workspace }}:/current-dir:rw" + + db: + restart: 'always' + image: postgres:17-alpine + environment: + POSTGRES_PASSWORD: postgres + healthcheck: + test: [ "CMD-SHELL", "pg_isready" ] + interval: 5s + timeout: 60s + retries: 10 + start_period: 5s + networks: + - db + volumes: + - 'data:/var/lib/postgresql/data:rw' + + login-external-tls: + restart: 'unless-stopped' + image: 'ghcr.io/zitadel/zitadel-login:latest' + environment: + - ZITADEL_API_URL=http://zitadel-external-tls:8080 + - NEXT_PUBLIC_BASE_PATH=/ui/v2/login + - ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat + - CUSTOM_REQUEST_HEADERS=Host:{{ zitadel_domain }} + volumes: + - "{{ zitadel_workspace }}:/current-dir:ro" + networks: + - app + depends_on: + zitadel-external-tls: + condition: 'service_healthy' + + login-enabled-tls: + restart: 'unless-stopped' + image: 'ghcr.io/zitadel/zitadel-login:latest' + environment: + - ZITADEL_API_URL=https://zitadel-enabled-tls:8080 + - NEXT_PUBLIC_BASE_PATH=/ui/v2/login + - ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat + - CUSTOM_REQUEST_HEADERS=Host:{{ zitadel_domain }} + - NODE_TLS_REJECT_UNAUTHORIZED=0 + volumes: + - "{{ zitadel_workspace }}:/current-dir:ro" + networks: + - app + depends_on: + zitadel-enabled-tls: + condition: 'service_healthy' + + proxy-external-tls: + image: nginx:mainline-alpine + container_name: proxy-external-tls + restart: unless-stopped + volumes: + - "{{ zitadel_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf" + - "{{ zitadel_workspace }}/nginx/conf.d:/etc/nginx/conf.d:ro" + - "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt" + - "{{ zitadel_workspace }}/certbot/www:/var/www/certbot" + ports: + - "80:80" + - "443:443" + networks: + - app + depends_on: + zitadel-external-tls: + condition: service_healthy + + certbot: + image: certbot/certbot + container_name: certbot + command: > + certonly --webroot + --webroot-path=/var/www/certbot + --email manbuzhe2009@qq.com + --agree-tos + --no-eff-email + -d {{ zitadel_domain }} + volumes: + - "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt" + - "{{ zitadel_workspace }}/certbot/www:/var/www/certbot" + depends_on: + proxy-external-tls: + condition: service_started + networks: + - app + +networks: + app: + db: + +volumes: + data: diff --git a/playbooks/roles/docker/zitadel/templates/nginx/conf.d/default.conf b/playbooks/roles/docker/zitadel/templates/nginx/conf.d/default.conf new file mode 100644 index 0000000..8dfe8c3 --- /dev/null +++ b/playbooks/roles/docker/zitadel/templates/nginx/conf.d/default.conf @@ -0,0 +1,42 @@ +# ---------------------------------------------------- +# 80 - ACME Challenge + Redirect to HTTPS +# ---------------------------------------------------- +server { + listen 80; + server_name {{ zitadel_domain }}; + + # Certbot HTTP-01 challenge + location ^~ /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # All HTTP → HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +# ---------------------------------------------------- +# 443 - TLS Termination +# ---------------------------------------------------- +server { + listen 443 ssl http2; + server_name {{ zitadel_domain }}; + + ssl_certificate /etc/letsencrypt/live/{{ zitadel_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ zitadel_domain }}/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + location /ui/v2/login { + proxy_pass http://login-external-tls:3000; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto https; + } + location / { + grpc_pass grpc://zitadel-external-tls:8080; + grpc_set_header Host $host; + grpc_set_header X-Forwarded-Proto https; + } +}