Repo complet : scripts, templates, docs et durcissement secrets
- Ajout scripts/ reproduisant chaque étape (secrets, render config, up CLI, init Garage, bootstrap prod, admin, vérif E2E, adoption Coolify, backup) - Templates envsubst (config.yml, garage.toml, cantaloupe.properties) + .example - deploy.conf (config non-secrète : domaine, versions, uuids Coolify) - docs/ : ARCHITECTURE, TROUBLESHOOTING (11 problèmes/fixes), RUNBOOK, SECURITY - Makefile (raccourcis) - .gitignore + retrait du suivi de .env/.secrets.env/config.yml/garage.toml (fichiers conservés sur disque ; secrets restent dans l'historique -> voir SECURITY.md) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0e08632bce
commit
18064be674
5
.env.example
Normal file
5
.env.example
Normal file
@ -0,0 +1,5 @@
|
||||
# Consommé par `docker compose` pour la substitution ${...} dans les compose.
|
||||
# Généré à partir de .secrets.env par scripts/01-render-config.sh. NE PAS committer .env.
|
||||
PG_PASS=CHANGE_ME
|
||||
GK_ID=CHANGE_ME
|
||||
GK_SECRET=CHANGE_ME
|
||||
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# ---- Secrets : NE JAMAIS committer ----
|
||||
.secrets.env
|
||||
.env
|
||||
config.yml
|
||||
garage.toml
|
||||
*.key
|
||||
*.pem
|
||||
.ark_coolify_token
|
||||
|
||||
# ---- Généré / local ----
|
||||
*.log
|
||||
tmp/
|
||||
backups/
|
||||
15
.secrets.env.example
Normal file
15
.secrets.env.example
Normal file
@ -0,0 +1,15 @@
|
||||
# Copie ce fichier en .secrets.env puis génère de VRAIES valeurs (scripts/00-gen-secrets.sh).
|
||||
# NE JAMAIS committer .secrets.env (il est dans .gitignore).
|
||||
|
||||
# Mot de passe PostgreSQL -> openssl rand -hex 16
|
||||
PG_PASS=CHANGE_ME
|
||||
# Django SECRET_KEY (50 chars) -> openssl rand -base64 48 | tr -d '\n/+=' | head -c 50
|
||||
SECRET_KEY=CHANGE_ME
|
||||
# Secret RPC Garage (64 hex) -> openssl rand -hex 32
|
||||
GARAGE_RPC=CHANGE_ME
|
||||
# Token admin Garage -> openssl rand -hex 24
|
||||
GARAGE_ADMIN_TOKEN=CHANGE_ME
|
||||
# Clé d'accès S3 Garage (GK + 24 hex) -> echo GK$(openssl rand -hex 12)
|
||||
GK_ID=CHANGE_ME
|
||||
# Clé secrète S3 Garage (64 hex) -> openssl rand -hex 32
|
||||
GK_SECRET=CHANGE_ME
|
||||
44
Makefile
Normal file
44
Makefile
Normal file
@ -0,0 +1,44 @@
|
||||
# Arkindex sur Coolify — cibles pratiques.
|
||||
# Les scripts détaillés sont dans scripts/. Voir README.md et docs/.
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
SHELL := /bin/bash
|
||||
|
||||
help: ## Affiche cette aide
|
||||
@grep -hE '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-16s\033[0m %s\n",$$1,$$2}'
|
||||
|
||||
secrets: ## Génère .secrets.env (si absent)
|
||||
@scripts/00-gen-secrets.sh
|
||||
|
||||
config: ## Rend config.yml / garage.toml / cantaloupe.properties / .env
|
||||
@scripts/01-render-config.sh
|
||||
|
||||
deploy-cli: ## Déploiement complet en docker compose (CLI)
|
||||
@scripts/10-up-cli.sh
|
||||
|
||||
garage: ## (Ré)initialise Garage (layout, clé, buckets)
|
||||
@scripts/20-garage-init.sh
|
||||
|
||||
init: ## Migrations + ImageServers + farm Ponos
|
||||
@scripts/30-arkindex-init.sh
|
||||
|
||||
admin: ## Crée/maj le compte admin (ADMIN_PASS=... optionnel)
|
||||
@scripts/40-create-admin.sh
|
||||
|
||||
verify: ## Vérification end-to-end (ADMIN_PASS=... pour tester le login)
|
||||
@scripts/50-verify.sh
|
||||
|
||||
coolify-adopt: ## Crée la ressource Coolify (API) — voir sortie pour la bascule
|
||||
@scripts/90-coolify-adopt.sh
|
||||
|
||||
backup: ## Dump PostgreSQL + inventaire Garage -> backups/
|
||||
@scripts/99-backup-db.sh
|
||||
|
||||
ps: ## Liste les conteneurs Arkindex
|
||||
@docker ps --filter name=ark- --format 'table {{.Names}}\t{{.Status}}'
|
||||
|
||||
logs: ## Logs backend (SVC=ark-worker pour un autre service)
|
||||
@docker logs -f $${SVC:-ark-backend}$${CONTAINER_SUFFIX:-}
|
||||
|
||||
.PHONY: help secrets config deploy-cli garage init admin verify coolify-adopt backup ps logs
|
||||
69
README.md
69
README.md
@ -8,8 +8,36 @@ Déploiement d'**Arkindex** (plateforme Teklia d'analyse de documents, édition
|
||||
- **Date** : 2026-07-09.
|
||||
- **Dossier** : `~/arkindex-deploy/`.
|
||||
|
||||
> ⚠️ Les secrets ne figurent PAS dans ce guide. Ils sont dans `.secrets.env` (généré aléatoirement)
|
||||
> et injectés dans `.env`, `config.yml`, `garage.toml`, `cantaloupe.properties`.
|
||||
> ⚠️ Les secrets ne figurent PAS dans le dépôt. Ils sont dans `.secrets.env` (git-ignoré, généré
|
||||
> aléatoirement) et injectés dans `.env`, `config.yml`, `garage.toml`. Voir `docs/SECURITY.md`.
|
||||
|
||||
## Démarrage rapide
|
||||
```bash
|
||||
make secrets # génère .secrets.env
|
||||
make config # rend config.yml / garage.toml / cantaloupe.properties / .env
|
||||
make deploy-cli # déploiement complet en docker compose (CLI)
|
||||
make verify # vérification end-to-end
|
||||
# adoption dans l'UI Coolify :
|
||||
make coolify-adopt # crée la ressource, puis suivre les instructions affichées
|
||||
make help # toutes les cibles
|
||||
```
|
||||
Adapter d'abord le domaine et les versions dans **`deploy.conf`**.
|
||||
|
||||
## Structure du dépôt
|
||||
```
|
||||
README.md ce guide (architecture, procédure, problèmes/fixes)
|
||||
deploy.conf config NON-secrète (domaine, versions, uuids Coolify)
|
||||
docker-compose.yml stack CLI (docker compose)
|
||||
docker-compose.coolify.yml variante adoptée par Coolify (services ark-*, volumes external)
|
||||
config.yml / garage.toml / fichiers de conf RÉELS (git-ignorés — contiennent des secrets ;
|
||||
cantaloupe.properties montés par la stack en cours)
|
||||
templates/*.tmpl templates rendus par scripts/01 (envsubst)
|
||||
scripts/ 00-gen-secrets, 01-render-config, 10-up-cli, 20-garage-init,
|
||||
30-arkindex-init, 40-create-admin, 50-verify, 90-coolify-adopt,
|
||||
99-backup-db, lib.sh
|
||||
docs/ ARCHITECTURE.md, TROUBLESHOOTING.md, RUNBOOK.md, SECURITY.md
|
||||
Makefile raccourcis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -260,17 +288,36 @@ free -h; docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}' | gr
|
||||
|
||||
---
|
||||
|
||||
## 7. Adoption dans l'UI Coolify (étape en attente)
|
||||
## 7. Adoption dans l'UI Coolify — ✅ FAIT
|
||||
|
||||
Utiliser `docker-compose.coolify.yml`. Deux voies :
|
||||
- **API Coolify** : `POST /api/v1/services` avec `docker_compose_raw` (nécessite un token API
|
||||
read/write) — créer un projet « arkindex », y coller le compose, fournir les secrets en variables
|
||||
d'environnement (`PG_PASS`, `GK_ID`, `GK_SECRET`), puis déployer.
|
||||
- **UI** : New Resource → Docker Compose → coller `docker-compose.coolify.yml`.
|
||||
La stack est désormais une ressource **gérée par Coolify** : projet **arkindex**, service **arkindex**
|
||||
(uuid `tdquwauzxsq0985fk9kjqqys`), conteneurs `ark-*-tdquwauzxsq0985fk9kjqqys`. Source =
|
||||
`docker-compose.coolify.yml`. Pilotage (deploy/logs/restart) via le dashboard Coolify.
|
||||
|
||||
Bascule sans perte : arrêter la stack CLI (`docker compose down` **sans `-v`** → les volumes
|
||||
persistent), puis déployer via Coolify qui réutilise les volumes `external`. Prévoir une **coupure de
|
||||
quelques secondes**.
|
||||
### Procédure suivie (API Coolify)
|
||||
```bash
|
||||
TOKEN=$(cat ~/.ark_coolify_token)
|
||||
# 1) projet + récup environnement production
|
||||
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d '{"name":"arkindex"}' http://localhost:8000/api/v1/projects
|
||||
# 2) créer le service (docker_compose_raw en BASE64 obligatoire), instant_deploy=false
|
||||
# body: server_uuid, project_uuid, environment_name/uuid, name, docker_compose_raw(b64)
|
||||
# 3) renseigner les secrets en variables d'env du service (PATCH .../envs) : PG_PASS, GK_ID, GK_SECRET
|
||||
# 4) arrêter la stack CLI puis déployer UNE SEULE FOIS :
|
||||
cd ~/arkindex-deploy && docker compose down # garde les volumes en backup
|
||||
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
|
||||
"http://localhost:8000/api/v1/deploy?uuid=tdquwauzxsq0985fk9kjqqys"
|
||||
```
|
||||
|
||||
### ⚠️ Points d'attention (cf. aussi P11)
|
||||
- **Coolify remappe les volumes nommés** en `<uuid>_*` et **ignore `external:`** → au premier
|
||||
déploiement la stack repart sur des **volumes vides**. Comme il n'y avait pas encore de données
|
||||
réelles, on a simplement **ré-initialisé** (Garage + `migrate` + image servers + admin) contre les
|
||||
conteneurs Coolify (`docker exec ark-backend-<uuid> …`). Les anciens volumes `arkindex-deploy_*`
|
||||
restent en backup (supprimables : `docker volume rm arkindex-deploy_pgdata …`).
|
||||
- **Coolify préserve** : réseau `coolify` externe, labels Traefik, montages bind **absolus**, et les
|
||||
**noms de service** (alias DNS `ark-*`). Il écrase `container_name` en `<service>-<uuid>`.
|
||||
- **Ne déclencher le deploy qu'une fois** (POST *ou* GET, pas les deux) — sinon double déploiement.
|
||||
|
||||
---
|
||||
|
||||
|
||||
40
deploy.conf
Normal file
40
deploy.conf
Normal file
@ -0,0 +1,40 @@
|
||||
# Configuration NON-secrète du déploiement Arkindex/Coolify.
|
||||
# Sourcé par les scripts (scripts/lib.sh). Les SECRETS sont dans .secrets.env (git-ignoré).
|
||||
|
||||
# --- Domaine ---
|
||||
# Hostname racine ; les sous-domaines s3.<DOMAIN>, iiif.<DOMAIN>, uploads.iiif.<DOMAIN>,
|
||||
# ingest.iiif.<DOMAIN> et <bucket>.s3.<DOMAIN> en découlent.
|
||||
DOMAIN="ark.nak0x.dev"
|
||||
|
||||
# --- Versions d'images (dernières finales stables, pas de -rc/-beta) ---
|
||||
BACKEND_TAG="1.12.2"
|
||||
FRONTEND_TAG="1.12.2"
|
||||
TASKS_TAG="0.6.2"
|
||||
CANTALOUPE_TAG="5.0.7"
|
||||
POSTGIS_TAG="17-3.5"
|
||||
SOLR_TAG="9"
|
||||
GARAGE_TAG="v2.1.0"
|
||||
REDIS_TAG="alpine"
|
||||
|
||||
# --- Compte admin ---
|
||||
ADMIN_EMAIL="theolesage38@gmail.com"
|
||||
ADMIN_DISPLAY_NAME="nak0x"
|
||||
|
||||
# --- Registry Teklia (images publiques, pull anonyme) ---
|
||||
REGISTRY="registry.gitlab.teklia.com"
|
||||
|
||||
# --- Buckets S3 Garage (fixes) ---
|
||||
GARAGE_BUCKETS="uploads ingest export iiif-cache ponos-artifacts ponos-logs staging thumbnails training"
|
||||
|
||||
# --- Coolify (identifiants de la ressource déployée ; le TOKEN est hors dépôt) ---
|
||||
COOLIFY_API="http://localhost:8000/api/v1"
|
||||
COOLIFY_TOKEN_FILE="$HOME/.ark_coolify_token"
|
||||
COOLIFY_SERVER_UUID="vedsaj5e96wtmu8jcd3nkxi2"
|
||||
COOLIFY_PROJECT_UUID="h1s06qu51vf7x33axjy94d0e"
|
||||
COOLIFY_ENV_UUID="gqje671dsr1ulvb7y9fw80t2"
|
||||
COOLIFY_ENV_NAME="production"
|
||||
COOLIFY_SERVICE_UUID="tdquwauzxsq0985fk9kjqqys"
|
||||
|
||||
# Suffixe des conteneurs gérés par Coolify (= <service>-<COOLIFY_SERVICE_UUID>).
|
||||
# Vide = mode CLI (conteneurs ark-* sans suffixe).
|
||||
CONTAINER_SUFFIX="-tdquwauzxsq0985fk9kjqqys"
|
||||
76
docs/ARCHITECTURE.md
Normal file
76
docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Architecture — Arkindex sur Coolify
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
```
|
||||
Internet (navigateur)
|
||||
│ HTTPS (Let's Encrypt, HTTP-01)
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ coolify-proxy (Traefik v3) │ :80/:443
|
||||
│ routage par labels Docker │
|
||||
└───────┬───────────┬──────────┘
|
||||
ark.nak0x.dev │ │ s3.nak0x… / iiif.nak0x…
|
||||
(split par chemin) │ │
|
||||
┌───────────────┬────────┘ └──────────┬──────────────┐
|
||||
▼ ▼ ▼ ▼
|
||||
ark-front ark-backend ark-garage ark-cantaloupe
|
||||
(SPA nginx) (API gunicorn) (S3) (IIIF)
|
||||
│ │ \ ▲ │
|
||||
│ │ \ hairpin S3 │ http interne│ http interne
|
||||
│ │ └──► https://s3.<DOMAIN> ───┘ :3900 │ :3900
|
||||
│ ▼ │
|
||||
│ ark-worker (RQ/Ponos) │
|
||||
│ │ ▼
|
||||
│ ┌──────┴──────┬──────────┐ (lit les images
|
||||
▼ ▼ ▼ ▼ dans les buckets)
|
||||
[réseau "ark"] ark-database ark-redis ark-solr
|
||||
(Postgres) (RQ+cache) (recherche)
|
||||
```
|
||||
|
||||
## Réseaux Docker
|
||||
- **`ark`** (interne) : tous les services ; communication par **nom de service** (`ark-database`,
|
||||
`ark-redis`, …). Ces noms sont **uniques** exprès (cf. Troubleshooting P4).
|
||||
- **`coolify`** (externe, partagé) : uniquement `ark-garage`, `ark-cantaloupe`, `ark-backend`,
|
||||
`ark-front`, `ark-worker` — pour être joignables par le proxy Traefik et pour le hairpin S3.
|
||||
- En mode Coolify, un 3ᵉ réseau par ressource (`<uuid>`) est ajouté automatiquement.
|
||||
|
||||
## TLS & hostnames (contrainte centrale)
|
||||
Coolify n'a que le challenge **HTTP-01** → **pas de certificat wildcard**. Or Arkindex sert S3 et
|
||||
IIIF en **virtual-hosted** (`<bucket>.s3.<DOMAIN>`, `<x>.iiif.<DOMAIN>`). Comme l'ensemble des
|
||||
hostnames est **fini et connu**, on les énumère dans les règles `Host(...)` Traefik → Traefik émet
|
||||
un certificat HTTP-01 par hostname. Le **wildcard DNS** `*.nak0x.dev` (résolution, pas TLS) fait
|
||||
pointer tous ces noms vers le serveur, donc **zéro entrée DNS à créer**.
|
||||
|
||||
Hostnames servis :
|
||||
- `ark.<DOMAIN>` — front (catch-all, priorité 1) + backend (`/api`,`/api-docs`,`/admin`,`/rq`,`/static`, priorité 100)
|
||||
- `s3.<DOMAIN>` + `<bucket>.s3.<DOMAIN>` × 9 buckets — Garage
|
||||
- `iiif.<DOMAIN>`, `uploads.iiif.<DOMAIN>`, `ingest.iiif.<DOMAIN>` — Cantaloupe
|
||||
|
||||
## Stockage S3 (Garage)
|
||||
- Le backend utilise boto3 avec `addressing_style="auto"` (codé en dur) → **virtual-hosted**
|
||||
(`<bucket>.s3.<DOMAIN>`). D'où l'énumération des sous-domaines de bucket + `root_domain` dans
|
||||
`garage.toml` (Garage mappe sous-domaine → bucket).
|
||||
- **Hairpin** : le backend doit joindre `https://s3.<DOMAIN>` (les URLs présignées données au
|
||||
navigateur utilisent ce host) : requête → IP publique → `coolify-proxy` (TLS valide) → Garage.
|
||||
Vérifié fonctionnel ici.
|
||||
- Cantaloupe, lui, parle à Garage **en interne** (`http://ark-garage:3900`, sans TLS).
|
||||
- 9 buckets : `uploads, ingest, export, iiif-cache, ponos-artifacts, ponos-logs, staging, thumbnails, training`.
|
||||
- Clé S3 : importée dans Garage (admin API/CLI) = `GK_ID`/`GK_SECRET` de `.secrets.env`.
|
||||
|
||||
## IIIF (Cantaloupe)
|
||||
- Image custom Teklia : `ScriptLookupStrategy` + `S3Source.top_domain = iiif.<DOMAIN>` → le bucket
|
||||
est déduit du sous-domaine (`uploads.iiif.<DOMAIN>` → bucket `uploads`).
|
||||
- 2 `ImageServer` en base : `12345` (uploads) et `67890` (ingest), URLs `https://…iiif.<DOMAIN>/iiif/2`.
|
||||
|
||||
## Recherche (Solr)
|
||||
- SolrCloud (`solr -f -cloud`). Les collections sont créées **par corpus** (`project-<uuid>`) à la
|
||||
volée lors de l'indexation (`arkindex reindex`). Rien à pré-créer.
|
||||
|
||||
## Images (versions)
|
||||
`backend/frontend 1.12.2`, `tasks 0.6.2`, `cantaloupe 5.0.7`, `postgis 17-3.5`, `solr 9`,
|
||||
`garage v2.1.0`, `redis alpine`. Toutes **publiques** (pull anonyme).
|
||||
|
||||
## Bootstrap : pourquoi on ne l'utilise pas
|
||||
`arkindex bootstrap` est **dev-only** : il refuse si `DEBUG=False` et code les URLs en
|
||||
`*.ark.localhost`. On réplique ses 3 actions avec le vrai domaine (`scripts/20` + `scripts/30`).
|
||||
76
docs/RUNBOOK.md
Normal file
76
docs/RUNBOOK.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Runbook — exploitation (day-2)
|
||||
|
||||
> En mode Coolify, les conteneurs ont le suffixe `-tdquwauzxsq0985fk9kjqqys` (cf. `deploy.conf`
|
||||
> `CONTAINER_SUFFIX`). Les helpers `scripts/*.sh` le gèrent automatiquement via `cname`.
|
||||
|
||||
## Consulter / piloter
|
||||
```bash
|
||||
docker ps --filter name=ark- # état
|
||||
docker logs -f ark-backend-<suffix> # logs backend
|
||||
docker logs -f ark-worker-<suffix> # logs worker (tâches)
|
||||
# depuis Coolify : dashboard -> projet arkindex -> service arkindex (Deploy / Logs / Restart)
|
||||
```
|
||||
|
||||
## Redémarrer / redéployer
|
||||
```bash
|
||||
# CLI :
|
||||
cd ~/arkindex-deploy && docker compose restart backend
|
||||
# Coolify (API) :
|
||||
curl -s -X POST -H "Authorization: Bearer $(cat ~/.ark_coolify_token)" \
|
||||
"http://localhost:8000/api/v1/deploy?uuid=tdquwauzxsq0985fk9kjqqys"
|
||||
```
|
||||
|
||||
## Mettre à jour Arkindex (nouvelle version d'image)
|
||||
1. Vérifier la dernière finale (cf. Troubleshooting P1).
|
||||
2. Changer les tags dans `docker-compose.coolify.yml` (backend/front) + `deploy.conf`.
|
||||
3. En Coolify : mettre à jour le compose du service (UI ou API) puis redéployer.
|
||||
4. **Toujours** relancer les migrations :
|
||||
```bash
|
||||
docker exec ark-backend-<suffix> arkindex migrate
|
||||
```
|
||||
|
||||
## Shell Django / admin
|
||||
```bash
|
||||
docker exec -it ark-backend-<suffix> arkindex shell
|
||||
docker exec -it ark-backend-<suffix> arkindex createsuperuser # (interactif)
|
||||
scripts/40-create-admin.sh # (scripté, idempotent)
|
||||
```
|
||||
|
||||
## Recherche (Solr)
|
||||
```bash
|
||||
# (re)indexer un corpus après import de données
|
||||
docker exec ark-backend-<suffix> arkindex reindex --corpus-id <UUID> --drop
|
||||
# tout réindexer
|
||||
docker exec ark-backend-<suffix> arkindex reindex --all
|
||||
```
|
||||
|
||||
## Stockage S3 (Garage)
|
||||
```bash
|
||||
G=ark-garage-<suffix>
|
||||
docker exec $G /garage bucket list
|
||||
docker exec $G /garage bucket info staging
|
||||
docker exec $G /garage stats
|
||||
```
|
||||
|
||||
## Sauvegardes
|
||||
```bash
|
||||
scripts/99-backup-db.sh # pg_dump + inventaire buckets -> backups/
|
||||
# volumes gérés par Coolify : arkindex-deploy_* (anciens, backup) et <uuid>_* (actifs)
|
||||
docker volume ls | grep -E 'arkindex-deploy_|tdquwauzxsq'
|
||||
```
|
||||
|
||||
## Nettoyage des anciens volumes CLI (après validation Coolify)
|
||||
```bash
|
||||
docker volume rm arkindex-deploy_pgdata arkindex-deploy_redisdata \
|
||||
arkindex-deploy_solrdata arkindex-deploy_garagedata
|
||||
```
|
||||
|
||||
## Santé / vérification complète
|
||||
```bash
|
||||
ADMIN_PASS='…' scripts/50-verify.sh
|
||||
```
|
||||
|
||||
## Mémoire serrée ?
|
||||
- Soupape : désactiver la recherche → `features.search: no` dans `config.yml`, retirer le service
|
||||
`ark-solr`, redéployer. Solr est le plus gros consommateur (~450 Mo).
|
||||
- Le swap (4 Go) absorbe les pics ; surveiller `free -h` et `docker stats`.
|
||||
97
docs/TROUBLESHOOTING.md
Normal file
97
docs/TROUBLESHOOTING.md
Normal file
@ -0,0 +1,97 @@
|
||||
# Troubleshooting — problèmes rencontrés & diagnostics
|
||||
|
||||
Récapitulatif des 11 problèmes du déploiement (symptôme → cause → fix), + commandes de diagnostic
|
||||
réutilisables. Voir aussi `README.md` §5.
|
||||
|
||||
---
|
||||
|
||||
## P1 — `manifest unknown` au pull d'une image
|
||||
**Symptôme** : `docker pull …/backend:1.13.0` → `manifest unknown`.
|
||||
**Cause** : `1.13.0` n'existe qu'en `-rc1` ; l'extraction du cœur numérique inventait une version.
|
||||
**Fix** : n'utiliser que des **finales** (dernière = `1.12.2`). Vérifier :
|
||||
```bash
|
||||
tok=$(curl -s "https://gitlab.teklia.com/jwt/auth?service=container_registry&scope=repository:arkindex/backend:pull" | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
|
||||
curl -s -H "Authorization: Bearer $tok" https://registry.gitlab.teklia.com/v2/arkindex/backend/tags/list \
|
||||
| tr ',' '\n' | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -uV | tail
|
||||
```
|
||||
|
||||
## P2 — Pas de certificat wildcard possible
|
||||
**Cause** : Coolify = challenge HTTP-01 seulement.
|
||||
**Fix** : énumérer chaque hostname dans les règles `Host(...)` (Garage, Cantaloupe). Diag certs :
|
||||
```bash
|
||||
for h in ark s3.ark staging.s3.ark uploads.iiif.ark; do
|
||||
curl -sS -o /dev/null -w "$h.nak0x.dev -> %{http_code} tls=%{ssl_verify_result}\n" "https://$h.nak0x.dev/"
|
||||
done # tls=0 = certificat valide
|
||||
```
|
||||
|
||||
## P3 — `PermissionError: /arkindex.yml`
|
||||
**Cause** : fichiers montés en `600` (uid 1000), backend en uid **2000**.
|
||||
**Fix** : `chmod 644 config.yml garage.toml cantaloupe.properties` (fait par `scripts/01`).
|
||||
|
||||
## P4 — Worker : `redis AuthenticationError` ⭐
|
||||
**Symptôme** : « HELLO must be called with the client already authenticated ».
|
||||
**Cause** : sur le réseau `coolify`, le nom générique `redis` résout vers **`coolify-redis`**
|
||||
(protégé par mot de passe), pas le nôtre.
|
||||
**Diagnostic** :
|
||||
```bash
|
||||
docker exec ark-worker-<suffix> getent hosts redis # IP de coolify-redis (mauvais)
|
||||
docker exec ark-worker-<suffix> getent hosts ark-redis # IP correcte
|
||||
```
|
||||
**Fix** : référencer les services par leur **nom unique** `ark-redis`/`ark-database`/`ark-solr`/
|
||||
`ark-garage` (jamais `redis`/`db`). Appliqué dans `config.yml` et `cantaloupe.properties`.
|
||||
|
||||
## P5 — `bootstrap` refuse en prod
|
||||
**Cause** : `You cannot run this script in production.` + URLs `*.ark.localhost` codées en dur.
|
||||
**Fix** : réplique manuelle → `scripts/20-garage-init.sh` + `scripts/30-arkindex-init.sh`.
|
||||
|
||||
## P6 — S3 en virtual-host (et non path-style)
|
||||
**Cause** : backend force `addressing_style="auto"` (boto3) → `<bucket>.s3.<DOMAIN>`.
|
||||
**Fix** : énumérer les 9 sous-domaines de bucket + `root_domain` dans `garage.toml`.
|
||||
**Test** :
|
||||
```bash
|
||||
docker exec -i ark-backend-<suffix> arkindex shell <<'PY'
|
||||
from arkindex.project.aws import s3
|
||||
o=s3.Bucket("staging").Object("t"); o.put(Body=b"x"); print(o.get()["Body"].read()); o.delete()
|
||||
PY
|
||||
```
|
||||
|
||||
## P7 — Trop de workers gunicorn
|
||||
**Cause** : `nproc*2+1` = 9.
|
||||
**Fix** : `MAX_WORKERS: "3"` dans l'env du backend.
|
||||
|
||||
## P8 — Statics admin/swagger non stylés
|
||||
**Cause** : pas de WhiteNoise ; statics servis par CDN.
|
||||
**Fix** : `static.cdn_assets_url: https://assets.teklia.com/arkindex`. Cosmétique (API/SPA OK).
|
||||
|
||||
## P9 — `reindex --setup` demande un corpus
|
||||
**Cause** : collections Solr créées **par corpus** à la volée. Rien à pré-créer.
|
||||
|
||||
## P10 — Backend sans Pillow (tests)
|
||||
**Cause** : traitement image côté workers. Pour un test : générer le JPEG hors backend +
|
||||
uploader via un conteneur `amazon/aws-cli` (cf. `scripts/50-verify.sh`).
|
||||
|
||||
## P11 — Adoption Coolify : volumes vidés + container_name écrasé
|
||||
**Causes** :
|
||||
1. Coolify **remappe** les volumes nommés en `<uuid>_*` et **ignore `external:`** → volumes vides.
|
||||
2. Coolify écrase `container_name` en `<service>-<uuid>` (mais garde le **nom de service** = alias DNS).
|
||||
**Fix** :
|
||||
1. Sur un déploiement neuf : ré-init (`scripts/20/30/40`) contre les conteneurs Coolify. Pour des
|
||||
données existantes : utiliser des **bind-mounts absolus** (dossiers hôte) au lieu de volumes nommés.
|
||||
2. Nommer les **services** `ark-*` (déjà unique → pas de collision P4).
|
||||
**Piège** : ne déclencher le deploy **qu'une fois** (POST *ou* GET `/api/v1/deploy?uuid=`).
|
||||
|
||||
---
|
||||
|
||||
## Diagnostics rapides
|
||||
```bash
|
||||
# logs d'un service
|
||||
docker logs --tail 50 ark-backend-<suffix>
|
||||
# résolution DNS depuis un conteneur
|
||||
docker exec ark-worker-<suffix> getent hosts ark-redis
|
||||
# état santé
|
||||
docker inspect ark-backend-<suffix> --format '{{.State.Health.Status}}'
|
||||
# mémoire par conteneur
|
||||
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}' | grep ark-
|
||||
# routers Traefik vus par le proxy
|
||||
docker exec coolify-proxy wget -qO- http://localhost:8080/api/http/routers 2>/dev/null | head
|
||||
```
|
||||
20
scripts/00-gen-secrets.sh
Executable file
20
scripts/00-gen-secrets.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Génère .secrets.env avec des valeurs aléatoires (idempotent : ne réécrit pas si déjà présent).
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
if [ -f "$ROOT/.secrets.env" ]; then
|
||||
warn ".secrets.env existe déjà — rien à faire (supprime-le pour régénérer)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Génération des secrets…"
|
||||
umask 077
|
||||
cat > "$ROOT/.secrets.env" <<EOF
|
||||
PG_PASS=$(openssl rand -hex 16)
|
||||
SECRET_KEY=$(openssl rand -base64 48 | tr -d '\n/+=' | head -c 50)
|
||||
GARAGE_RPC=$(openssl rand -hex 32)
|
||||
GARAGE_ADMIN_TOKEN=$(openssl rand -hex 24)
|
||||
GK_ID=GK$(openssl rand -hex 12)
|
||||
GK_SECRET=$(openssl rand -hex 32)
|
||||
EOF
|
||||
ok ".secrets.env généré (mode 600)."
|
||||
27
scripts/01-render-config.sh
Executable file
27
scripts/01-render-config.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rend config.yml, garage.toml, cantaloupe.properties et .env à partir des templates + secrets.
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
load_secrets
|
||||
|
||||
command -v envsubst >/dev/null || { echo "❌ 'envsubst' requis (paquet gettext)"; exit 1; }
|
||||
|
||||
log "Rendu des fichiers de configuration (DOMAIN=$DOMAIN)…"
|
||||
export DOMAIN SECRET_KEY PG_PASS GK_ID GK_SECRET GARAGE_RPC GARAGE_ADMIN_TOKEN
|
||||
|
||||
envsubst '${DOMAIN} ${SECRET_KEY} ${PG_PASS} ${GK_ID} ${GK_SECRET}' \
|
||||
< "$ROOT/templates/config.yml.tmpl" > "$ROOT/config.yml"
|
||||
envsubst '${DOMAIN} ${GARAGE_RPC} ${GARAGE_ADMIN_TOKEN}' \
|
||||
< "$ROOT/templates/garage.toml.tmpl" > "$ROOT/garage.toml"
|
||||
envsubst '${DOMAIN}' \
|
||||
< "$ROOT/templates/cantaloupe.properties.tmpl" > "$ROOT/cantaloupe.properties"
|
||||
|
||||
cat > "$ROOT/.env" <<EOF
|
||||
PG_PASS=${PG_PASS}
|
||||
GK_ID=${GK_ID}
|
||||
GK_SECRET=${GK_SECRET}
|
||||
EOF
|
||||
|
||||
# Le backend tourne en uid 2000 : les fichiers montés doivent être lisibles (644).
|
||||
chmod 644 "$ROOT/config.yml" "$ROOT/garage.toml" "$ROOT/cantaloupe.properties"
|
||||
chmod 600 "$ROOT/.env"
|
||||
ok "config.yml, garage.toml, cantaloupe.properties, .env générés."
|
||||
33
scripts/10-up-cli.sh
Executable file
33
scripts/10-up-cli.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Déploiement complet en mode "docker compose" (CLI), de A à Z.
|
||||
# Prérequis : Docker + réseau externe "coolify" (proxy Traefik) + DNS *.<DOMAIN> -> serveur.
|
||||
# NB : ce script force le mode CLI (conteneurs sans suffixe Coolify).
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
export CONTAINER_SUFFIX="" # mode CLI
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
log "1/6 Secrets + configs"
|
||||
scripts/00-gen-secrets.sh
|
||||
scripts/01-render-config.sh
|
||||
|
||||
log "2/6 Infra de base (db, redis, solr, garage)"
|
||||
docker compose up -d db redis solr garage
|
||||
# attendre Postgres
|
||||
until docker inspect ark-database --format '{{.State.Health.Status}}' 2>/dev/null | grep -q healthy; do sleep 3; done
|
||||
|
||||
log "3/6 Init Garage"
|
||||
scripts/20-garage-init.sh
|
||||
|
||||
log "4/6 Backend + migrations + bootstrap prod"
|
||||
docker compose up -d backend
|
||||
until docker inspect ark-backend --format '{{.State.Health.Status}}' 2>/dev/null | grep -q healthy; do sleep 3; done
|
||||
scripts/30-arkindex-init.sh
|
||||
|
||||
log "5/6 Reste de la stack (cantaloupe, front, worker)"
|
||||
docker compose up -d cantaloupe front worker
|
||||
|
||||
log "6/6 Compte admin"
|
||||
scripts/40-create-admin.sh
|
||||
|
||||
ok "Déploiement CLI terminé. Vérifie avec scripts/50-verify.sh"
|
||||
30
scripts/20-garage-init.sh
Executable file
30
scripts/20-garage-init.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Initialise Garage : layout du cluster (1 nœud), import de la clé S3, création + droits des buckets.
|
||||
# Idempotent. Fonctionne en mode CLI ou Coolify (via CONTAINER_SUFFIX).
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
load_secrets
|
||||
|
||||
G=$(cname ark-garage)
|
||||
log "Init Garage ($G)…"
|
||||
|
||||
NODE=$(docker exec "$G" /garage status 2>/dev/null | awk '/NO ROLE ASSIGNED/{print $1}')
|
||||
if [ -n "${NODE:-}" ]; then
|
||||
docker exec "$G" /garage layout assign -z dev -c 1G "$NODE" >/dev/null
|
||||
# applique la version courante+1 (1 si cluster neuf)
|
||||
VER=$(docker exec "$G" /garage layout show 2>/dev/null | awk '/Current cluster layout version/{print $NF}')
|
||||
docker exec "$G" /garage layout apply --version "$(( ${VER:-0} + 1 ))" >/dev/null
|
||||
ok "Layout appliqué."
|
||||
else
|
||||
warn "Nœud déjà configuré (layout existant)."
|
||||
fi
|
||||
|
||||
# Import de la clé S3 (même GK_ID/GK_SECRET que config.yml)
|
||||
docker exec "$G" /garage key import --yes -n arkindex "$GK_ID" "$GK_SECRET" >/dev/null 2>&1 \
|
||||
&& ok "Clé S3 importée." || warn "Clé S3 déjà présente."
|
||||
|
||||
for b in $GARAGE_BUCKETS; do
|
||||
docker exec "$G" /garage bucket create "$b" >/dev/null 2>&1 || true
|
||||
docker exec "$G" /garage bucket allow --read --write --owner --key "$GK_ID" "$b" >/dev/null 2>&1 || true
|
||||
done
|
||||
N=$(docker exec "$G" /garage bucket list 2>/dev/null | grep -c '20' || true)
|
||||
ok "Buckets prêts ($N)."
|
||||
26
scripts/30-arkindex-init.sh
Executable file
26
scripts/30-arkindex-init.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Migrations DB + équivalent PROD de `arkindex bootstrap` (dev-only) :
|
||||
# crée les 2 ImageServers (uploads/ingest) avec le VRAI domaine + la farm Ponos.
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
log "Migrations…"
|
||||
dex ark-backend arkindex migrate 2>&1 | tail -2
|
||||
ok "Migrations appliquées."
|
||||
|
||||
log "ImageServers + farm Ponos (domaine=$DOMAIN)…"
|
||||
dexi ark-backend env DOMAIN="$DOMAIN" arkindex shell <<'PYEOF' 2>&1 | tail -3
|
||||
import os
|
||||
from arkindex.images.models import ImageServer
|
||||
from arkindex.ponos.models import Farm
|
||||
d = os.environ["DOMAIN"]
|
||||
for sid, url, bucket, name in [
|
||||
(12345, f"https://uploads.iiif.{d}/iiif/2", "uploads", "Local uploads IIIF server"),
|
||||
(67890, f"https://ingest.iiif.{d}/iiif/2", "ingest", "Ingest IIIF server"),
|
||||
]:
|
||||
ImageServer.objects.update_or_create(id=sid, defaults=dict(url=url, s3_bucket=bucket, s3_region="local", display_name=name))
|
||||
Farm.objects.update_or_create(
|
||||
id="001e411a-1111-2222-3333-444455556666",
|
||||
defaults=dict(name="Bootstrap farm", seed="b12868101dab84984481741663d809d2393784894d6e807ceee0bd95051bf971"))
|
||||
print("ImageServers + farm OK")
|
||||
PYEOF
|
||||
ok "Bootstrap (prod) terminé."
|
||||
25
scripts/40-create-admin.sh
Executable file
25
scripts/40-create-admin.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Crée (ou met à jour) le compte admin avec toutes les capacités. Génère un mot de passe si non fourni.
|
||||
# Usage: ADMIN_PASS='monmotdepasse' scripts/40-create-admin.sh (sinon mot de passe aléatoire)
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
PASS="${ADMIN_PASS:-$(openssl rand -base64 18 | tr -d '/+=' | head -c 20)}"
|
||||
log "Compte admin $ADMIN_EMAIL…"
|
||||
dexi ark-backend env E="$ADMIN_EMAIL" P="$PASS" N="$ADMIN_DISPLAY_NAME" arkindex shell <<'PYEOF' 2>&1 | tail -2
|
||||
import os
|
||||
from django.contrib.auth import get_user_model
|
||||
U = get_user_model()
|
||||
email, pw, name = os.environ["E"], os.environ["P"], os.environ["N"]
|
||||
u = U.objects.filter(email=email).first() or U.objects.create_superuser(email=email, display_name=name, password=pw)
|
||||
u.set_password(pw); u.is_admin = True; u.verified_email = True
|
||||
for f in ["can_upload_s3_image","can_create_iiif_image","can_ingest","can_manage_workers","can_create_worker_version","can_validate_images"]:
|
||||
setattr(u, f, True)
|
||||
u.save()
|
||||
print("admin OK:", u.email, "is_admin =", u.is_admin)
|
||||
PYEOF
|
||||
ok "Admin prêt."
|
||||
echo "########################################"
|
||||
echo " Email : $ADMIN_EMAIL"
|
||||
echo " Password : $PASS"
|
||||
echo " (à changer après la 1ère connexion)"
|
||||
echo "########################################"
|
||||
44
scripts/50-verify.sh
Executable file
44
scripts/50-verify.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vérifications end-to-end : HTTPS/API, S3 (hairpin), pipeline IIIF, état conteneurs, RAM.
|
||||
# Usage: [ADMIN_PASS=... ] scripts/50-verify.sh
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
load_secrets
|
||||
|
||||
echo "=== 1) HTTPS public + certificat ==="
|
||||
curl -sS -o /dev/null -w " health -> HTTP %{http_code} (tls_verify=%{ssl_verify_result})\n" \
|
||||
"https://${DOMAIN}/api/v1/health/"
|
||||
|
||||
if [ -n "${ADMIN_PASS:-}" ]; then
|
||||
echo "=== 2) Login admin ==="
|
||||
curl -sS -X POST "https://${DOMAIN}/api/v1/user/login/" -H 'Content-Type: application/json' \
|
||||
-d "{\"email\":\"${ADMIN_EMAIL}\",\"password\":\"${ADMIN_PASS}\"}" \
|
||||
-o /dev/null -w " login -> HTTP %{http_code}\n"
|
||||
fi
|
||||
|
||||
echo "=== 3) S3 hairpin (backend write/read/delete via https://s3.${DOMAIN}) ==="
|
||||
dexi ark-backend arkindex shell <<'PY' 2>&1 | tail -2
|
||||
from arkindex.project.aws import s3
|
||||
o=s3.Bucket("staging").Object("healthcheck.txt")
|
||||
o.put(Body=b"ok"); print(" S3 read:", o.get()["Body"].read()); o.delete(); print(" S3 delete: ok")
|
||||
PY
|
||||
|
||||
echo "=== 4) Pipeline IIIF (upload image de test -> Cantaloupe) ==="
|
||||
TMP=$(mktemp -d)
|
||||
docker run --rm -v "$TMP":/out alpine sh -c "apk add -q imagemagick imagemagick-jpeg && magick -size 400x300 xc:'#1e78c8' /out/p.jpg" >/dev/null 2>&1
|
||||
docker run --rm --network ark -v "$TMP/p.jpg":/img.jpg:ro \
|
||||
-e AWS_ACCESS_KEY_ID="$GK_ID" -e AWS_SECRET_ACCESS_KEY="$GK_SECRET" \
|
||||
amazon/aws-cli --endpoint-url http://ark-garage:3900 --region local \
|
||||
s3 cp /img.jpg s3://uploads/_verify/p.jpg >/dev/null 2>&1
|
||||
curl -sS -o /dev/null -w " IIIF info.json -> HTTP %{http_code}\n" \
|
||||
"https://uploads.iiif.${DOMAIN}/iiif/2/_verify%2Fp.jpg/info.json"
|
||||
curl -sS -o /dev/null -w " IIIF render -> HTTP %{http_code}\n" \
|
||||
"https://uploads.iiif.${DOMAIN}/iiif/2/_verify%2Fp.jpg/full/200,/0/default.jpg"
|
||||
docker run --rm --network ark -e AWS_ACCESS_KEY_ID="$GK_ID" -e AWS_SECRET_ACCESS_KEY="$GK_SECRET" \
|
||||
amazon/aws-cli --endpoint-url http://ark-garage:3900 --region local s3 rm s3://uploads/_verify/p.jpg >/dev/null 2>&1
|
||||
rm -rf "$TMP"
|
||||
|
||||
echo "=== 5) Conteneurs ==="
|
||||
docker ps --filter "name=ark-" --format ' {{.Names}} {{.Status}}' | sort
|
||||
|
||||
echo "=== 6) RAM ==="
|
||||
free -h | awk 'NR==2{printf " used=%s free=%s dispo=%s\n",$3,$4,$7}'
|
||||
53
scripts/90-coolify-adopt.sh
Executable file
53
scripts/90-coolify-adopt.sh
Executable file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# Adopte la stack comme ressource "Docker Compose" gérée par Coolify (via l'API).
|
||||
# Prérequis : token API Coolify dans $COOLIFY_TOKEN_FILE (Settings -> API Tokens, read/write).
|
||||
#
|
||||
# ⚠️ Coolify remappe les volumes nommés en <uuid>_* et IGNORE `external:` -> la stack repart sur
|
||||
# des volumes VIDES. Sur un déploiement neuf : ré-init ensuite (20/30/40). Sur une stack avec
|
||||
# données : préférer des bind-mounts absolus (host dirs) plutôt que des volumes nommés.
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
load_secrets
|
||||
[ -f "$COOLIFY_TOKEN_FILE" ] || { echo "❌ Token Coolify absent ($COOLIFY_TOKEN_FILE)"; exit 1; }
|
||||
|
||||
COMPOSE_FILE="$ROOT/docker-compose.coolify.yml"
|
||||
|
||||
log "1) Projet 'arkindex' (si absent)"
|
||||
if [ -z "${COOLIFY_PROJECT_UUID:-}" ]; then
|
||||
COOLIFY_PROJECT_UUID=$(capi POST /projects -d '{"name":"arkindex","description":"Arkindex full stack"}' \
|
||||
| python3 -c 'import sys,json;print(json.load(sys.stdin)["uuid"])')
|
||||
echo " project_uuid=$COOLIFY_PROJECT_UUID (à reporter dans deploy.conf)"
|
||||
fi
|
||||
|
||||
log "2) Création du service (docker_compose_raw en BASE64)"
|
||||
BODY=$(python3 - "$COMPOSE_FILE" <<PY
|
||||
import base64,json,sys
|
||||
raw=open(sys.argv[1]).read()
|
||||
print(json.dumps({
|
||||
"server_uuid":"$COOLIFY_SERVER_UUID","project_uuid":"$COOLIFY_PROJECT_UUID",
|
||||
"environment_name":"$COOLIFY_ENV_NAME","environment_uuid":"$COOLIFY_ENV_UUID",
|
||||
"name":"arkindex","description":"Arkindex full stack",
|
||||
"docker_compose_raw":base64.b64encode(raw.encode()).decode(),"instant_deploy":False}))
|
||||
PY
|
||||
)
|
||||
RESP=$(capi POST /services -d "$BODY")
|
||||
SVC=$(echo "$RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("uuid",""))' 2>/dev/null)
|
||||
[ -n "$SVC" ] || { echo "❌ Échec création service: $RESP"; exit 1; }
|
||||
echo " service_uuid=$SVC (à reporter dans deploy.conf : COOLIFY_SERVICE_UUID + CONTAINER_SUFFIX=-$SVC)"
|
||||
|
||||
log "3) Variables d'env (secrets) sur le service"
|
||||
for K in PG_PASS GK_ID GK_SECRET; do
|
||||
V="${!K}"
|
||||
capi PATCH "/services/$SVC/envs" -d "{\"key\":\"$K\",\"value\":\"$V\"}" >/dev/null \
|
||||
|| capi POST "/services/$SVC/envs" -d "{\"key\":\"$K\",\"value\":\"$V\"}" >/dev/null
|
||||
done
|
||||
ok "Env posées."
|
||||
|
||||
warn "Étape suivante MANUELLE (coupure) :"
|
||||
echo " cd $ROOT && docker compose down # arrête la stack CLI (garde les volumes)"
|
||||
echo " # UNE SEULE fois :"
|
||||
echo " curl -s -X POST -H \"Authorization: Bearer \$(cat $COOLIFY_TOKEN_FILE)\" \\"
|
||||
echo " \"$COOLIFY_API/deploy?uuid=$SVC\""
|
||||
echo " # puis ré-init sur les volumes neufs :"
|
||||
echo " CONTAINER_SUFFIX=-$SVC scripts/20-garage-init.sh"
|
||||
echo " CONTAINER_SUFFIX=-$SVC scripts/30-arkindex-init.sh"
|
||||
echo " CONTAINER_SUFFIX=-$SVC scripts/40-create-admin.sh"
|
||||
14
scripts/99-backup-db.sh
Executable file
14
scripts/99-backup-db.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sauvegarde PostgreSQL (pg_dump) + liste des buckets Garage. Sortie dans backups/.
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
mkdir -p "$ROOT/backups"
|
||||
TS=$(date +%Y%m%d-%H%M%S)
|
||||
OUT="$ROOT/backups/arkindex-db-$TS.sql.gz"
|
||||
|
||||
log "Dump PostgreSQL -> $OUT"
|
||||
dex ark-database pg_dump -U arkindex arkindex | gzip > "$OUT"
|
||||
ok "Dump DB : $(du -h "$OUT" | cut -f1)"
|
||||
|
||||
log "Inventaire Garage"
|
||||
dex ark-garage /garage bucket list 2>/dev/null | tee "$ROOT/backups/garage-buckets-$TS.txt"
|
||||
ok "Backup terminé dans backups/"
|
||||
39
scripts/lib.sh
Executable file
39
scripts/lib.sh
Executable file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fonctions et variables partagées par les scripts de déploiement.
|
||||
# Usage : source "$(dirname "$0")/lib.sh"
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Config non-secrète
|
||||
[ -f "$ROOT/deploy.conf" ] && . "$ROOT/deploy.conf"
|
||||
|
||||
# Secrets (optionnels selon le script)
|
||||
load_secrets() {
|
||||
if [ ! -f "$ROOT/.secrets.env" ]; then
|
||||
echo "❌ $ROOT/.secrets.env manquant. Lance d'abord scripts/00-gen-secrets.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
set -a; . "$ROOT/.secrets.env"; set +a
|
||||
}
|
||||
|
||||
# Nom réel d'un conteneur (avec suffixe Coolify si défini)
|
||||
# ex: cname ark-backend -> ark-backend-tdquwauzxsq0985fk9kjqqys (mode Coolify)
|
||||
# -> ark-backend (mode CLI)
|
||||
cname() { echo "$1${CONTAINER_SUFFIX:-}"; }
|
||||
|
||||
# docker exec sur un service Arkindex
|
||||
dex() { local svc="$1"; shift; docker exec "$(cname "$svc")" "$@"; }
|
||||
dexi() { local svc="$1"; shift; docker exec -i "$(cname "$svc")" "$@"; }
|
||||
|
||||
log() { printf '\033[1;34m▶ %s\033[0m\n' "$*"; }
|
||||
ok() { printf '\033[1;32m✅ %s\033[0m\n' "$*"; }
|
||||
warn() { printf '\033[1;33m⚠ %s\033[0m\n' "$*"; }
|
||||
|
||||
coolify_token() { cat "${COOLIFY_TOKEN_FILE}"; }
|
||||
capi() { # capi METHOD PATH [curl-args...]
|
||||
local m="$1" p="$2"; shift 2
|
||||
curl -s -X "$m" -H "Authorization: Bearer $(coolify_token)" \
|
||||
-H "Content-Type: application/json" -H "Accept: application/json" \
|
||||
"$@" "${COOLIFY_API}${p}"
|
||||
}
|
||||
39
templates/cantaloupe.properties.tmpl
Normal file
39
templates/cantaloupe.properties.tmpl
Normal file
@ -0,0 +1,39 @@
|
||||
# Template config Cantaloupe (serveur IIIF). Rendu par scripts/01-render-config.sh (envsubst).
|
||||
# Variable attendue : DOMAIN.
|
||||
# - Cantaloupe lit les images dans Garage EN INTERNE (http://ark-garage:3900, sans TLS).
|
||||
# - Le bucket est déduit du sous-domaine de la requête via ScriptLookupStrategy + top_domain
|
||||
# (ex: uploads.iiif.<DOMAIN> -> bucket "uploads"). Le script delegate est fourni par l'image.
|
||||
# - Les identifiants S3 viennent des variables d'env AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY.
|
||||
|
||||
http.enabled = true
|
||||
http.host = 0.0.0.0
|
||||
http.port = 80
|
||||
http.http2.enabled = false
|
||||
https.enabled = false
|
||||
|
||||
endpoint.iiif.1.enabled = false
|
||||
endpoint.iiif.2.enabled = true
|
||||
|
||||
source.static = S3Source
|
||||
S3Source.endpoint = http://ark-garage:3900
|
||||
S3Source.region = local
|
||||
S3Source.top_domain = iiif.${DOMAIN}
|
||||
S3Source.lookup_strategy = ScriptLookupStrategy
|
||||
S3Source.BasicLookupStrategy.bucket.name =
|
||||
|
||||
cache.server.derivative.enabled = true
|
||||
cache.server.derivative = S3Cache
|
||||
cache.server.derivative.ttl_seconds = 604800
|
||||
S3Cache.endpoint = http://ark-garage:3900
|
||||
S3Cache.region = local
|
||||
S3Cache.bucket.name = iiif-cache
|
||||
|
||||
log.application.level = info
|
||||
log.application.ConsoleAppender.enabled = true
|
||||
log_error_responses = true
|
||||
|
||||
HeapCache.target_size = 512M
|
||||
HeapCache.persist = false
|
||||
|
||||
processor.downscale_filter = lanczos3
|
||||
processor.upscale_filter = lanczos3
|
||||
74
templates/config.yml.tmpl
Normal file
74
templates/config.yml.tmpl
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
# Template de la configuration Arkindex. Rendu par scripts/01-render-config.sh (envsubst).
|
||||
# Variables attendues : DOMAIN, SECRET_KEY, PG_PASS, GK_ID, GK_SECRET.
|
||||
# IMPORTANT : les services internes sont référencés par leur NOM DE CONTENEUR UNIQUE
|
||||
# (ark-database, ark-redis, ark-solr) et jamais par db/redis/solr — sinon collision DNS
|
||||
# avec coolify-redis/coolify-db sur le réseau partagé "coolify".
|
||||
|
||||
arkindex_env: production
|
||||
public_hostname: https://${DOMAIN}
|
||||
local_imageserver_id: 12345
|
||||
secret_key: "${SECRET_KEY}"
|
||||
|
||||
cache:
|
||||
type: redis
|
||||
url: redis://ark-redis:6379/1
|
||||
|
||||
database:
|
||||
host: ark-database
|
||||
port: 5432
|
||||
name: arkindex
|
||||
user: arkindex
|
||||
password: "${PG_PASS}"
|
||||
|
||||
redis:
|
||||
host: ark-redis
|
||||
port: 6379
|
||||
|
||||
s3:
|
||||
access_key_id: "${GK_ID}"
|
||||
secret_access_key: "${GK_SECRET}"
|
||||
endpoint: https://s3.${DOMAIN}
|
||||
region: local
|
||||
|
||||
allowed_hosts:
|
||||
- ${DOMAIN}
|
||||
|
||||
session:
|
||||
cookie_domain: ${DOMAIN}
|
||||
cookie_secure: true
|
||||
|
||||
csrf:
|
||||
cookie_domain: ${DOMAIN}
|
||||
cookie_secure: true
|
||||
trusted_origins:
|
||||
- 'https://${DOMAIN}'
|
||||
|
||||
cors:
|
||||
origin_whitelist:
|
||||
- https://${DOMAIN}
|
||||
|
||||
features:
|
||||
signup: no
|
||||
search: yes
|
||||
|
||||
solr:
|
||||
api_url: http://ark-solr:8983/solr/
|
||||
|
||||
static:
|
||||
cdn_assets_url: https://assets.teklia.com/arkindex
|
||||
frontend_version: 1.12.2
|
||||
|
||||
ponos:
|
||||
default_env:
|
||||
ARKINDEX_API_URL: https://${DOMAIN}/api/v1/
|
||||
|
||||
ingest:
|
||||
access_key_id: "${GK_ID}"
|
||||
secret_access_key: "${GK_SECRET}"
|
||||
endpoint: https://s3.${DOMAIN}
|
||||
region: local
|
||||
imageserver_id: 67890
|
||||
extra_buckets:
|
||||
- ingest
|
||||
prefix_by_bucket_name: false
|
||||
23
templates/garage.toml.tmpl
Normal file
23
templates/garage.toml.tmpl
Normal file
@ -0,0 +1,23 @@
|
||||
# Template de la config Garage (S3). Rendu par scripts/01-render-config.sh (envsubst).
|
||||
# Variables attendues : DOMAIN, GARAGE_RPC, GARAGE_ADMIN_TOKEN.
|
||||
# root_domain permet l'adressage "virtual-hosted" <bucket>.s3.<DOMAIN> (imposé par boto3
|
||||
# addressing_style=auto côté backend).
|
||||
|
||||
metadata_dir = "/var/lib/garage/meta"
|
||||
data_dir = "/var/lib/garage/data"
|
||||
db_engine = "sqlite"
|
||||
|
||||
replication_factor = 1
|
||||
|
||||
rpc_bind_addr = "[::]:3901"
|
||||
rpc_public_addr = "127.0.0.1:3901"
|
||||
rpc_secret = "${GARAGE_RPC}"
|
||||
|
||||
[s3_api]
|
||||
s3_region = "local"
|
||||
api_bind_addr = "[::]:3900"
|
||||
root_domain = ".s3.${DOMAIN}"
|
||||
|
||||
[admin]
|
||||
api_bind_addr = "[::]:3903"
|
||||
admin_token = "${GARAGE_ADMIN_TOKEN}"
|
||||
Loading…
Reference in New Issue
Block a user