test #1
17
.commitlintrc.yml
Normal file
17
.commitlintrc.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
rules:
|
||||
# Types autorisés (à ajuster au besoin)
|
||||
type-enum:
|
||||
- 2
|
||||
- always
|
||||
- [feat, fix, chore, docs, style, refactor, perf, test, build, ci, revert]
|
||||
subject-empty:
|
||||
- 2
|
||||
- never
|
||||
header-max-length:
|
||||
- 2
|
||||
- always
|
||||
- 100
|
||||
type-case:
|
||||
- 2
|
||||
- always
|
||||
- lower-case
|
||||
173
.gitea/workflows/ci.yml
Normal file
173
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,173 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
REGISTRY: ${{ secrets.DOCKER_REGISTRY || 'registry.local' }}
|
||||
IMAGE_NAME: ${{ secrets.DOCKER_IMAGE || 'mon-projet' }}
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
runs-on: docker
|
||||
env:
|
||||
FROM_REF: ${{ github.event.pull_request.base.sha || github.event.before || '' }}
|
||||
TO_REF: ${{ github.sha }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run commitlint (conventional commits)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
from="${FROM_REF}"
|
||||
if [ -z "$from" ]; then
|
||||
from="HEAD~1"
|
||||
fi
|
||||
docker run --rm -v "$PWD:/workspace" -w /workspace ghcr.io/conventional-changelog/commitlint:latest --from "$from" --to "$TO_REF"
|
||||
|
||||
lint:
|
||||
runs-on: docker
|
||||
needs: commitlint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup tools
|
||||
run: |
|
||||
echo "TODO: installer vos dépendances de lint (npm ci, pip install -r requirements.txt, etc.)"
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
echo "TODO: remplacer par la commande réelle de lint, ex: npm run lint"
|
||||
|
||||
test:
|
||||
runs-on: docker
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
echo "TODO: installer les dépendances de test"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
echo "TODO: remplacer par la commande réelle de tests, ex: npm test"
|
||||
|
||||
build:
|
||||
runs-on: docker
|
||||
needs: test
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ env.REGISTRY }}
|
||||
DOCKER_IMAGE: ${{ env.IMAGE_NAME }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags --force
|
||||
|
||||
- name: Compute next version (semver)
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
if [ -z "$last_tag" ]; then
|
||||
base="v0.0.0"
|
||||
commits=$(git log --format=%s%n%b HEAD)
|
||||
else
|
||||
base="$last_tag"
|
||||
commits=$(git log --format=%s%n%b "${last_tag}..HEAD")
|
||||
fi
|
||||
|
||||
bump="patch"
|
||||
if echo "$commits" | grep -qiE "(^| )feat!"; then
|
||||
bump="major"
|
||||
elif echo "$commits" | grep -qiE "BREAKING CHANGE"; then
|
||||
bump="major"
|
||||
elif echo "$commits" | grep -qiE "^feat:"; then
|
||||
bump="minor"
|
||||
elif echo "$commits" | grep -qiE "^fix:"; then
|
||||
bump="patch"
|
||||
fi
|
||||
|
||||
semver="${base#v}"
|
||||
major=${semver%%.*}
|
||||
minor=${semver#*.}; minor=${minor%%.*}
|
||||
patch=${semver##*.}
|
||||
|
||||
case "$bump" in
|
||||
major) major=$((major+1)); minor=0; patch=0 ;;
|
||||
minor) minor=$((minor+1)); patch=0 ;;
|
||||
patch) patch=$((patch+1)) ;;
|
||||
esac
|
||||
|
||||
next="v${major}.${minor}.${patch}"
|
||||
echo "next=$next" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Docker login
|
||||
if: env.DOCKER_USER != '' && env.DOCKER_PASSWORD != ''
|
||||
run: |
|
||||
echo "${DOCKER_PASSWORD}" | docker login "${DOCKER_REGISTRY}" -u "${DOCKER_USER}" --password-stdin
|
||||
|
||||
- name: Build Docker image (latest)
|
||||
run: docker build -t "${DOCKER_REGISTRY}/${DOCKER_IMAGE}:latest" -f Dockerfile .
|
||||
|
||||
- name: Build Docker image (versioned)
|
||||
run: docker build -t "${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${{ steps.version.outputs.next }}" -f Dockerfile .
|
||||
|
||||
- name: Push Docker images
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
docker push "${DOCKER_REGISTRY}/${DOCKER_IMAGE}:latest"
|
||||
docker push "${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${{ steps.version.outputs.next }}"
|
||||
|
||||
- name: Generate changelog
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
id: changelog
|
||||
shell: bash
|
||||
run: |
|
||||
last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
if [ -z "$last_tag" ]; then
|
||||
range="HEAD"
|
||||
last_tag="initial"
|
||||
else
|
||||
range="${last_tag}..HEAD"
|
||||
fi
|
||||
{
|
||||
echo "Changelog since $last_tag"
|
||||
git log --pretty=format:"- %s" $range
|
||||
} > changelog.md
|
||||
|
||||
- name: Create and push tag
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="${{ steps.version.outputs.next }}"
|
||||
git tag -a "$tag" -m "Release $tag"
|
||||
origin="${SERVER_URL#https://}"
|
||||
origin="${origin#http://}"
|
||||
git push "https://oauth2:${TOKEN}@${origin}/${REPOSITORY}" "$tag"
|
||||
|
||||
- name: Upload changelog
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: changelog
|
||||
path: changelog.md
|
||||
140
.gitea/workflows/deploy.yml
Normal file
140
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,140 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environnement cible"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- prod
|
||||
default: staging
|
||||
version:
|
||||
description: "Tag explicite vX.Y.Z (utilisé pour prod, sinon dernier tag existant)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
env:
|
||||
REGISTRY: ${{ secrets.DOCKER_REGISTRY || 'registry.local' }}
|
||||
IMAGE_NAME: ${{ secrets.DOCKER_IMAGE || 'mon-projet' }}
|
||||
PROJECT_ROOT: /opt/mon-projet
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Prepare version and sources
|
||||
runs-on: docker
|
||||
outputs:
|
||||
version: ${{ steps.resolve_version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags --force
|
||||
|
||||
- name: Resolve target version
|
||||
id: resolve_version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
explicit="${{ inputs.version }}"
|
||||
if [ -n "$explicit" ]; then
|
||||
echo "version=$explicit" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
tag=$(git describe --tags --abbrev=0 2>/dev/null || true)
|
||||
if [ -z "$tag" ]; then
|
||||
echo "No tag found and no version provided." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Sync repo to /opt/mon-projet
|
||||
run: |
|
||||
mkdir -p "${PROJECT_ROOT}"
|
||||
cp -a . "${PROJECT_ROOT}/"
|
||||
|
||||
build:
|
||||
name: Build images (latest + versioned)
|
||||
runs-on: docker
|
||||
needs: prepare
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
VERSION_TAG: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker login
|
||||
if: env.DOCKER_USER != '' && env.DOCKER_PASSWORD != ''
|
||||
run: |
|
||||
echo "${DOCKER_PASSWORD}" | docker login "${REGISTRY}" -u "${DOCKER_USER}" --password-stdin
|
||||
|
||||
- name: Build latest
|
||||
run: docker build -t "${REGISTRY}/${IMAGE_NAME}:latest" -f Dockerfile .
|
||||
|
||||
- name: Build versioned
|
||||
run: docker build -t "${REGISTRY}/${IMAGE_NAME}:${VERSION_TAG}" -f Dockerfile .
|
||||
|
||||
- name: Push images
|
||||
run: |
|
||||
docker push "${REGISTRY}/${IMAGE_NAME}:latest"
|
||||
docker push "${REGISTRY}/${IMAGE_NAME}:${VERSION_TAG}"
|
||||
|
||||
deploy-staging:
|
||||
name: Deploy to staging
|
||||
runs-on: docker
|
||||
needs: build
|
||||
if: inputs.env == 'staging'
|
||||
env:
|
||||
ENV_DIR: ${{ env.PROJECT_ROOT }}/env/staging
|
||||
DOTENV: ${{ env.PROJECT_ROOT }}/env/staging/.env
|
||||
steps:
|
||||
- name: List synced sources
|
||||
run: ls -la "${PROJECT_ROOT}"
|
||||
|
||||
- name: Generate staging .env from secret
|
||||
env:
|
||||
STAGING_ENV: ${{ secrets.STAGING_ENV_VARS }}
|
||||
run: |
|
||||
mkdir -p "${ENV_DIR}"
|
||||
echo "${STAGING_ENV}" > "${DOTENV}"
|
||||
|
||||
- name: Deploy with docker compose
|
||||
working-directory: ${{ env.PROJECT_ROOT }}
|
||||
run: |
|
||||
docker compose -f docker-compose.staging.yml --env-file "${DOTENV}" pull
|
||||
docker compose -f docker-compose.staging.yml --env-file "${DOTENV}" up -d
|
||||
|
||||
deploy-prod:
|
||||
name: Deploy to prod
|
||||
runs-on: docker
|
||||
needs: build
|
||||
if: inputs.env == 'prod'
|
||||
env:
|
||||
ENV_DIR: ${{ env.PROJECT_ROOT }}/env/prod
|
||||
DOTENV: ${{ env.PROJECT_ROOT }}/env/prod/.env
|
||||
VERSION_TAG: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Check target version
|
||||
run: echo "Deploying prod with ${VERSION_TAG}"
|
||||
|
||||
- name: Generate prod .env from secret
|
||||
env:
|
||||
PROD_ENV: ${{ secrets.PROD_ENV_VARS }}
|
||||
run: |
|
||||
mkdir -p "${ENV_DIR}"
|
||||
echo "${PROD_ENV}" > "${DOTENV}"
|
||||
|
||||
- name: Deploy with docker compose
|
||||
working-directory: ${{ env.PROJECT_ROOT }}
|
||||
env:
|
||||
IMAGE_TAG: ${{ env.VERSION_TAG }}
|
||||
run: |
|
||||
docker compose -f docker-compose.prod.yml --env-file "${DOTENV}" pull
|
||||
docker compose -f docker-compose.prod.yml --env-file "${DOTENV}" up -d
|
||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM alpine:3.19
|
||||
|
||||
# TODO: install your runtime dependencies (node, python, etc.)
|
||||
# COPY . /app
|
||||
# WORKDIR /app
|
||||
|
||||
CMD ["sh", "-c", "echo Build your application image here"]
|
||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# CI/CD Gitea starter
|
||||
|
||||
Empty on purpose: only CI/CD, Docker, compose, and instructions so you can drop your app in.
|
||||
|
||||
## Structure
|
||||
- `.gitea/workflows/ci.yml`: lint -> test -> build with auto-versioning, git tags, Docker images `latest` + `vX.Y.Z`.
|
||||
- `.gitea/workflows/deploy.yml`: manual Deploy (`workflow_dispatch`) to staging or prod.
|
||||
- `docker-compose.staging.yml` / `docker-compose.prod.yml`: per-environment compose.
|
||||
- `Dockerfile`: skeleton image to replace with your app build.
|
||||
- `/opt/mon-projet/env/{staging,prod}/.env`: generated on the VPS from Gitea secrets.
|
||||
- `.commitlintrc.yml`: commit linting rules (conventional-style) enforced in CI.
|
||||
- `scripts/commit-msg.sh`: local commit-msg hook helper (commitlint via npx, no Docker).
|
||||
|
||||
## Required Gitea secrets
|
||||
- `DOCKER_REGISTRY`: registry (e.g. registry.example.com).
|
||||
- `DOCKER_IMAGE`: image name (e.g. mon-projet).
|
||||
- `DOCKER_USERNAME` / `DOCKER_PASSWORD`: registry credentials.
|
||||
- `GITEA_TOKEN`: token with push rights on the repo (for git tag push).
|
||||
- `STAGING_ENV_VARS`: full `.env` content for staging (multi-line allowed).
|
||||
- `PROD_ENV_VARS`: full `.env` content for prod.
|
||||
|
||||
## CI workflow (ci.yml)
|
||||
- Triggers: `push` and `pull_request`.
|
||||
- Strict order: `commitlint` -> `lint` -> `test` -> `build`. Any failure stops later jobs.
|
||||
- Auto-versioning rules (conventional commits):
|
||||
- `feat:` => MINOR, `fix:` => PATCH, `feat!` or `BREAKING CHANGE` => MAJOR.
|
||||
- Tag created (`vX.Y.Z`) + changelog artifact on pushes to `main`.
|
||||
- Builds and pushes Docker images `latest` and `vX.Y.Z` to the configured registry.
|
||||
|
||||
## Deploy workflow (deploy.yml)
|
||||
- Trigger: manual only (`workflow_dispatch`) with input `env` (`staging` | `prod`) and optional `version` (to force a prod tag).
|
||||
- Jobs: `prepare` -> `build` -> `deploy-*` (staging or prod). Deployments always depend on the build.
|
||||
- Build: rebuilds and pushes both tags (`latest` + `vX.Y.Z`) before any deploy.
|
||||
- `.env` generation: secrets `STAGING_ENV_VARS` / `PROD_ENV_VARS` are written on the VPS to `/opt/mon-projet/env/<env>/.env`.
|
||||
- Docker deploy: `docker compose pull` then `docker compose up -d` with the dedicated compose files.
|
||||
- Prod uses the explicit version tag; staging consumes `latest`.
|
||||
|
||||
## Local commitlint (optional)
|
||||
- Requires Node.js + npm locally (no Docker).
|
||||
- One-time setup: `npm install --save-dev @commitlint/cli`
|
||||
- Install the hook: `cp scripts/commit-msg.sh .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg`
|
||||
- The hook will block commits whose messages are not conventional-style (`feat`, `fix`, `chore`, etc.).
|
||||
- Windows (Git Bash) : chmod est optionnel ; copie simplement le fichier dans `.git/hooks/commit-msg`. Vérifie que `npx` fonctionne (`npx --version`).
|
||||
|
||||
## Pour un débutant : comment ça marche ?
|
||||
- Commits propres : écris tes messages comme `feat: ...` (nouvelle fonctionnalité) ou `fix: ...` (correction). Ça pilote l’auto-versioning.
|
||||
- Pousser le code : un `git push` ou une PR lance la CI (vérifie les commits, le lint, les tests, construit l’image Docker).
|
||||
- Versions automatiques : sur la branche `main`, la CI calcule la prochaine version `vX.Y.Z`, tague le repo et pousse les images Docker (`latest` et `vX.Y.Z`).
|
||||
- Déploiements : rien d’automatique. Tu lances manuellement le workflow **Deploy** dans l’onglet Actions de Gitea :
|
||||
- Choisis `staging` pour déployer l’image `latest`.
|
||||
- Choisis `prod` pour déployer l’image `vX.Y.Z` (dernier tag ou celui que tu fournis dans l’input `version`).
|
||||
- Secrets : tes mots de passe/variables sensibles restent dans l’UI Gitea (pas dans le code). Ils servent à créer les fichiers `.env` sur le serveur et à pousser les images.
|
||||
- Serveur : le runner doit avoir Docker/compose et pouvoir écrire dans `/opt/mon-projet/`. Les `.env` sont générés à chaque déploiement et ne sont jamais commités.
|
||||
|
||||
## How to use
|
||||
1) Update `Dockerfile`, lint/test commands in `ci.yml`, and services/ports/volumes in compose files to match your app.
|
||||
2) Add the secrets above in the Gitea repo settings.
|
||||
3) Push code with commit messages `feat:`, `fix:`, or `feat!`/`BREAKING CHANGE` to drive semver bumps.
|
||||
4) Make sure CI is green. No automatic deployment will run if CI fails (and deploy stays manual).
|
||||
5) Run the **Deploy** workflow in Gitea Actions:
|
||||
- `env=staging`: deploys image `latest`.
|
||||
- `env=prod`: deploys image tagged `vX.Y.Z` (latest tag by default or the one supplied via `version`).
|
||||
|
||||
## Notes
|
||||
- Runner must access `/opt/mon-projet/` and Docker/compose.
|
||||
- `.env` files are never versioned; they are regenerated on each deploy from secrets.
|
||||
- If no tag exists yet, provide `version` manually for a prod deploy.
|
||||
9
docker-compose.prod.yml
Normal file
9
docker-compose.prod.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${REGISTRY:-registry.local}/${IMAGE_NAME:-mon-projet}:${IMAGE_TAG:?vX.Y.Z_required}
|
||||
env_file:
|
||||
- /opt/mon-projet/env/prod/.env
|
||||
restart: unless-stopped
|
||||
command: ["sh", "-c", "echo Replace this command with your application start"]
|
||||
10
docker-compose.staging.yml
Normal file
10
docker-compose.staging.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${REGISTRY:-registry.local}/${IMAGE_NAME:-mon-projet}:latest
|
||||
env_file:
|
||||
- /opt/mon-projet/env/staging/.env
|
||||
restart: unless-stopped
|
||||
# TODO: expose ports/volumes command to fit your app
|
||||
command: ["sh", "-c", "echo Replace this command with your application start"]
|
||||
1217
package-lock.json
generated
Normal file
1217
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.1.0"
|
||||
}
|
||||
}
|
||||
20
scripts/commit-msg.sh
Normal file
20
scripts/commit-msg.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Local commit-msg hook using commitlint via npx (no Docker/GHCR auth required).
|
||||
# Usage: scripts/commit-msg.sh <commit-msg-file>
|
||||
|
||||
MSG_FILE="${1:?path to commit message file required}"
|
||||
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "npx not found. Install Node.js and run: npm install --save-dev @commitlint/cli" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure commitlint is available (installed as devDependency)
|
||||
if ! npx --yes @commitlint/cli@latest --help >/dev/null 2>&1; then
|
||||
echo "Install commitlint locally: npm install --save-dev @commitlint/cli" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npx --yes @commitlint/cli@latest --config .commitlintrc.yml --edit "${MSG_FILE}"
|
||||
Reference in New Issue
Block a user