Aller au contenu

Guide d'installation en production — Repod

Ce guide couvre l'installation complète de Repod en production sur un serveur Linux. Repod est une application unique — un backend FastAPI, une base PostgreSQL, une interface web React — capable de servir des paquets .deb, .rpm et Alpine .apk selon la variable REPO_FORMAT. Il n'existe plus d'« édition APT » ou « édition RPM » séparées. Suivez chaque étape dans l'ordre.


Table des matières

  1. Prérequis système
  2. Ports réseau
  3. Récupération du projet
  4. Configuration
  5. Structure des volumes de données
  6. Premier démarrage
  7. Configuration GPG
  8. Vérification du bon fonctionnement
  9. Configuration du pare-feu
  10. Activation au démarrage (systemd)
  11. Pile RPM autonome
  12. Sauvegarde automatique
  13. Mise à jour de l'application
  14. Résolution des problèmes courants

1. Prérequis système

Système d'exploitation

  • Debian 11/12 ou Ubuntu 22.04/24.04 LTS (système hôte — Repod tourne dans des conteneurs et peut servir n'importe quelle combinaison APT/RPM/APK)
  • Accès root ou sudo

Logiciels requis

Logiciel Version minimale Vérification
Docker Engine 24.0 docker --version
Docker Compose 2.20 (plugin) docker compose version
Git 2.x git --version
OpenSSL 1.1+ openssl version

Utiliser le plugin Compose v2

Utilisez docker compose (plugin v2), pas l'ancienne commande docker-compose.

Installation de Docker (si absent)

sudo apt-get remove -y docker docker-engine docker.io containerd runc
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

sudo usermod -aG docker $USER
newgrp docker

Ressources matérielles minimales

Ressource Minimum Recommandé
CPU 2 vCPU 4 vCPU
RAM 3 Go 6 Go+
Disque 20 Go 100 Go+

La RAM doit couvrir PostgreSQL, ClamAV (clamd charge ~800 Mo de signatures dans le conteneur backend) et Grype. L'espace disque doit couvrir tous les binaires de paquets sous repos/pool/ et repos/rpm/, plus le volume Docker postgres_data. Dimensionnez selon le volume de paquets attendu.


2. Ports réseau

Le docker-compose.yaml fourni démarre REPO_FORMAT=all par défaut — les serveurs de dépôts APT/APK et RPM sont tous deux démarrés.

Port Service Exposition
80 depot-apt — dépôts APT (.deb) et Alpine (.apk) LAN ou public (clients apt/apk)
8080 depot-rpm — dépôts RPM (.rpm) LAN ou public (clients dnf/zypper)
3003 Interface web (frontend-ui) Interne ou VPN
8000 API backend (backend-api) Reverse proxy uniquement — jamais exposé directement
PostgreSQL (repod-db) Réseau Docker interne uniquement, non publié

Si vous n'avez besoin que d'un seul format de paquet, définissez REPO_FORMAT en conséquence (apt, rpm ou apk) et retirez le service de dépôt inutilisé de docker-compose.yaml — voir Démarrage rapide — Étape 2.


3. Récupération du projet

sudo mkdir -p /opt/repod
sudo chown $USER:$USER /opt/repod
cd /opt/repod

git clone https://github.com/getautoflow/repod .

Vérifier la structure du projet :

ls /opt/repod
# Attendu : backend/ frontend/ repos/ docker-compose.yaml .env.example backend.env.example

4. Configuration

4.1 Fichier .env (ports, mot de passe de la base et URLs frontend)

cp .env.example .env
nano .env

Contenu type — remplacez repo.example.com par votre domaine ou adresse IP :

.env
# Lier sur loopback quand un reverse proxy gère le trafic externe
BIND_HOST=127.0.0.1

# URLs publiques intégrées au bundle frontend à la construction
# REACT_APP_API_URL doit rester vide (appels relatifs /api/v1/...)
REACT_APP_API_URL=
REACT_APP_REPO_URL=https://repo.example.com
REACT_APP_RPM_REPO_URL=https://repo.example.com:8080

# Mapping des ports
BACKEND_PORT=8000
FRONTEND_PORT=3003
APT_PORT=80
RPM_REPO_PORT=8080

# PostgreSQL — doit correspondre à DATABASE_URL dans backend.env
POSTGRES_PASSWORD=<sortie de openssl rand -hex 24>

4.2 Fichier backend.env (secrets, connexion base de données, configuration)

cp backend.env.example backend.env

Générer les secrets requis (un openssl rand -hex 32 par secret) :

openssl rand -hex 32
backend.env
# ── Base de données ─────────────────────────────────────────────────────────
# Le mot de passe doit correspondre à POSTGRES_PASSWORD dans .env
DATABASE_URL=postgresql://repod:<même-mot-de-passe-que-.env>@db:5432/repod

# ── Format de dépôt ─────────────────────────────────────────────────────────
REPO_FORMAT=all

# ── Sécurité ────────────────────────────────────────────────────────────────
JWT_SECRET_KEY=<sortie de openssl rand -hex 32>
JWT_EXPIRE_MINUTES=60
SETTINGS_ENCRYPTION_KEY=<sortie de openssl rand -hex 32>
WEBHOOK_SECRET=<sortie de openssl rand -hex 32>

# ── Environnement ───────────────────────────────────────────────────────────
ENV=production
APP_VERSION=v1.2.0

# ── Reverse proxy ────────────────────────────────────────────────────────────
TRUSTED_PROXIES=127.0.0.1
CORS_ORIGINS=https://repo.example.com

Obligatoires en production

JWT_SECRET_KEY et WEBHOOK_SECRET sont obligatoires. L'application refuse de démarrer en production si une valeur par défaut ou vide est détectée.

Sécuriser les fichiers :

chmod 600 /opt/repod/.env
chmod 600 /opt/repod/backend.env

4.3 Créer le compte admin

Aucun compte admin n'est créé au démarrage. Une fois la pile lancée (section 6), créez le premier admin via l'assistant de configuration :

curl -X POST http://localhost:8000/api/v1/setup/ \
  -H "Content-Type: application/json" \
  -d '{"admin_username":"admin","admin_password":"VotreMotDePasse!"}'

Politique de mot de passe : - Minimum 8 caractères - Au moins une majuscule - Au moins un chiffre ou caractère spécial

POST /api/v1/setup retourne 409 si un admin existe déjà — l'assistant ne peut être exécuté qu'une seule fois.

Pré-provisionner un admin (optionnel, déploiements automatisés)

Définir ADMIN_USERNAME et ADMIN_PASSWORD_HASH (hash bcrypt, $ doublés en $$) dans backend.env avant le premier démarrage :

python3 -c "from passlib.hash import bcrypt; print(bcrypt.hash('VotreMotDePasse!'))"

Si ADMIN_PASSWORD_HASH est absent, vide ou invalide, aucun admin n'est créé et l'assistant de configuration reste disponible — c'est le comportement sûr par défaut.

Protéger l'assistant de configuration (optionnel)

Définir SETUP_TOKEN=<sortie de openssl rand -hex 32> dans backend.env pour exiger un header X-Setup-Token sur POST /api/v1/setup jusqu'à la création du premier admin. GET /api/v1/setup/status reste public dans tous les cas.


5. Structure des volumes de données

mkdir -p /opt/repod/repos/{audit,clamav-db,conf,db,dists,gnupg,grype-db,\
imports,logs,manifests,package-index,pool,rpm,apk,security,staging/incoming,staging/quarantine}
Répertoire Contenu
audit/ Journaux d'audit JSONL en ajout seul (un fichier par jour)
clamav-db/ Base de signatures ClamAV (~800 Mo)
conf/ Configuration des distributions reprepro (mode APT)
db/ Base de données interne reprepro (mode APT)
dists/ Arborescences de distributions APT, servies par depot-apt
rpm/ Arborescences de distributions RPM (<codename>/<arch>/repodata/), servies par depot-rpm
apk/ Dépôts Alpine (<codename>/main/<arch>/APKINDEX.tar.gz), servis par depot-apt sous /apk/
gnupg/ Trousseau GPG partagé entre le backend et les conteneurs de dépôt
grype-db/ Cache de la base de données CVE Grype
imports/ Répertoire de travail pour les imports sync/mirror
logs/ Journaux de téléchargement Nginx (analysés pour les statistiques)
manifests/ Manifestes JSON par paquet et index.json central
pool/ Binaires de paquets .deb / .rpm (stockage canonique)
security/ Décisions CVE, caches CISA KEV et EPSS
staging/ Zone d'arrivée des uploads et quarantaine

Les comptes utilisateurs, l'index des manifestes, les données d'inventaire et les empreintes de clés SSH ne sont pas stockés sous /repos/ — ils vivent dans PostgreSQL, dans le volume Docker postgres_data géré par le service db. Aucune création manuelle de répertoire n'est nécessaire pour la base de données ; docker compose up crée le volume automatiquement et Alembic applique les migrations au démarrage du backend.


6. Premier démarrage

cd /opt/repod
docker compose up -d --build

Avec REPO_FORMAT=all (par défaut), Docker construit et démarre cinq conteneurs :

Conteneur Rôle Port
repod-db PostgreSQL 16 — base de données applicative (interne)
depot-apt Nginx — dépôts APT (.deb) + Alpine (.apk) 80
depot-rpm Nginx — dépôts RPM (.rpm) 8080
backend-api FastAPI, pipeline de sécurité 8000
frontend-ui Interface web React + Nginx 3003

Vérifier que tous les conteneurs tournent et sont sains :

docker compose ps

Suivre les logs de démarrage :

docker compose logs -f backend-api
# Attendre : INFO:     Application startup complete.

Le premier démarrage est plus lent

ClamAV charge ~800 Mo de signatures au premier démarrage (jusqu'à 2 minutes — le endpoint de santé peut indiquer "clamav": false pendant ce temps). PostgreSQL effectue également sa propre initialisation et Alembic applique les migrations. C'est normal.


7. Configuration GPG

Les index de dépôt doivent être signés GPG pour que les clients APT/RPM/APK vérifient les paquets.

  1. Ouvrir http://VOTRE_HOTE:3003 dans un navigateur
  2. Se connecter avec le compte admin créé via l'assistant de configuration
  3. Aller dans Paramètres → GPG
  4. Cliquer sur Générer une clé GPG
  5. Renseigner le nom réel et l'adresse e-mail, puis cliquer Générer
docker exec backend-api gpg --homedir /repos/gnupg \
  --batch --gen-key <<EOF
%no-protection
Key-Type: RSA
Key-Length: 4096
Name-Real: Repod Repository
Name-Email: repod@example.com
Expire-Date: 2y
%commit
EOF

# Initialiser les distributions après génération de la clé
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"VotreMotDePasse!"}' | jq -r .access_token)

curl -X POST http://localhost:8000/api/v1/distributions/init \
  -H "Authorization: Bearer $TOKEN"

8. Vérification du bon fonctionnement

# 1. Tous les conteneurs sont en cours d'exécution et sains
docker compose ps

# 2. Sonde de vivacité de l'API
curl -s http://localhost:8000/health/live
# Attendu : {"status":"ok"}

# 3. Health check complet (inclut PostgreSQL, ClamAV et Grype)
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"VotreMotDePasse!"}' | jq -r .access_token)
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/health | jq .

# 4. Interface web accessible
curl -s -o /dev/null -w "%{http_code}" http://localhost:3003
# Attendu : 200

# 5. Serveur de dépôt APT/APK accessible
curl -s -o /dev/null -w "%{http_code}" http://localhost:80
# Attendu : 200

# 6. Serveur de dépôt RPM accessible (REPO_FORMAT=rpm/both/all)
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080
# Attendu : 200

# 7. Swagger UI désactivé en production
curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/docs
# Attendu : 404

9. Configuration du pare-feu

sudo ufw enable
sudo ufw allow 22/tcp          # SSH — ne pas s'oublier
sudo ufw allow 3003/tcp        # Interface web
sudo ufw allow 80/tcp          # Clients APT / APK
sudo ufw allow 8080/tcp        # Clients RPM (à omettre si REPO_FORMAT=apt)
# Port 8000 — NE PAS exposer publiquement, utiliser un reverse proxy
sudo ufw status numbered
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-port=3003/tcp   # Interface web
sudo firewall-cmd --permanent --add-port=80/tcp     # Clients APT / APK
sudo firewall-cmd --permanent --add-port=8080/tcp   # Clients RPM
# Port 8000 — NE PAS exposer, utiliser un reverse proxy
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Ne jamais exposer le port 8000 directement

L'API backend (port 8000) ne doit être accessible que via un reverse proxy TLS. Une exposition publique directe transmet identifiants et tokens en clair. Voir Reverse proxy →


10. Activation au démarrage (systemd)

cat > /etc/systemd/system/repod.service << 'EOF'
[Unit]
Description=Repod Package Repository Manager
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/repod
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable repod
sudo systemctl start repod

11. Pile RPM autonome

Si vous n'avez besoin que d'un dépôt RPM et souhaitez une isolation complète (base PostgreSQL propre, réseau propre, noms de conteneurs propres — aucun état partagé avec un déploiement APT/all), utilisez le fichier compose dédié au lieu de la section 6 :

docker compose -f docker-compose.rpm.yml up -d --build

Cela démarre repod-db-rpm (PostgreSQL), depot-rpm, backend-api-rpm (REPO_FORMAT=rpm) et frontend-ui-rpm, sur les ports RPM_REPO_PORT (défaut 8080), BACKEND_PORT (défaut 8001) et FRONTEND_PORT (défaut 3004). À exécuter de manière autonome — ne pas le fusionner avec docker-compose.yaml via -f.


12. Sauvegarde automatique

Repod fournit une sauvegarde intégrée déclenchable par un administrateur ou planifiée via le scheduler (backup_daily, configurable dans settings.json). Elle archive le dump PostgreSQL (pg_dump) ainsi que pool/, manifests/, audit/, security/, gnupg/ et settings.json.

Sauvegarde manuelle via l'API

curl -X POST http://localhost:8000/api/v1/backup/ \
  -H "Authorization: Bearer $TOKEN"

Planification

Dans Paramètres → Sauvegardes, activer backup.enabled et définir l'heure quotidienne (par défaut 04:30).

Sauvegarde des secrets

Les éléments suivants doivent être sauvegardés séparément, hors serveur, dans un coffre-fort sécurisé :

/opt/repod/.env
/opt/repod/backend.env
/opt/repod/repos/gnupg/     # Clé GPG privée

Perte de la clé GPG

La perte de la clé GPG privée (repos/gnupg/) rend impossibles les futures signatures du dépôt. Sauvegardez-la de manière sécurisée et séparée des sauvegardes automatiques.


13. Mise à jour de l'application

cd /opt/repod

# 1. Sauvegarder avant la mise à jour (via l'API, voir section 12)

# 2. Récupérer les nouvelles versions
git fetch origin
git pull origin main

# 3. Reconstruire et redémarrer
docker compose up -d --build

# 4. Vérifier le bon démarrage
docker compose ps
curl http://localhost:8000/health/live

Alembic applique automatiquement les migrations PostgreSQL au démarrage du backend — aucune étape manuelle de migration n'est nécessaire.

Rollback en cas de problème

cd /opt/repod
git log --oneline -10
git checkout <commit-précédent>
docker compose up -d --build

14. Résolution des problèmes courants

Le conteneur backend-api ne démarre pas

Symptôme : docker compose ps affiche Exited pour backend-api.

docker compose logs backend-api --tail 50

Causes fréquentes :

  • DATABASE_URL absent ou mot de passe ne correspondant pas à POSTGRES_PASSWORDdb/engine.py lève une RuntimeError
  • JWT_SECRET_KEY ou WEBHOOK_SECRET non défini ou utilisant la valeur par défaut dans backend.env
  • $ non doublé en $$ dans le hash bcrypt de backend.env
  • Erreur de syntaxe dans backend.env (espaces autour des =)

Le conteneur repod-db ne démarre pas / migrations en échec

docker compose logs repod-db --tail 50
docker compose logs backend-api --tail 50 | grep -i alembic

Cause fréquente : le volume postgres_data contient des données d'une version incompatible. Vérifiez les logs Alembic pour l'erreur de migration exacte avant toute action — ne supprimez jamais le volume sans sauvegarde préalable.

L'interface web affiche une erreur de connexion à l'API

curl http://localhost:8000/health/live
docker compose logs backend-api --tail 20 | grep -i cors

Cause fréquente : CORS_ORIGINS dans backend.env ne correspond pas exactement à l'URL réellement utilisée par les navigateurs (protocole, port, domaine).

Les clients rejettent le dépôt (erreur de signature)

Symptôme : apt update / dnf makecache / apk update échoue avec une erreur de signature.

docker exec backend-api gpg --homedir /repos/gnupg --list-keys

Solution : Si aucune clé n'est listée, générez une clé GPG (section 7) puis re-publiez le dépôt via l'interface web.

ClamAV ne se met pas à jour

docker exec backend-api freshclam
ls -la /opt/repod/repos/clamav-db/

Espace disque insuffisant

df -h /opt/repod/
du -sh /opt/repod/repos/pool/* /opt/repod/repos/rpm/* | sort -rh | head -20
ls /opt/repod/repos/staging/quarantine/

Réinitialiser le mot de passe admin

Si le mot de passe admin est perdu, voir la procédure d'urgence dans CLAUDE.md — génération d'un hash bcrypt et mise à jour directe de la table users en PostgreSQL :

docker exec backend-api python3 -c "
import bcrypt
print(bcrypt.hashpw(b'NouveauMotDePasse!', bcrypt.gensalt(12)).decode())
"

docker exec repod-db psql -U repod -d repod -c \
  "UPDATE users SET hashed_password = '<hash>' WHERE username = 'admin';"

Consulter tous les logs en temps réel

docker compose logs -f
docker compose logs -f backend-api
docker compose logs -f frontend-ui
docker compose logs -f depot-apt
docker compose logs -f depot-rpm

Annexe : Commandes de référence rapide

# Démarrer en production
docker compose up -d

# Arrêter tous les services
docker compose down

# Redémarrer un service spécifique
docker compose restart backend-api

# Voir l'état des services
docker compose ps

# Health check
curl http://localhost:8000/health/live

# Voir les logs
docker compose logs -f backend-api

# Accéder au shell d'un conteneur
docker exec -it backend-api bash
docker exec -it repod-db psql -U repod -d repod