Skip to content

Forgejo

perfshop-forgejo is the self-hosted Git server embedded in PerfShop. It plays three roles:

  1. 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.
  2. SCM for Squash TM — Squash TM points to Forgejo to retrieve scripts at execution time through its Git SCM plugin.
  3. Backend for the Scripts UI editor — all file editing operations from perfshop-scripts-ui are 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_PASSWORD and FORGEJO_CI_PASSWORD
  • Set FORGEJO__service__DISABLE_REGISTRATION=true to 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