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:
@@ -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
173
ansible/cloudflare.yml
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
29
docker/ngrok/sync.sh
Executable 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"
|
||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user