Forgejo¶
perfshop-forgejo is the self-hosted Git server embedded in PerfShop. It plays three roles:
- Single source of test scripts — the Robot Framework and pytest scripts used by the QA stack are versioned in a Forgejo repository (
perfshop-tests), not hard-copied into Docker images. - SCM for Squash TM — Squash TM points to Forgejo to retrieve scripts at execution time through its Git SCM plugin.
- Backend for the Scripts UI editor — all file editing operations from
perfshop-scripts-uiare calls to the Forgejo REST API.
Forgejo is a community fork of Gitea, driven by an independent community since 2022. PerfShop uses version 14, configured in single-node mode with a SQLite database — sufficient for pedagogical use and significantly lighter than a full PostgreSQL installation.
Source of truth
This page is drawn from the perfshop-forgejo (and perfshop-forgejo-seed) blocks of the docker-compose.desktop.yml / docker-compose.build.yml files, and from the Dockerfile and seed.py script in the forgejo-seed/ folder.
Docker Compose configuration¶
perfshop-forgejo — Git server¶
| Key | Value |
|---|---|
| Image | codeberg.org/forgejo/forgejo:14 |
container_name |
perfshop-forgejo |
| Exposed port | 3009 (host) → 3000 (container), variable FORGEJO_HTTP_PORT |
| Volume | forgejo-data:/data (named volume, holds the SQLite DB and Git storage) |
| Network | perfshop-network |
Environment variables¶
The Forgejo configuration uses the FORGEJO__<section>__<key> convention which is internally translated into the [section] sections of the official app.ini file.
| Variable | Value | Role |
|---|---|---|
FORGEJO__server__DOMAIN |
${FORGEJO_DOMAIN:-localhost} |
Git domain (appears in clone URLs) |
FORGEJO__server__ROOT_URL |
${PUBLIC_FORGEJO_URL:-http://localhost:3009} |
Full public URL (used for UI links) |
FORGEJO__server__HTTP_PORT |
3000 |
Internal listening port |
FORGEJO__database__DB_TYPE |
sqlite3 |
SQLite database (no dedicated DB service) |
FORGEJO__database__PATH |
/data/gitea/forgejo.db |
SQLite file persisted in the volume |
FORGEJO__security__INSTALL_LOCK |
true |
Locks the initial install screen (no web wizard) |
FORGEJO__service__DISABLE_REGISTRATION |
false |
Allows account creation from the UI (pedagogical use) |
FORGEJO__security__PASSWORD_CHECK_PWN |
false |
Disables HaveIBeenPwned check (avoids outbound calls) |
FORGEJO__oauth2__ENABLE |
false |
No OAuth2 — only local auth and tokens |
FORGEJO__api__ENABLE_SWAGGER |
true |
Exposes Swagger documentation at /api/swagger |
FORGEJO__api__MAX_RESPONSE_ITEMS |
50 |
Pagination cap for the REST API |
FORGEJO__http__ENABLE_PPROF |
false |
Disables the Go profiling endpoint (reduced attack surface) |
FORGEJO__security__DISABLE_GIT_HOOKS |
false |
Allows server-side Git hooks (used by Squash TM) |
FORGEJO__git__DISABLE_DIFF_HIGHLIGHT |
false |
Enables syntax highlighting in diffs |
FORGEJO__admin__DEFAULT_EMAIL_NOTIFICATIONS |
disabled |
No email sending (no SMTP configured) |
Healthcheck¶
| Parameter | Value |
|---|---|
| Test | curl -f http://localhost:3000/api/v1/version |
interval |
10s |
timeout |
5s |
retries |
10 |
start_period |
30s |
The 30-second start_period gives Forgejo time to initialize its SQLite database and load its Go modules on first startup.
perfshop-forgejo-seed — one-shot initialization¶
| Key | Value |
|---|---|
| Build | forgejo-seed/Dockerfile |
container_name |
perfshop-forgejo-seed |
| Volumes | forgejo-token-data:/token (writes the token), /var/run/docker.sock:/var/run/docker.sock |
depends_on |
perfshop-forgejo with condition: service_healthy |
restart |
"no" (one-shot) |
Critical environment variables:
| Variable | Default | Role |
|---|---|---|
FORGEJO_URL |
http://perfshop-forgejo:3000 |
Internal URL for API calls |
FORGEJO_ADMIN_USER |
forgejo-admin |
Bootstrapped admin account |
FORGEJO_ADMIN_PASSWORD |
perfshop |
Admin password (must be changed in production) |
FORGEJO_CI_USER |
perfshop-ci |
CI account used by Squash TM and Scripts UI |
FORGEJO_CI_PASSWORD |
perfshop |
CI password |
FORGEJO_CI_EMAIL |
ci@perfshop.fr |
CI email |
FORGEJO_REPO |
perfshop-tests |
Target repository name |
FORGEJO_CONTAINER |
perfshop-forgejo |
Forgejo container name (enables CLI mode through docker exec) |
PERFSHOP_LANG |
fr |
Filters pushed scripts by language |
The seed.py script¶
The forgejo-seed/seed.py script is idempotent: it can be replayed without breaking an existing deployment. Every step checks the current state before acting (HTTP 200 = already exists, HTTP 404 = to be created).
The 9 functions of the script¶
| Function | Line | Role |
|---|---|---|
log(msg, error=False) |
24 | stdout/stderr logging prefixed with [forgejo-seed] |
wait_for_forgejo(max_retries, delay) |
28 | Waits for GET /api/v1/version to return 200, then creates the admin account |
ensure_ci_user() |
94 | Creates the CI account through the API (idempotent) |
ensure_token() |
111 | Generates an API token for the CI account and writes it to /token/forgejo_token |
ensure_squash_token() |
149 | Generates a second token for Squash TM (Git SCM plugin) |
ensure_repo() |
171 | Creates the empty perfshop-tests repository |
ensure_source_repo() |
190 | Creates a second perfshop repository (mirror of the application source code) |
collect_scripts() |
210 | Walks /scripts and filters files per PERFSHOP_LANG |
push_script(rel_path, token) |
239 | Pushes a file through the REST API (PUT /repos/{owner}/{repo}/contents/{path}) |
Full seed flow¶
sequenceDiagram
autonumber
participant SD as perfshop-forgejo-seed
participant FG as perfshop-forgejo<br/>(healthy)
participant FTD as forgejo-token-data volume
participant SUI as perfshop-scripts-ui<br/>(delayed startup)
participant STM as perfshop-squash-seed<br/>(depends_on ok)
Note over SD,FG: Step 1 — Wait for Forgejo to respond
SD->>FG: GET /api/v1/version
FG-->>SD: 200 {version: "14.x.x"}
Note over SD,FG: Step 2 — Create admin account
alt CLI mode (FORGEJO_CONTAINER defined)
SD->>SD: docker exec --user git perfshop-forgejo<br/>forgejo admin user create --admin
else REST API mode
SD->>FG: POST /api/v1/admin/users<br/>Authorization: Basic (GITEA_ADMIN_*)
end
FG-->>SD: 201 / 422 (already exists)
Note over SD,FG: Step 3 — Create CI account
SD->>FG: GET /api/v1/users/perfshop-ci
FG-->>SD: 404
SD->>FG: POST /api/v1/admin/users<br/>{username: "perfshop-ci", password, email}
FG-->>SD: 201
Note over SD,FG: Step 4 — Generate API token for CI account
SD->>FG: POST /api/v1/users/perfshop-ci/tokens<br/>{name: "scripts-ui"}
FG-->>SD: 201 {sha1: "xxxxx"}
SD->>FTD: writes /token/forgejo_token
Note over SD,FG: Step 5 — Generate token for Squash TM
SD->>FG: POST /api/v1/users/perfshop-ci/tokens<br/>{name: "squash-tm"}
FG-->>SD: 201 {sha1: "yyyyy"}
SD->>FTD: writes /token/squash_forgejo_token
Note over SD,FG: Step 6 — Create repositories
SD->>FG: POST /api/v1/user/repos<br/>{name: "perfshop-tests"}
SD->>FG: POST /api/v1/user/repos<br/>{name: "perfshop"}
Note over SD,FG: Step 7 — Push initial scripts
SD->>SD: Walks /scripts, filters by PERFSHOP_LANG
loop For each script
SD->>FG: PUT /api/v1/repos/perfshop-ci/perfshop-tests/contents/{path}<br/>{content: base64, message: "initial push"}
end
SD-->>SD: exit 0 (restart: "no")
Note over SUI,FTD: scripts-ui starts after the seed
SUI->>FTD: reads /token/forgejo_token at boot
STM->>FTD: reads /token/squash_forgejo_token through squash-seed
Two admin bootstrap modes¶
The seed.py script supports two strategies for creating the initial admin account:
Mode A — CLI through docker exec¶
Triggered when the FORGEJO_CONTAINER variable is defined (which is the case in both PerfShop compose files). The seed uses the bind-mounted Docker socket to run:
docker exec --user git perfshop-forgejo \
forgejo admin user create --admin \
--username <user> --password <pass> --email <email>
This is the preferred method in PerfShop because it works even when no account exists yet (initial creation through the REST API would require pre-existing admin credentials, which is a chicken-and-egg problem).
Mode B — REST API with GITEA_ADMIN_*¶
Fallback if FORGEJO_CONTAINER is absent. Uses the GITEA_ADMIN_USERNAME, GITEA_ADMIN_PASSWORD, and GITEA_ADMIN_EMAIL variables passed to Forgejo at startup, which automatically create the admin account on first boot. The seed can then call the API with those credentials.
This mode is kept in the code for portability but is not activated in the shipped PerfShop configurations.
The two generated tokens¶
scripts-ui token¶
| File | /token/forgejo_token |
|---|---|
| Volume | forgejo-token-data (named) |
| Consumer | perfshop-scripts-ui (mounts RO) |
| Usage | Forgejo API calls from the forgejo.js module of Scripts UI |
squash-tm token¶
| File | /token/squash_forgejo_token |
|---|---|
| Volume | forgejo-token-data (named) |
| Consumer | perfshop-squash-seed (relays to Squash TM through its API) |
| Usage | Git SCM plugin configuration on the Squash TM side |
The second token is created specifically so that the same token is not used by two different consumers — if one has to be revoked, the other stays valid.
The two created repositories¶
perfshop-tests¶
The main repository for tests. This is the one edited from Scripts UI and consumed by Squash TM.
Typical structure after the initial seed:
perfshop-tests/
├── tests/
│ ├── smoke/
│ │ ├── login.robot
│ │ └── checkout.robot
│ ├── api/
│ │ ├── products.robot
│ │ └── cart.robot
│ └── e2e/
│ └── full_journey.robot
├── resources/
│ ├── variables.robot
│ └── keywords.robot
└── pytest/
├── test_api.py
└── conftest.py
The files actually pushed depend on PERFSHOP_LANG: the collect_scripts() function filters files by suffix or parent folder to only push scripts in the right language (e.g., login_fr.robot vs login_en.robot).
perfshop¶
A second repository that (optionally) hosts a copy of PerfShop's own application source code. Its usage is more limited: it lets an instructor make a pedagogical commit (e.g., "add this line to fix the postal code validation bug") without touching the real deployed code.
Usage by consumers¶
flowchart TB
subgraph seed["perfshop-forgejo-seed (one-shot)"]
S1["1. Bootstrap admin<br/>(docker exec CLI)"]
S2["2. Create perfshop-ci account"]
S3["3. Generate 2 tokens"]
S4["4. Create repositories"]
S5["5. Push initial scripts"]
S1 --> S2 --> S3 --> S4 --> S5
end
subgraph fg["perfshop-forgejo (permanent service)"]
GIT["perfshop-tests repository<br/>+ perfshop repository"]
end
subgraph vol["forgejo-token-data (named volume)"]
T1["forgejo_token"]
T2["squash_forgejo_token"]
end
subgraph consumers["Consumers"]
SUI["perfshop-scripts-ui<br/>(interactive editing)"]
STM["perfshop-testmgmt<br/>(Git SCM plugin)"]
TR["perfshop-test-runner<br/>(git pull at the start<br/>of a run)"]
end
seed --> fg
seed --> vol
SUI -->|HTTP API + Bearer token| fg
STM -->|HTTP API + Bearer token| fg
TR -->|git clone / git pull| fg
SUI -.->|reads| T1
STM -.->|reads through squash-seed| T2
Security and exposure¶
Pedagogical use¶
By default, the shipped configuration is demo-oriented:
- Default admin password
perfshop(must be changed for any exposed deployment) FORGEJO__service__DISABLE_REGISTRATION=false— anyone can create an account from the UI (useful in training to give every student their own access)FORGEJO__security__PASSWORD_CHECK_PWN=false— no HaveIBeenPwned check- No SMTP configured — no verification or notification emails
Recommendations for public exposure¶
Before exposing on the internet
- Change
FORGEJO_ADMIN_PASSWORDandFORGEJO_CI_PASSWORD - Set
FORGEJO__service__DISABLE_REGISTRATION=trueto block open sign-ups - Add a reverse proxy with HTTPS and rate limiting
- Enable
FORGEJO__security__PASSWORD_CHECK_PWN=true(requires outbound connectivity to api.pwnedpasswords.com) - Configure SMTP for verification and password reset emails
- Switch to a dedicated PostgreSQL database if the repositories grow beyond a few GB (SQLite stays performant below that)
Going further¶
- QA Overview
- Scripts UI — main Forgejo API consumer
- Squash TM — also consumes Forgejo through its Git SCM plugin
- Test Runner — clones the
perfshop-testsrepository at the start of a run - Docker Compose — QA group — overview of all QA services