TimeTrackr Stack : PostgreSQL 17 + tunnel SSH + sync Odoo
1. Quoi ? — Définition et contexte
Section intitulée « 1. Quoi ? — Définition et contexte »La TimeTrackr Stack est la couche données du système de suivi du temps qui alimente les timesheets Odoo. Elle se distingue des autres stacks par le fait qu’elle n’expose aucun port à Internet : seul le client desktop, après ouverture d’un tunnel SSH dédié, peut atteindre la base.
| Composant | Rôle | Localisation |
|---|---|---|
| TimeTrackr.exe | Client Windows desktop (timer + saisie d’entrées) | Poste utilisateur |
| Tunnel SSH | Transport chiffré vers le VPS, restreint au port PG | Connexion utilisateur ↔ VPS |
| PostgreSQL 17 | Stockage local des entrées de temps | VPS (timetrackr-stack) |
| N8N webhooks | Sync vers Odoo (projets en lecture, entrées en écriture) | VPS (n8n-stack) |
| Odoo | Source des projets et destination des timesheets | VPS (odoo-stack) |
Architecture visuelle
Section intitulée « Architecture visuelle »2. Pourquoi ? — Enjeux et motivations
Section intitulée « 2. Pourquoi ? — Enjeux et motivations »Pourquoi pas exposer PostgreSQL directement ?
Section intitulée « Pourquoi pas exposer PostgreSQL directement ? »| Approche | Pour | Contre |
|---|---|---|
| Port 5432 public | Simple, IPs autorisées par firewall | Surface d’attaque permanente, brute force, scans, fuite si firewall mal configuré |
| Tunnel SSH dédié | Aucun port DB sur Internet, auth par clé SSH, restrictions sshd granulaires | Setup initial plus lourd (clé à déployer côté client) |
| VPN (WireGuard) | Confort multi-services | Surcharge pour un seul port DB, gestion d’IPs/conf à entretenir |
Le tunnel SSH gagne pour ce cas : un seul utilisateur, un seul port, et l’auth timetrackr-tunnel ne peut rien faire d’autre que forwarder vers localhost:5433 (pas de shell, pas d’exécution).
Pourquoi un buffer local plutôt qu’écrire direct dans Odoo ?
Section intitulée « Pourquoi un buffer local plutôt qu’écrire direct dans Odoo ? »Pourquoi sync via N8N et pas connexion directe Odoo ?
Section intitulée « Pourquoi sync via N8N et pas connexion directe Odoo ? »| Choix | Avantage |
|---|---|
| N8N webhooks | Logique de mapping (username TimeTrackr → employee Odoo) dehors du client, modifiable sans redéployer l’exe |
| Header Auth | Token rotatable, indépendant des credentials Odoo |
| Pas de XML-RPC dans le client | Le client ne connaît jamais les credentials Odoo |
| Audit centralisé | Toutes les créations de timesheets sont visibles dans les exécutions N8N |
3. Comment ? — Mise en œuvre technique
Section intitulée « 3. Comment ? — Mise en œuvre technique »Le client TimeTrackr.exe
Section intitulée « Le client TimeTrackr.exe »Application desktop Windows qui vit dans la barre des tâches. Fonctionnalités principales :
- Timer projet/tâche : démarrer / arrêter un chrono lié à une (projet, tâche) Odoo.
- Liste de projets dynamique : récupérée au démarrage via
/webhook/timetrackr-projects. - Synchronisation par batch : envoie les entrées accumulées via
/webhook/timetrackr-entries. - Mode déconnecté : continue d’enregistrer en local (PostgreSQL), retente la sync au retour réseau.
Configuration côté client (.env)
Section intitulée « Configuration côté client (.env) »# Connexion DB (via tunnel SSH local)DB_HOST=127.0.0.1DB_PORT=15433 # port forwardé localement (configurable)DB_NAME=timetrackr_dbDB_USER=timetrackr_userDB_PASSWORD=<from VPS .env>DB_SSLMODE=require
# Webhooks N8N (HTTPS public)PROJECTS_URL=https://n8n.guigpap.com/webhook/timetrackr-projectsWEBHOOK_URL=https://n8n.guigpap.com/webhook/timetrackr-entriesWEBHOOK_TOKEN=<même token que le credential Header Auth dans N8N>Ouverture du tunnel SSH
Section intitulée « Ouverture du tunnel SSH »Le client (ou un service Windows associé) ouvre le tunnel avant d’accéder à la base :
ssh -N -L 15433:localhost:5433 timetrackr-tunnel@85.31.237.23 -i ~/.ssh/timetrackr_key-N: pas de commande, juste le forward.-L 15433:localhost:5433: le port local 15433 redirige verslocalhost:5433côté VPS.- L’utilisateur
timetrackr-tunnelest restreint à ce seul forward (voir bloc sshd ci-dessous).
Restrictions sshd côté VPS
Section intitulée « Restrictions sshd côté VPS »Le fichier sshd_timetrackr-tunnel.conf est déployé dans /etc/ssh/sshd_config.d/ :
Match User timetrackr-tunnel AllowTcpForwarding yes PermitOpen localhost:5433 ForceCommand /bin/false PermitTTY no X11Forwarding no AllowAgentForwarding no PermitTunnel no| Directive | Effet |
|---|---|
ForceCommand /bin/false | L’utilisateur ne peut pas obtenir de shell |
PermitOpen localhost:5433 | Aucun autre port forward autorisé |
PermitTTY no | Pas de terminal interactif |
X11Forwarding no / AllowAgentForwarding no / PermitTunnel no | Tous les autres types de forward désactivés |
Le déploiement d’une clé client se fait via le script utilitaire :
./timetrackr-stack/setup.sh tunnel-user # création + reload sshd (one-time)./timetrackr-stack/deploy_tunnel_key.sh client.pub # ajout d'une clé clientConfiguration PostgreSQL
Section intitulée « Configuration PostgreSQL »# timetrackr-stack/docker-compose.yaml (extrait)postgres: image: postgres:17-alpine container_name: timetrackr-postgres environment: POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" TIMETRACKR_APP_USER: ${TIMETRACKR_APP_USER:-timetrackr_user} TIMETRACKR_APP_PASSWORD: ${TIMETRACKR_APP_PASSWORD:?...} volumes: - timetrackr-db:/var/lib/postgresql/data - ./postgresql.conf:/etc/postgresql/postgresql.conf:ro - ./pg_hba.conf:/etc/postgresql/pg_hba.conf:ro - ./init-ssl.sh:/usr/local/bin/init-ssl.sh:ro - ./init-app-user.sh:/docker-entrypoint-initdb.d/10-init-app-user.sh:ro - ./ssl:/etc/postgresql/ssl:ro entrypoint: ["/bin/bash", "/usr/local/bin/init-ssl.sh"] ports: - "127.0.0.1:5433:5432" # JAMAIS 0.0.0.0 deploy: resources: limits: memory: 512M reservations: { memory: 128M }| Couche | Protection |
|---|---|
| Réseau | 127.0.0.1:5433 uniquement (pas d’exposition Internet) |
| Transport | SSL/TLS obligatoire (hostnossl reject dans pg_hba.conf), AEAD + ECDHE seulement |
| Authentification | SCRAM-SHA-256 (pas de MD5, pas de plaintext) |
| Autorisation | App user limité à SELECT/INSERT/UPDATE/DELETE sur le schéma public |
| Quotas | App user max 10 connexions concurrentes |
| Timeouts | 60 s par statement, 300 s en idle-in-transaction, TCP keepalives |
| Conteneur | no-new-privileges:true, mémoire plafonnée |
init-ssl.sh — modes SSL
Section intitulée « init-ssl.sh — modes SSL »L’entrypoint init-ssl.sh choisit dynamiquement comment monter les certificats :
| Mode | Déclencheur | Cas d’usage |
|---|---|---|
| Pré-monté | ./ssl/server.crt + server.key + ca.crt présents | sslmode=verify-full côté client (production) |
| Volume persistant | Un certificat valide existe déjà dans le volume | Reprise après restart |
| Self-signed | Aucun cert disponible | Génération à la volée (validité 10 ans), sslmode=require |
init-app-user.sh — privilèges minimaux
Section intitulée « init-app-user.sh — privilèges minimaux »À la première initialisation, l’utilisateur applicatif est créé avec exactement ce qu’il faut :
CREATE ROLE timetrackr_user WITH LOGIN PASSWORD '...';ALTER ROLE timetrackr_user CONNECTION LIMIT 10;GRANT CONNECT ON DATABASE timetrackr_db TO timetrackr_user;GRANT USAGE, CREATE ON SCHEMA public TO timetrackr_user;GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO timetrackr_user;L’utilisateur postgres (admin) reste limité à 127.0.0.1 via pg_hba.conf — donc accessible uniquement depuis l’intérieur du container.
Sync vers Odoo : les deux webhooks N8N
Section intitulée « Sync vers Odoo : les deux webhooks N8N »Côté N8N, deux workflows actifs gèrent l’aller-retour :
| Workflow | Endpoint | Méthode | Rôle |
|---|---|---|---|
| TimeTrackr - Projects | /webhook/timetrackr-projects | GET | Liste des projets/tâches Odoo pour les menus du client |
| TimeTrackr - Receive Entries | /webhook/timetrackr-entries | POST | Création des account.analytic.line dans Odoo |
Les deux webhooks sont protégés par Header Auth (X-TimeTrackr-Token). Ils ne sont pas dans la liste de blocage Caddy, contrairement aux webhooks internes — donc accessibles depuis le poste client en HTTPS public.
Le mapping username TimeTrackr → employee Odoo est stocké dans la Data Table N8N timetrackr_user_mapping. Aucun secret Odoo ne traverse le client, aucune connexion DB N8N → TimeTrackr.
Voir TimeTrackr → Odoo pour le détail des nodes, le format des payloads, et les règles de validation.
Commandes d’exploitation
Section intitulée « Commandes d’exploitation »# Stack managementdocker compose -f timetrackr-stack/docker-compose.yaml up -ddocker compose -f timetrackr-stack/docker-compose.yaml logs -f
# Vérifier que SSL est actifdocker exec timetrackr-postgres psql -U postgres -c "SHOW ssl;"
# Vérifier que le port n'est exposé qu'en localss -tlnp | grep 5433 # doit montrer 127.0.0.1:5433, jamais 0.0.0.0
# Tester l'app user en localdocker exec timetrackr-postgres psql -U timetrackr_user -d timetrackr_db -c "SELECT 1;"
# Taille de la basedocker exec timetrackr-postgres psql -U postgres -d timetrackr_db \ -c "SELECT pg_size_pretty(pg_database_size('timetrackr_db'));"
# Vérifier qu'un client tunnel n'a pas de shellssh timetrackr-tunnel@localhost # doit fermer immédiatement4. Et si ? — Perspectives et limites
Section intitulée « 4. Et si ? — Perspectives et limites »Limites actuelles
Section intitulée « Limites actuelles »| Limite | Impact | Mitigation |
|---|---|---|
| Mono-utilisateur effectif | Une seule entrée dans timetrackr_user_mapping | OK pour mon usage actuel ; ajout d’employés via la Data Table |
| Client Windows uniquement | Pas de macOS / Linux / mobile | À envisager si besoin (le protocole tunnel SSH + webhooks reste portable) |
| Self-signed par défaut | Avertissement au premier démarrage du client | Monter des certs Let’s Encrypt dans ./ssl/ pour verify-full |
| Pas de re-sync automatique côté N8N | Si un POST échoue côté Odoo, l’entrée reste côté client | Le client retente ; un workflow de réconciliation reste à écrire |
Scénarios d’évolution
Section intitulée « Scénarios d’évolution »Si je veux ouvrir à un deuxième utilisateur :
- Déployer une seconde clé via
deploy_tunnel_key.sh. - Ajouter une ligne dans
timetrackr_user_mapping(username, employee_id Odoo). - Le reste de la config (DB user, port forward) est partagé.
Si je veux remplacer le tunnel SSH :
- WireGuard à
wg.guigpap.comexposerait un sous-réseau privé incluant Postgres. Plus confortable pour multi-services (admin Postgres en plus du seul client), mais coût opérationnel supérieur (gestion d’IPs, MTU, pair-config).
Si je veux changer le mapping de timesheet :
- Modifier directement le workflow N8N
TimeTrackr - Receive Entries(par exemple ajouter une catégorie analytique, filtrer certaines tâches). - Aucun déploiement client requis — c’est tout l’intérêt de la sync via webhooks.
Métriques à surveiller
Section intitulée « Métriques à surveiller »| Métrique | Source | Seuil d’attention |
|---|---|---|
| Taille de la base | pg_database_size('timetrackr_db') | Croissance anormale = revoir la rétention locale côté client |
| Connexions actives | SELECT count(*) FROM pg_stat_activity | Proche de 10 = limite app user atteinte |
| Échecs sync N8N | Exécutions du workflow TimeTrackr - Receive Entries | Spike = vérifier le token, l’état Odoo, le mapping |
Tentatives SSH timetrackr-tunnel | logs auth.log | Brute force = revoir les IPs autorisées au niveau firewall |
Pages liées
Section intitulée « Pages liées »Infrastructure
Section intitulée « Infrastructure »- Architecture VPS — Vue d’ensemble
- Security Stack — Caddy ne route pas TimeTrackr (tunnel SSH dédié)
- Odoo 18 Setup — Modèle
account.analytic.linecible
Workflows
Section intitulée « Workflows »- TimeTrackr → Odoo — Workflows N8N de sync (Projects + Entries)
Référence
Section intitulée « Référence »- Glossaire — SCRAM-SHA-256, SSH tunnel, timesheet