Mirror: This repo is a read-only mirror of gitlab.com/vauchi/website. Please open issues and merge requests there.
Landing page for Vauchi at vauchi.app.
| Tool | Required | Purpose |
|---|---|---|
| Python 3 | For build scripts | Content validation and manifest generation |
| Docker | For deployment | Containerization |
| Nginx | In container | Static file serving |
pip install jsonschemaRequired for validate-content.py schema validation.
Static website explaining what Vauchi is and linking to app downloads. Also serves dynamic app content (locales, themes, networks) via JSON manifests with checksums.
website/
├── public/ # Static files served by nginx
│ ├── index.html # Main landing page
│ ├── logo.png # Brand logo
│ └── app-files/ # Generated app content (via build script)
│ ├── manifest.json # Content manifest with checksums
│ ├── networks.json # Network configurations
│ ├── locales/ # Localization files
│ └── themes/ # Theme definitions
│
├── app-files-src/ # Source content (before build)
│ ├── networks.json # Network definitions
│ ├── locales/ # Language files (en.json, etc.)
│ └── schemas/ # JSON validation schemas
│ ├── networks.schema.json
│ ├── locales.schema.json
│ └── manifest.schema.json
│
├── scripts/ # Build and validation scripts
│ ├── build-manifest.py # Build manifest and copy content
│ └── validate-content.py # Validate JSON against schemas
│
├── nginx.conf # Nginx server configuration
├── Dockerfile # Container definition
└── .gitlab-ci.yml # CI/CD pipeline
# Using Python
python -m http.server 8000 -d public
# Using Node
npx serve publicThen open http://localhost:8000
Validate and build the app-files manifest:
# Validate content against schemas
python scripts/validate-content.py
# Build manifest and copy to public/app-files/
python scripts/build-manifest.py
# With options
python scripts/build-manifest.py --version 1.0.0 --output public/app-filesValidates all JSON content files against their schemas:
python scripts/validate-content.py [--src app-files-src]
# Validates:
# - networks.json vs networks.schema.json
# - locales/*.json vs locales.schema.jsonGenerates content manifest with SHA-256 checksums:
python scripts/build-manifest.py [options]
Options:
--version VERSION Content version (default: 1.0.0)
--output DIR Output directory (default: public/app-files)
--base-url URL CDN URL (default: https://vauchi.app/app-files/)
--src DIR Source directory (default: app-files-src)The generated manifest.json contains checksums for integrity verification by the mobile apps.
# Build
docker build -t vauchi-website .
# Run
docker run -p 8080:80 vauchi-websiteThe image runs as the unprivileged nginx user. Technical error and
waitlist service logs are written to /var/log/vauchi.
Deployed via Kamal to the same server as vauchi-relay. See vauchi/infra for deployment config.
# From infra repo
kamal deploy -c config/deploy.vauchi-landing.ymlThe .gitlab-ci.yml pipeline:
- Validates content (
validate-content.py) - Builds manifest (
build-manifest.py) - Builds Docker image
- Deploys via Kamal (on main branch)
- Add
{lang}.jsonin thelocalesrepo — the canonical source. This repo carries no locale copies; the build resolves the sibling checkout or the GitLab API and fails loudly otherwise. - Run
python scripts/build-manifest.pyhere to verify resolution
Themes are managed in the vauchi/themes repo. See the themes repo README for contributing instructions.
- Edit
app-files-src/networks.json - Validate and build as above
The opening shows one of nine versions of the copy (A–I). A random
letter is kept in the current browser tab so that the copy stays the
same while browsing. Static preview links select a specific version.
Source copy lives in i18n/{lang}.json under hero.variant.{a-i}.*.
Choosing copy does not send click counts, time on page or other behavior
reports. This applies to all visitors, including browsers without privacy
signals. The old /beacon endpoint returns HTTP 410 for cached clients.
The website image has no analytics collector and disables Nginx access logs.
The servers still receive connection details needed to serve a page, such as an IP address. Technical error logs can contain request details. The waitlist handles email addresses separately and keeps its own service logs. For example, signing up gives the waitlist your email even though viewing a copy variant sends no behavior report. See the privacy policy.
These statements describe this source version. Existing containers can retain older analytics files; an upgrade does not prove their erasure.
The landing page includes a self-hosted waitlist with double-opt-in confirmation
emails. Confirmation links return a localized HTML result directly, without
JavaScript, external assets, or an automatic redirect to the landing page.
Both successful and invalid links work with scripts and styles disabled;
the result is not cached and sends no referrer. The form posts to
/waitlist/join; a small Flask app inside the
container handles storage (SQLite) and sends confirmation mail via the existing
Vauchi mailserver.
This optional email list is separate from Vauchi identities. It creates no app account. Both the site form and mail/removal pages support English, German, French, Italian and Spanish.
Set these environment variables in the infra deployment (see vauchi/infra):
| Variable | Default | Description |
|---|---|---|
WAITLIST_DB_PATH |
./waitlist.db |
SQLite database path |
WAITLIST_REDIRECT_BASE_URL |
https://vauchi.app/ |
Base URL for redirect replies |
WAITLIST_ENCRYPTION_KEY |
— | Required base64-encoded 32-byte AES-256-GCM key |
WAITLIST_SMTP_HOST |
mail.vauchi.app |
Outbound SMTP server |
WAITLIST_SMTP_PORT |
587 |
SMTP port |
WAITLIST_SMTP_USER |
waitlist@vauchi.app |
SMTP auth user |
WAITLIST_SMTP_PASSWORD |
— | Required SMTP auth password |
WAITLIST_SMTP_FROM |
waitlist@vauchi.app |
From address for confirmation emails |
WAITLIST_SMTP_STARTTLS |
true |
Use STARTTLS |
WAITLIST_ADMIN_API_KEY |
— | Required API key for CSV export at /waitlist/admin/export |
WAITLIST_PENDING_DAYS |
7 |
Pending entry lifetime, 1–30 days from first submission |
WAITLIST_CONFIRMED_DAYS |
365 |
Confirmed entry lifetime, 1–365 days from confirmation |
WAITLIST_CLEANUP_SECONDS |
300 |
Supervised cleanup interval, 60–3600 seconds |
WAITLIST_CONFIRMATION_SUBJECT |
localized | Optional subject override for confirmation mail |
The confirmation email also contains a removal link. Existing subscribers can
request one at /waitlist/manage. Opening the link displays a form; removal
requires its explicit POST action, so a mail preview does not remove an entry.
Invalid, expired and replayed removal links receive the same generic response.
Requests for removal mail also receive the same response for known and unknown
addresses; a valid address receives mail in either case.
Leaving removes the active email record, its lookup hash and its confirmation and removal capabilities. It cannot recall mail already being sent, or erase previous CSV exports, mail-server records or backups. The nightly backup includes the database and keeps 7 daily, 8 weekly and 12 monthly snapshots, so a removed address can stay in a backup for up to about a year. Operators must manage those copies separately. Restoring an old database can restore withdrawn entries; do not use a restored campaign for mailing without reconciling removals.
Expired entries are excluded from new exports. Each API request performs expiry, and the supervised cleanup process also runs without incoming requests. An outage can delay physical removal. Resending a pending confirmation does not extend its original expiry. Existing dated entries are subject to these periods on migration; the defaults are reviewable policy choices, not a readiness claim.
Only confirmed, unexpired entries are exported. Keep any export only as long as needed to send the launch note; removal cannot update an exported copy.
curl -H "Authorization: Bearer $WAITLIST_ADMIN_API_KEY" \
https://vauchi.app/waitlist/admin/exportAn authenticated POST /waitlist/admin/close persistently closes the campaign
and deletes its active entries. Repeating it is safe, and closure survives a
service restart. New submissions then receive a closed response. It does not
remove earlier exports or backups.
- Emails are encrypted at rest with AES-256-GCM. A deterministic SHA-256 hash is kept for duplicate detection; it can be guessed from a known email address. The operator holds the encryption key and can read the addresses. Management mail and the admin export decrypt them; this is not end-to-end encryption.
- The waitlist API shares the container's unprivileged
nginxruntime; the SQLite database is stored on a persistent volume owned bynginx. - Confirmation tokens are stored as SHA-256 hashes; only the emailed link contains the raw token. Reusable removal links are stored encrypted, with a hash for lookup, so a requested reminder can reuse the same link. A reminder does not invalidate an earlier removal link. Treat both link types as secrets.
- A hidden honeypot field (
name="website") catches naive bots without revealing itself. - The service does not write request audit or access logs. Delivery errors use fixed messages without addresses, IPs, tokens or SMTP exception text. Existing logs and mail-server records have a separate retention lifecycle. The outer TLS proxy has its own logging configuration; application settings do not disable that layer or delete historical records.
- The admin export endpoint uses constant-time Bearer token comparison.
- Nginx rate-limits
/waitlist/joinand/waitlist/manageto a shared allowance of 5 requests per minute per IP, with a burst of 3. The transient in-memory limiter is separate from durable request logging.
| Repository | Description |
|---|---|
| vauchi/core | Core Rust library |
| vauchi/android | Android app |
| vauchi/ios | iOS app |
| vauchi/docs | Documentation |
| vauchi/assets | Brand assets, logos |
| vauchi/infra | Deployment configs |
Vauchi is open source and community-funded — no VC money, no data harvesting.
- GitHub Sponsors
- Liberapay
- SUPPORTERS.md for sponsorship tiers
MIT