Les backups nocturnes du 4 et 5 octobre ont échoué ("restic : commande
introuvable") : cron ne fournit que /usr/bin:/bin alors que restic est
dans /usr/local/bin. Testé avec le PATH de cron (systemd-run -E).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 KiB
Serveur sans.pub
Lorsque tu interroges le serveur, ne lances que des commandes en LECTURE SEULE (non destructrices). Sauf à la demande explicite de l'utilisateur.
Accès
- SSH:
ssh zuck@sans.pub(alias~/.ssh/config:Host sans.pub) — sudo sans mot de passe. Note : ce document mentionnait auparavant un utilisateurcloud, à vérifier/clarifier (voirinstall-serveur.md, le compte créé estzuck). - CapRover: https://captain.cloud.sans.pub
- Gitea:
git.sans.pub(port SSH 2242,User gitdans~/.ssh/config)
Infrastructure
- OS: Debian 9 (kernel 4.9.0-12-amd64)
- Docker: 19.03.12 (mode Swarm)
- Disque: 914 Go total, ~193 Go utilisés (23%)
- RAM: 23 Go (~6 Go utilisés, swap quasi vide — vérifié 2026-02-19)
Structure Caprover
/captain/data/- données persistantes/captain/generated/- configs générées/captain/temp/- fichiers temporaires
Services actifs (~40 services)
Outils collaboratifs
- etherpad - éditeur collaboratif
- ethercalc - tableur collaboratif
- cryptpad - suite bureautique chiffrée
- nextcloud - cloud personnel
Développement
- gitea - forge Git
- adminer - gestion BDD
Communication
- writefreely - blog
- peertube - vidéos
- rss + rss-bridge - flux RSS
Sécurité/Outils
- bitwarden - gestionnaire mots de passe
- privatebin - partage de texte chiffré
- ots - secrets temporaires
- it-tools - boîte à outils
- cyberchef - manipulation de données
Sites perso
- pierre-martin - site perso
- sans-pub-site - site principal
- une-espace - autre projet
Enquêtes
- limesurvey - sondages
Services en erreur
Aucun service en erreur connu (vérifié 2026-02-19). mail tourne correctement (1/1, poste.io).
Commandes et skills Claude
Commande
/supervision- analyse complète de la santé du serveur (invocation manuelle)
Skill (auto-activé)
caprover- gestion des apps via API CapRoverlist: lister les appsinfo NOM: détails d'une appenv NOM: variables d'environnementscale NOM N: changer le nombre d'instancesrestart NOM: redémarrer une app
Prérequis : définir CAPROVER_PASSWORD dans .env (voir .envrc)
CLI CapRover (alternative à l'API brute)
Installé localement via fnm (Node géré sans toucher au système) :
curl -fsSL https://fnm.vercel.app/install | bash # une seule fois
fnm install --lts && fnm use lts-latest
npm install -g caprover
caprover login # à faire soi-même dans un vrai terminal (TTY requis pour le mot de passe)
Ensuite : caprover list (machines connectées), caprover api -n <nom-machine> -t /chemin/api -m GET -d '{}' pour des appels génériques (toujours passer -d même vide, sinon prompt interactif qui plante sans TTY).
Endpoints utiles :
GET /user/apps/appDefinitions→ liste toutes les apps avecenvVars,volumes,ports, etc.POST /user/apps/appDefinitions/update(payload :appName+ les champs à conserver/modifier) → met à jour la config d'une app sans redéployer l'image (mais redémarre le service)
⚠️ appDefinitions/update remplace, ne fusionne pas : un champ absent du payload est réinitialisé à sa valeur par défaut (vécu en prod le 2026-09-23 : containerHttpPort oublié → réinitialisé à 80 au lieu de 3000 → 502 sur git.sans.pub pendant plusieurs minutes, cf incidents/2026-09-14-gitea-cryptominer.md). Toujours faire un GET /user/apps/appDefinitions juste avant et renvoyer l'objet complet, en ne modifiant que le(s) champ(s) voulu(s).
Gitea — détails techniques
- Containers Docker (Swarm) :
srv-captain--gitea.1.*(gitea/gitea:1.27.3, mis à jour le 2026-09-23) +srv-captain--gitea-db.1.*(mysql:5.7) - Volume de données :
captain--gitea-data→ monté sur/datadans le container- Dépôts bare :
/data/git/repositories/<owner>/<repo>.git - Config app :
/data/gitea/conf/app.ini(contient des secrets en clair :INTERNAL_TOKEN,LFS_JWT_SECRET, mot de passe MySQL — jamais versionner ce fichier) .gitconfigglobal (compte système utilisé par Gitea pour exécuter git) :/data/gitea/home/.gitconfig- Logs applicatifs :
/data/gitea/log/gitea.log(rotation quotidienne en.gz)
- Dépôts bare :
- Réseau : container Gitea sur
captain-overlay-network, IP interne observée10.0.1.75. Le reverse-proxy CapRover (captain-nginx) apparaît côté Gitea sous l'IP10.0.1.105pour toutes les requêtes publiques (pas d'IP client réelle sans configX-Forwarded-For/trusted proxies). - Connexion à la base MySQL depuis le host/container : passer par
-h 127.0.0.1(TCP), pas par défaut socket local (localhost) — le grant MySQL de l'utilisateurgiteane couvre que%/TCP, paslocalhost. - Variables d'env CapRover de l'app
gitea(vérifié viadocker service inspect srv-captain--gitea) :DB_HOST,DB_PASSWD,DB_TYPE,DB_USER,RUN_MODE. ⚠️ Aucune n'est au formatGITEA__<section>__<clé>attendu par l'image officielle pour réinjecter dansapp.iniau démarrage — elles sont donc inertes pour Gitea (c'estapp.inidans le volume qui fait foi). En cas de rotation de secret (mot de passe DB notamment), mettre à jourapp.ini+ redémarrer suffit fonctionnellement ; penser quand même à resynchroniserDB_PASSWDcôté CapRover pour ne pas laisser une copie obsolète du secret. - ⚠️ Voir
incidents/2026-09-14-gitea-cryptominer.md: l'API interne Gitea (/api/internal/manager/add-logger) a été exploitée pour injecter despackObjectsHookmalveillants dans.gitconfig. Toujours vérifier ce fichier ne contient pas d'entrées[uploadpack] packObjectsHookinattendues.
Backup
- Système actuel (depuis le 2026-10-03) : restic 0.19.1 (
/usr/local/bin/restic) → OVH Swift, conteneursans-pub-restic(même projet OVH que l'ancien backup, région GRA)- Script :
/root/restic/backup.sh(source versionnée :backup/restic-backup.shdans ce dépôt), cron/etc/cron.d/restic-backupà 03:30 - Config :
/root/restic/env(identifiants OVH) +/root/restic/password(mot de passe du dépôt) —600, jamais afficher. Le mot de passe doit aussi être dans Vaultwarden, sinon le backup est inutilisable si le serveur disparaît - Contenu : dumps SQL de toutes les bases (
/var/backups/sans-pub-db/, détection auto MySQL/MariaDB/PostgreSQL), volumes Docker (hors registres, previews et logs Nextcloud),/captain/data(hors registry),/home/cloud/apps_data,/root/backup,/etc. Les fichiers bruts d'une base sont exclus seulement si son dump a réussi (sinon sauvegardés en repli) - Particularités des bases :
limesurvey-mariadb-dbn'accepte root qu'en TCP (-h127.0.0.1, le mot de passe deroot@localhostest différent) ;writefreely-dba une tablemysql.procincompatible (dump sans--routines). Le script essaie automatiquement ces variantes - Premier snapshot complet : 2026-10-03 (~53 Gio, ~45 Gio stockés) ; restauration testée (
restic dump+ comparaison SHA256) - ⚠️ Les scripts lancés par
/etc/cron.d/n'ont quePATH=/usr/bin:/bin: d'où leexport PATH=...en tête des scripts (restic dans/usr/local/bin→ backups du 4 et 5 octobre 2026 en échec). Tester un script cron avecsystemd-run -E PATH=/usr/bin:/bin <script> - Rétention : 7 quotidiens, 4 hebdos, 6 mensuels ;
prune+check(5 % des données) le dimanche - Log :
/var/log/restic-backup.log; mail àpierre@sans.puben cas d'échec (via SMTP de poste.iomail.sans.pub:465, donc pas d'alerte si le serveur entier tombe) - Consulter :
ssh sans.pub 'sudo bash -c ". /root/restic/env && restic snapshots"'
- Script :
- Ancien système (arrêté) : Duplicity (
tecnativa/duplicity) configuré dans/root/backup/sur le serveur (docker-compose.yaml+config.json, nov. 2020) → OVH Public Cloud Archivebackup-principal(données) + Swiftbackup-principal-metadata(manifestes), région GRA. Conteneur et image absents du serveur au 2026-10-03 : plus aucun backup automatique. Ces deux fichiers contiennent des secrets (identifiants OVH, passphrase de chiffrement) — ne jamais les afficher ni les versionner. - Suites (Vaultwarden, alerte externe, rotation des identifiants OVH) : voir
plan-migration.md, phase 0.
Mail (poste.io) — certificat TLS
- poste.io (
analogic/poste.io:2.4.11) est derrière le nginx de CapRover : son renouvellement Let's Encrypt intégré ne fonctionne pas (certificat resté expiré du 2021-02-06 au 2026-10-03) - Solution (2026-10-03) :
/root/mail-cert-sync.sh(source :mail/mail-cert-sync.sh), cron/etc/cron.d/mail-cert-syncà 04:15. Si le certificatmail.sans.pubrenouvelé par CapRover (/captain/data/letencrypt/etc/live/mail.sans.pub/) diffère de/data/ssl/server.crtde poste.io : copie +21-certificate.sh+ rechargement Dovecot (SIGHUP) + redémarrage Haraka (quelques secondes). Log :/var/log/mail-cert-sync.log - Le certificat ne couvre que
mail.sans.pub: configurer les clients mail avec ce nom (passmtp.sans.pub, qui donnerait une erreur de nom) - Vérifier :
echo | openssl s_client -connect mail.sans.pub:993 2>/dev/null | openssl x509 -noout -enddate
Nextcloud — détails techniques
- Cron : service CapRover
nextcloud-cron(image persoimg-captain-nextcloud-cron:4, PHP 8.1, mai 2023 — Nextcloud lui-même tourne en PHP 8.3) qui monte le même volumenextcloud-dataet lancecron.phptoutes les 5 min. Modebackgroundjobs_mode = cron.- Vérifier :
occ config:app:get core lastcron(timestamp Unix, doit dater de < 10 min) - ⚠️ Panne du 2026-08-09 au 2026-10-03 : le conteneur, non redémarré lors du redémarrage Docker du 2026-02-16, avait perdu la résolution DNS du réseau overlay (
getaddrinfo for srv-captain--nextcloud-db failed). Corrigé par redémarrage viaappDefinitions/update(définition complète renvoyée). Si ça se reproduit : même remède.
- Vérifier :
- Rétention (depuis le 2026-10-03) :
trashbin_retention_obligationetversions_retention_obligation=auto, 30(suppression au plus tard après 30 jours ; avant,autosans quota = jamais supprimé → corbeille de 28 Go) occ:docker exec -u www-data <conteneur srv-captain--nextcloud.1> php occ ...
Suivi des tâches
Voir todo.txt (format todo.txt standard)
Mettre à jour une app CapRover
TOUJOURS passer par l'API CapRover — jamais via docker service update directement (CapRover ne le verrait pas).
Procédure
- Login (obtenir un token) :
import urllib.request, json
data = json.dumps({'password': 'CAPROVER_PASSWORD'}).encode()
req = urllib.request.Request('https://captain.cloud.sans.pub/api/v2/login', data=data, headers={'Content-Type': 'application/json'}, method='POST')
with urllib.request.urlopen(req) as r:
token = json.loads(r.read().decode())['data']['token']
- Déployer une nouvelle image :
headers = {'Content-Type': 'application/json', 'x-captain-auth': token, 'x-namespace': 'captain'}
captain_def = json.dumps({'schemaVersion': 2, 'imageName': 'IMAGE:TAG'})
payload = json.dumps({'appName': 'NOM_APP', 'captainDefinitionContent': captain_def, 'gitHash': ''}).encode()
req = urllib.request.Request('https://captain.cloud.sans.pub/api/v2/user/apps/appData/NOM_APP', data=payload, headers=headers, method='POST')
with urllib.request.urlopen(req) as r:
print(r.read().decode()) # {"status":100,"description":"Deploy is done"}
Note :
curla un problème d'encodage dans cet environnement — utiliserpython3à la place.
Commandes utiles
# Voir les logs d'un service
ssh cloud@sans.pub "docker service logs srv-captain--NOM --tail 100"
# Espace disque des volumes
ssh cloud@sans.pub "docker system df -v"
# Nettoyer les images inutilisées
ssh cloud@sans.pub "docker image prune -a"
Attention
- Debian 9 est obsolète (fin de support LTS)
- RAM : surveiller si ajout de nouveaux services (mail = 1,78 Go, scenari = 862 Mo)