Rattrapage pb de gitea
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
This commit is contained in:
@@ -1,125 +0,0 @@
|
|||||||
# CI/CD — Pipelines Gitea Actions
|
|
||||||
|
|
||||||
Bonsai API et Bonsai Webapp disposent chacune de trois workflows Gitea Actions : `ci.yml`, `release.yml` et `rollback.yml`. **Luz** dispose d'un workflow `release.yml` à déclencheur automatique sur `main`. Le runner Gitea s'exécute sur le même serveur que les applications et a accès direct au socket Docker (`/var/run/docker.sock`), ce qui lui permet de piloter les conteneurs sans passer par SSH.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow CI (`ci.yml`)
|
|
||||||
|
|
||||||
**Déclencheur** : tout `push` sur n'importe quelle branche, et toute pull request vers `main`.
|
|
||||||
|
|
||||||
### Bonsai API
|
|
||||||
|
|
||||||
| Étape | Ce qu'elle fait |
|
|
||||||
|---|---|
|
|
||||||
| **Checkout** | Clone le dépôt |
|
|
||||||
| **Cache Java 25** | Vérifie si le JDK est déjà présent dans le cache du runner. Si oui, le téléchargement est sauté (~11 min économisées) |
|
|
||||||
| **Install Java 25** | *(seulement si cache absent)* Télécharge et extrait le JDK 25 (Temurin, build Alpine) dans `/opt/` |
|
|
||||||
| **Set up Java 25** | Exporte `JAVA_HOME` et ajoute le JDK au `PATH` pour les étapes suivantes |
|
|
||||||
| **Run tests** | Lance `./gradlew test` — la suite de tests échoue le workflow si un test est rouge |
|
|
||||||
| **Upload test report** | *(seulement en cas d'échec)* Publie le rapport HTML de Gradle comme artifact téléchargeable dans l'interface Gitea |
|
|
||||||
|
|
||||||
### Bonsai Webapp
|
|
||||||
|
|
||||||
| Étape | Ce qu'elle fait |
|
|
||||||
|---|---|
|
|
||||||
| **Checkout** | Clone le dépôt |
|
|
||||||
| **Setup Node.js** | Installe Node.js 22 et active le cache npm automatique |
|
|
||||||
| **Install dependencies** | Lance `npm ci` pour une installation reproductible (utilise le cache npm si disponible) |
|
|
||||||
| **Run tests with coverage** | Lance `npm test -- --watch=false` — exécute Vitest avec rapport de couverture |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow Release (`release.yml`)
|
|
||||||
|
|
||||||
**Déclencheur** : publication d'une release Gitea (bouton "Publish release" sur un tag `vX.Y.Z`).
|
|
||||||
|
|
||||||
### Bonsai API
|
|
||||||
|
|
||||||
| Étape | Ce qu'elle fait |
|
|
||||||
|---|---|
|
|
||||||
| **Checkout** | Clone le dépôt avec l'historique complet (`fetch-depth: 0`) — nécessaire pour le merge vers `main` |
|
|
||||||
| **Cache Java 25** | Même logique que le CI |
|
|
||||||
| **Install Java 25** | *(si cache absent)* Installe le JDK |
|
|
||||||
| **Set up Java 25** | Configure `JAVA_HOME` et `PATH` |
|
|
||||||
| **Bump version in build.gradle** | Remplace la version dans `build.gradle` par le tag de la release (ex. `v1.2.3` → `1.2.3`). Modification locale uniquement, non commitée |
|
|
||||||
| **Build JAR** | Lance `./gradlew build -x test` pour produire le JAR avec la bonne version. Les tests sont skippés (déjà passés en CI) |
|
|
||||||
| **Set up Docker Buildx** | Active Buildx pour le build multi-plateforme avec support du cache registry |
|
|
||||||
| **Login to Gitea registry** | Authentifie le runner sur `git.goutailler-olivier.com` avec le token `RELEASE_TOKEN` |
|
|
||||||
| **Set lowercase repo name** | Normalise le nom du repo en minuscules (requis par Docker) |
|
|
||||||
| **Build and push** | Build l'image Docker et la pousse avec deux tags : `vX.Y.Z` (archive) et `latest` (production). Utilise le cache registry pour accélérer les builds suivants |
|
|
||||||
| **Backup database before deployment** | Via le socket Docker, exécute `pg_dump` dans le conteneur `bonsai-api-db` et sauvegarde le dump compressé dans `/opt/backups/bonsai-api/`. **Bloque le déploiement si le backup échoue.** Conserve les 10 derniers backups |
|
|
||||||
| **Trigger production deployment** | Appelle le webhook Watchtower — Watchtower détecte la nouvelle image `:latest` et redémarre le conteneur API |
|
|
||||||
| **Merge release to main** | Restore `build.gradle` (pour annuler la modification locale), puis merge la branche de release dans `main` avec un commit de merge `--no-ff` |
|
|
||||||
| **Bump version to next SNAPSHOT on develop** | Incrémente le patch sur `develop` (ex. `1.2.3` → `1.2.4-SNAPSHOT`) et pousse le commit |
|
|
||||||
|
|
||||||
### Bonsai Webapp
|
|
||||||
|
|
||||||
| Étape | Ce qu'elle fait |
|
|
||||||
|---|---|
|
|
||||||
| **Checkout** | Clone le dépôt |
|
|
||||||
| **Set up Docker Buildx** | Active Buildx avec cache registry |
|
|
||||||
| **Login to Gitea registry** | Authentifie le runner |
|
|
||||||
| **Set lowercase repo name** | Normalise le nom du repo |
|
|
||||||
| **Bump version in package.json** | Met à jour le champ `version` dans `package.json` avec le tag de release |
|
|
||||||
| **Build and push** | Build l'image Docker (inclut le `npm run build` via le Dockerfile) et pousse `vX.Y.Z` + `latest` |
|
|
||||||
| **Trigger production deployment** | Appelle le webhook Watchtower |
|
|
||||||
| **Merge release to main** | Merge la branche de release dans `main` |
|
|
||||||
| **Bump version to next SNAPSHOT on develop** | Incrémente le patch sur `develop` dans `package.json` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow Rollback (`rollback.yml`)
|
|
||||||
|
|
||||||
**Déclencheur** : manuel via **Actions → Rollback → Run workflow** dans l'interface Gitea.
|
|
||||||
|
|
||||||
**Paramètres** :
|
|
||||||
- `version` *(obligatoire)* — version cible, ex. `v1.2.3`
|
|
||||||
- `restore_db` *(API seulement, défaut : `no`)* — `yes` pour restaurer aussi la base de données
|
|
||||||
|
|
||||||
| Étape | Ce qu'elle fait |
|
|
||||||
|---|---|
|
|
||||||
| **Login to Gitea registry** | Authentifie le runner sur le registry |
|
|
||||||
| **Set lowercase repo name** | Normalise le nom du repo |
|
|
||||||
| **Retag version as latest** | Pull l'image `vX.Y.Z`, la retague en `latest` et la repousse — Watchtower déploiera cette version |
|
|
||||||
| **Restore database backup** | *(si `restore_db=yes`)* Arrête le conteneur API, supprime et recrée la base de données, restaure le dump compressé correspondant à la version cible depuis `/opt/backups/bonsai-api/` |
|
|
||||||
| **Trigger production deployment** | Appelle le webhook Watchtower pour redéployer avec l'image rétrogradée |
|
|
||||||
|
|
||||||
> **Voir aussi** : [Mise à jour des applications](Mise-a-jour-Applications) pour la procédure de rollback complète et la gestion des backups.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow Release Luz (`release.yml`)
|
|
||||||
|
|
||||||
**Déclencheur** : tout `push` sur `main`.
|
|
||||||
|
|
||||||
**Versionnage automatique** : le workflow lit le dernier tag `vX.Y.Z`, incrémente le patch (`Z+1`) et crée un nouveau tag. Si aucun tag n'existe, démarre à `v0.1.0`.
|
|
||||||
|
|
||||||
| Étape | Ce qu'elle fait |
|
|
||||||
|---|---|
|
|
||||||
| **Tests & couverture** | *(job `test`)* Lance `npm ci` puis `npm test -- --watch=false` — bloque la release si les tests échouent |
|
|
||||||
| **Checkout** | Clone avec `fetch-depth: 0` pour récupérer tous les tags |
|
|
||||||
| **Calculate next version** | Calcule le prochain tag en incrémentant le patch du dernier tag |
|
|
||||||
| **Bump version in package.json** | Met à jour le champ `version`, commite et pousse sur `main` |
|
|
||||||
| **Set up Docker Buildx** | Active Buildx avec cache registry |
|
|
||||||
| **Login to Gitea registry** | Authentifie le runner sur `git.goutailler-olivier.com` avec `RELEASE_TOKEN` |
|
|
||||||
| **Build and push** | Build l'image Docker et pousse `vX.Y.Z` + `latest` avec cache registry |
|
|
||||||
| **Create Gitea release** | Crée la release sur Gitea via l'API avec le tag calculé |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Secrets requis
|
|
||||||
|
|
||||||
| Secret | Utilisé par | Rôle |
|
|
||||||
|---|---|---|
|
|
||||||
| `RELEASE_TOKEN` | `release.yml`, `rollback.yml` | Token Gitea avec accès en écriture au registry et au dépôt (push sur `main` et `develop`) |
|
|
||||||
| `WATCHTOWER_TOKEN` | `release.yml`, `rollback.yml` | Token Bearer pour l'API HTTP de Watchtower |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Infrastructure du runner
|
|
||||||
|
|
||||||
Le runner (`gitea-runner`) est configuré dans `Infra/gitea/gitea-compose.yml` avec :
|
|
||||||
- **Label** `ubuntu-latest:host` — les jobs s'exécutent directement dans le conteneur runner
|
|
||||||
- **Socket Docker monté** (`/var/run/docker.sock`) — accès direct aux conteneurs de l'hôte sans SSH
|
|
||||||
- **Dossier backups monté** (`/opt/backups`) — les backups écrits par le runner sont persistés sur l'hôte
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## 2026-06-05 — Workflow release automatique pour Luz
|
|
||||||
|
|
||||||
- `CI-CD.md` — documentation du workflow `release.yml` de Luz : déclencheur push main, versionnage automatique, jobs test + release
|
|
||||||
|
|
||||||
## 2026-06-05 — Ajout du projet Luz
|
|
||||||
|
|
||||||
- `Home.md` — ajout de Luz dans l'architecture et le tableau des services
|
|
||||||
- `Installation-Production.md` — section 7 Luz avec commandes de démarrage et secrets requis
|
|
||||||
- `Mise-a-jour-Applications.md` — procédure de rollback Luz, Luz ajouté dans la boucle de mise à jour globale
|
|
||||||
|
|
||||||
## 2026-06-02 — Fix CI release Bonsai-webapp : bump version commité avant merge main
|
|
||||||
|
|
||||||
- `Mise-a-jour-Applications.md` — documentation du bug (package.json modifié non commité bloquait git checkout main) et de la correction (commit du bump avant le merge, sauvegarde du RELEASE_HEAD)
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Infrastructure — Vue d'ensemble
|
|
||||||
|
|
||||||
Ce dépôt contient toutes les configurations Docker Compose de l'infrastructure auto-hébergée.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Traefik (reverse proxy, TLS Let's Encrypt)
|
|
||||||
│
|
|
||||||
├── git.goutailler-olivier.com → Gitea (forge + CI/CD)
|
|
||||||
├── auth.goutailler-olivier.com → Keycloak (SSO / OAuth2)
|
|
||||||
├── bonsai.goutailler-olivier.com
|
|
||||||
│ ├── /api → Bonsai API (Spring Boot)
|
|
||||||
│ └── / → Bonsai Webapp (front-end)
|
|
||||||
├── luz.goutailler-olivier.com → Luz (front-end Angular)
|
|
||||||
├── cloud.goutailler-olivier.com → Nextcloud
|
|
||||||
└── notes.goutailler-olivier.com → Trilium
|
|
||||||
```
|
|
||||||
|
|
||||||
## Services
|
|
||||||
|
|
||||||
| Service | Dossier | URL |
|
|
||||||
|---|---|---|
|
|
||||||
| Traefik | `traefik/` | `traefik.goutailler-olivier.com` |
|
|
||||||
| Gitea | `gitea/` | `git.goutailler-olivier.com` |
|
|
||||||
| Keycloak | `keycloak/` | `auth.goutailler-olivier.com` |
|
|
||||||
| Bonsai API | `bonsai-api/` | `bonsai.goutailler-olivier.com/api` |
|
|
||||||
| Bonsai Webapp | `bonsai-webapp/` | `bonsai.goutailler-olivier.com` |
|
|
||||||
| Luz | `luz/` | `luz.goutailler-olivier.com` |
|
|
||||||
| Nextcloud | `nextcloud/` | `cloud.goutailler-olivier.com` |
|
|
||||||
| Trilium | `trilium/` | `notes.goutailler-olivier.com` |
|
|
||||||
|
|
||||||
## Pages
|
|
||||||
|
|
||||||
- [Installation Production](Installation-Production)
|
|
||||||
- [Installation Développement](Installation-Developpement)
|
|
||||||
- [Mise à jour des applications](Mise-a-jour-Applications)
|
|
||||||
- [CI/CD — Pipelines Gitea Actions](CI-CD)
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
# Installation Développement
|
|
||||||
|
|
||||||
Cette page décrit comment démarrer l'environnement de développement local pour le projet **Bonsai API**.
|
|
||||||
|
|
||||||
## Prérequis
|
|
||||||
|
|
||||||
| Outil | Version minimale |
|
|
||||||
|---|---|
|
|
||||||
| Java JDK | 25 |
|
|
||||||
| Docker + Docker Compose | 24+ |
|
|
||||||
| Gradle | 8+ (wrapper inclus dans le dépôt) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cloner le dépôt
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://git.goutailler-olivier.com/bonsai/bonsai-api.git
|
|
||||||
cd bonsai-api
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Option A — Hot-reload avec Docker Compose (recommandé)
|
|
||||||
|
|
||||||
Cette option monte les sources depuis l'hôte dans le conteneur. Gradle détecte les changements et relance automatiquement l'application.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose -f docker-compose.dev.yml up --build
|
|
||||||
```
|
|
||||||
|
|
||||||
- L'API est disponible sur `http://localhost:8080`
|
|
||||||
- PostgreSQL est disponible sur `localhost:5432`
|
|
||||||
- Le cache Gradle est conservé dans un volume dédié (`gradle_home`) : le premier build télécharge les dépendances, les suivants sont rapides
|
|
||||||
|
|
||||||
Pour arrêter et tout supprimer (y compris les volumes) :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose -f docker-compose.dev.yml down -v
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Option B — Gradle en local + PostgreSQL Docker
|
|
||||||
|
|
||||||
Démarrer uniquement la base de données :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up db -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Lancer l'API avec le wrapper Gradle :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew bootRun
|
|
||||||
```
|
|
||||||
|
|
||||||
Flyway applique automatiquement la migration `V1__init.sql` au premier démarrage.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Variables d'environnement
|
|
||||||
|
|
||||||
En développement, les valeurs par défaut sont utilisées automatiquement (définies dans `src/main/resources/application.yml`). Aucun `.env` n'est nécessaire.
|
|
||||||
|
|
||||||
| Variable | Valeur locale | Description |
|
|
||||||
|---|---|---|
|
|
||||||
| `DATASOURCE_URL` | `jdbc:postgresql://localhost:5432/bonsai` | URL JDBC |
|
|
||||||
| `DATASOURCE_USERNAME` | `bonsai` | Utilisateur PostgreSQL |
|
|
||||||
| `DATASOURCE_PASSWORD` | `bonsai` | Mot de passe PostgreSQL |
|
|
||||||
| `KEYCLOAK_JWKS_URI` | `https://auth.goutailler-olivier.com/realms/bonsai/protocol/openid-connect/certs` | Endpoint JWKS |
|
|
||||||
| `CORS_ALLOWED_ORIGIN_PROD` | `https://bonsai.goutailler-olivier.com` | Origine CORS de prod |
|
|
||||||
|
|
||||||
L'origine `http://localhost:4200` est toujours autorisée en CORS (front Angular en dev).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation de l'API
|
|
||||||
|
|
||||||
Swagger UI est disponible à l'adresse suivante une fois l'API démarrée :
|
|
||||||
|
|
||||||
```
|
|
||||||
http://localhost:8080/swagger-ui.html
|
|
||||||
```
|
|
||||||
|
|
||||||
La spécification OpenAPI (JSON) est accessible sur :
|
|
||||||
|
|
||||||
```
|
|
||||||
http://localhost:8080/v3/api-docs
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lancer les tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew test
|
|
||||||
```
|
|
||||||
|
|
||||||
Le rapport HTML est généré dans `build/reports/tests/test/index.html`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Structure du projet
|
|
||||||
|
|
||||||
```
|
|
||||||
src/main/java/fr/bonsai/api/
|
|
||||||
├── domain/model/ # Entités métier (sans dépendance Spring)
|
|
||||||
├── application/
|
|
||||||
│ ├── port/in/ # Interfaces des use cases
|
|
||||||
│ ├── port/out/ # Interface du repository
|
|
||||||
│ └── usecase/ # Logique métier (IssueService)
|
|
||||||
├── adapter/
|
|
||||||
│ ├── in/web/ # Controllers REST et DTOs
|
|
||||||
│ └── out/persistence/ # Entités JPA et adaptateur repository
|
|
||||||
└── config/ # Configuration Spring (Security, CORS, Beans)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sécurité
|
|
||||||
|
|
||||||
Toutes les routes nécessitent un token JWT Bearer valide émis par Keycloak :
|
|
||||||
|
|
||||||
- **Realm** : `bonsai`
|
|
||||||
- **Client** : `bonsai-webapp`
|
|
||||||
- **Issuer** : `https://auth.goutailler-olivier.com/realms/bonsai`
|
|
||||||
|
|
||||||
Pour tester sans Keycloak local, utiliser l'instance de production comme fournisseur JWKS (valeur par défaut).
|
|
||||||
|
|
||||||
```http
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Endpoints disponibles
|
|
||||||
|
|
||||||
| Méthode | Route | Description |
|
|
||||||
|---|---|---|
|
|
||||||
| `GET` | `/issues` | Liste toutes les issues |
|
|
||||||
| `POST` | `/issues` | Crée une issue |
|
|
||||||
| `PUT` | `/issues/{id}` | Remplace une issue |
|
|
||||||
| `DELETE` | `/issues/{id}` | Supprime une issue (204) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CI/CD
|
|
||||||
|
|
||||||
Le pipeline Gitea Actions (`.gitea/workflows/`) construit l'image Docker et la pousse sur le registre interne à chaque push sur `main`. Le runner `ubuntu-latest` est fourni par le conteneur `act_runner` de la stack Gitea.
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
# Installation Production
|
|
||||||
|
|
||||||
## Prérequis
|
|
||||||
|
|
||||||
- Serveur Linux avec Docker 24+ et Docker Compose V2
|
|
||||||
- Domaine DNS pointant vers le serveur (`goutailler-olivier.com`)
|
|
||||||
- Ports **80**, **443** et **2222** ouverts en entrée
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Réseau Docker partagé
|
|
||||||
|
|
||||||
Tous les services communiquent via un réseau externe `proxy`. À créer une seule fois :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker network create proxy
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Traefik
|
|
||||||
|
|
||||||
Traefik est le point d'entrée unique : il gère le TLS (Let's Encrypt) et route les requêtes vers chaque service.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd traefik/
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Le dashboard est exposé sur `https://traefik.goutailler-olivier.com` (accès restreint par défaut).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Keycloak
|
|
||||||
|
|
||||||
Keycloak gère l'authentification SSO pour toute l'infrastructure.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd keycloak/
|
|
||||||
cp .env.example .env
|
|
||||||
# Éditer .env avec des mots de passe sécurisés
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Variables à définir dans `.env` :
|
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
|---|---|
|
|
||||||
| `POSTGRES_PASSWORD` | Mot de passe de la base PostgreSQL |
|
|
||||||
| `KEYCLOAK_ADMIN` | Login administrateur (défaut : `admin`) |
|
|
||||||
| `KEYCLOAK_ADMIN_PASSWORD` | Mot de passe administrateur |
|
|
||||||
|
|
||||||
**Configuration post-démarrage :**
|
|
||||||
|
|
||||||
1. Se connecter à `https://auth.goutailler-olivier.com/admin`
|
|
||||||
2. Créer le realm `bonsai`
|
|
||||||
3. Créer le client `bonsai-webapp` (type *OpenID Connect*, flux *Authorization Code*)
|
|
||||||
4. Configurer les *Valid redirect URIs* : `https://bonsai.goutailler-olivier.com/*`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Gitea
|
|
||||||
|
|
||||||
Gitea est la forge Git avec le runner CI/CD intégré.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd gitea/
|
|
||||||
docker compose -f gitea-compose.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration post-démarrage :**
|
|
||||||
|
|
||||||
1. Accéder à `https://git.goutailler-olivier.com` et terminer l'installation via l'interface web
|
|
||||||
2. Créer l'organisation `bonsai`
|
|
||||||
3. Récupérer un token d'enregistrement pour le runner dans *Administration → Actions → Runners*
|
|
||||||
4. Mettre à jour `GITEA_RUNNER_REGISTRATION_TOKEN` dans `gitea-compose.yml`, puis redémarrer le service `act_runner`
|
|
||||||
|
|
||||||
Le runner est configuré avec le label `ubuntu-latest` mappé sur `ubuntu:22.04`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Bonsai API
|
|
||||||
|
|
||||||
L'image est construite par la CI Gitea et poussée sur le registre `git.goutailler-olivier.com/bonsai/bonsai-api:latest`.
|
|
||||||
|
|
||||||
Le registre Gitea requiert une authentification. Se connecter une première fois avec son compte Gitea :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker login git.goutailler-olivier.com -u <utilisateur>
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note :** par défaut, Docker stocke le mot de passe en clair dans `~/.docker/config.json`. Pour éviter cela, configurer un [credential helper](https://docs.docker.com/engine/reference/commandline/login/#credentials-store).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd bonsai-api/
|
|
||||||
# Créer un fichier .env avec le mot de passe PostgreSQL
|
|
||||||
echo "POSTGRES_PASSWORD=<mot_de_passe_sécurisé>" > .env
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Variables d'environnement :
|
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
|---|---|
|
|
||||||
| `POSTGRES_PASSWORD` | Mot de passe PostgreSQL (injecté via `.env`) |
|
|
||||||
| `DATASOURCE_URL` | `jdbc:postgresql://db:5432/bonsai` (défaut réseau interne) |
|
|
||||||
| `KEYCLOAK_JWKS_URI` | `https://auth.goutailler-olivier.com/realms/bonsai/protocol/openid-connect/certs` |
|
|
||||||
| `CORS_ALLOWED_ORIGIN_PROD` | `https://bonsai.goutailler-olivier.com` |
|
|
||||||
|
|
||||||
Flyway applique automatiquement les migrations SQL au démarrage.
|
|
||||||
|
|
||||||
**Documentation API (Swagger UI) :**
|
|
||||||
|
|
||||||
| Environnement | URL |
|
|
||||||
|---|---|
|
|
||||||
| Production | `https://bonsai.goutailler-olivier.com/api/swagger-ui.html` |
|
|
||||||
| Développement local | `http://localhost:8080/swagger-ui.html` |
|
|
||||||
|
|
||||||
La définition OpenAPI brute est disponible sur `/v3/api-docs`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Bonsai Webapp
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd bonsai-webapp/
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
L'image `git.goutailler-olivier.com/bonsai/bonsai-webapp:latest` est également construite par la CI. Aucune variable d'environnement spécifique n'est requise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Luz
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd luz/
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
L'image `git.goutailler-olivier.com/gato/luz:latest` est construite par la CI Gitea lors d'une release. Aucune variable d'environnement spécifique n'est requise.
|
|
||||||
|
|
||||||
**Secrets Gitea à configurer dans le dépôt Luz :**
|
|
||||||
|
|
||||||
| Secret | Description |
|
|
||||||
|---|---|
|
|
||||||
| `RELEASE_TOKEN` | Token Gitea avec droits `write:packages` et `write:repository` |
|
|
||||||
| `WATCHTOWER_TOKEN` | Token HTTP de l'API Watchtower pour déclencher le redéploiement |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Nextcloud
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd nextcloud/
|
|
||||||
# Adapter les mots de passe dans docker-compose.yml avant le premier démarrage
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Variables à personnaliser dans `docker-compose.yml` avant le premier lancement :
|
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
|---|---|
|
|
||||||
| `POSTGRES_PASSWORD` | Mot de passe PostgreSQL |
|
|
||||||
| `NEXTCLOUD_ADMIN_USER` | Compte administrateur Nextcloud |
|
|
||||||
| `NEXTCLOUD_ADMIN_PASSWORD` | Mot de passe administrateur |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Trilium
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd trilium/
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Les données sont persistées dans `/home/gato/Applications/Trilium/data` sur l'hôte. S'assurer que ce chemin existe avant le démarrage :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /home/gato/Applications/Trilium/data
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ordre de démarrage recommandé
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Réseau proxy (une seule fois)
|
|
||||||
2. Traefik
|
|
||||||
3. Keycloak
|
|
||||||
4. Gitea
|
|
||||||
5. Bonsai API
|
|
||||||
6. Bonsai Webapp
|
|
||||||
7. Luz
|
|
||||||
8. Nextcloud
|
|
||||||
9. Trilium
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vérifications
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# État de tous les conteneurs
|
|
||||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
||||||
|
|
||||||
# Logs d'un service
|
|
||||||
docker logs <nom_conteneur> --tail 50 -f
|
|
||||||
|
|
||||||
# Certificats TLS Traefik
|
|
||||||
docker logs traefik 2>&1 | grep -i "certificate\|acme"
|
|
||||||
```
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
# Mise à jour des applications
|
|
||||||
|
|
||||||
Chaque application est packagée sous forme d'image Docker publiée sur le registre Gitea (`git.goutailler-olivier.com`) par la CI lors d'une release. La mise à jour en production consiste à récupérer la nouvelle image et à recréer le conteneur.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Principe général
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd <dossier-du-service>/
|
|
||||||
docker compose pull # télécharge la nouvelle image
|
|
||||||
docker compose up -d # recrée le conteneur si l'image a changé
|
|
||||||
```
|
|
||||||
|
|
||||||
`docker compose up -d` détecte automatiquement que l'image locale est différente de celle utilisée par le conteneur en cours et le recrée. Le temps d'indisponibilité est limité à la durée du redémarrage du conteneur (quelques secondes).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Exemple : mettre à jour Bonsai Webapp
|
|
||||||
|
|
||||||
Une nouvelle release a été publiée sur Gitea et l'image `bonsai-webapp:latest` a été mise à jour par la CI.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd bonsai-webapp/
|
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Vérifier que le conteneur tourne bien avec la nouvelle image :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker inspect bonsai-webapp --format '{{.Image}}'
|
|
||||||
# ou
|
|
||||||
docker compose ps
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Mettre à jour une version précise (tag de release)
|
|
||||||
|
|
||||||
Par défaut les `docker-compose.yml` pointent sur `:latest`. Pour épingler une version spécifique, éditer le fichier et remplacer le tag :
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
image: git.goutailler-olivier.com/bonsai/bonsai-webapp:v1.2.3
|
|
||||||
```
|
|
||||||
|
|
||||||
Puis appliquer :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes sur le workflow de release (CI)
|
|
||||||
|
|
||||||
### `fetch-depth: 0` requis sur le checkout
|
|
||||||
|
|
||||||
Le job `release.yml` de Bonsai-webapp (et Bonsai-api) effectue un `git merge` du tag de release dans `main` à la fin du pipeline. `actions/checkout@v4` fait par défaut un clone superficiel (`--depth 1`), ce qui empêche git de trouver l'ancêtre commun entre le tag et `main` → erreur `refusing to merge unrelated histories`.
|
|
||||||
|
|
||||||
**Solution appliquée** : ajouter `fetch-depth: 0` sur le step Checkout :
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Checkout
|
|
||||||
uses: https://github.com/actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
```
|
|
||||||
|
|
||||||
Sans ce paramètre, tout nouveau runner qui clone le dépôt avec historique limité fera échouer l'étape « Merge release to main ».
|
|
||||||
|
|
||||||
### Bump de version commité avant le merge
|
|
||||||
|
|
||||||
Le step « Bump version in package.json » modifie `package.json` pour y inscrire la version de la release. Ce changement est **commité sur le HEAD détaché** (tag checkout) avant que le build Docker ne commence. Le step « Merge release to main » sauvegarde ensuite le hash de ce commit (`RELEASE_HEAD`) et merge ce commit (et non le tag) sur `main` :
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Bump version in package.json
|
|
||||||
run: |
|
|
||||||
# ... sed + git config ...
|
|
||||||
git add package.json
|
|
||||||
git commit -m "chore: set version to $VERSION"
|
|
||||||
|
|
||||||
- name: Merge release to main
|
|
||||||
run: |
|
|
||||||
RELEASE_HEAD=$(git rev-parse HEAD)
|
|
||||||
git checkout main
|
|
||||||
git merge --no-ff "$RELEASE_HEAD" -m "chore: release ..."
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pourquoi** : sans ce commit préalable, `git checkout main` échoue avec *"Your local changes would be overwritten by checkout"* car `package.json` est modifié mais non commité.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Revenir à une version précédente (rollback)
|
|
||||||
|
|
||||||
Un workflow Gitea Actions dédié permet de déclencher un rollback sans toucher au serveur manuellement.
|
|
||||||
|
|
||||||
### Rollback Bonsai Webapp
|
|
||||||
|
|
||||||
1. Aller dans **Gitea → Bonsai-webapp → Actions → Rollback → Run workflow**
|
|
||||||
2. Saisir la version cible (ex. `v1.2.3`)
|
|
||||||
3. Lancer — Watchtower redéploie automatiquement
|
|
||||||
|
|
||||||
### Rollback Bonsai API (avec ou sans restauration BDD)
|
|
||||||
|
|
||||||
1. Aller dans **Gitea → Bonsai-api → Actions → Rollback → Run workflow**
|
|
||||||
2. Saisir la version cible (ex. `v1.2.3`)
|
|
||||||
3. Choisir `restore_db` :
|
|
||||||
- `no` (défaut) — rollback du code uniquement (migrations Flyway compatibles)
|
|
||||||
- `yes` — rollback du code **et** restauration de la base de données depuis le backup créé avant ce déploiement
|
|
||||||
|
|
||||||
> **Attention** : `restore_db=yes` écrase toutes les données créées depuis le déploiement à annuler. À réserver aux cas où la migration de schéma est incompatible avec l'ancienne version.
|
|
||||||
|
|
||||||
### Ce que fait le workflow en coulisse
|
|
||||||
|
|
||||||
1. Retague l'image `image:vX.Y.Z` en `image:latest` dans le registry Gitea
|
|
||||||
2. (Si `restore_db=yes`) Via le socket Docker du runner, arrête l'API et restaure le dump PostgreSQL depuis `/opt/backups/bonsai-api/`
|
|
||||||
3. Déclenche Watchtower → redéploie le conteneur avec la version rétrogradée
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backups de la base de données
|
|
||||||
|
|
||||||
Un backup compressé (`pg_dump | gzip`) est créé automatiquement **avant chaque déploiement** de Bonsai API.
|
|
||||||
|
|
||||||
- **Emplacement sur le serveur** : `/opt/backups/bonsai-api/`
|
|
||||||
- **Format du nom** : `bonsai_v1.2.3_20260531_143000.sql.gz`
|
|
||||||
- **Rétention** : les 10 derniers backups sont conservés, les plus anciens sont supprimés automatiquement
|
|
||||||
|
|
||||||
Pour lister les backups disponibles sur le serveur :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls -lh /opt/backups/bonsai-api/
|
|
||||||
```
|
|
||||||
|
|
||||||
Pour restaurer manuellement un backup :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
VERSION="v1.2.3"
|
|
||||||
BACKUP_FILE=$(ls -t /opt/backups/bonsai-api/bonsai_${VERSION}_*.sql.gz | head -1)
|
|
||||||
|
|
||||||
docker stop bonsai-api
|
|
||||||
docker exec bonsai-api-db psql -U bonsai postgres \
|
|
||||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='bonsai' AND pid != pg_backend_pid();"
|
|
||||||
docker exec bonsai-api-db dropdb -U bonsai bonsai
|
|
||||||
docker exec bonsai-api-db createdb -U bonsai bonsai
|
|
||||||
gunzip -c "$BACKUP_FILE" | docker exec -i bonsai-api-db psql -U bonsai bonsai
|
|
||||||
docker start bonsai-api
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback Luz
|
|
||||||
|
|
||||||
1. Aller dans **Gitea → Luz → Actions → Rollback → Run workflow**
|
|
||||||
2. Saisir la version cible (ex. `v1.2.3`)
|
|
||||||
3. Lancer — Watchtower redéploie automatiquement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Mettre à jour tous les services d'un coup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for dir in traefik keycloak gitea bonsai-api bonsai-webapp luz nextcloud trilium; do
|
|
||||||
echo "=== $dir ==="
|
|
||||||
(cd "$dir" && docker compose pull && docker compose up -d)
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vérifications après mise à jour
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# État de tous les conteneurs
|
|
||||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
|
|
||||||
|
|
||||||
# Logs du service mis à jour
|
|
||||||
docker logs bonsai-webapp --tail 50 -f
|
|
||||||
|
|
||||||
# Supprimer les anciennes images devenues inutiles
|
|
||||||
docker image prune -f
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
name: bonsai-api-stack
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: bonsai-api-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: bonsai
|
||||||
|
POSTGRES_USER: bonsai
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
TZ: Europe/Paris
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U bonsai -d bonsai"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- ./db_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- bonsai-api-net
|
||||||
|
|
||||||
|
api:
|
||||||
|
image: git.goutailler-olivier.com/bonsai/bonsai-api:latest
|
||||||
|
container_name: bonsai-api
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
DATASOURCE_URL: jdbc:postgresql://db:5432/bonsai
|
||||||
|
DATASOURCE_USERNAME: bonsai
|
||||||
|
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
KEYCLOAK_JWKS_URI: https://auth.goutailler-olivier.com/realms/bonsai/protocol/openid-connect/certs
|
||||||
|
CORS_ALLOWED_ORIGIN_PROD: https://bonsai.goutailler-olivier.com
|
||||||
|
TZ: Europe/Paris
|
||||||
|
networks:
|
||||||
|
- bonsai-api-net
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.bonsai-api.rule=Host(`bonsai.goutailler-olivier.com`) && PathPrefix(`/api`)
|
||||||
|
- traefik.http.routers.bonsai-api.entrypoints=websecure
|
||||||
|
- traefik.http.routers.bonsai-api.tls.certresolver=le
|
||||||
|
- traefik.http.services.bonsai-api.loadbalancer.server.port=8080
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bonsai-api-net:
|
||||||
|
driver: bridge
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: bonsai-webapp
|
||||||
|
|
||||||
|
services:
|
||||||
|
bonsai-webapp:
|
||||||
|
image: git.goutailler-olivier.com/bonsai/bonsai-webapp:latest
|
||||||
|
container_name: bonsai-webapp
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
environment:
|
||||||
|
TZ: Europe/Paris
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.bonsai-webapp.rule=Host(`bonsai.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.bonsai-webapp.entrypoints=websecure
|
||||||
|
- traefik.http.routers.bonsai-webapp.tls.certresolver=le
|
||||||
|
- traefik.http.services.bonsai-webapp.loadbalancer.server.port=80
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
FROM gitea/act_runner:latest
|
||||||
|
|
||||||
|
# Installer Node.js et npm
|
||||||
|
RUN apk add --no-cache nodejs npm
|
||||||
|
|
||||||
|
# Docker CLI déjà installé via le wget précédent
|
||||||
|
RUN wget -O /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-26.1.4.tgz \
|
||||||
|
&& tar -xzf /tmp/docker.tgz --strip-components=1 -C /usr/local/bin docker/docker \
|
||||||
|
&& rm /tmp/docker.tgz
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
## Mise a jours
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f gitea-compose.yml down
|
||||||
|
tar -czvf gitea_backup_$(date +%Y%m%d).tar.gz ./gitea ./db_data
|
||||||
|
docker compose -f gitea-compose.yml pull gitea
|
||||||
|
docker compose -f gitea-compose.yml up -d
|
||||||
|
docker exec gitea gitea --version
|
||||||
|
```
|
||||||
|
Relancer le runner
|
||||||
|
```bash
|
||||||
|
docker compose -f gitea-compose.yml restart act_runner
|
||||||
|
```
|
||||||
@@ -88,7 +88,7 @@ services:
|
|||||||
- gitea
|
- gitea
|
||||||
environment:
|
environment:
|
||||||
GITEA_INSTANCE_URL: http://gitea:3000
|
GITEA_INSTANCE_URL: http://gitea:3000
|
||||||
GITEA_RUNNER_REGISTRATION_TOKEN: IZM8wKkzR4XZogOxsb5or3JKiugXyguFtA0zjNWZ
|
GITEA_RUNNER_REGISTRATION_TOKEN: Rvi31evVGlyH8o1h2lw200uMjOJyCrBQJXLKQqJk
|
||||||
GITEA_RUNNER_NAME: docker-runner
|
GITEA_RUNNER_NAME: docker-runner
|
||||||
GITEA_RUNNER_LABELS: ubuntu-latest:host
|
GITEA_RUNNER_LABELS: ubuntu-latest:host
|
||||||
CONFIG_FILE: /config.yaml
|
CONFIG_FILE: /config.yaml
|
||||||
@@ -98,7 +98,6 @@ services:
|
|||||||
- ./runner_data:/data
|
- ./runner_data:/data
|
||||||
- ./runner_data/config.yaml:/config.yaml
|
- ./runner_data/config.yaml:/config.yaml
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /opt/backups:/opt/backups
|
|
||||||
networks:
|
networks:
|
||||||
- gitea-net
|
- gitea-net
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
POSTGRES_PASSWORD=changeme
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=changeme
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
name: keycloak-stack
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: keycloak-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: keycloak
|
||||||
|
POSTGRES_USER: keycloak
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
TZ: Europe/Paris
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- ./db_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- keycloak-net
|
||||||
|
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:26.2
|
||||||
|
container_name: keycloak
|
||||||
|
restart: unless-stopped
|
||||||
|
command: start
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
KC_DB: postgres
|
||||||
|
KC_DB_URL: jdbc:postgresql://db:5432/keycloak
|
||||||
|
KC_DB_USERNAME: keycloak
|
||||||
|
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
|
||||||
|
KC_HOSTNAME: auth.goutailler-olivier.com
|
||||||
|
KC_HOSTNAME_STRICT: "true"
|
||||||
|
KC_HTTP_ENABLED: "true"
|
||||||
|
KC_PROXY_HEADERS: xforwarded
|
||||||
|
|
||||||
|
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin}
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
||||||
|
|
||||||
|
TZ: Europe/Paris
|
||||||
|
KC_SPI_THEME_STATIC_MAX_AGE: "-1"
|
||||||
|
KC_SPI_THEME_CACHE_THEMES: "false"
|
||||||
|
KC_SPI_THEME_CACHE_TEMPLATES: "false"
|
||||||
|
volumes:
|
||||||
|
- ./themes:/opt/keycloak/themes
|
||||||
|
networks:
|
||||||
|
- keycloak-net
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.keycloak.rule=Host(`auth.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.keycloak.entrypoints=websecure
|
||||||
|
- traefik.http.routers.keycloak.tls.certresolver=le
|
||||||
|
- traefik.http.services.keycloak.loadbalancer.server.port=8080
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
keycloak-net:
|
||||||
|
driver: bridge
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="${(locale.currentLanguageTag)!'fr'}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Bonsai — Réinitialisation du mot de passe</title>
|
||||||
|
<link rel="stylesheet" href="${url.resourcesPath}/css/login.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<div class="card">
|
||||||
|
|
||||||
|
<div class="logo">
|
||||||
|
<svg viewBox="0 0 48 56" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path d="M14 56 L16 49 L32 49 L34 56 Z" fill="#C05621"/>
|
||||||
|
<rect x="12" y="45" width="24" height="5" rx="2" fill="#9C4221"/>
|
||||||
|
<path d="M24 45 C24 39 22 33 20 27 C18 22 19 17 22 13" stroke="#744210" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M21 28 C26 25 31 22 33 18" stroke="#744210" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<path d="M20 35 C15 32 11 28 10 24" stroke="#744210" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="10" cy="21" r="9" fill="#276749"/>
|
||||||
|
<circle cx="34" cy="16" r="10" fill="#276749"/>
|
||||||
|
<circle cx="22" cy="11" r="11" fill="#2F855A"/>
|
||||||
|
<circle cx="26" cy="17" r="8" fill="#38A169"/>
|
||||||
|
<circle cx="18" cy="16" r="6" fill="#48BB78"/>
|
||||||
|
</svg>
|
||||||
|
<span class="logo-name">Bonsai</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="title">Mot de passe oublié ?</h1>
|
||||||
|
<p class="subtitle">Saisissez votre email pour recevoir un lien de réinitialisation.</p>
|
||||||
|
|
||||||
|
<#if message?has_content>
|
||||||
|
<div class="alert alert--${message.type}">
|
||||||
|
${message.summary}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<form action="${url.loginAction}" method="post">
|
||||||
|
<div class="field">
|
||||||
|
<label for="username">Email ou nom d'utilisateur</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
value="${(auth.attemptedUsername!'')}"
|
||||||
|
autocomplete="username"
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn-primary">Envoyer le lien</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="register-link">
|
||||||
|
<a href="${url.loginUrl}">← Retour à la connexion</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="${(locale.currentLanguageTag)!'fr'}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Bonsai — Connexion</title>
|
||||||
|
<link rel="stylesheet" href="${url.resourcesPath}/css/login.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<div class="card">
|
||||||
|
|
||||||
|
<div class="logo">
|
||||||
|
<svg viewBox="0 0 48 56" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path d="M14 56 L16 49 L32 49 L34 56 Z" fill="#C05621"/>
|
||||||
|
<rect x="12" y="45" width="24" height="5" rx="2" fill="#9C4221"/>
|
||||||
|
<path d="M24 45 C24 39 22 33 20 27 C18 22 19 17 22 13" stroke="#744210" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M21 28 C26 25 31 22 33 18" stroke="#744210" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<path d="M20 35 C15 32 11 28 10 24" stroke="#744210" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="10" cy="21" r="9" fill="#276749"/>
|
||||||
|
<circle cx="34" cy="16" r="10" fill="#276749"/>
|
||||||
|
<circle cx="22" cy="11" r="11" fill="#2F855A"/>
|
||||||
|
<circle cx="26" cy="17" r="8" fill="#38A169"/>
|
||||||
|
<circle cx="18" cy="16" r="6" fill="#48BB78"/>
|
||||||
|
</svg>
|
||||||
|
<span class="logo-name">Bonsai</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="title">Connexion</h1>
|
||||||
|
|
||||||
|
<#if message?has_content>
|
||||||
|
<div class="alert alert--${message.type}">
|
||||||
|
${message.summary}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if realm.password>
|
||||||
|
<form action="${url.loginAction}" method="post" novalidate>
|
||||||
|
<input type="hidden" id="id-hidden-input" name="credentialId"
|
||||||
|
<#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="username">Email ou nom d'utilisateur</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
value="${(login.username!'')}"
|
||||||
|
autocomplete="username"
|
||||||
|
<#if usernameEditDisabled??>disabled</#if>
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="field-label-row">
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
<#if realm.resetPasswordAllowed>
|
||||||
|
<a href="${url.loginResetCredentialsUrl}" class="forgot-link" tabindex="5">
|
||||||
|
Mot de passe oublié ?
|
||||||
|
</a>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#if realm.rememberMe && !usernameEditDisabled??>
|
||||||
|
<div class="remember">
|
||||||
|
<input type="checkbox" id="rememberMe" name="rememberMe"
|
||||||
|
<#if login.rememberMe??>checked</#if>>
|
||||||
|
<label for="rememberMe">Se souvenir de moi</label>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<button type="submit" class="btn-primary">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if social.providers?has_content>
|
||||||
|
<div class="divider"><span>ou</span></div>
|
||||||
|
<div class="socials">
|
||||||
|
<#list social.providers as p>
|
||||||
|
<a href="${p.loginUrl}" class="btn-social">
|
||||||
|
Continuer avec ${p.displayName!''}
|
||||||
|
</a>
|
||||||
|
</#list>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
|
||||||
|
<p class="register-link">
|
||||||
|
Pas encore de compte ?
|
||||||
|
<a href="${url.registrationUrl}">Créer un compte</a>
|
||||||
|
</p>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #111827;
|
||||||
|
background: #f0fdf4;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Page ── */
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Card ── */
|
||||||
|
.card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Logo ── */
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo svg {
|
||||||
|
width: 52px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-name {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #111827;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Titles ── */
|
||||||
|
.title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #6b7280;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Alert ── */
|
||||||
|
.alert {
|
||||||
|
padding: 0.65rem 0.875rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert--error {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert--warning {
|
||||||
|
background: #fffbeb;
|
||||||
|
color: #d97706;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert--success {
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #16a34a;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert--info {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Form fields ── */
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-label-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.55rem 0.75rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #111827;
|
||||||
|
background: #ffffff;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input:focus {
|
||||||
|
border-color: #2F855A;
|
||||||
|
box-shadow: 0 0 0 3px rgba(47, 133, 90, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input:disabled {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-link {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #2F855A;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Remember me ── */
|
||||||
|
.remember {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember input[type="checkbox"] {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
accent-color: #2F855A;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #374151;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Primary button ── */
|
||||||
|
.btn-primary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.6rem;
|
||||||
|
background: #2F855A;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #276749;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:active {
|
||||||
|
background: #1e5236;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Divider ── */
|
||||||
|
.divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider::before,
|
||||||
|
.divider::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Social buttons ── */
|
||||||
|
.socials {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-social {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.55rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.1s, border-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-social:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Register link ── */
|
||||||
|
.register-link {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.83rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-link a {
|
||||||
|
color: #2F855A;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-link a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
parent=keycloak
|
||||||
|
styles=css/login.css
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: luz
|
||||||
|
|
||||||
|
services:
|
||||||
|
luz:
|
||||||
|
image: git.goutailler-olivier.com/gato/luz:latest
|
||||||
|
container_name: luz
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
environment:
|
||||||
|
TZ: Europe/Paris
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.luz.rule=Host(`luz.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.luz.entrypoints=websecure
|
||||||
|
- traefik.http.routers.luz.tls.certresolver=le
|
||||||
|
- traefik.http.services.luz.loadbalancer.server.port=80
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Nextcloud on port 8088 with Postgres and pgAdmin
|
||||||
|
# ------------------------------------------------
|
||||||
|
# Quick start:
|
||||||
|
# docker compose up -d # (Compose V2 syntax; no `version:` key)
|
||||||
|
|
||||||
|
name: nextcloud-stack
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: nextcloud-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: nextcloud
|
||||||
|
POSTGRES_USER: nextcloud
|
||||||
|
POSTGRES_PASSWORD: changeme
|
||||||
|
TZ: Europe/Paris
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- ./db_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- nextcloud-net
|
||||||
|
|
||||||
|
nextcloud:
|
||||||
|
image: nextcloud:latest
|
||||||
|
container_name: nextcloud-app
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
POSTGRES_HOST: db
|
||||||
|
POSTGRES_DB: nextcloud
|
||||||
|
POSTGRES_USER: nextcloud
|
||||||
|
POSTGRES_PASSWORD: changeme
|
||||||
|
|
||||||
|
NEXTCLOUD_ADMIN_USER: admin
|
||||||
|
NEXTCLOUD_ADMIN_PASSWORD: adminpass
|
||||||
|
|
||||||
|
NEXTCLOUD_TRUSTED_DOMAINS: cloud.goutailler-olivier.com
|
||||||
|
NEXTCLOUD_OVERWRITEHOST: cloud.goutailler-olivier.com
|
||||||
|
NEXTCLOUD_OVERWRITEPROTOCOL: https
|
||||||
|
|
||||||
|
NEXTCLOUD_TRUSTED_PROXIES: 172.23.0.0/16
|
||||||
|
|
||||||
|
APACHE_DISABLE_REWRITE_IP: "1"
|
||||||
|
PHP_MEMORY_LIMIT: 1G
|
||||||
|
PHP_UPLOAD_LIMIT: 2G
|
||||||
|
TZ: Europe/Paris
|
||||||
|
volumes:
|
||||||
|
- ./nextcloud_app:/var/www/html
|
||||||
|
- ./nextcloud_data:/var/www/html/data
|
||||||
|
networks:
|
||||||
|
- nextcloud-net
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.nextcloud.rule=Host(`cloud.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.nextcloud.entrypoints=websecure
|
||||||
|
- traefik.http.routers.nextcloud.tls.certresolver=le
|
||||||
|
- traefik.http.services.nextcloud.loadbalancer.server.port=80
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
# (optionnel) quelques en-têtes de sécurité
|
||||||
|
- traefik.http.routers.nextcloud.middlewares=nc-sec
|
||||||
|
- traefik.http.middlewares.nc-sec.headers.stsSeconds=31536000
|
||||||
|
- traefik.http.middlewares.nc-sec.headers.stsIncludeSubdomains=true
|
||||||
|
- traefik.http.middlewares.nc-sec.headers.stsPreload=true
|
||||||
|
- traefik.http.middlewares.nc-sec.headers.contentTypeNosniff=true
|
||||||
|
- traefik.http.middlewares.nc-sec.headers.browserXssFilter=true
|
||||||
|
|
||||||
|
pgadmin:
|
||||||
|
image: dpage/pgadmin4:latest
|
||||||
|
container_name: nextcloud-pgadmin
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: adminpass
|
||||||
|
PGADMIN_CONFIG_SERVER_MODE: 'False'
|
||||||
|
TZ: Europe/Paris
|
||||||
|
volumes:
|
||||||
|
- ./pgadmin_data:/var/lib/pgadmin
|
||||||
|
- ./pgadmin/servers.json:/pgadmin4/servers.json:ro
|
||||||
|
networks:
|
||||||
|
- nextcloud-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nextcloud-net:
|
||||||
|
driver: bridge
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:v3.0
|
||||||
|
container_name: traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- --providers.docker=true
|
||||||
|
- --providers.docker.exposedbydefault=false
|
||||||
|
- --providers.docker.network=proxy
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --entrypoints.websecure.address=:443
|
||||||
|
# Redirection HTTP -> HTTPS
|
||||||
|
- --entrypoints.web.http.redirections.entryPoint.to=websecure
|
||||||
|
- --entrypoints.web.http.redirections.entryPoint.scheme=https
|
||||||
|
# Let's Encrypt (HTTP-01)
|
||||||
|
- --certificatesresolvers.le.acme.email=cedric@goutailler-olivier.com
|
||||||
|
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
||||||
|
- --certificatesresolvers.le.acme.httpchallenge=true
|
||||||
|
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
||||||
|
- --entrypoints.ssh.address=:2222
|
||||||
|
# (Optionnel) Dashboard interne
|
||||||
|
- --api.dashboard=true
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "2222:2222"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- ./traefik-letsencrypt:/letsencrypt
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.traefik.rule=Host(`traefik.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.traefik.entrypoints=websecure
|
||||||
|
- traefik.http.routers.traefik.tls.certresolver=le
|
||||||
|
- traefik.http.routers.traefik.service=api@internal
|
||||||
|
networks:
|
||||||
|
# nextcloud-net:
|
||||||
|
# driver: bridge
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
services:
|
||||||
|
trilium:
|
||||||
|
image: triliumnext/trilium:latest
|
||||||
|
container_name: trilium
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
environment:
|
||||||
|
TRILIUM_DATA_DIR: /home/node/trilium-data
|
||||||
|
TZ: Europe/Paris
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- /home/gato/Applications/Trilium/data:/home/node/trilium-data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.trilium.rule=Host(`notes.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.trilium.entrypoints=websecure
|
||||||
|
- traefik.http.routers.trilium.tls.certresolver=le
|
||||||
|
- traefik.http.services.trilium.loadbalancer.server.port=8080
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
WATCHTOWER_TOKEN=votre_token_secret_ici
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: watchtower
|
||||||
|
|
||||||
|
services:
|
||||||
|
watchtower:
|
||||||
|
image: containrrr/watchtower
|
||||||
|
container_name: watchtower
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /root/.docker/config.json:/root/.docker/config.json:ro
|
||||||
|
environment:
|
||||||
|
WATCHTOWER_HTTP_API_UPDATE: "true"
|
||||||
|
WATCHTOWER_HTTP_API_TOKEN: ${WATCHTOWER_TOKEN}
|
||||||
|
WATCHTOWER_LABEL_ENABLE: "true"
|
||||||
|
WATCHTOWER_CLEANUP: "true"
|
||||||
|
TZ: Europe/Paris
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.watchtower.rule=Host(`watchtower.goutailler-olivier.com`)
|
||||||
|
- traefik.http.routers.watchtower.entrypoints=websecure
|
||||||
|
- traefik.http.routers.watchtower.tls.certresolver=le
|
||||||
|
- traefik.http.services.watchtower.loadbalancer.server.port=8080
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
Reference in New Issue
Block a user