Add Cloudflare automation, ngrok tunnel, fix Dockerfiles

- Ansible cloudflare.yml: DNS, SSL, HSTS, Brotli, bot fight, SEO bots allow
- Vault: add cloudflare_zone_id
- Workflow: run cloudflare config before deploy
- docker-compose-dev: add ngrok tunnel, vault, minio
- Ngrok sync script: writes OUTSIDE_URL to .env.local
- Fix Dockerfiles: remove mbstring/xml (built-in PHP 8.4), fix libfreetype-dev
- Makefile: maintenance_on/off, clear_prod
- Playbook: stop_prod, install_prod, start_prod, migrate, clear steps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-18 21:06:11 +01:00
parent 507500e20d
commit e3de0da1bf
7 changed files with 266 additions and 13 deletions

View File

@@ -20,5 +20,8 @@ jobs:
chmod 600 ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519
ssh-keyscan 34.90.187.4 >> ~/.ssh/known_hosts ssh-keyscan 34.90.187.4 >> ~/.ssh/known_hosts
- name: Configure Cloudflare
run: ansible-playbook ansible/cloudflare.yml --vault-password-file <(echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}")
- name: Deploy - name: Deploy
run: ansible-playbook -i ansible/hosts.ini ansible/deploy-caddy.yml --vault-password-file <(echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}") run: ansible-playbook -i ansible/hosts.ini ansible/deploy-caddy.yml --vault-password-file <(echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}")

173
ansible/cloudflare.yml Normal file
View File

@@ -0,0 +1,173 @@
---
- name: Configure Cloudflare for ticket.e-cosplay.fr
hosts: localhost
connection: local
vars_files:
- vault.yml
vars:
zone_id: "{{ cloudflare_zone_id }}"
cloudflare_record: ticket.e-cosplay.fr
server_ip: 34.90.187.4
tasks:
# --- DNS ---
- name: Create or update DNS A record
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/dns_records"
method: POST
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
type: A
name: "{{ cloudflare_record }}"
content: "{{ server_ip }}"
ttl: 1
proxied: true
status_code: [200, 409]
register: dns_result
ignore_errors: true
- name: Update DNS A record if already exists
when: dns_result.status == 409 or (dns_result.json is defined and not dns_result.json.success)
block:
- name: Get existing DNS record
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/dns_records?name={{ cloudflare_record }}&type=A"
method: GET
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
return_content: true
register: existing_dns
- name: Update DNS record
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/dns_records/{{ existing_dns.json.result[0].id }}"
method: PUT
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
type: A
name: "{{ cloudflare_record }}"
content: "{{ server_ip }}"
ttl: 1
proxied: true
# --- SSL/TLS ---
- name: Set SSL mode to Full (Strict)
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/ssl"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value: strict
- name: Enable Always Use HTTPS
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/always_use_https"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value: "on"
- name: Set minimum TLS version to 1.2
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/min_tls_version"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value: "1.2"
# --- Security headers ---
- name: Enable HSTS
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/security_header"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value:
strict_transport_security:
enabled: true
max_age: 31536000
include_subdomains: true
nosniff: true
# --- Performance ---
- name: Enable Brotli compression
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/brotli"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value: "on"
- name: Set browser cache TTL to 1 month
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/browser_cache_ttl"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value: 2592000
# --- Security ---
- name: Set security level to medium
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/settings/security_level"
method: PATCH
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
value: medium
- name: Enable bot fight mode
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/bot_management"
method: PUT
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
fight_mode: true
ignore_errors: true
# --- Allow SEO bots ---
- name: Allow Googlebot
uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ zone_id }}/firewall/rules"
method: POST
headers:
Authorization: "Bearer {{ cloudflare_api_token }}"
Content-Type: application/json
body_format: json
body:
- filter:
expression: '(cf.client.bot) or (http.user_agent contains "Googlebot") or (http.user_agent contains "Bingbot") or (http.user_agent contains "bingbot") or (http.user_agent contains "Yandex") or (http.user_agent contains "DuckDuckBot") or (http.user_agent contains "Baiduspider") or (http.user_agent contains "facebookexternalhit") or (http.user_agent contains "Twitterbot") or (http.user_agent contains "LinkedInBot")'
action: allow
description: "Allow SEO and social media bots"
status_code: [200, 409]
ignore_errors: true

View File

@@ -1,8 +1,11 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
34376230633964343735383363613430386439326535303762646264333330383166636539643439 64616263316537643530626465343665623830646361623061333265373065353535643435333632
3663303564386133313965343530383761353837626632390a323831366566356234626166646234 6639663636363630376437323232633662643430643865630a636431653266353930306231383031
64316232613836376264363237346433393931623863653562656164346534663666373364626130 34393965623762356632633262303632316439333464313161383638366331623833666534653930
3833346535373064660a336234373730383438373233623231363335323162326666346136326162 6435656537306566630a333332663632343030643664626261373536393232666262623466643934
65303265386365656164323838666239303639333534626264333962386631323531656262633363 35636534656530623865663737336139386137353738623362393933376563323463346432313562
64333734326466356236633061663933663962646165313935633361356339326366613731613765 36383933306237363936383230303830643030336338353466323933356231343865663663666633
383336626531663034666532636363306130 34386361303033663366356639353933356466333637653436363261613833373664363861633236
65656162643837633336363836663635626164323763323832396236633131393864363064636463
61303636346661373362656561356532646364613937663261333939303865326534616237336335
3737616162653034666634653736393833356331343430653637

View File

@@ -90,6 +90,53 @@ services:
- "1025:1025" - "1025:1025"
- "8025:8025" - "8025:8025"
vault:
image: hashicorp/vault:latest
container_name: e-ticket_vault
cap_add:
- IPC_LOCK
environment:
VAULT_DEV_ROOT_TOKEN_ID: e-ticket
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
ports:
- "8200:8200"
volumes:
- vault-data:/vault/file
minio:
image: minio/minio:latest
container_name: e-ticket_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: e-ticket
MINIO_ROOT_PASSWORD: e-ticket
ports:
- "9090:9000"
- "9001:9001"
volumes:
- minio-data:/data
ngrok:
image: ngrok/ngrok:latest
container_name: e-ticket_ngrok
command: http caddy:80 --log stdout
environment:
NGROK_AUTHTOKEN: GXtZtKtRxRF5TFV5pCKD_25f1ALUyQQ9LkyQJgv1dr
ports:
- "4040:4040"
depends_on:
- caddy
ngrok-sync:
image: curlimages/curl:latest
container_name: e-ticket_ngrok_sync
volumes:
- .:/app
- ./docker/ngrok/sync.sh:/sync.sh
depends_on:
- ngrok
entrypoint: sh /sync.sh
redisinsight: redisinsight:
image: redis/redisinsight:latest image: redis/redisinsight:latest
container_name: e-ticket_redisinsight container_name: e-ticket_redisinsight
@@ -103,3 +150,5 @@ volumes:
db-data: db-data:
redis-data: redis-data:
bun-modules: bun-modules:
vault-data:
minio-data:

29
docker/ngrok/sync.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/sh
set -e
echo "Waiting for ngrok to start..."
sleep 5
NGROK_URL=""
RETRIES=10
while [ -z "$NGROK_URL" ] && [ "$RETRIES" -gt 0 ]; do
NGROK_URL=$(curl -s http://ngrok:4040/api/tunnels | grep -o '"public_url":"https://[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$NGROK_URL" ]; then
echo "Waiting for tunnel..."
sleep 2
RETRIES=$((RETRIES - 1))
fi
done
if [ -z "$NGROK_URL" ]; then
echo "ERROR: Could not get ngrok URL"
exit 1
fi
touch /app/.env.local
sed -i '/^OUTSIDE_URL=/d' /app/.env.local
echo "OUTSIDE_URL=$NGROK_URL" >> /app/.env.local
echo "Ngrok URL: $NGROK_URL"
echo "Written to .env.local"

View File

@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y \
libicu-dev \ libicu-dev \
libpng-dev \ libpng-dev \
libjpeg-dev \ libjpeg-dev \
libfreetype6-dev \ libfreetype-dev \
libmagickwand-dev \ libmagickwand-dev \
unzip \ unzip \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@@ -19,9 +19,7 @@ RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
pdo_pgsql \ pdo_pgsql \
pdo_sqlite \ pdo_sqlite \
zip \ zip \
xml \
intl \ intl \
mbstring \
gd gd
RUN pecl install redis imagick \ RUN pecl install redis imagick \

View File

@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y \
libicu-dev \ libicu-dev \
libpng-dev \ libpng-dev \
libjpeg-dev \ libjpeg-dev \
libfreetype6-dev \ libfreetype-dev \
libmagickwand-dev \ libmagickwand-dev \
unzip \ unzip \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@@ -19,9 +19,7 @@ RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
pdo_pgsql \ pdo_pgsql \
pdo_sqlite \ pdo_sqlite \
zip \ zip \
xml \
intl \ intl \
mbstring \
gd \ gd \
opcache opcache