Aller au contenu

TimeTrackr Stack : PostgreSQL 17 + tunnel SSH + sync Odoo

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.

ComposantRôleLocalisation
TimeTrackr.exeClient Windows desktop (timer + saisie d’entrées)Poste utilisateur
Tunnel SSHTransport chiffré vers le VPS, restreint au port PGConnexion utilisateur ↔ VPS
PostgreSQL 17Stockage local des entrées de tempsVPS (timetrackr-stack)
N8N webhooksSync vers Odoo (projets en lecture, entrées en écriture)VPS (n8n-stack)
OdooSource des projets et destination des timesheetsVPS (odoo-stack)

VPS Hostinger

odoo-stack

n8n-stack

timetrackr-stack · timetrackr-internal

HTTPS · X-TimeTrackr-Token

HTTPS · X-TimeTrackr-Token

port 22 · clé SSH

sshd · Match User timetrackr-tunnel

ForceCommand /bin/false

PermitOpen localhost:5433

PermitTTY no

TimeTrackr.exe · Windows · barre des tâches

SSH tunnel · timetrackr-tunnel@VPS:22

timetrackr-postgres · PG 17 · 127.0.0.1:5433

SSL/TLS · SCRAM-SHA-256

Webhook · /webhook/timetrackr-projects

Webhook · /webhook/timetrackr-entries

Data Table · timetrackr_user_mapping

project.project / project.task

account.analytic.line


ApprochePourContre
Port 5432 publicSimple, IPs autorisées par firewallSurface 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 granulairesSetup initial plus lourd (clé à déployer côté client)
VPN (WireGuard)Confort multi-servicesSurcharge 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 ? »
ChoixAvantage
N8N webhooksLogique de mapping (username TimeTrackr → employee Odoo) dehors du client, modifiable sans redéployer l’exe
Header AuthToken rotatable, indépendant des credentials Odoo
Pas de XML-RPC dans le clientLe client ne connaît jamais les credentials Odoo
Audit centraliséToutes les créations de timesheets sont visibles dans les exécutions N8N

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.
# Connexion DB (via tunnel SSH local)
DB_HOST=127.0.0.1
DB_PORT=15433 # port forwardé localement (configurable)
DB_NAME=timetrackr_db
DB_USER=timetrackr_user
DB_PASSWORD=<from VPS .env>
DB_SSLMODE=require
# Webhooks N8N (HTTPS public)
PROJECTS_URL=https://n8n.guigpap.com/webhook/timetrackr-projects
WEBHOOK_URL=https://n8n.guigpap.com/webhook/timetrackr-entries
WEBHOOK_TOKEN=<même token que le credential Header Auth dans N8N>

Le client (ou un service Windows associé) ouvre le tunnel avant d’accéder à la base :

Fenêtre de terminal
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 vers localhost:5433 côté VPS.
  • L’utilisateur timetrackr-tunnel est restreint à ce seul forward (voir bloc sshd ci-dessous).

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
DirectiveEffet
ForceCommand /bin/falseL’utilisateur ne peut pas obtenir de shell
PermitOpen localhost:5433Aucun autre port forward autorisé
PermitTTY noPas de terminal interactif
X11Forwarding no / AllowAgentForwarding no / PermitTunnel noTous les autres types de forward désactivés

Le déploiement d’une clé client se fait via le script utilitaire :

Fenêtre de terminal
./timetrackr-stack/setup.sh tunnel-user # création + reload sshd (one-time)
./timetrackr-stack/deploy_tunnel_key.sh client.pub # ajout d'une clé client
# 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 }
CoucheProtection
Réseau127.0.0.1:5433 uniquement (pas d’exposition Internet)
TransportSSL/TLS obligatoire (hostnossl reject dans pg_hba.conf), AEAD + ECDHE seulement
AuthentificationSCRAM-SHA-256 (pas de MD5, pas de plaintext)
AutorisationApp user limité à SELECT/INSERT/UPDATE/DELETE sur le schéma public
QuotasApp user max 10 connexions concurrentes
Timeouts60 s par statement, 300 s en idle-in-transaction, TCP keepalives
Conteneurno-new-privileges:true, mémoire plafonnée

L’entrypoint init-ssl.sh choisit dynamiquement comment monter les certificats :

ModeDéclencheurCas d’usage
Pré-monté./ssl/server.crt + server.key + ca.crt présentssslmode=verify-full côté client (production)
Volume persistantUn certificat valide existe déjà dans le volumeReprise après restart
Self-signedAucun cert disponibleGénération à la volée (validité 10 ans), sslmode=require

À 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.

Côté N8N, deux workflows actifs gèrent l’aller-retour :

WorkflowEndpointMéthodeRôle
TimeTrackr - Projects/webhook/timetrackr-projectsGETListe des projets/tâches Odoo pour les menus du client
TimeTrackr - Receive Entries/webhook/timetrackr-entriesPOSTCré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.

Fenêtre de terminal
# Stack management
docker compose -f timetrackr-stack/docker-compose.yaml up -d
docker compose -f timetrackr-stack/docker-compose.yaml logs -f
# Vérifier que SSL est actif
docker exec timetrackr-postgres psql -U postgres -c "SHOW ssl;"
# Vérifier que le port n'est exposé qu'en local
ss -tlnp | grep 5433 # doit montrer 127.0.0.1:5433, jamais 0.0.0.0
# Tester l'app user en local
docker exec timetrackr-postgres psql -U timetrackr_user -d timetrackr_db -c "SELECT 1;"
# Taille de la base
docker 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 shell
ssh timetrackr-tunnel@localhost # doit fermer immédiatement

LimiteImpactMitigation
Mono-utilisateur effectifUne seule entrée dans timetrackr_user_mappingOK pour mon usage actuel ; ajout d’employés via la Data Table
Client Windows uniquementPas de macOS / Linux / mobileÀ envisager si besoin (le protocole tunnel SSH + webhooks reste portable)
Self-signed par défautAvertissement au premier démarrage du clientMonter des certs Let’s Encrypt dans ./ssl/ pour verify-full
Pas de re-sync automatique côté N8NSi un POST échoue côté Odoo, l’entrée reste côté clientLe client retente ; un workflow de réconciliation reste à écrire

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.com exposerait 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étriqueSourceSeuil d’attention
Taille de la basepg_database_size('timetrackr_db')Croissance anormale = revoir la rétention locale côté client
Connexions activesSELECT count(*) FROM pg_stat_activityProche de 10 = limite app user atteinte
Échecs sync N8NExécutions du workflow TimeTrackr - Receive EntriesSpike = vérifier le token, l’état Odoo, le mapping
Tentatives SSH timetrackr-tunnellogs auth.logBrute force = revoir les IPs autorisées au niveau firewall

  • Glossaire — SCRAM-SHA-256, SSH tunnel, timesheet