- TypeScript 57.9%
- CSS 26.6%
- Nunjucks 13.9%
- Dockerfile 0.8%
- Shell 0.6%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Le nom de conteneur était figé (cv-online2 / cursus), ce qui empêchait de lancer un second déploiement sur la même machine. STACK_NAME pilote maintenant le nom de projet Compose, le nom du conteneur et donc le préfixe du volume (<STACK_NAME>_cv-data). Deux instances avec des STACK_NAME + APP_PORT distincts sont totalement indépendantes, même depuis le même dossier de sources (docker compose --env-file .env.autre up -d). Les valeurs par défaut préservent les déploiements existants : "cv" depuis les sources (volume cv_cv-data inchangé), "cursus" depuis l'image. PUBLIC_BASE_URL suit désormais APP_PORT au lieu du 3000 codé en dur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| prisma | ||
| public | ||
| scripts | ||
| src | ||
| themes | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.image.yml | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| DOCKERHUB.md | ||
| next.config.mjs | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
Cursus
Cursus est une application web auto-hébergée pour créer, héberger et exporter un CV. On édite son CV en ligne (enregistrement automatique), on le publie à une URL publique, et on l'exporte en PDF A4 au rendu identique quel que soit le navigateur — la génération est faite côté serveur par un Chromium headless.
Le logo reprend la case de l'élément Cu (n°29) du tableau périodique — clin d'œil à Cursus.
Fonctionnalités
- ✍️ Éditeur en ligne avec enregistrement automatique (aucun bouton « Enregistrer ») et interface en barre latérale.
- 🌐🔒 CV public et CV privé : chaque section / contact / élément a une visibilité Public, Privé ou Public et privé.
- 🎨 7 thèmes intégrés + thèmes « fichiers » : déposez un dossier (
theme.json+template.njk+styles.css) dansthemes/et il apparaît au runtime, sans recompiler (voirthemes/README.md). - 🖨️ Export PDF déterministe (A4, marges par thème) identique à l'écran, généré par Playwright/Chromium.
- 📄 Aperçu paginé sur le web : la page s'affiche comme le PDF (feuilles A4 empilées), avec respect de l'option « ne pas couper un bloc ».
- 🖼️ Photo avec recadrage / zoom / rotation avant upload.
- 🔁 Export / Import JSON du CV complet.
- 🧩 Sections repliables, champs contextuels selon le type de section, couleur d'accent et taille de texte réglables.
Prérequis
- Docker et Docker Compose v2 (
docker compose …). - Rien d'autre : Node, la base SQLite et le Chromium d'export PDF sont embarqués dans l'image.
Déploiement express (image Docker Hub)
Le plus simple, sans cloner les sources : l'image est publiée sur Docker Hub
(jlequen1/cursus). Dans un dossier vide,
récupérez docker-compose.image.yml et
.env.example, puis :
cp .env.example .env
# éditez .env : générez AUTH_SECRET avec `openssl rand -hex 32`, choisissez l'admin…
docker compose -f docker-compose.image.yml up -d
Application sur http://localhost:63546. Mise à jour : docker compose -f docker-compose.image.yml pull && docker compose -f docker-compose.image.yml up -d (image en :latest).
Déploiement depuis les sources (Docker Compose, pas à pas)
1. Récupérer le projet
git clone https://forge.lequen.fr/jlequen/Cursus.git cursus
cd cursus
2. Créer le fichier de configuration .env
cp .env.example .env
Puis éditez .env et renseignez au minimum :
| Variable | À mettre |
|---|---|
AUTH_SECRET |
Un secret long et aléatoire — générez-le avec la commande ci-dessous |
ADMIN_EMAIL |
L'email de connexion à l'éditeur |
ADMIN_PASSWORD |
Le mot de passe initial de ce compte |
PUBLIC_BASE_URL |
L'URL publique finale (ex. https://cv.mon-domaine.fr) |
APP_PORT |
Le port exposé sur l'hôte (par défaut 63546) |
STACK_NAME |
Nom de l'instance (conteneur, projet, volume) — utile pour en déployer plusieurs |
Générer un AUTH_SECRET solide :
openssl rand -base64 32
⚠️
.envcontient vos secrets et n'est pas versionné (il est dans.gitignore). Ne le committez jamais.
3. Construire et lancer
docker compose up -d --build
Au premier démarrage, le conteneur applique les migrations, crée le compte admin (à partir de ADMIN_EMAIL / ADMIN_PASSWORD) puis démarre l'app. Suivez les logs :
docker compose logs -f
4. Accéder à l'application
- CV public : http://localhost:63546/
- Éditeur : http://localhost:63546/admin → connectez-vous avec
ADMIN_EMAIL/ADMIN_PASSWORD.
(Remplacez le port si vous avez changé APP_PORT.)
Le CV est vide au départ : tout se remplit dans l'éditeur, ou via Import JSON.
5. Mettre à jour, arrêter, redémarrer
# Récupérer une nouvelle version puis reconstruire
git pull
docker compose up -d --build
# Arrêter (les données sont conservées dans le volume)
docker compose down
# Voir les logs
docker compose logs -f
Nouveaux thèmes intégrés à chaque version : les thèmes livrés avec l'app sont embarqués dans l'image (
/app/builtin-themes, non monté). Un simplegit pull && docker compose up -d --buildles fait apparaître automatiquement — même si le volume./themes(vos thèmes perso) n'est pas à jour.
Déployer plusieurs CV sur la même machine
Chaque instance est identifiée par STACK_NAME : il sert de nom de projet
Compose, de nom de conteneur, et préfixe le volume de données
(<STACK_NAME>_cv-data). Deux instances avec des STACK_NAME différents sont
donc totalement indépendantes — y compris depuis le même dossier de sources.
Créez un fichier d'environnement par instance :
# .env.natacha
STACK_NAME=cv-natacha
APP_PORT=63547
AUTH_SECRET=… # un secret distinct par instance
ADMIN_EMAIL=natacha@exemple.fr
ADMIN_PASSWORD=…
PUBLIC_BASE_URL=https://cv-natacha.mon-domaine.fr
puis lancez-la avec :
docker compose --env-file .env.natacha up -d --build
# arrêt / logs de cette instance uniquement :
docker compose --env-file .env.natacha logs -f
docker compose --env-file .env.natacha down
Les trois valeurs à rendre uniques sont STACK_NAME, APP_PORT et
PUBLIC_BASE_URL. Sans STACK_NAME, la valeur par défaut (cv depuis les
sources, cursus depuis l'image) s'applique : c'est ce qui provoquait le conflit
de nom de conteneur entre deux déploiements.
⚠️ Ne changez pas le
STACK_NAMEd'une instance déjà en service : elle repartirait sur un volume vide. Exportez d'abord le CV en JSON, ou renommez le volume.
Mise en production derrière un reverse proxy
Le conteneur écoute en interne sur le port 3000 et est exposé sur l'hôte via APP_PORT (63546 par défaut). Placez un reverse proxy (Nginx, Caddy, Traefik…) devant, en TLS, qui transmet vers http://127.0.0.1:63546, et réglez PUBLIC_BASE_URL sur l'URL HTTPS publique.
Configuration (variables d'environnement)
| Variable | Défaut | Rôle |
|---|---|---|
STACK_NAME |
cv (sources) / cursus (image) |
Nom du projet Compose, du conteneur, et préfixe du volume |
APP_PORT |
63546 |
Port exposé sur l'hôte |
ADMIN_EMAIL |
admin@example.com |
Login de l'éditeur (créé au 1er démarrage) |
ADMIN_PASSWORD |
changeme-please |
Mot de passe initial (à changer !) |
AUTH_SECRET |
— | Secret de signature des sessions JWT (obligatoire) |
PUBLIC_BASE_URL |
http://localhost:3000 |
URL publique (liens canoniques / rendu PDF) |
DATABASE_URL |
file:/app/data/cv.db |
Base SQLite (sur le volume persistant) |
THEMES_DIR |
/app/themes |
Vos thèmes perso (monté en volume, prioritaires) |
BUILTIN_THEMES_DIR |
/app/builtin-themes |
Thèmes intégrés livrés dans l'image (non monté) |
CHROMIUM_PATH |
/usr/bin/chromium |
Chromium headless pour l'export PDF (fourni par l'image) |
Le mot de passe n'est créé qu'au premier démarrage. Pour le changer ensuite, utilisez Compte → Changer le mot de passe dans l'éditeur (modifier
ADMIN_PASSWORDaprès coup n'a pas d'effet).
Données & sauvegarde
- Toutes les données (base SQLite + photos uploadées) vivent dans un volume Docker — elles survivent à
down/up/--build. Le volume est préfixé par le nom de l'instance :cv_cv-datapar défaut depuis les sources,cursus_cv-datadepuis l'image,<STACK_NAME>_cv-datasi vous l'avez défini. Retrouvez le vôtre avecdocker volume ls | grep cv-data. - Sauvegarde rapide du contenu : éditeur → Compte → Exporter en JSON. Restauration via Importer.
- Sauvegarde du volume complet :
docker run --rm -v cv_cv-data:/data -v "$PWD":/backup alpine \
tar czf /backup/cv-data-backup.tar.gz -C /data .
Thèmes
Deux possibilités :
- Thèmes intégrés (livrés avec l'image, dans
/app/builtin-themes) : sélectionnables dans Apparence → Thème. Ils se mettent à jour avec l'image (up -d --build). - Thèmes perso : déposez un dossier dans
themes/(monté en volume→ /app/themes), rechargez la page — aucun rebuild. Un thème perso portant le même identifiant qu'un thème intégré le remplace. Format et variables documentés dansthemes/README.md.
Sécurité
- Changez
AUTH_SECRETetADMIN_PASSWORDavant toute mise en ligne. .env, la base de données (*.db,/data) et les sauvegardes (/backups) sont exclus du dépôt (.gitignore) : aucun secret ni donnée personnelle n'est versionné.- Servez l'application en HTTPS derrière un reverse proxy.
Développement local (sans Docker)
npm install
cp .env.example .env # adaptez DATABASE_URL en local si besoin
npx prisma migrate deploy
npm run seed
npm run dev # http://localhost:3000
L'export PDF en local nécessite un Chromium/Chrome installé ; pointez CHROMIUM_PATH dessus.
Stack technique
- Next.js 15 (App Router) · React 19 · TypeScript · Tailwind CSS v4
- Prisma + SQLite
- Playwright (Chromium headless) pour l'export PDF A4 déterministe
- Nunjucks (+
marked/sanitize-html) pour les thèmes fichiers - Authentification par session JWT (
jose) + bcrypt