Files
GGThedandClaude Opus 5 bda23dbb67 fix(ops): la sauvegarde des contrats archivait le mauvais repertoire
OBS-006. Trois racines etaient baties sur os.getcwd() : le magasin de
documents, les journaux et les sauvegardes. La vague G a corrige la
premiere, parce qu'elle bloquait aussi OPS-011, et a laisse les deux autres.
C'est la lecon deja consignee deux fois : un motif fautif corrige dans une
seule couche reste dans les autres.

Le plus serieux n'est pas le motif, c'est l'ecart qu'il a ouvert.
backup.py gardait sa propre constante DOCUMENTS_DIR sur os.getcwd(), donc
il ignorait DOCUMENTS_ROOT -- la variable que la vague G a introduite et que
docs/deployment.md dit maintenant de regler pour sortir les televersements
des repertoires de version. Des qu'un exploitant suit cette consigne, le
script archive un repertoire ou l'application n'a jamais rien ecrit. Et
comme il repond a un repertoire absent par une ligne d'information et un
code de sortie 0, une tache planifiee qui surveille le code de sortie voit
vert indefiniment.

Autrement dit : plus l'exploitant suivait correctement la documentation de
deploiement, plus surement ses sauvegardes de contrats etaient vides.

Les trois racines viennent desormais d'app/storage.py, resolues a l'appel et
non a l'import, et la sauvegarde imprime la source qu'elle a utilisee. Le
message d'absence nomme le chemin ou elle a cherche : "No documents
directory found" se lisait comme "il n'y a pas de documents" plutot que
comme "je regarde au mauvais endroit".

Le test qui porte est celui qui ouvre l'archive : un zip vide est un fichier
de taille non nulle, donc verifier qu'un fichier a ete produit ne prouvait
rien. Verifie par mutation.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-11 18:34:52 -04:00

145 lines
6.3 KiB
Bash

# Team Tryouts — environment variables
#
# Copy to .env and fill in. Every value here is a PRODUCTION-SAFE default:
# copying this file and changing nothing gives a locked-down configuration
# that refuses to start until the two required secrets are set, rather than
# a working one that happens to be wide open (OPS-003).
#
# The previous version shipped FLASK_DEBUG=true under a heading that said
# "fill in the values for production". The Werkzeug debugger executes code
# submitted through the browser, so that one line turned a copy-paste into a
# remote shell.
#
# For local development, see the DEVELOPMENT block at the bottom.
# =============================================================================
# Required — the application refuses to start without these
# =============================================================================
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
# Never reuse one between environments: this key signs session cookies, so
# whoever holds it can forge a session for any account.
SECRET_KEY=
# Expected form: postgresql://user:password@host:5432/database
# The psycopg 3 driver is named for you by create_app(); postgresql:// alone
# would send SQLAlchemy looking for psycopg2, which is not installed.
DATABASE_URL=
# =============================================================================
# Security — these defaults assume HTTPS in front. Do not relax them on a
# deployed instance.
# =============================================================================
# Session cookies are only sent over HTTPS.
SESSION_COOKIE_SECURE=true
# Plain HTTP is redirected to HTTPS.
FORCE_HTTPS=true
# The Werkzeug debugger is a remote code execution primitive by design.
# Never true on anything reachable from a network you do not control.
FLASK_DEBUG=false
# Inline <script> without a nonce. Off: every block carries one, and turning
# this on gives up the protection that would have blocked the stored XSS
# (SEC-WEB-001). It exists as an escape hatch, not as a setting to tune.
CSP_ALLOW_INLINE_SCRIPT=false
# Comma-separated origins allowed to call this API cross-site. Empty means
# no CORS policy at all, which is correct: the site renders its own HTML on
# one origin and needs none.
CORS_ALLOWED_ORIGINS=
# =============================================================================
# Networking
# =============================================================================
# Interface Waitress binds. 127.0.0.1 keeps it reachable only through the
# local reverse proxy; 0.0.0.0 exposes it directly and is only correct if
# something else in front is doing the filtering.
HOST=127.0.0.1
PORT=5000
# Whether to believe X-Forwarded-For, and from whom. This decides which IP
# the rate limiter and the audit log record.
#
# (empty) — trust nobody. Correct when nothing proxies the app.
# 127.0.0.1 — trust a reverse proxy on this same machine. The usual case.
# * — trust everyone. Only ever correct if the app cannot be reached
# except through the proxy, at the network level. Otherwise any
# caller can claim any IP and walk around the rate limit.
#
# See docs/deployment.md before changing this (OPS-002).
TRUSTED_PROXY=127.0.0.1
# =============================================================================
# Optional — Discord
# =============================================================================
# Leave ENABLE_DISCORD_BOT=false and the token empty to run without Discord.
ENABLE_DISCORD_BOT=false
DISCORD_BOT_TOKEN=
# OAuth2, for "Connect Discord" on the sign-up page.
# Create an application at https://discord.com/developers/applications
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_REDIRECT_URI=https://your-domain/auth/discord/callback
# =============================================================================
# Optional — storage
# =============================================================================
# Where uploaded contracts live. Empty means `documents/` beside the
# application. Set it to a path OUTSIDE the deployment directory if you move
# to a release-directory layout, or a deployment will take the documents with
# it (OPS-011, app/storage.py).
#
# IMPORTANT: the backup script reads this same variable. Before wave J it
# did not, and archived `./documents` regardless — so setting this here and
# nowhere else produced empty contract backups that still exited 0.
DOCUMENTS_ROOT=
# Where the log files go. Empty means `logs/` beside the application. Both
# defaults are anchored on the application, not on the directory the process
# was started from, which is what they used to be (OBS-006).
LOG_DIR=
# Where the backup script writes its archives. Empty means `backups/` beside
# the application.
BACKUP_DIR=
# =============================================================================
# Optional — rate limiting
# =============================================================================
# Where the rate limiter keeps its counters. Empty means `memory://`, which
# is correct for a single Waitress process and is what this deployment runs.
#
# Set it to a shared backend (redis://…) BEFORE running more than one worker:
# in-memory counters are per-process, so N workers let through N times every
# configured limit, with nothing to show for it in the logs.
#
# Note that shared storage does not by itself make the limits sound: they are
# keyed on the client IP, which is forgeable until TRUSTED_PROXY is set
# correctly (SEC-WEB-002 / OPS-002 — see above).
RATELIMIT_STORAGE_URI=
# Tables are created at startup when missing. Set to false once Alembic owns
# the schema (DB-002/DB-004): create_all() never ALTERs, so a column added to
# a model is silently absent from an existing database.
AUTO_CREATE_TABLES=true
# =============================================================================
# DEVELOPMENT ONLY — the values to change on a laptop, and nowhere else
# =============================================================================
#
# FLASK_DEBUG=true reloader and interactive debugger
# SESSION_COOKIE_SECURE=false cookies over plain HTTP
# FORCE_HTTPS=false no redirect to HTTPS
# DISCORD_REDIRECT_URI=http://localhost:5000/auth/discord/callback
#
# `python run.py` reads DEV_HOST and DEV_PORT rather than HOST and PORT, so a
# development session cannot accidentally inherit a production binding.