Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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
|
||||||
|
```
|
||||||
@@ -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: Rvi31evVGlyH8o1h2lw200uMjOJyCrBQJXLKQqJk
|
||||||
|
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
|
||||||
|
- ~/Applications/data/gitea/runner_data/config.yaml:/config.yaml
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
networks:
|
||||||
|
- gitea-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
gitea-net:
|
||||||
|
driver: bridge
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
@@ -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,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