Face-recognition attendance for multi-site organisations
Employees check in and out by looking at a phone camera on their site's Wi-Fi. Each site runs a small relay server that keeps working when the head office is unreachable, and one central main server collects everything.
Takes the picture once a face is in front of the camera. It never decides who is checking in and never receives anyone's face data.
Relay server
A Windows (or Linux) PC on the site LAN
Recognises the face (OpenCV YuNet + SFace), stamps the check-in with trusted internet time, stores it in SQLite and pushes it to the main server. Shows today's check-ins and a QR code for phones.
Main server
A Linux server reachable from every site
Public API for relays and the admin web console. Stores users, photos, settings and every check-in in PostgreSQL, recomputes OK/KO and re-checks each snapshot against the user's photo.
The admin console on the main server: today's counts, each relay's last contact, and the latest check-ins.
Open the relay's QR code on a phone and do a test check-in.
Requirements at a glance
Main server
OS
Linux with systemd (Debian/Ubuntu or Fedora/RHEL family)
Python
3.10 or newer, with venv
Database
PostgreSQL 12+ (the install script can install it)
Network
A public HTTPS name the relays can reach; outbound HTTPS to the npm registry and OpenCV model zoo on first start
Clock
NTP-synchronised: relay requests more than 5 min off are refused
Relay server
OS
Windows 10/11 (main target) or Linux desktop
Software
Nothing to install: the relay is delivered as a single executable
LAN
Fixed IP or DHCP reservation; TCP 8443 open to phones
Internet
Required for check-ins (trusted time). Main server may be down; internet may not.
Disk
About 130 MB for the executable plus ~39 MB of face models
in-Time · documentation
Pointage par reconnaissance faciale pour les organisations multi-sites
Les employés pointent leur entrée et leur sortie en regardant la caméra d'un téléphone connecté au Wi-Fi du site. Chaque site fait tourner un petit serveur relais qui continue de fonctionner quand le siège est injoignable, et un serveur principal central rassemble tout.
Le navigateur de n'importe quel téléphone sur le Wi-Fi du site
Prend la photo dès qu'un visage est devant la caméra. Elle ne décide jamais qui pointe et ne reçoit jamais les données faciales de quiconque.
Serveur relais
Un PC Windows (ou Linux) sur le réseau local du site
Reconnaît le visage (OpenCV YuNet + SFace), horodate le pointage avec une heure Internet fiable, l'enregistre dans SQLite et l'envoie au serveur principal. Affiche les pointages du jour et un QR code pour les téléphones.
Serveur principal
Un serveur Linux joignable depuis tous les sites
API publique pour les relais et console web d'administration. Stocke les utilisateurs, photos, paramètres et pointages dans PostgreSQL, recalcule OK/KO et revérifie chaque photo de pointage avec la photo de l'utilisateur.
La console d'administration du serveur principal : chiffres du jour, dernier contact de chaque relais et derniers pointages.
Ouvrez le QR code du relais sur un téléphone et faites un pointage de test.
Prérequis en bref
Serveur principal
Système
Linux avec systemd (famille Debian/Ubuntu ou Fedora/RHEL)
Python
3.10 ou plus récent, avec venv
Base de données
PostgreSQL 12+ (le script d'installation peut l'installer)
Réseau
Un nom HTTPS public joignable par les relais ; HTTPS sortant vers le registre npm et le dépôt de modèles OpenCV au premier démarrage
Horloge
Synchronisée par NTP : les requêtes des relais décalées de plus de 5 min sont refusées
Serveur relais
Système
Windows 10/11 (cible principale) ou Linux avec bureau
Logiciels
Rien à installer : le relais est livré sous forme d'un exécutable unique
Réseau local
IP fixe ou réservation DHCP ; TCP 8443 ouvert aux téléphones
Internet
Indispensable aux pointages (heure fiable). Le serveur principal peut être indisponible, Internet non.
Disque
Environ 130 Mo pour l'exécutable et ~39 Mo de modèles faciaux
Understand
Deployment architecture
Phones talk only to their site's relay over the LAN. Relays talk to the main server over the internet through one authenticated API. Nothing on the main server ever calls a relay.
Deployment diagram
Solid blue: the only link between sites and head office. Dashed: outbound calls to services you don't host.
What runs where
Component
Host
Stack
Data it keeps
Phone page
Served by the relay
HTML/JS + face-api.js
Nothing. It only detects that a face is present before sending the picture.
On the main server: ID, full name, department and a photo. The server checks that the photo holds exactly one usable face and keeps it as the reference.
Relays sync every 30 s
Each relay calls GET /ping, /settings, /users and downloads new or changed photos, then computes their face data locally.
Phone takes the picture
The employee opens https://<relay-ip>:8443. When a face is in view the picture is sent to POST /api/identify. The relay recognises the person and returns the name with a one-time token valid 90 s for that phone.
Phone confirms IN or OUT
IN/OUT is suggested and sent after 4 s unless the person taps the other option. POST /api/checkins is accepted only with the token; the stored snapshot is the picture the relay recognised.
Relay records and pushes
The check-in is stamped with trusted internet time, shown OK/KO in the relay window, written to SQLite and pushed to POST /api/v1/checkins immediately, or later if the main server is unreachable.
Main server verifies
It recomputes OK/KO from the site's time zone and settings and compares the snapshot with the user's photo. Mismatches, missing faces, replays and late deliveries are flagged on the dashboard for review.
Network and ports
From
To
Port
Purpose
Phones
Relay
8443/tcp LAN
Phone page and private API, self-signed HTTPS. Only private-network clients are accepted unless allow_public_clients = true.
Relay
Main server
443/tcp
Relay API /api/v1 through the reverse proxy.
Relay
Google, Cloudflare, Microsoft
443/tcp
Trusted time and internet check.
Relay, main
npm registry, OpenCV model zoo
443/tcp
First start only: face-api.js and the face models (~39 MB, SHA-256 checked).
Reverse proxy
gunicorn
8000/tcp loopback
Keep it on 127.0.0.1. Use the LAN IP only for local relays.
Flask app
PostgreSQL
5432/tcp loopback
Or the local Unix socket when the install script created the database.
Offline behaviour
The relay caches users and settings, decides OK/KO locally and queues check-ins. Trusted time is the one thing it cannot do without.
Internet
Main server
Check-ins
OK
Reachable
Accepted and synced immediately
OK
Unreachable
Accepted, synced when it is back (grey cloud in the relay window)
Down
any
Refused: red banner on the relay, "check-in unavailable" on phones, retried every 10 s
Trust and security model
Relay authentication. Every API call carries a bearer token, comes from an allowed IP/CIDR, names the relay's machine ID in X-Relay-Id and is signed with the relay's private key (X-Relay-Time, X-Relay-Signature). A token binds to the first relay that uses it: its machine ID and its public key (X-Relay-Key) are recorded with the first request, and every later request and check-in must be signed with the matching private key. A stolen token or a cloned relay without that key is refused.
Time. Check-in times come from HTTPS Date headers, counted forward with a monotonic clock, so changing the Windows clock or time zone has no effect. The site time zone is pinned in relay.ini.
Server-side re-check. The main server recomputes OK/KO itself, refuses timestamps in the future, and flags check-ins delivered more than 10 minutes late while the relay was connected.
Secrets on the relay. The token is moved from relay.ini into data/secrets.bin (Windows DPAPI for the running account) and the data/ folder is restricted to that account, SYSTEM and Administrators.
Biometric dataPhotos, snapshots and face data are personal biometric data. Keep them out of version control, restrict access to both servers' data folders and check the data-protection rules that apply to you. There is no liveness detection: a printed photo can still fool the camera, which is why every check-in keeps its snapshot.
Comprendre
Architecture de déploiement
Les téléphones ne parlent qu'au relais de leur site, sur le réseau local. Les relais parlent au serveur principal par Internet, via une seule API authentifiée. Le serveur principal n'appelle jamais un relais.
Schéma de déploiement
Trait bleu plein : le seul lien entre les sites et le siège. Pointillés : appels sortants vers des services que vous n'hébergez pas.
Qui tourne où
Composant
Hôte
Technologies
Données conservées
Page téléphone
Servie par le relais
HTML/JS + face-api.js
Rien. Elle détecte seulement la présence d'un visage avant d'envoyer la photo.
Serveur relais
PC du site
Python, Flask, PySide6 (Qt), SQLite, OpenCV
data/ : cache SQLite chiffré, photos des utilisateurs en cache, photos de pointage, certificat auto-signé, secrets.bin, server.id, journal
Application principale
Serveur Linux
Python, Flask, gunicorn, OpenCV
photos/<ID>.jpg|png, snapshots/, models/, .env
Base de données
Même serveur (par défaut)
PostgreSQL 12+
Utilisateurs, relais (jetons hachés, IP autorisées), paramètres, pointages, résultats du contrôle facial
Déroulement d'un pointage
L'administrateur ajoute un utilisateur
Sur le serveur principal : ID, nom complet, département et photo. Le serveur vérifie que la photo contient exactement un visage exploitable et la garde comme référence.
Les relais se synchronisent toutes les 30 s
Chaque relais appelle GET /ping, /settings, /users et télécharge les photos nouvelles ou modifiées, puis calcule lui-même leurs données faciales.
Le téléphone prend la photo
L'employé ouvre https://<ip-du-relais>:8443. Quand un visage est visible, la photo est envoyée à POST /api/identify. Le relais reconnaît la personne et renvoie son nom avec un jeton à usage unique valable 90 s pour ce téléphone.
Le téléphone confirme ENTRÉE ou SORTIE
IN/OUT est proposé puis envoyé après 4 s, sauf si la personne choisit l'autre option. POST /api/checkins n'est accepté qu'avec le jeton ; la photo enregistrée est celle que le relais a reconnue.
Le relais enregistre et transmet
Le pointage est horodaté avec l'heure Internet fiable, affiché OK/KO dans la fenêtre du relais, écrit dans SQLite et envoyé à POST /api/v1/checkins immédiatement, ou plus tard si le serveur principal est injoignable.
Le serveur principal vérifie
Il recalcule OK/KO à partir du fuseau horaire et des paramètres du site et compare la photo de pointage à celle de l'utilisateur. Les non-correspondances, visages absents, rejeux et arrivées tardives sont signalés sur le tableau de bord.
Réseau et ports
De
Vers
Port
Usage
Téléphones
Relais
8443/tcp LAN
Page téléphone et API privée, HTTPS auto-signé. Seuls les clients des réseaux privés sont acceptés, sauf si allow_public_clients = true.
Relais
Serveur principal
443/tcp
API des relais /api/v1 via le proxy inverse.
Relais
Google, Cloudflare, Microsoft
443/tcp
Heure fiable et contrôle de la connexion Internet.
Relais, principal
Registre npm, dépôt de modèles OpenCV
443/tcp
Premier démarrage uniquement : face-api.js et les modèles faciaux (~39 Mo, vérifiés par SHA-256).
Proxy inverse
gunicorn
8000/tcp boucle locale
Gardez-le sur 127.0.0.1. N'utilisez l'IP du LAN que pour les relais locaux.
Application Flask
PostgreSQL
5432/tcp boucle locale
Ou le socket Unix local quand le script d'installation a créé la base.
Fonctionnement hors ligne
Le relais garde en cache les utilisateurs et les paramètres, décide OK/KO localement et met les pointages en file d'attente. La seule chose dont il ne peut pas se passer est l'heure fiable.
Internet
Serveur principal
Pointages
OK
Joignable
Acceptés et synchronisés immédiatement
OK
Injoignable
Acceptés, synchronisés à son retour (nuage gris dans la fenêtre du relais)
Coupé
quel qu'il soit
Refusés : bandeau rouge sur le relais, « pointage indisponible » sur les téléphones, nouvel essai toutes les 10 s
Modèle de confiance et de sécurité
Authentification des relais. Chaque appel d'API porte un jeton bearer, provient d'une IP/CIDR autorisée, indique l'ID machine du relais dans X-Relay-Id et est signé avec la clé privée du relais (X-Relay-Time, X-Relay-Signature). Un jeton est lié au premier relais qui l'utilise : son ID machine et sa clé publique (X-Relay-Key) sont enregistrés à la première requête, et chaque requête et pointage suivants doivent être signés avec la clé privée correspondante. Un jeton volé ou un relais cloné sans cette clé est refusé.
Heure. L'heure des pointages provient des en-têtes HTTPS Date, puis avance avec une horloge monotone : modifier l'heure ou le fuseau de Windows n'a aucun effet. Le fuseau du site est fixé dans relay.ini.
Revérification côté serveur. Le serveur principal recalcule lui-même OK/KO, refuse les horodatages dans le futur et signale les pointages livrés plus de 10 minutes en retard alors que le relais était connecté.
Secrets sur le relais. Le jeton est déplacé de relay.ini vers data/secrets.bin (Windows DPAPI pour le compte qui exécute le relais) et le dossier data/ est réservé à ce compte, à SYSTEM et aux Administrateurs.
Données biométriquesLes photos, photos de pointage et données faciales sont des données personnelles biométriques. Restreignez l'accès aux dossiers de données des deux serveurs et vérifiez les règles de protection des données qui vous concernent. Il n'y a pas de détection du vivant : une photo imprimée peut encore tromper la caméra, c'est pourquoi chaque pointage conserve sa photo.
Deploy · Linux
Deploy the main server
The recommended path is the install script: it creates a system user, a Python environment, the database if you want it, a systemd service and a cron job, and generates the secrets. It is the supported way to deploy the main server.
Before you start
A Linux server with systemd, sudo rights and Python 3.10+. The script installs python3-venv, PostgreSQL and cron with apt, dnf or yum when they are missing.
A DNS name for the server (for example intime.example.com) and a way to get a TLS certificate.
Time synchronisation (NTP / systemd-timesyncd) enabled. Relay requests whose signed time is more than 5 minutes away from the server clock are refused with clock_skew.
Install with the script
Run the installer
On the server, as a user with sudo rights:
$ curl -sSL https://raw.githubusercontent.com/dijey099/in-Time/refs/heads/master/main_server/install_main_server.sh \
| sh
The script asks whether you already have a PostgreSQL server. Answer n to let it install PostgreSQL and create the in-time role and database (local socket, peer authentication, no password). On a host without a terminal, answer in advance with IN_TIME_POSTGRES=install or IN_TIME_POSTGRES=existing.
Save the admin password
On a first install the script prints the generated console login (user admin, a random password). It is also stored in /var/lib/in-time/main-server/.env.
Edit the configuration
$ sudo nano /var/lib/in-time/main-server/.env
At minimum check DEPARTMENTS and DATABASE_URL (already set if the script created the database). Behind a proxy also set TRUSTED_PROXIES, CF_PROXY and SESSION_COOKIE_SECURE=true; see HTTPS & reverse proxy. All variables are in the configuration reference.
The server refuses to start whenSECRET_KEY is empty, a placeholder or under 32 characters; ADMIN_PASSWORD is empty, a placeholder or under 12 characters; DEPARTMENTS is not set; or TRUSTED_PEERS contains an invalid address. The reason is printed in the journal.
HTTPS and reverse proxy
Relays must reach the main server over HTTPS, and the camera option on the admin user page only works over HTTPS. Put nginx (or Apache, Traefik, Cloudflare) in front of gunicorn and tell the app how many proxies it sits behind, so the relay IP check sees the real public address.
EXAMPLE
# /etc/nginx/sites-available/in-time (adapt the name and certificate paths)
server {
listen 443 ssl http2;
server_name intime.example.com;
ssl_certificate /etc/letsencrypt/live/intime.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intime.example.com/privkey.pem;
client_max_body_size 32m; # matches the app's MAX_CONTENT_LENGTH (photos, snapshots)
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
}
server {
listen 80;
server_name intime.example.com;
return 301 https://$host$request_uri;
}
Then in .env: TRUSTED_PROXIES=1 and SESSION_COOKIE_SECURE=true, and restart the service. With Cloudflare, pick the row that matches your setup:
Setup
.env
No proxy
TRUSTED_PROXIES=0, CF_PROXY=false
nginx / Apache / Traefik on the same host
TRUSTED_PROXIES=1 (proxy sends X-Forwarded-For)
Cloudflare → app, or Cloudflare Tunnel
CF_PROXY=true
Cloudflare → nginx → app
CF_PROXY=true and TRUSTED_PROXIES=1
Forwarded headers are only read from TRUSTED_PEERS (default: this machine). If the proxy or a cloudflared tunnel runs on another host or in Docker, add its address there. The IP the server sees for each relay is shown on the Relays page under Last seen.
First configuration
Log in
Open https://intime.example.com and log in as admin with the password from .env. The dashboard warns you while the face check models are still downloading.
Review settings
Set the global max in time and min out time on the Settings page. Each site can override them.
Add users
Users page: ID, full name, department and a photo (upload or camera capture). The face check must succeed before the user can be recognised.
Register one relay per site
Relays → Add a relay: enter the public IP(s) the site connects from (CIDR allowed; * means any and is for testing only) and copy the token. It is shown once. From the shell:
Hand the token to whoever installs the relay at that site. It binds to the first relay server that uses it.
Updating
Run the install script again. It stops the service, replaces the code and keeps .env, .venv, photos/, snapshots/, models/, app/static/vendor/ and cf_ips.txt, reinstalls dependencies and restarts the service if it was running. Compare your .env with .env.example afterwards for new settings.
Back up before updatingThe data that matters is the PostgreSQL database plus the photos/ and snapshots/ folders and .env. For example sudo -u in-time pg_dump in-time > in-time.sql when the script created the database.
Déployer · Linux
Déployer le serveur principal
Le script d'installation crée un utilisateur système, un environnement Python, la base de données si vous le souhaitez, un service systemd et une tâche cron, et génère les secrets. C'est la méthode prise en charge pour déployer le serveur principal.
Avant de commencer
Un serveur Linux avec systemd, des droits sudo et Python 3.10+. Le script installe python3-venv, PostgreSQL et cron avec apt, dnf ou yum s'ils manquent.
Un nom DNS pour le serveur (par exemple intime.example.com) et un moyen d'obtenir un certificat TLS.
La synchronisation de l'heure (NTP / systemd-timesyncd) activée. Les requêtes de relais dont l'heure signée s'écarte de plus de 5 minutes de l'horloge du serveur sont refusées avec clock_skew.
Installer avec le script
Lancer l'installateur
Sur le serveur, avec un utilisateur disposant des droits sudo :
$ curl -sSL https://raw.githubusercontent.com/dijey099/in-Time/refs/heads/master/main_server/install_main_server.sh \
| sh
Le script demande si vous avez déjà un serveur PostgreSQL. Répondez n pour qu'il installe PostgreSQL et crée le rôle et la base in-time (socket local, authentification peer, sans mot de passe). Sur une machine sans terminal, répondez à l'avance avec IN_TIME_POSTGRES=install ou IN_TIME_POSTGRES=existing.
Conserver le mot de passe administrateur
Lors d'une première installation, le script affiche l'identifiant généré pour la console (utilisateur admin, mot de passe aléatoire). Il est aussi enregistré dans /var/lib/in-time/main-server/.env.
Modifier la configuration
$ sudo nano /var/lib/in-time/main-server/.env
Vérifiez au minimum DEPARTMENTS et DATABASE_URL (déjà renseignée si le script a créé la base). Derrière un proxy, renseignez aussi TRUSTED_PROXIES, CF_PROXY et SESSION_COOKIE_SECURE=true ; voir HTTPS et proxy inverse. Toutes les variables sont dans la référence de configuration.
Le service est déjà activé au démarrage de la machine. Il écoute sur 127.0.0.1:8000. Au premier démarrage, il télécharge face-api.js et les modèles faciaux OpenCV.
Options de l'installateur
Variable
Effet
IN_TIME_BRANCH
Branche ou tag à installer (par défaut master). Préférez un tag de version en production.
IN_TIME_SHA256
SHA-256 attendu de l'archive téléchargée. L'installation s'arrête s'il diffère.
IN_TIME_POSTGRES
existing ou install. Répond à la question PostgreSQL ; obligatoire sans terminal.
IN_TIME_SRC
Installer depuis un dossier main_server local au lieu de télécharger (serveurs hors ligne).
IN_TIME_REPO
Dépôt à télécharger (par défaut dijey099/in-Time), par exemple votre fork.
IN_TIME_GITHUB_TOKEN
Jeton GitHub, seulement pour installer depuis un fork privé.
Ce que le script met en place
Élément
Emplacement / valeur
Application
/var/lib/in-time/main-server
Utilisateur système
in-time (répertoire /var/lib/in-time, sans shell de connexion)
Environnement Python
/var/lib/in-time/main-server/.venv
Configuration
.env, mode 600, avec SECRET_KEY et ADMIN_PASSWORD aléatoires
/etc/cron.d/in-time-cf-ips, toutes les 2 h, journal dans /var/lib/in-time/cf_ips_update.log
Base de données (si installée)
rôle et base in-time, DATABASE_URL=postgresql+psycopg://in-time@/in-time?host=/var/run/postgresql
L'adresse d'écoute et le nombre de workers de gunicorn viennent de SERVER (par défaut 127.0.0.1:8000) et WORKERS (par défaut 2) dans .env.
Exemple d'installation sans interaction
# version figée, archive vérifiée, PostgreSQL installé localement, aucune question$ curl -sSL https://raw.githubusercontent.com/dijey099/in-Time/refs/heads/master/main_server/install_main_server.sh \
| IN_TIME_BRANCH=<tag de version> IN_TIME_SHA256=<sha256 de l'archive> \
IN_TIME_POSTGRES=install sh
Le serveur refuse de démarrer quandSECRET_KEY est vide, une valeur d'exemple ou fait moins de 32 caractères ; ADMIN_PASSWORD est vide, une valeur d'exemple ou fait moins de 12 caractères ; DEPARTMENTS n'est pas défini ; ou TRUSTED_PEERS contient une adresse invalide. La raison est écrite dans le journal.
HTTPS et proxy inverse
Les relais doivent joindre le serveur principal en HTTPS, et l'option caméra de la page utilisateur ne fonctionne qu'en HTTPS. Placez nginx (ou Apache, Traefik, Cloudflare) devant gunicorn et indiquez à l'application derrière combien de proxys elle se trouve, pour que le contrôle d'IP des relais voie la vraie adresse publique.
EXEMPLE
# /etc/nginx/sites-available/in-time (adaptez le nom et les chemins du certificat)
server {
listen 443 ssl http2;
server_name intime.example.com;
ssl_certificate /etc/letsencrypt/live/intime.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intime.example.com/privkey.pem;
client_max_body_size 32m; # correspond à MAX_CONTENT_LENGTH de l'application (photos)
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
}
server {
listen 80;
server_name intime.example.com;
return 301 https://$host$request_uri;
}
Puis dans .env : TRUSTED_PROXIES=1 et SESSION_COOKIE_SECURE=true, et redémarrez le service. Avec Cloudflare, choisissez la ligne qui correspond à votre installation :
Les en-têtes transmis ne sont lus que depuis TRUSTED_PEERS (par défaut : cette machine). Si le proxy ou un tunnel cloudflared tourne sur une autre machine ou dans Docker, ajoutez son adresse. L'IP vue pour chaque relais apparaît sur la page Relays, sous Last seen.
Première configuration
Se connecter
Ouvrez https://intime.example.com et connectez-vous en tant qu'admin avec le mot de passe du fichier .env. Le tableau de bord vous avertit tant que les modèles du contrôle facial sont en cours de téléchargement.
Vérifier les paramètres
Définissez l'heure d'arrivée maximale et l'heure de départ minimale globales sur la page Settings. Chaque site peut les modifier.
Ajouter les utilisateurs
Page Users : ID, nom complet, département et photo (envoi d'un fichier ou prise par la caméra). Le contrôle facial doit réussir pour que l'utilisateur puisse être reconnu.
Enregistrer un relais par site
Relays → Add a relay : saisissez la ou les IP publiques depuis lesquelles le site se connecte (CIDR accepté ; * signifie toutes et ne sert qu'aux tests) et copiez le jeton. Il n'est affiché qu'une fois. Depuis le terminal :
Transmettez le jeton à la personne qui installe le relais sur ce site. Il est lié au premier serveur relais qui l'utilise.
Mise à jour
Relancez le script d'installation. Il arrête le service, remplace l'application et conserve .env, .venv, photos/, snapshots/, models/, app/static/vendor/ et cf_ips.txt, réinstalle les dépendances et redémarre le service s'il tournait. Comparez ensuite votre .env avec .env.example pour repérer les nouveaux paramètres.
Sauvegardez avant de mettre à jourLes données importantes sont la base PostgreSQL, les dossiers photos/ et snapshots/ et le fichier .env. Par exemple sudo -u in-time pg_dump in-time > in-time.sql quand le script a créé la base.
Deploy · Windows / Linux
Deploy a relay server
One relay per site. It is a single executable next to a relay.ini file: double-click it and the relay window opens with a QR code for the phones.
The relay window: trusted time and main server status, the address phones open, and today's check-ins with their sync state.
Before you start
A PC at the site, on the same network as the phones, with a fixed IP or DHCP reservation. The relay's certificate is regenerated when its LAN IP changes, and phones would then see a new warning.
Internet access from that PC: HTTPS to the main server, the time sources, and on first start the npm registry and OpenCV model zoo.
From the main server admin: the relay token for this site (Relays → Add a relay), with the site's public IP in Allowed IPs.
The site's IANA time zone, for example Indian/Antananarivo or Europe/Paris.
The relay executable for the target OS: start_relay.exe (Windows) or start_relay (Linux), downloaded from the project's GitHub releases or built from source with build.bat / build.sh. You can also run it from source with Python 3.10+.
Install on a site
Choose the account and folder
Pick the Windows account that will always run the relay and create a folder for it, for example C:\in-Time\relay. Copy start_relay.exe into it.
Keep the same accountThe token and the relay's keys are encrypted with Windows DPAPI for the account that runs the relay, and the encrypted cache depends on them. Starting the relay under another account means re-entering the secrets and losing any check-ins not yet sent.
Write relay.ini
Create relay.ini in the same folder (from the relay.ini.example delivered with the executable) and fill in at least these values:
Double-click start_relay.exe. The console hides and only the relay window shows. At start-up the relay:
moves the token out of relay.ini into data\secrets.bin and restricts data\ to the account, SYSTEM and Administrators;
checks trusted time and creates its signing key (first start), whose public key the main server records with its first request;
creates its stable ID in data\server.id and a self-signed certificate in data\certs\;
downloads face-api.js into data\vendor\ and the face models into data\models\ (first start only).
Start-up errors (missing relay.ini, port in use) appear in a message box and in data\relay.log. Run start_relay.exe --no-gui from a terminal to see the log live.
Check the status lines
In the relay window, Internet should read OK with the site time zone and Main serverReachable. On the main server, the Relays page shows the relay online with its public IP under Last seen. The first sync downloads users and photos within 30 seconds.
Connect phones
Join the phone to the site Wi-Fi.
Scan the QR code in the relay window, or open https://<relay-ip>:8443.
Accept the certificate warning once. Browsers only allow the camera over HTTPS, so the relay uses a self-signed certificate.
Look at the camera: the name appears, IN/OUT is suggested and sent after 4 seconds.
Identification is rate-limited to 15 requests per 10 seconds per phone. Phones on public addresses are refused unless allow_public_clients = true.
Start automatically
in-Time does not include an autostart setup. These are suggested ways to keep a relay running after a reboot.
Windows, with the window
The window needs a logged-in desktop session. Use Task Scheduler: Create Task → trigger At log on of the relay account → action Start a programC:\in-Time\relay\start_relay.exe with Start in set to the same folder. Configure the PC to log that account in automatically, or place a shortcut in shell:startup.
On first start the relay downloads face-api.js and the face models (about 39 MB). If the PC cannot reach the npm registry or the OpenCV model zoo, copy data/vendor/ and data/models/ from another relay into its data/ folder before starting it. Remember that check-ins themselves still need internet for trusted time.
Move or replace a relay
Situation
What to do
New PC for a site
Generate a new token for that relay on the main server: a token binds to the first machine that uses it (token_in_use). The new token binds to the new PC at its first request.
Same PC, reinstalled
Keep the data/ folder (above all secrets.bin): it holds the relay's signing key. If it is lost, the relay creates a new key and the main server refuses it (bad_signature) until you generate a new token.
Cloned PC or VM
Install fresh instead of cloning, and give each relay its own token. A clone with another machine ID is refused with token_in_use; a clone that kept the same machine ID and data/ folder would act as the same relay.
Token changed
Type the new value into relay.ini and restart; it is moved to secrets.bin again.
Site's public IP changed
Update Allowed IPs for that relay on the main server, otherwise calls fail with ip_not_allowed.
Déployer · Windows / Linux
Déployer un serveur relais
Un relais par site. C'est un exécutable unique accompagné d'un fichier relay.ini : double-cliquez dessus et la fenêtre du relais s'ouvre avec un QR code pour les téléphones.
La fenêtre du relais : heure fiable et état du serveur principal, adresse à ouvrir sur les téléphones, et pointages du jour avec leur état de synchronisation.
Avant de commencer
Un PC sur le site, sur le même réseau que les téléphones, avec une IP fixe ou une réservation DHCP. Le certificat du relais est régénéré quand son IP locale change, et les téléphones verraient alors un nouvel avertissement.
Un accès Internet depuis ce PC : HTTPS vers le serveur principal, les sources d'heure et, au premier démarrage, le registre npm et le dépôt de modèles OpenCV.
Fourni par l'administrateur du serveur principal : le jeton du relais de ce site (Relays → Add a relay), avec l'IP publique du site dans Allowed IPs.
Le fuseau horaire IANA du site, par exemple Indian/Antananarivo ou Europe/Paris.
L'exécutable du relais pour le système cible : start_relay.exe (Windows) ou start_relay (Linux), téléchargé depuis les versions GitHub du projet ou compilé depuis les sources avec build.bat / build.sh. Vous pouvez aussi le lancer depuis les sources avec Python 3.10+.
Installer sur un site
Choisir le compte et le dossier
Choisissez le compte Windows qui exécutera toujours le relais et créez-lui un dossier, par exemple C:\in-Time\relay. Copiez-y start_relay.exe.
Gardez le même compteLe jeton et les clés du relais sont chiffrés avec Windows DPAPI pour le compte qui exécute le relais, et le cache chiffré en dépend. Lancer le relais sous un autre compte oblige à ressaisir les secrets et fait perdre les pointages pas encore envoyés.
Écrire relay.ini
Créez relay.ini dans le même dossier (à partir du relay.ini.example livré avec l'exécutable) et renseignez au moins ces valeurs :
Chaque clé est décrite dans la référence de configuration. Utilisez verify_ssl = <chemin vers .pem> si le serveur principal utilise une autorité de certification privée.
Double-cliquez sur start_relay.exe. La console se masque et seule la fenêtre du relais s'affiche. Au démarrage, le relais :
déplace le jeton de relay.ini vers data\secrets.bin et réserve data\ au compte, à SYSTEM et aux Administrateurs ;
vérifie l'heure fiable et crée sa clé de signature (premier démarrage), dont le serveur principal enregistre la clé publique à sa première requête ;
crée son ID stable dans data\server.id et un certificat auto-signé dans data\certs\ ;
télécharge face-api.js dans data\vendor\ et les modèles faciaux dans data\models\ (premier démarrage uniquement).
Les erreurs de démarrage (relay.ini absent, port déjà utilisé) s'affichent dans une boîte de dialogue et dans data\relay.log. Lancez start_relay.exe --no-gui depuis un terminal pour suivre le journal en direct.
Vérifier les lignes d'état
Dans la fenêtre du relais, Internet doit indiquer OK avec le fuseau du site et Main serverReachable. Sur le serveur principal, la page Relays montre le relais en ligne avec son IP publique sous Last seen. La première synchronisation récupère utilisateurs et photos en moins de 30 secondes.
Connecter les téléphones
Connectez le téléphone au Wi-Fi du site.
Scannez le QR code affiché dans la fenêtre du relais, ou ouvrez https://<ip-du-relais>:8443.
Acceptez une fois l'avertissement de certificat. Les navigateurs n'autorisent la caméra qu'en HTTPS, c'est pourquoi le relais utilise un certificat auto-signé.
Regardez la caméra : le nom s'affiche, IN/OUT est proposé et envoyé après 4 secondes.
L'identification est limitée à 15 requêtes par 10 secondes et par téléphone. Les téléphones sur des adresses publiques sont refusés sauf si allow_public_clients = true.
Démarrage automatique
in-Time ne fournit pas de configuration de démarrage automatique. Voici des façons suggérées de relancer le relais après un redémarrage.
Windows, avec la fenêtre
La fenêtre a besoin d'une session de bureau ouverte. Utilisez le Planificateur de tâches : Créer une tâche → déclencheur À l'ouverture de session du compte du relais → action Démarrer un programmeC:\in-Time\relay\start_relay.exe, avec Commencer dans réglé sur ce même dossier. Configurez le PC pour ouvrir automatiquement la session de ce compte, ou placez un raccourci dans shell:startup.
Au premier démarrage, le relais télécharge face-api.js et les modèles faciaux (environ 39 Mo). Si le PC ne peut pas joindre le registre npm ou le dépôt de modèles OpenCV, copiez data/vendor/ et data/models/ d'un autre relais dans son dossier data/ avant de le démarrer. Les pointages, eux, nécessitent toujours Internet pour l'heure fiable.
Déplacer ou remplacer un relais
Situation
Que faire
Nouveau PC pour un site
Générez un nouveau jeton pour ce relais sur le serveur principal : un jeton est lié à la première machine qui l'utilise (token_in_use). Le nouveau jeton est lié au nouveau PC dès sa première requête.
Même PC, réinstallé
Gardez le dossier data/ (surtout secrets.bin) : il contient la clé de signature du relais. S'il est perdu, le relais crée une nouvelle clé et le serveur principal le refuse (bad_signature) jusqu'à ce que vous génériez un nouveau jeton.
PC ou VM clonés
Faites une installation neuve plutôt qu'un clonage, et donnez à chaque relais son propre jeton. Un clone avec un autre ID machine est refusé avec token_in_use ; un clone qui a gardé le même ID machine et le même dossier data/ se comporterait comme le même relais.
Jeton modifié
Saisissez la nouvelle valeur dans relay.ini et redémarrez ; elle est de nouveau déplacée vers secrets.bin.
IP publique du site modifiée
Mettez à jour Allowed IPs pour ce relais sur le serveur principal, sinon les appels échouent avec ip_not_allowed.
Reference
Configuration reference
The main server reads main_server/.env; each relay reads relay.ini next to its executable.
Main server · .env
Variable
Default
Purpose
SECRET_KEY
required
Flask session key, 32+ random characters.
ADMIN_USERNAME
admin
Admin console login.
ADMIN_PASSWORD
required
12+ characters, not a placeholder.
DEPARTMENTS
required
Comma-separated, e.g. Sales,Direction,Manager. Every user belongs to one.
SOURCE_URL
this repository
Source code link in the admin console footer (AGPL-3.0). If you run a modified version, set it to where you publish your source.
DATABASE_URL
set by the script when it creates the database
PostgreSQL URL.
SESSION_COOKIE_SECURE
false
true when the console is served over HTTPS.
LOGIN_MAX_FAILURES
5
Failed logins in a row before the console is locked (see admin lock).
ADMIN_LOCK_FILE
admin_login.lock
Lock file, next to .env by default.
PHOTOS_DIR
./photos
User reference photos, <ID>.jpg|png.
SNAPSHOTS_DIR
./snapshots
Check-in snapshots.
FACE_MODELS_DIR
models
OpenCV face models.
TRUSTED_PROXIES
0
Number of reverse proxies in front of the app.
TRUSTED_PEERS
127.0.0.1/32,::1/128
Proxy addresses allowed to send forwarded headers.
CF_PROXY
false
true when Cloudflare is in front.
CF_IPS_FILE
cf_ips.txt
Cloudflare ranges file, reloaded automatically.
FACE_MIN_SIMILARITY
0.363
Snapshot/photo similarity needed for a match. Higher is stricter.
FACE_REPLAY_SIMILARITY
0.97
At or above this, the snapshot is flagged replay.
CHECKIN_MAX_DELAY_SECONDS
600
Flag delayed above this while the relay was connected.
CHECKIN_MAX_FUTURE_SECONDS
300
Refuse timestamps further ahead than this.
SERVER
127.0.0.1:8000
gunicorn bind address (systemd service).
WORKERS
2
gunicorn workers (systemd service).
Local relays
A relay on the same network as the main server can be marked Local relay on the Relays page. Its IP is then the address that opened the connection; TRUSTED_PROXIES and CF_PROXY are ignored for it. It must connect straight to gunicorn (for example SERVER=<LAN IP>:8000) with that LAN IP in Allowed IPs. Never bind 0.0.0.0 on a server reachable from the internet.
Cloudflare ranges
cf_ips.txt is refreshed by update_cf_ips.py, which rewrites it only when Cloudflare's list changes (--check only reports). The install script schedules it every 2 hours in /etc/cron.d/in-time-cf-ips. To refresh it by hand:
$ cd /var/lib/in-time/main-server
$ sudo -u in-time .venv/bin/python update_cf_ips.py
Relay · relay.ini
Key
Default
Purpose
[main_server] url
required
Public HTTPS URL of the main server.
[main_server] token
required
Relay token. Moved to data/secrets.bin at start-up.
[main_server] verify_ssl
true
true, false, or a CA bundle path.
[main_server] sync_interval
30
Seconds between syncs. A new check-in triggers one immediately.
[relay] host
0.0.0.0
Listen address for phones.
[relay] port
8443
HTTPS port for phones.
[relay] duplicate_window
60
Ignore an identical IN/OUT by the same person within N seconds (10–600).
[relay] allow_public_clients
false
Accept clients outside private networks.
[relay] data_dir
data
Cache, photos, snapshots, certificate, secrets and log.
[face] min_similarity
0.363
Minimum 0.363; 0.40–0.45 is stricter. A higher FACE_MIN_SIMILARITY on the main server wins.
[time] timezone
PC time zone
IANA name of the site. Set it, or the relay shows a warning.
[time] check_interval
60
Seconds between internet/time checks (30–300).
Référence
Référence de configuration
Le serveur principal lit /var/lib/in-time/main-server/.env ; chaque relais lit le relay.ini placé à côté de son exécutable.
Serveur principal · .env
Variable
Défaut
Rôle
SECRET_KEY
obligatoire
Clé de session Flask, 32+ caractères aléatoires.
ADMIN_USERNAME
admin
Identifiant de la console d'administration.
ADMIN_PASSWORD
obligatoire
12+ caractères, pas une valeur d'exemple.
DEPARTMENTS
obligatoire
Séparés par des virgules, ex. Sales,Direction,Manager. Chaque utilisateur appartient à un département.
SOURCE_URL
ce dépôt
Lien vers le code source en bas de la console d'administration (AGPL-3.0). Si vous faites tourner une version modifiée, indiquez l'adresse de votre code publié.
DATABASE_URL
définie par le script quand il crée la base
URL PostgreSQL.
SESSION_COOKIE_SECURE
false
true quand la console est servie en HTTPS.
LOGIN_MAX_FAILURES
5
Échecs de connexion consécutifs avant le verrouillage de la console (voir verrouillage admin).
ADMIN_LOCK_FILE
admin_login.lock
Fichier de verrouillage, à côté de .env par défaut.
PHOTOS_DIR
./photos
Photos de référence des utilisateurs, <ID>.jpg|png.
SNAPSHOTS_DIR
./snapshots
Photos des pointages.
FACE_MODELS_DIR
models
Modèles faciaux OpenCV.
TRUSTED_PROXIES
0
Nombre de proxys inverses devant l'application.
TRUSTED_PEERS
127.0.0.1/32,::1/128
Adresses des proxys autorisés à envoyer des en-têtes transmis.
CF_PROXY
false
true quand Cloudflare est devant.
CF_IPS_FILE
cf_ips.txt
Fichier des plages Cloudflare, rechargé automatiquement.
FACE_MIN_SIMILARITY
0.363
Similarité photo de pointage / photo requise pour une correspondance. Plus haut = plus strict.
FACE_REPLAY_SIMILARITY
0.97
À partir de cette valeur, la photo de pointage est signalée replay.
CHECKIN_MAX_DELAY_SECONDS
600
Signale delayed au-delà de ce délai si le relais était connecté.
CHECKIN_MAX_FUTURE_SECONDS
300
Refuse les horodatages plus en avance que cette valeur.
SERVER
127.0.0.1:8000
Adresse d'écoute de gunicorn (service systemd).
WORKERS
2
Workers gunicorn (service systemd).
Relais locaux
Un relais situé sur le même réseau que le serveur principal peut être marqué Local relay sur la page Relays. Son IP est alors l'adresse qui a ouvert la connexion ; TRUSTED_PROXIES et CF_PROXY sont ignorés pour lui. Il doit se connecter directement à gunicorn (par exemple SERVER=<IP du LAN>:8000) avec cette IP dans Allowed IPs. N'utilisez jamais 0.0.0.0 sur un serveur joignable depuis Internet.
Plages Cloudflare
cf_ips.txt est mis à jour par update_cf_ips.py, qui ne le réécrit que si la liste de Cloudflare a changé (--check se contente de signaler). Le script d'installation le planifie toutes les 2 heures dans /etc/cron.d/in-time-cf-ips. Pour le mettre à jour à la main :
$ cd /var/lib/in-time/main-server
$ sudo -u in-time .venv/bin/python update_cf_ips.py
Relais · relay.ini
Clé
Défaut
Rôle
[main_server] url
obligatoire
URL HTTPS publique du serveur principal.
[main_server] token
obligatoire
Jeton du relais. Déplacé vers data/secrets.bin au démarrage.
[main_server] verify_ssl
true
true, false, ou le chemin d'un bundle CA.
[main_server] sync_interval
30
Secondes entre deux synchronisations. Un nouveau pointage en déclenche une immédiatement.
[relay] host
0.0.0.0
Adresse d'écoute pour les téléphones.
[relay] port
8443
Port HTTPS pour les téléphones.
[relay] duplicate_window
60
Ignore un IN/OUT identique de la même personne dans les N secondes (10 à 600).
[relay] allow_public_clients
false
Accepter les clients hors réseaux privés.
[relay] data_dir
data
Cache, photos, photos de pointage, certificat, secrets et journal.
[face] min_similarity
0.363
Minimum 0.363 ; 0.40–0.45 est plus strict. Un FACE_MIN_SIMILARITY plus élevé sur le serveur principal l'emporte.
[time] timezone
fuseau du PC
Nom IANA du fuseau du site. Renseignez-le, sinon le relais affiche un avertissement.
[time] check_interval
60
Secondes entre deux contrôles Internet/heure (30 à 300).
Reference
Operations and troubleshooting
Where the logs are, the command-line tools on the main server, and what the common error codes mean.
Logs and files
What
Where
Main server log
sudo journalctl -u in-time-main -f
Cloudflare ranges refresh
/var/lib/in-time/cf_ips_update.log
Relay log
data/relay.log next to the relay executable
Relay ID
data/server.id (also printed in the log at start-up)
Main server commands
Run them from /var/lib/in-time/main-server as the service user: sudo -u in-time .venv/bin/flask --app main <command>.
Command
Does
create-relay "SITE" --ips 203.0.113.10
Registers a relay and prints its token once.
verify-checkins
Runs the server-side face check on unchecked items. --all re-checks everything, e.g. after changing FACE_MIN_SIMILARITY.
unlock-admin
Unlocks the admin login after too many failures.
init-db
Creates tables (also done automatically at start-up).
Admin login lock
After LOGIN_MAX_FAILURES failed logins in a row (from any address) the console is locked and admin_login.lock is written next to .env, listing the attempts. It is never removed automatically: delete the file or run unlock-admin. A successful login resets the count.
Relay errors seen by the main server
Code
Meaning
Fix
ip_not_allowed
The relay's public IP is not in Allowed IPs, or the real IP is hidden by a proxy.
Compare with Last seen; fix TRUSTED_PROXIES / CF_PROXY / TRUSTED_PEERS or the allowed list.
token_in_use
The token is bound to another relay machine.
Generate a new token for the replacement relay.
relay_id_in_use
This relay machine is already bound to another token.
Use the token it already has, or delete that other relay entry on the Relays page.
bad_signature
The request is not signed with the key recorded for this token: the relay lost data/secrets.bin, or another relay used the token first.
Generate a new token for the relay (Relays page) and type it in relay.ini.
clock_skew
Main server clock is more than 5 min from trusted time.
Enable NTP on the main server.
HTTP 401
Missing or invalid token, relay ID, relay key or signature.
Re-enter the token in relay.ini and restart the relay.
Common symptoms
Symptom
Likely cause
Relay: red banner, phones say "check-in unavailable"
No internet, so no trusted time. If the PC date is wrong by months, HTTPS itself fails: fix the date.
Relay: Main server Unreachable
Main server down or URL wrong. Check-ins are still accepted and queued.
Relay: Main server Error
Authentication refused; see the error codes above.
Phones show a certificate warning again
The relay's LAN IP changed and the certificate was regenerated. Give it a fixed IP.
Main server does not start
A required .env value is missing or weak; the journal names it.
Camera button missing on the user page
The console is not served over HTTPS.
Relay log: cache moved to unreadable-…
secrets.bin was deleted or replaced, or the relay ran under another account. Unsent check-ins in the old cache are lost.
Face check flags on check-ins
Flagged check-ins are still recorded. Review the snapshot and decide.
Flag
Meaning
match
Snapshot matches the user's photo (similarity shown).
mismatch
Similarity below FACE_MIN_SIMILARITY.
no face · no snapshot
No usable face in the snapshot, or no snapshot received.
replay
The snapshot is the reference photo, practically identical to it, or reused from another check-in.
delayed
Received more than 10 min after it was made while the relay was connected.
Référence
Exploitation et dépannage
Où trouver les journaux, les commandes du serveur principal et la signification des codes d'erreur courants.
Journaux et fichiers
Quoi
Où
Journal du serveur principal
sudo journalctl -u in-time-main -f
Mise à jour des plages Cloudflare
/var/lib/in-time/cf_ips_update.log
Journal du relais
data/relay.log à côté de l'exécutable du relais
ID du relais
data/server.id (également écrit dans le journal au démarrage)
Commandes du serveur principal
Exécutez-les depuis /var/lib/in-time/main-server avec l'utilisateur du service : sudo -u in-time .venv/bin/flask --app main <commande>.
Commande
Effet
create-relay "SITE" --ips 203.0.113.10
Enregistre un relais et affiche son jeton une seule fois.
verify-checkins
Lance le contrôle facial côté serveur sur les éléments non vérifiés. --all revérifie tout, par exemple après une modification de FACE_MIN_SIMILARITY.
unlock-admin
Déverrouille la connexion admin après trop d'échecs.
init-db
Crée les tables (également fait automatiquement au démarrage).
Verrouillage de la connexion admin
Après LOGIN_MAX_FAILURES échecs de connexion consécutifs (depuis n'importe quelle adresse), la console est verrouillée et admin_login.lock est écrit à côté de .env, avec la liste des tentatives. Il n'est jamais supprimé automatiquement : supprimez le fichier ou lancez unlock-admin. Une connexion réussie remet le compteur à zéro.
Erreurs de relais vues par le serveur principal
Code
Signification
Solution
ip_not_allowed
L'IP publique du relais n'est pas dans Allowed IPs, ou la vraie IP est masquée par un proxy.
Comparez avec Last seen ; corrigez TRUSTED_PROXIES / CF_PROXY / TRUSTED_PEERS ou la liste autorisée.
token_in_use
Le jeton est lié à une autre machine relais.
Générez un nouveau jeton pour le relais de remplacement.
relay_id_in_use
Cette machine relais est déjà liée à un autre jeton.
Utilisez le jeton qu'elle a déjà, ou supprimez l'autre entrée de relais sur la page Relays.
bad_signature
La requête n'est pas signée avec la clé enregistrée pour ce jeton : le relais a perdu data/secrets.bin, ou un autre relais a utilisé le jeton en premier.
Générez un nouveau jeton pour le relais (page Relays) et saisissez-le dans relay.ini.
clock_skew
L'horloge du serveur principal s'écarte de plus de 5 min de l'heure fiable.
Activez NTP sur le serveur principal.
HTTP 401
Jeton, ID de relais, clé de relais ou signature absent ou invalide.
Ressaisissez le jeton dans relay.ini et redémarrez le relais.
Symptômes courants
Symptôme
Cause probable
Relais : bandeau rouge, « pointage indisponible » sur les téléphones
Pas d'Internet, donc pas d'heure fiable. Si la date du PC est fausse de plusieurs mois, le HTTPS lui-même échoue : corrigez la date.
Relais : Main server Unreachable
Serveur principal arrêté ou URL erronée. Les pointages restent acceptés et mis en attente.
Relais : Main server Error
Authentification refusée ; voir les codes d'erreur ci-dessus.
Les téléphones affichent de nouveau un avertissement de certificat
L'IP locale du relais a changé et le certificat a été régénéré. Donnez-lui une IP fixe.
Le serveur principal ne démarre pas
Une valeur obligatoire du .env manque ou est trop faible ; le journal la nomme.
Bouton caméra absent sur la page utilisateur
La console n'est pas servie en HTTPS.
Journal du relais : cache déplacé vers unreadable-…
secrets.bin a été supprimé ou remplacé, ou le relais a tourné sous un autre compte. Les pointages non envoyés de l'ancien cache sont perdus.
Indicateurs du contrôle facial
Les pointages signalés sont tout de même enregistrés. Examinez la photo et décidez.
Indicateur
Signification
match
La photo de pointage correspond à la photo de l'utilisateur (similarité affichée).
mismatch
Similarité inférieure à FACE_MIN_SIMILARITY.
no face · no snapshot
Aucun visage exploitable sur la photo, ou aucune photo reçue.
replay
La photo de pointage est la photo de référence, quasi identique à celle-ci, ou réutilisée d'un autre pointage.
delayed
Reçu plus de 10 min après sa création alors que le relais était connecté.