Compare commits
17 Commits
1a73df83f1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 85350cfe86 | |||
| 2c420e3fce | |||
| c25996ea3a | |||
| 14e73f0850 | |||
| cfbff8ecbe | |||
| 90bc7f6e35 | |||
| c1deba1b56 | |||
| 56a384a9d8 | |||
| ad9fbcbbdc | |||
| 5ba69d1e16 | |||
| 86fa4f8519 | |||
| 16668b1961 | |||
| bd6ee2ba2b | |||
| 135187f69f | |||
| df3a672387 | |||
| 7312d2832f | |||
| 1aef6e395e |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ls /var/home/Gato/IdeaProjects/Infra/)",
|
||||
"Read(//var/home/Gato/IdeaProjects/Infra/**)",
|
||||
"Read(//var/home/Gato/Applications/Infra/keycloak/themes/bonsai/**)",
|
||||
"Read(//var/home/Gato/Applications/Infra/keycloak/themes/bonsai/login/**)",
|
||||
"Bash(realpath /var/home/Gato/Applications 2>/dev/null || ls /var/home/Gato/ | grep -i app)",
|
||||
"Bash(ls /var/home/Gato/IdeaProjects/Infra && ls /var/home/Gato/IdeaProjects/bonsai-api)",
|
||||
"Read(//var/home/Gato/IdeaProjects/**)",
|
||||
"Bash(mkdir -p /var/home/Gato/IdeaProjects/Infra/bonsai-api)",
|
||||
"Bash(git *)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/var/home/Gato/IdeaProjects/Infra"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
traefik-letsencrypt/
|
||||
.env
|
||||
@@ -1,37 +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)
|
||||
├── 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` |
|
||||
| Nextcloud | `nextcloud/` | `cloud.goutailler-olivier.com` |
|
||||
| Trilium | `trilium/` | `notes.goutailler-olivier.com` |
|
||||
|
||||
## Pages
|
||||
|
||||
- [Installation Production](Installation-Production)
|
||||
- [Installation Développement](Installation-Developpement)
|
||||
@@ -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,176 +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`.
|
||||
|
||||
```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.
|
||||
|
||||
---
|
||||
|
||||
## 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. 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 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 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. Nextcloud
|
||||
8. 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"
|
||||
```
|
||||
@@ -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:
|
||||
- ~/Applications/data/bonsai-api/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,19 @@
|
||||
## 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
|
||||
```
|
||||
|
||||
Build le runner
|
||||
```bash
|
||||
docker compose up -d --build act_runner
|
||||
```
|
||||
|
||||
Relancer le runner
|
||||
```bash
|
||||
docker compose restart act_runner
|
||||
```
|
||||
@@ -0,0 +1,109 @@
|
||||
name: gitea-stack
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: gitea-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: gitea
|
||||
POSTGRES_USER: gitea
|
||||
POSTGRES_PASSWORD: change_me
|
||||
TZ: Europe/Paris
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- ~/Applications/data/gitea/db_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- gitea-net
|
||||
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
container_name: gitea
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
USER_UID: 1000
|
||||
USER_GID: 1000
|
||||
# ---- URLs / proxy (Traefik) ----
|
||||
GITEA__server__DOMAIN: git.goutailler-olivier.com
|
||||
GITEA__server__ROOT_URL: https://git.goutailler-olivier.com/
|
||||
GITEA__server__HTTP_PORT: "3000"
|
||||
|
||||
# ---- SSH (optionnel) ----
|
||||
# Laisse l’SSH intégré de Gitea activé et expose un port hôte 2222 (voir plus bas)
|
||||
GITEA__server__SSH_DOMAIN: git.goutailler-olivier.com
|
||||
GITEA__server__START_SSH_SERVER: "true"
|
||||
GITEA__server__SSH_PORT: "2222"
|
||||
GITEA__server__SSH_LISTEN_PORT: "2222"
|
||||
|
||||
# ---- Base de données ----
|
||||
GITEA__database__DB_TYPE: postgres
|
||||
GITEA__database__HOST: db:5432
|
||||
GITEA__database__NAME: gitea
|
||||
GITEA__database__USER: gitea
|
||||
GITEA__database__PASSWD: change_me
|
||||
|
||||
TZ: Europe/Paris
|
||||
volumes:
|
||||
- ~/Applications/data/gitea:/data
|
||||
# (facultatif) pour horloge locale dans les logs :
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
|
||||
# Pas d'exposition du port HTTP: Traefik s'en charge
|
||||
# On expose seulement l'SSH si tu veux cloner/pusher en SSH
|
||||
networks:
|
||||
- gitea-net
|
||||
- proxy
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.gitea.rule=Host(`git.goutailler-olivier.com`)
|
||||
- traefik.http.routers.gitea.entrypoints=websecure
|
||||
- traefik.http.routers.gitea.tls.certresolver=le
|
||||
- traefik.http.services.gitea.loadbalancer.server.port=3000
|
||||
- traefik.docker.network=proxy
|
||||
|
||||
# SSH (TCP router)
|
||||
- traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)
|
||||
- traefik.tcp.routers.gitea-ssh.tls=false
|
||||
- traefik.tcp.routers.gitea-ssh.entrypoints=ssh
|
||||
- traefik.tcp.routers.gitea-ssh.service=gitea-ssh
|
||||
- traefik.tcp.services.gitea-ssh.loadbalancer.server.port=2222
|
||||
|
||||
act_runner:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.runner
|
||||
extra_hosts:
|
||||
- "git.goutailler-olivier.com:host-gateway"
|
||||
container_name: gitea-runner
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- gitea
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: http://gitea:3000
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: VBV30irSexdiyvHyzlX0HAmb1iV3XGiDm37raumU
|
||||
GITEA_RUNNER_NAME: docker-runner
|
||||
GITEA_RUNNER_LABELS: ubuntu-latest:host
|
||||
CONFIG_FILE: /config.yaml
|
||||
GITEA__actions__ENABLED: "true"
|
||||
GITEA__actions__DEFAULT_ACTIONS_URL: http://gitea:3000
|
||||
volumes:
|
||||
- ~/Applications/data/gitea/runner_data:/data
|
||||
- ./runner-config.yaml:/config.yaml:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- gitea-net
|
||||
|
||||
networks:
|
||||
gitea-net:
|
||||
driver: bridge
|
||||
proxy:
|
||||
external: true
|
||||
name: proxy
|
||||
@@ -0,0 +1,16 @@
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: .runner
|
||||
capacity: 2
|
||||
timeout: 3h
|
||||
insecure: false
|
||||
fetch_timeout: 5s
|
||||
fetch_interval: 2s
|
||||
|
||||
cache:
|
||||
enabled: false
|
||||
|
||||
host:
|
||||
workdir_parent:
|
||||
@@ -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:
|
||||
- ~/Applications/data/keycloak/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:
|
||||
- ~/Applications/data/nextcloud/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:
|
||||
- ~/Applications/data/nextcloud/nextcloud_app:/var/www/html
|
||||
- ~/Applications/data/nextcloud/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:
|
||||
- ~/Applications/data/nextcloud/pgadmin_data:/var/lib/pgadmin
|
||||
- ~/Applications/data/nextcloud/pgadmin/servers.json:/pgadmin4/servers.json:ro
|
||||
networks:
|
||||
- nextcloud-net
|
||||
|
||||
networks:
|
||||
nextcloud-net:
|
||||
driver: bridge
|
||||
proxy:
|
||||
external: true
|
||||
name: proxy
|
||||
@@ -0,0 +1,54 @@
|
||||
name: olhar-api-stack
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: olhar-api-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: olhar
|
||||
POSTGRES_USER: olhar
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
TZ: Europe/Paris
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U olhar -d olhar"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- ~/Applications/data/olhar-api/db_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- olhar-api-net
|
||||
|
||||
api:
|
||||
image: git.goutailler-olivier.com/gato/olhar-api:latest
|
||||
container_name: olhar-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATASOURCE_URL: jdbc:postgresql://db:5432/olhar
|
||||
DATASOURCE_USERNAME: olhar
|
||||
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
KEYCLOAK_JWKS_URI: https://auth.goutailler-olivier.com/realms/olhar/protocol/openid-connect/certs
|
||||
CORS_ALLOWED_ORIGIN_PROD: https://olhar.goutailler-olivier.com
|
||||
TZ: Europe/Paris
|
||||
networks:
|
||||
- olhar-api-net
|
||||
- proxy
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.olhar-api.rule=Host(`olhar.goutailler-olivier.com`) && PathPrefix(`/api`)
|
||||
- traefik.http.routers.olhar-api.entrypoints=websecure
|
||||
- traefik.http.routers.olhar-api.tls.certresolver=le
|
||||
- traefik.http.services.olhar-api.loadbalancer.server.port=8080
|
||||
- traefik.docker.network=proxy
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
|
||||
networks:
|
||||
olhar-api-net:
|
||||
driver: bridge
|
||||
proxy:
|
||||
external: true
|
||||
name: proxy
|
||||
@@ -0,0 +1,27 @@
|
||||
name: olhar
|
||||
|
||||
services:
|
||||
oalhar:
|
||||
image: git.goutailler-olivier.com/gato/olhar-pwa:latest
|
||||
container_name: olhar
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
TZ: Europe/Paris
|
||||
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.olhar.rule=Host(`olhar.goutailler-olivier.com`)
|
||||
- traefik.http.routers.olhar.entrypoints=websecure
|
||||
- traefik.http.routers.olhar.tls.certresolver=le
|
||||
- traefik.http.services.olhar.loadbalancer.server.port=80
|
||||
- traefik.docker.network=proxy
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
|
||||
networks:
|
||||
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:
|
||||
- ~/Applications/data/trilium:/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