docs: fix roadmap inconsistencies between test-env and prod-env

Corrects six documentation files to match the actual deployed pipeline
behavior and align test/prod approaches where they share the same code.

prod-env/02-godaddy-credentials.md
- Step 1: correct secret file from .env.secrets.shared to .env.secrets.swag;
  add clarifying note that .env.secrets.shared holds AppRole/DB secrets
  and must not be used for GoDaddy credentials.
- Step 4: document that GoDaddy A records are now managed automatically
  by the pipeline's 'Update DNS Records' step via the GoDaddy API;
  reference the Gitea variable PROD_FLOATING_IP that must be set once.

prod-env/08-deploy-pipeline-update.md
- Add Step 2 documenting the new 'Update DNS Records' pipeline step
  (GoDaddy API, idempotent check-before-update, requires jq and
  vars.PROD_FLOATING_IP).
- Renumber subsequent steps 3-8 to accommodate the new step.
- Fix DB hostnames in Step 7 (Run Database Init Scripts) from
  iklimco_postgresql/iklimco_mongodb to postgresql/mongodb, matching
  how Swarm overlay DNS resolves service names inside iklimco-net.
- Update context block: correct DB hostname description, replace
  outdated storagebox path note with env-var approach, list new steps.
- Update final step order to 24 steps including the DNS step and
  Release Deploy Lock; mark Wait for etcd as NEW.

prod-env/09-verify.md
- Insert check #2 for the precipitation image directory
  (/mnt/storagebox/precipitation/images) and iklimco_image-data volume
  bind mount, mirroring the equivalent check in test-env/08-verify.md.
- Renumber all subsequent checks (3-12) to maintain sequential ordering.

test-env/03-infra-stack-changes.md
- Update SWAG service volume snippet: replace hardcoded paths
  (swag-vl:/config, /opt/iklimco/swag/dns-conf, /opt/iklimco/swag/site-confs)
  with env-var forms (${SWAG_CONFIG_DIR:-swag-vl}, ${SWAG_DNS_CONF_DIR:-...},
  ${SWAG_SITE_CONFS_DIR:-...}) to match docker-stack-infra.yml.
- Update cert-reloader volume snippet: replace swag-vl and /opt/iklimco/ssl
  with ${SWAG_CONFIG_DIR:-swag-vl} and ${SWAG_CERT_DIR:-/opt/iklimco/ssl},
  enabling StorageBox override in prod without changing the base file.

test-env/04-swag-nginx-configs.md
- Replace RESTRICTED_IP_1/RESTRICTED_IP_2 individual env vars with
  RESTRICTED_IPS (comma-separated CIDR list) in the required-vars section,
  matching env-test/.env and the actual pipeline.
- Update all three IP-restricted template examples (apigw, rabbitmq,
  grafana) from allow ${RESTRICTED_IP_1}; allow ${RESTRICTED_IP_2}; to
  ${RESTRICTED_IPS_BLOCK}, matching the actual .conf.tpl files in the repo.
- Rewrite the deploy step section to match the real pipeline: docker run
  alpine for file writing, RESTRICTED_IPS_BLOCK generation via sed, and
  envsubst with explicit SWAG_VARS filter to protect nginx $upstream_* vars.

test-env/07-deploy-pipeline-update.md
- Step 2 (Prepare SWAG Directories): replace sudo-tee approach with the
  actual docker-run-alpine method used in deploy-test.yml; add nginx
  reload block; update notes to reflect RESTRICTED_IPS_BLOCK generation.
- Step 4 (Re-order): correct step numbering to match actual pipeline
  (21 steps); mark 'Wait for etcd' as already present in pipeline rather
  than a new addition; add Bootstrap Vault TLS Placeholder which was
  missing from the documented order.
This commit is contained in:
Murat ÖZDEMİR 2026-05-16 16:52:48 +03:00
parent 5ddba7eba4
commit fd6a0b4f46
6 changed files with 190 additions and 102 deletions

View File

@ -12,11 +12,11 @@ If credentials were shared in any chat log, Slack message, or email, **revoke th
**Never commit credentials to the repository.** **Never commit credentials to the repository.**
## Step 1 — Add credentials to storagebox `.env.secrets.shared` (prod path) ## Step 1 — Add credentials to storagebox `.env.secrets.swag` (prod path)
Open the file at storagebox path: Open the file at storagebox path:
``` ```
prod/secrets/iklim.co/.env.secrets.shared prod/secrets/iklim.co/.env.secrets.swag
``` ```
Add: Add:
@ -25,6 +25,9 @@ GODADDY_KEY=<your-new-api-key>
GODADDY_SECRET=<your-new-api-secret> GODADDY_SECRET=<your-new-api-secret>
``` ```
> `.env.secrets.swag` contains SWAG/GoDaddy credentials only.
> `.env.secrets.shared` contains AppRole IDs, DB passwords, and other runtime secrets — do not mix.
## Step 2 — Repo template file ## Step 2 — Repo template file
Same file as test: `swag/dns-conf/godaddy.ini.tpl` (already created in test step 02). Same file as test: `swag/dns-conf/godaddy.ini.tpl` (already created in test step 02).
@ -41,17 +44,21 @@ envsubst < swag/dns-conf/godaddy.ini.tpl > "$SWAG_DNS_CONF_DIR/godaddy.ini"
chmod 600 "$SWAG_DNS_CONF_DIR/godaddy.ini" chmod 600 "$SWAG_DNS_CONF_DIR/godaddy.ini"
``` ```
## Step 4 — GoDaddy A records for prod subdomains ## Step 4 — GoDaddy A records for prod subdomains (handled by pipeline)
In GoDaddy DNS panel for `iklim.co`, add/update A records pointing to the **Floating IP** (`iklim-prod-app-fip`). The deploy pipeline's **Update DNS Records** step automatically manages A records via GoDaddy API.
To get the Floating IP value: `terraform output prod_floating_ip` It reads the Floating IP from the Gitea variable `vars.PROD_FLOATING_IP` — set this once in Gitea project settings.
To get the Floating IP: `terraform output prod_floating_ip`
| Record | Value | | Record | Value |
|--------|-------| |--------|-------|
| `api` | `<iklim-prod-app-fip>` | | `api` | `vars.PROD_FLOATING_IP` |
| `apigw` | `<iklim-prod-app-fip>` | | `apigw` | `vars.PROD_FLOATING_IP` |
| `rabbitmq` | `<iklim-prod-app-fip>` | | `rabbitmq` | `vars.PROD_FLOATING_IP` |
| `grafana` | `<iklim-prod-app-fip>` | | `grafana` | `vars.PROD_FLOATING_IP` |
Logic: for each record, pipeline queries the current value via GoDaddy API. If already correct, it skips. Otherwise it creates/updates the record.
> The Floating IP is assigned to `iklim-app-01` (`06-prod-terraform-iaac.md``floating_ip.tf`). > The Floating IP is assigned to `iklim-app-01` (`06-prod-terraform-iaac.md``floating_ip.tf`).
> If failover is needed, the Floating IP can be reassigned to another app node; DNS does not change. > If failover is needed, the Floating IP can be reassigned to another app node; DNS does not change.

View File

@ -6,8 +6,9 @@
adapted for prod paths and prod runner. adapted for prod paths and prod runner.
- **Prod-specific differences from test:** - **Prod-specific differences from test:**
- `SPRING_PROFILES_ACTIVE=prod` (not `test`) in Run APISIX Init - `SPRING_PROFILES_ACTIVE=prod` (not `test`) in Run APISIX Init
- DB hostnames use Swarm VIP prefixes: `iklimco_postgresql`, `iklimco_mongodb` - DB hostnames: `postgresql`, `mongodb` (Swarm overlay DNS — same as test)
- Storagebox paths use `prod/` instead of `test/` - Storagebox paths via env vars (`SWAG_CERT_DIR`, `SWAG_CONFIG_DIR`, vb.) instead of local host paths
- Extra steps: Update DNS Records (GoDaddy API), Wait for etcd
## Step 1 — Remove manual cert scp lines from `Initialize Servers` ## Step 1 — Remove manual cert scp lines from `Initialize Servers`
@ -23,7 +24,43 @@ Also remove from `Prepare Init Files`:
sudo cp STAR.iklim.co.full.crt STAR.iklim.co_key.pem /opt/iklimco/ssl/ sudo cp STAR.iklim.co.full.crt STAR.iklim.co_key.pem /opt/iklimco/ssl/
``` ```
## Step 2 — Add `Prepare SWAG Directories` step ## Step 2 — Add `Update DNS Records` step
Insert **after** `Docker Login to Harbor` and **before** `Prepare SWAG Directories`.
```yaml
- name: Update DNS Records
run: |
set -a; . ./.env; . ./.env.secrets.swag; set +a
FLOATING_IP="${{ vars.PROD_FLOATING_IP }}"
DOMAIN="iklim.co"
for record in api apigw rabbitmq grafana; do
CURRENT=$(curl -s \
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \
2>/dev/null | jq -r '.[0].data // empty' 2>/dev/null || true)
if [ "$CURRENT" = "$FLOATING_IP" ]; then
echo "✅ ${record}.${DOMAIN} → ${FLOATING_IP} (mevcut, atlanıyor)"
else
curl -sf -X PUT \
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \
-H "Content-Type: application/json" \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \
-d "[{\"data\":\"${FLOATING_IP}\",\"ttl\":600}]"
echo "✅ ${record}.${DOMAIN} → ${FLOATING_IP} (eklendi/güncellendi)"
fi
done
working-directory: /workspace/iklim.co
```
> `GODADDY_KEY` ve `GODADDY_SECRET` `.env.secrets.swag`'dan okunur.
> `PROD_FLOATING_IP` Gitea project variable olarak tanımlanmalı (`terraform output prod_floating_ip`).
> `jq` gereklidir — `Update Apt Repository` adımına eklenmiş olmalı: `apt-get install -y gettext tree jq`.
> Her deploy'da çalışır; mevcut ve doğru kayıtlar atlanır (idempotent).
## Step 3 — Add `Prepare SWAG Directories` step
Insert **before** `Bootstrap Vault TLS Placeholder`: Insert **before** `Bootstrap Vault TLS Placeholder`:
@ -67,7 +104,7 @@ Insert **before** `Bootstrap Vault TLS Placeholder`:
> `.env` is sourced first so `API_SUBDOMAIN=api.iklim.co` (prod values) are used. > `.env` is sourced first so `API_SUBDOMAIN=api.iklim.co` (prod values) are used.
> Ensure these vars are in `prod/secrets/iklim.co/.env.prod` on storagebox. > Ensure these vars are in `prod/secrets/iklim.co/.env.prod` on storagebox.
## Step 3 — Add `Wait for etcd` step ## Step 4 — Add `Wait for etcd` step
Insert **after** `Deploy Swarm Stack` and **before** `Run APISIX Init`. Insert **after** `Deploy Swarm Stack` and **before** `Run APISIX Init`.
APISIX reads its entire configuration from etcd; init script will fail silently if etcd is not ready. APISIX reads its entire configuration from etcd; init script will fail silently if etcd is not ready.
@ -90,7 +127,7 @@ APISIX reads its entire configuration from etcd; init script will fail silently
> **Note:** In prod, the standalone `etcd` service from `docker-stack-infra.yml` still runs (Docker Compose overlay files cannot remove services). APISIX currently uses this etcd; the Patroni etcd migration happens via `docker-stack-infra.prod.yml`. The `http://etcd:2379/health` check targets this standalone service and is correct for the current setup. > **Note:** In prod, the standalone `etcd` service from `docker-stack-infra.yml` still runs (Docker Compose overlay files cannot remove services). APISIX currently uses this etcd; the Patroni etcd migration happens via `docker-stack-infra.prod.yml`. The `http://etcd:2379/health` check targets this standalone service and is correct for the current setup.
## Step 4 — Add `Run APISIX Init` step ## Step 5 — Add `Run APISIX Init` step
Insert **after** `Wait for etcd` and **before** `Bootstrap SWAG Certificate`. Insert **after** `Wait for etcd` and **before** `Bootstrap SWAG Certificate`.
@ -115,7 +152,7 @@ Insert **after** `Wait for etcd` and **before** `Bootstrap SWAG Certificate`.
> The init script is idempotent (PUT semantics); safe to re-run on subsequent deploys. > The init script is idempotent (PUT semantics); safe to re-run on subsequent deploys.
> With `replicas: 3` in prod, all APISIX instances read the same etcd state — no per-replica init needed. > With `replicas: 3` in prod, all APISIX instances read the same etcd state — no per-replica init needed.
## Step 5 — Add `Bootstrap SWAG Certificate` step ## Step 6 — Add `Bootstrap SWAG Certificate` step
Insert **after** `Run APISIX Init`: Insert **after** `Run APISIX Init`:
@ -163,7 +200,7 @@ Insert **after** `Run APISIX Init`:
working-directory: /workspace/iklim.co working-directory: /workspace/iklim.co
``` ```
## Step 6 — Add `Run Database Init Scripts` step ## Step 7 — Add `Run Database Init Scripts` step
Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment`. Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment`.
@ -176,7 +213,7 @@ Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment
until docker run --rm --network iklimco-net \ until docker run --rm --network iklimco-net \
-e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \ -e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \
postgis/postgis:17-3.5 \ postgis/postgis:17-3.5 \
pg_isready -h iklimco_postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" -q 2>/dev/null; do pg_isready -h postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" -q 2>/dev/null; do
sleep 5 sleep 5
done done
for sql_file in $(ls ./init/postgresql/*.sql 2>/dev/null | sort); do for sql_file in $(ls ./init/postgresql/*.sql 2>/dev/null | sort); do
@ -184,31 +221,31 @@ Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment
docker run --rm -i --network iklimco-net \ docker run --rm -i --network iklimco-net \
-e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \ -e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \
postgis/postgis:17-3.5 \ postgis/postgis:17-3.5 \
psql -h iklimco_postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" < "$sql_file" psql -h postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" < "$sql_file"
done done
echo "⏳ Waiting for MongoDB..." echo "⏳ Waiting for MongoDB..."
until docker run --rm --network iklimco-net mongo:8 \ until docker run --rm --network iklimco-net mongo:8 \
mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@iklimco_mongodb/admin" \ mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb/admin" \
--eval "db.runCommand({ping:1})" --quiet 2>/dev/null; do --eval "db.runCommand({ping:1})" --quiet 2>/dev/null; do
sleep 5 sleep 5
done done
for js_file in $(ls ./init/mongodb/*.js 2>/dev/null | sort); do for js_file in $(ls ./init/mongodb/*.js 2>/dev/null | sort); do
echo "▶ $(basename "$js_file")" echo "▶ $(basename "$js_file")"
docker run --rm -i --network iklimco-net mongo:8 \ docker run --rm -i --network iklimco-net mongo:8 \
mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@iklimco_mongodb/admin" \ mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb/admin" \
--quiet < "$js_file" --quiet < "$js_file"
done done
echo "✅ Database init scripts completed" echo "✅ Database init scripts completed"
working-directory: /workspace/iklim.co working-directory: /workspace/iklim.co
``` ```
> **Prod-specific:** DB hostnames are `iklimco_postgresql` and `iklimco_mongodb` (Swarm VIP service names). > **Prod-specific:** DB hostnames are `postgresql` and `mongodb` (Swarm VIP service names).
> Test pipeline uses `postgresql` / `mongodb` (unqualified aliases within the same stack). > Test pipeline uses `postgresql` / `mongodb` (unqualified aliases within the same stack).
> SQL and JS files are generated by `Prepare Init Files` step via `init_postgresql` / `init_mongodb` functions in `common-functions.sh`. > SQL and JS files are generated by `Prepare Init Files` step via `init_postgresql` / `init_mongodb` functions in `common-functions.sh`.
> Step is idempotent — scripts use `CREATE IF NOT EXISTS` / `createCollection` semantics. > Step is idempotent — scripts use `CREATE IF NOT EXISTS` / `createCollection` semantics.
## Step 7 — Ensure subdomain env vars are in prod `.env` ## Step 8 — Ensure subdomain env vars are in prod `.env`
Add to `prod/secrets/iklim.co/.env.prod` on storagebox: Add to `prod/secrets/iklim.co/.env.prod` on storagebox:
@ -221,24 +258,27 @@ GRAFANA_SUBDOMAIN=grafana.iklim.co
## Step 8 — Final step order for prod pipeline ## Step 8 — Final step order for prod pipeline
1. Checkout Branch 1. Acquire Deploy Lock
2. Prepare Folders 2. Checkout Branch
3. Set up SSH Key and Add to known_hosts 3. Prepare Folders
4. Update Apt Repository and Install Required Tools 4. Set up SSH Key and Add to known_hosts
5. Fetch Service Secret Files 5. Update Apt Repository and Install Required Tools (`gettext tree jq`)
6. Initialize Servers ← cert scp lines removed 6. Fetch Service Secret Files
7. Upload Updated Secrets to Storagebox 7. Initialize Servers ← cert scp lines removed
8. Provision Vault AppRole IDs and Docker Secrets 8. Upload Updated Secrets to Storagebox
9. Upload Updated Env to Storagebox 9. Provision Vault AppRole IDs and Docker Secrets
10. Prepare Init Files ← cert copy lines removed 10. Upload Updated Env to Storagebox
11. Initialize Docker Swarm 11. Prepare Init Files ← cert copy lines removed
12. Stop Docker Compose Services 12. Initialize Docker Swarm
13. Docker Login to Harbor 13. Stop Docker Compose Services
14. **Prepare SWAG Directories** ← NEW 14. Docker Login to Harbor
15. Bootstrap Vault TLS Placeholder 15. **Update DNS Records** ← NEW (GoDaddy API, idempotent)
16. Deploy Swarm Stack 16. **Prepare SWAG Directories** ← NEW
17. **Wait for etcd** ← NEW 17. Bootstrap Vault TLS Placeholder
18. **Run APISIX Init** ← NEW (`SPRING_PROFILES_ACTIVE=prod`) 18. Deploy Swarm Stack
19. **Bootstrap SWAG Certificate** ← NEW 19. **Wait for etcd** ← NEW
20. **Run Database Init Scripts** ← NEW (`iklimco_postgresql`, `iklimco_mongodb`) 20. **Run APISIX Init** ← NEW (`SPRING_PROFILES_ACTIVE=prod`)
21. Review Environment 21. **Bootstrap SWAG Certificate** ← NEW
22. **Run Database Init Scripts** ← NEW (`postgresql`, `mongodb`)
23. Review Environment
24. Release Deploy Lock

View File

@ -15,7 +15,21 @@ docker service ls --filter label=project=co.iklim
``` ```
All services show `REPLICAS X/X` (target met). All services show `REPLICAS X/X` (target met).
## 2 — SWAG cert is valid ## 2 — Precipitation image directory exists
```bash
ls -ld /mnt/storagebox/precipitation/images
```
Expected: directory exists. This must be created before `iklimco_precipitation-service` is deployed.
```bash
docker volume inspect iklimco_image-data
```
Expected: `Options.device` is `/mnt/storagebox/precipitation/images`.
## 3 — SWAG cert is valid
```bash ```bash
docker exec $(docker ps -q -f name=iklimco_swag) certbot certificates docker exec $(docker ps -q -f name=iklimco_swag) certbot certificates
@ -29,14 +43,14 @@ echo | openssl s_client -connect api.iklim.co:443 -servername api.iklim.co 2>/de
``` ```
Expected: `CN=*.iklim.co`, `notAfter` > 2026-07-15 (cert is Let's Encrypt, not expiring old one). Expected: `CN=*.iklim.co`, `notAfter` > 2026-07-15 (cert is Let's Encrypt, not expiring old one).
## 3 — Public API ## 4 — Public API
```bash ```bash
curl -si https://api.iklim.co/health curl -si https://api.iklim.co/health
``` ```
HTTP 2xx, no TLS errors. HTTP 2xx, no TLS errors.
## 4 — IP restriction working ## 5 — IP restriction working
From a non-whitelisted IP: From a non-whitelisted IP:
```bash ```bash
@ -53,7 +67,7 @@ curl -si https://apigw.iklim.co # HTTP 200 APISIX Dashboard
curl -si https://rabbitmq.iklim.co # HTTP 200 RabbitMQ Management curl -si https://rabbitmq.iklim.co # HTTP 200 RabbitMQ Management
``` ```
## 5 — Vault not reachable externally ## 6 — Vault not reachable externally
```bash ```bash
# From outside — must fail # From outside — must fail
@ -68,14 +82,14 @@ docker exec $(docker ps -q -f name=iklimco_apisix | head -1) \
# Expected: {"sealed":false,...} # Expected: {"sealed":false,...}
``` ```
## 6 — cert-reloader watching ## 7 — cert-reloader watching
```bash ```bash
docker service logs iklimco_cert-reloader --tail 5 docker service logs iklimco_cert-reloader --tail 5
``` ```
Expected: `[cert-reloader] started`, no errors. Expected: `[cert-reloader] started`, no errors.
## 7 — No unexpected published ports ## 8 — No unexpected published ports
```bash ```bash
docker service ls --format "{{.Name}}\t{{.Ports}}" \ docker service ls --format "{{.Name}}\t{{.Ports}}" \
@ -83,7 +97,7 @@ docker service ls --format "{{.Name}}\t{{.Ports}}" \
``` ```
Only `iklimco_swag` should show `*:80->80/tcp, *:443->443/tcp`. Only `iklimco_swag` should show `*:80->80/tcp, *:443->443/tcp`.
## 8 — DB nodes running correct services ## 9 — DB nodes running correct services
```bash ```bash
# Patroni (PostgreSQL HA) stack # Patroni (PostgreSQL HA) stack
@ -104,21 +118,21 @@ docker service ps iklim-db_mongodb-03
All tasks should show node names matching `iklim-db-01`, `iklim-db-02`, or `iklim-db-03` with placement constraint `role=db`. All tasks should show node names matching `iklim-db-01`, `iklim-db-02`, or `iklim-db-03` with placement constraint `role=db`.
## 9 — APISIX replicas ## 10 — APISIX replicas
```bash ```bash
docker service ps iklimco_apisix docker service ps iklimco_apisix
``` ```
Expected: 3 tasks, all `Running`, on different nodes. Expected: 3 tasks, all `Running`, on different nodes.
## 10 — fail2ban active ## 11 — fail2ban active
```bash ```bash
docker exec $(docker ps -q -f name=iklimco_swag) fail2ban-client status docker exec $(docker ps -q -f name=iklimco_swag) fail2ban-client status
``` ```
Expected: multiple jails listed. Expected: multiple jails listed.
## 11 — Microservice health (post-deploy) ## 12 — Microservice health (post-deploy)
After microservices are deployed (separate pipeline), verify via the public API: After microservices are deployed (separate pipeline), verify via the public API:
```bash ```bash

View File

@ -45,9 +45,9 @@ Add after the `apisix-dashboard` service block:
- EMAIL=muratozdemir@tarla.io - EMAIL=muratozdemir@tarla.io
- DNSPROPAGATION=90 - DNSPROPAGATION=90
volumes: volumes:
- swag-vl:/config - ${SWAG_CONFIG_DIR:-swag-vl}:/config
- /opt/iklimco/swag/dns-conf:/config/dns-conf:ro - ${SWAG_DNS_CONF_DIR:-/opt/iklimco/swag/dns-conf}:/config/dns-conf
- /opt/iklimco/swag/site-confs:/config/nginx/site-confs:ro - ${SWAG_SITE_CONFS_DIR:-/opt/iklimco/swag/site-confs}:/config/nginx/site-confs
ports: ports:
- target: 80 - target: 80
published: 80 published: 80
@ -78,8 +78,8 @@ Add after the `swag` service block:
cert-reloader: cert-reloader:
image: docker:27-cli image: docker:27-cli
volumes: volumes:
- swag-vl:/swag-config:ro - ${SWAG_CONFIG_DIR:-swag-vl}:/swag-config:ro
- /opt/iklimco/ssl:/host-ssl - ${SWAG_CERT_DIR:-/opt/iklimco/ssl}:/host-ssl
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
entrypoint: ["/bin/sh", "-c"] entrypoint: ["/bin/sh", "-c"]
command: command:

View File

@ -12,8 +12,8 @@ API_SUBDOMAIN=api-test.iklim.co
APIGW_SUBDOMAIN=apigw-test.iklim.co APIGW_SUBDOMAIN=apigw-test.iklim.co
RABBITMQ_SUBDOMAIN=rabbitmq-test.iklim.co RABBITMQ_SUBDOMAIN=rabbitmq-test.iklim.co
GRAFANA_SUBDOMAIN=grafana-test.iklim.co GRAFANA_SUBDOMAIN=grafana-test.iklim.co
RESTRICTED_IP_1=78.187.87.109 # Comma-separated list of allowed CIDRs for IP-restricted subdomains
RESTRICTED_IP_2=95.70.151.248 RESTRICTED_IPS="78.187.87.109/32,95.70.151.248/32"
``` ```
## Files to create ## Files to create
@ -78,8 +78,7 @@ server {
client_max_body_size 0; client_max_body_size 0;
location / { location / {
allow ${RESTRICTED_IP_1}; ${RESTRICTED_IPS_BLOCK}
allow ${RESTRICTED_IP_2};
deny all; deny all;
include /config/nginx/proxy.conf; include /config/nginx/proxy.conf;
@ -92,6 +91,9 @@ server {
} }
``` ```
> `${RESTRICTED_IPS_BLOCK}` is generated at deploy time from `RESTRICTED_IPS` (comma-separated CIDRs)
> as multi-line `allow` directives with `/32` suffix. See `07-deploy-pipeline-update.md` for the pipeline step.
### `swag/site-confs/rabbitmq.conf.tpl` ### `swag/site-confs/rabbitmq.conf.tpl`
RabbitMQ Management UI — IP restricted. RabbitMQ Management UI — IP restricted.
@ -107,8 +109,7 @@ server {
client_max_body_size 0; client_max_body_size 0;
location / { location / {
allow ${RESTRICTED_IP_1}; ${RESTRICTED_IPS_BLOCK}
allow ${RESTRICTED_IP_2};
deny all; deny all;
include /config/nginx/proxy.conf; include /config/nginx/proxy.conf;
@ -136,8 +137,7 @@ server {
client_max_body_size 0; client_max_body_size 0;
location / { location / {
allow ${RESTRICTED_IP_1}; ${RESTRICTED_IPS_BLOCK}
allow ${RESTRICTED_IP_2};
deny all; deny all;
include /config/nginx/proxy.conf; include /config/nginx/proxy.conf;
@ -153,20 +153,31 @@ server {
## Deploy step (handled by pipeline — see `07-deploy-pipeline-update.md`) ## Deploy step (handled by pipeline — see `07-deploy-pipeline-update.md`)
```bash ```bash
# Process templates and write to host set -a; . ./.env; . ./.env.secrets.swag; set +a
mkdir -p /opt/iklimco/swag/site-confs
set -a; . ./.env; set +a docker run --rm -v /opt/iklimco/swag:/output alpine \
export RESTRICTED_IP_1="78.187.87.109" mkdir -p /output/dns-conf /output/site-confs
export RESTRICTED_IP_2="95.70.151.248"
envsubst < swag/dns-conf/godaddy.ini.tpl | docker run --rm -i \
-v /opt/iklimco/swag/dns-conf:/output \
alpine sh -c "cat > /output/godaddy.ini && chmod 600 /output/godaddy.ini"
# RESTRICTED_IPS → multi-line allow block (indented 8 spaces per nginx style)
export RESTRICTED_IPS_BLOCK="$(echo "$RESTRICTED_IPS" | tr ',' '\n' | sed 's|.*| allow &;|')"
# Explicit var list prevents nginx $upstream_* from being substituted by envsubst
SWAG_VARS='${API_SUBDOMAIN}${APIGW_SUBDOMAIN}${GRAFANA_SUBDOMAIN}${RABBITMQ_SUBDOMAIN}${RESTRICTED_IPS_BLOCK}'
for tpl in swag/site-confs/*.conf.tpl; do for tpl in swag/site-confs/*.conf.tpl; do
out="/opt/iklimco/swag/site-confs/$(basename "${tpl%.tpl}")" fname=$(basename "${tpl%.tpl}")
envsubst < "$tpl" > "$out" envsubst "$SWAG_VARS" < "$tpl" | docker run --rm -i \
echo "✅ $out" -v /opt/iklimco/swag/site-confs:/output \
alpine sh -c "cat > /output/${fname}"
echo "✅ ${fname}"
done done
cp swag/site-confs/default.conf /opt/iklimco/swag/site-confs/default.conf cat swag/site-confs/default.conf | docker run --rm -i \
-v /opt/iklimco/swag/site-confs:/output \
alpine sh -c "cat > /output/default.conf"
``` ```
## Verification ## Verification

View File

@ -36,31 +36,45 @@ Insert this step **before** `Deploy Swarm Stack`:
run: | run: |
set -a; . ./.env; . ./.env.secrets.swag; set +a set -a; . ./.env; . ./.env.secrets.swag; set +a
# GoDaddy credentials file docker run --rm -v /opt/iklimco/swag:/output alpine \
sudo mkdir -p /opt/iklimco/swag/dns-conf mkdir -p /output/dns-conf /output/site-confs
envsubst < swag/dns-conf/godaddy.ini.tpl | sudo tee /opt/iklimco/swag/dns-conf/godaddy.ini > /dev/null
sudo chmod 600 /opt/iklimco/swag/dns-conf/godaddy.ini envsubst < swag/dns-conf/godaddy.ini.tpl | docker run --rm -i \
-v /opt/iklimco/swag/dns-conf:/output \
alpine sh -c "cat > /output/godaddy.ini && chmod 600 /output/godaddy.ini"
echo "✅ godaddy.ini written" echo "✅ godaddy.ini written"
# Nginx site conf files # RESTRICTED_IPS → multi-line allow block (indented 8 spaces per nginx style)
sudo mkdir -p /opt/iklimco/swag/site-confs export RESTRICTED_IPS_BLOCK="$(echo "$RESTRICTED_IPS" | tr ',' '\n' | sed 's|.*| allow &;|')"
export RESTRICTED_IP_1="78.187.87.109"
export RESTRICTED_IP_2="95.70.151.248"
# Explicit var list prevents nginx $upstream_* from being substituted by envsubst
SWAG_VARS='${API_SUBDOMAIN}${APIGW_SUBDOMAIN}${GRAFANA_SUBDOMAIN}${RABBITMQ_SUBDOMAIN}${RESTRICTED_IPS_BLOCK}'
for tpl in swag/site-confs/*.conf.tpl; do for tpl in swag/site-confs/*.conf.tpl; do
out="/opt/iklimco/swag/site-confs/$(basename "${tpl%.tpl}")" fname=$(basename "${tpl%.tpl}")
envsubst < "$tpl" | sudo tee "$out" > /dev/null envsubst "$SWAG_VARS" < "$tpl" | docker run --rm -i \
echo "✅ $out" -v /opt/iklimco/swag/site-confs:/output \
alpine sh -c "cat > /output/${fname}"
echo "✅ ${fname}"
done done
sudo cp swag/site-confs/default.conf /opt/iklimco/swag/site-confs/default.conf cat swag/site-confs/default.conf | docker run --rm -i \
-v /opt/iklimco/swag/site-confs:/output \
alpine sh -c "cat > /output/default.conf"
echo "✅ SWAG directories ready" echo "✅ SWAG directories ready"
SWAG_CTR=$(docker ps -q -f name=iklimco_swag 2>/dev/null | head -1)
if [ -n "$SWAG_CTR" ]; then
docker exec "$SWAG_CTR" nginx -t && docker exec "$SWAG_CTR" nginx -s reload
echo "✅ SWAG nginx reloaded"
fi
working-directory: /workspace/iklim.co working-directory: /workspace/iklim.co
``` ```
> `GODADDY_KEY` and `GODADDY_SECRET` must be present in `.env.secrets.swag` (see step 02). > `GODADDY_KEY` ve `GODADDY_SECRET` `.env.secrets.swag` içinde olmalı (bkz. step 02).
> `API_SUBDOMAIN`, `APIGW_SUBDOMAIN`, etc. must be in `.env` (see step 04). > `API_SUBDOMAIN`, `APIGW_SUBDOMAIN` vb. `.env` içinde olmalı (bkz. step 04).
> Dosyalar `docker run alpine` ile yazılır — host'a `sudo` erişimi gerekmez.
> `RESTRICTED_IPS` comma-separated CIDR listesinden (`env-test/.env`) her satıra `allow` direktifi üretilir.
## Step 3 — Add `Bootstrap SWAG Certificate` step ## Step 3 — Add `Bootstrap SWAG Certificate` step
@ -123,20 +137,22 @@ Final step order in the pipeline:
3. Set up SSH Key 3. Set up SSH Key
4. Update Apt / Install Tools 4. Update Apt / Install Tools
5. Fetch Service Secret Files 5. Fetch Service Secret Files
6. Initialize Servers 6. Initialize Docker Swarm
7. Upload Updated Secrets to Storagebox 7. Initialize Servers
8. Provision Vault AppRole IDs and Docker Secrets 8. Upload Updated Secrets to Storagebox
9. Upload Updated Env to Storagebox 9. Provision Vault AppRole IDs and Docker Secrets
10. Prepare Init Files ← `sudo cp STAR.iklim.co.*.crt` lines removed 10. Upload Updated Env to Storagebox
11. Initialize Docker Swarm 11. Prepare Init Files ← `sudo cp STAR.iklim.co.*.crt` lines removed
12. Stop Docker Compose Services 12. Stop Docker Compose Services
13. Docker Login to Harbor 13. Docker Login to Harbor
14. **Prepare SWAG Directories** ← NEW 14. **Prepare SWAG Directories** ← NEW
15. Deploy Swarm Stack 15. Bootstrap Vault TLS Placeholder
16. **Run APISIX Init** ← NEW (Swarm etcd volume'süz başlar; idempotent PUT) 16. Deploy Swarm Stack
17. **Bootstrap SWAG Certificate** ← NEW 17. **Wait for etcd** ← zaten pipeline'da mevcut; etcd health endpoint'i kontrol eder
18. **Run Database Init Scripts** ← NEW (önceki oturumda eklendi) 18. **Run APISIX Init** ← NEW (`SPRING_PROFILES_ACTIVE=test`)
19. Review Environment 19. **Bootstrap SWAG Certificate** ← NEW
20. **Run Database Init Scripts** ← NEW
21. Review Environment
> Steps 8 (Provision Vault) runs before SWAG because it creates Docker secrets and > Steps 8 (Provision Vault) runs before SWAG because it creates Docker secrets and
> AppRole IDs — Vault must be reachable for this. On re-deploys, Vault is already > AppRole IDs — Vault must be reachable for this. On re-deploys, Vault is already