Web-app-gitea Role¶
Description: Lightweight self-hosted Git service for code collaboration, version control, and repository management
Variables¶
author: Kevin Veen-Birkenbach
description: Lightweight self-hosted Git service for code collaboration, version control, and repository management
license: Infinito.Nexus Community License (Non-Commercial)
company: Kevin Veen-Birkenbach https://www.veen.world
galaxy_tags: [‘gitea’, ‘docker’, ‘git’, ‘self-hosted’, ‘version control’]
issue_tracker_url: https://s.infinito.nexus/issues
min_ansible_version: 2.9
platforms: [{‘name’: ‘ArchLinux’, ‘versions’: [‘all’]}, {‘name’: ‘Debian’, ‘versions’: [‘all’]}, {‘name’: ‘EL’, ‘versions’: [‘all’]}, {‘name’: ‘Fedora’, ‘versions’: [‘all’]}, {‘name’: ‘Ubuntu’, ‘versions’: [‘all’]}]
README¶
Gitea¶
Description¶
Boost your development journey with Gitea, a lightweight and energetic self-hosted Git service that offers efficient code collaboration, intuitive version control, and an agile environment for your projects. Ignite your coding spirit, innovate faster, and code with confidence!
Overview¶
This role deploys Gitea using Docker. It automates the setup and update
processes for your self-hosted Git service, integrating with a central
MariaDB for the database. With functionalities for updating, recreating
the container, variable management, database access, and shell access to
the application container, this role streamlines the management of your
Gitea instance. With Hermes Agent or OpenClaw deployed alongside it, the
role adds a gitea-mcp sidecar and declares it as the MCP server of
the deployment: an internal /mcp endpoint on the container network
plus the Gitea personal access token that MCP clients present.
Cosmos¶
The diagram places Gitea in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
flowchart LR
subgraph deps [Dependencies]
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_svc_db_mariadb["svc-db-mariadb 🐳🐝"]
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
dep_svc_db_redis["svc-db-redis 🐳🐝"]
dep_svc_net_tor["svc-net-tor 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_seaweedfs["web-app-seaweedfs 🐳🐝"]
dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
dep_web_svc_logout["web-svc-logout 🐳🐝"]
end
subgraph role [web-app-gitea 🐳🐝]
svc_logout["logout"]
svc_ldap["ldap"]
svc_sso["sso"]
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_email["email"]
svc_mariadb["mariadb"]
svc_gitea["gitea"]
svc_giteamcp["giteamcp"]
svc_giteamcpupstream["giteamcpupstream"]
svc_redis["redis"]
svc_minio["minio ❌"]
svc_seaweedfs["seaweedfs"]
svc_css["css"]
svc_prometheus["prometheus"]
svc_tor["tor"]
svc_container_backup["container_backup"]
svc_openwebui["openwebui"]
svc_hermes["hermes"]
svc_openclaw["openclaw"]
svc_flowise["flowise ❌"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_mariadb -. "0..1" .-> svc_mariadb
dep_svc_db_openldap -. "0..1" .-> svc_ldap
dep_svc_db_redis -. "0..1" .-> svc_redis
dep_svc_net_tor -. "0..1" .-> svc_tor
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_hermes
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_openclaw
dep_web_app_openwebui -. "0..1" .-> svc_openwebui
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_seaweedfs -. "0..1" .-> svc_seaweedfs
dep_web_app_stalwart -. "0..1" .-> svc_email
dep_web_svc_css -. "0..1" .-> svc_css
dep_web_svc_logout -. "0..1" .-> svc_logoutSolid 1:1 edges are fixed relationships; dashed 0..1 edges are
conditional (enabled only in matching deployments); red 0..0 edges
are turned off in this role. Node markers show the role’s deploy modes
(💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly
turned off, and ⚙️ an Ansible role dependency declared in
meta/main.yml.
Features¶
Lightweight and Fast: Enjoy a minimal yet efficient Git service tailored for development teams.
Efficient Code Collaboration: Manage repositories and workflows with an intuitive interface.
Automated Updates & Re-creation: Simplify maintenance with automated update and container recreation procedures.
Built-in Database Access: Seamlessly interact with the underlying MariaDB for your Git service.
Integrated Configuration: Easily manage settings via environment variables and Docker Compose templates.
MCP server contract: Declares the
/mcppath over streamable HTTP as an internal, bearer-token-authenticated endpoint served by thesvc-ai-mcp-adapterinstance running as compose servicegiteamcp. The adapter listens on container port8080and is never published to a host port or fronted by the public reverse proxy; clients reach it ashttp://giteamcp:8080/mcpon the container network. It is off by default and switches on only where Hermes Agent or OpenClaw is part of the deployment.Adapter in front of the sidecar: The
gitea-mcpsidecar runs as compose servicegiteamcpupstreamon the role’s own network only, so nothing but the adapter reaches it. It answersinitializeandtools/listto any caller because it holds no credential of its own and expects each client to supply a Gitea token per request, which makes it unsafe to expose directly. The adapter is what authenticates: a client presents themcp_bearercredential and is refused with 401 otherwise, and the adapter presents the minted Gitea token upstream viaADAPTER_UPSTREAM_KEY.Administrator-issued token: The role mints a Gitea personal access token named
gitea-mcpfor theadministratoraccount with the read-only scope setread:repository,read:issue,read:user,read:organization,read:notification, and persists it in the token store MCP clients read. Every deploy asks the API whether the stored token still authenticates, revokes the stale entry under that name, and re-mints when the API rejects it. The deploy fails if the fresh token is rejected too.Read-only tool surface: The sidecar runs with
-randGITEA_READONLY=true, which hides every write tool. What remains are the read categoriesuser,repository,issue,pull_request,search,file,branch,tag,commitandrelease, pinned throughGITEA_SCOPES. Making the instance mutate anything through MCP is an explicit operator change to the command line and the token scopes.Handshake verified against the sidecar: After minting, the deploy sends a JSON-RPC
initializetohttp://giteamcpupstream:8080/mcpwith that bearer and fails when the endpoint does not answer.Contract probe: The
svc-ai-mcp-adapterprobe presents themcp_bearer, asserts the served tool surface matches the pinned contract, and separately asserts that an unauthenticatedinitializeis refused with a 4xx and discloses no tool inventory.Pinned tool surface:
files/mcp/tools.jsonholds the 24 read-only tools captured from the sidecar, pinned twice:tools.schema_sha256over the parsed mapping the adapter rehashes at startup, andadapter.specification_sha256over the file bytes.
Quick Setup¶
Development¶
Clone, set up the workstation, and deploy Gitea onto the local stack:
git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-gitea full_cycle=false
Production¶
Run the published image to provision the inventory and deploy Gitea to a managed server (the mounted volume persists the inventory):
APP=web-app-gitea
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"
docker run --rm -it \
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
-e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
ghcr.io/infinito-nexus/core/debian bash -c '
INVENTORY=/etc/infinito.nexus/inventories/production
infinito administration inventory provision "$INVENTORY" \
--inventory-file "$INVENTORY/devices.yml" \
--host "$HOST" \
--include "$APP" \
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
--password-file "$INVENTORY/.password" \
--diff -vv'
Further Resources¶
MCP Server¶
Gitea has no built-in MCP server. The role runs the project-owned
gitea-mcp package as a sidecar container next to Gitea and points it
at the instance over the container network. Because that sidecar
authenticates nothing itself, the svc-ai-mcp-adapter instance sits
in front of it and is what clients address.
Endpoint¶
Property |
Value |
|---|---|
Transport |
Streamable HTTP |
URL |
|
Upstream |
|
Exposure |
|
Implementation |
|
The image ships no ENTRYPOINT, so the compose command carries
the binary path followed by its flags.
Auth¶
Clients present a Gitea personal access token as
Authorization: Bearer <token>. The token is issued against the
administrator account and kept in sys-token-store. Its
auth_subject is administrator, so MCP calls run with
administrator rights rather than as the requesting user.
Tool categories¶
The sidecar starts with -r (read-only) and a scope list covering
user, repository, issue, pull request, search, file, branch, tag, commit
and release.
Default state¶
Off. mcp.enabled is false unless web-app-hermes,
web-app-openclaw or web-app-openwebui is deployed alongside.
Public vhost¶
The sidecar is never routed publicly. A bearerless probe of /mcp on
the public vhost is answered with an SSO redirect, never with an MCP
response.
How to disable¶
Remove the MCP client roles from the deployment, or pin
mcp.enabled: false for this role. The giteamcp service is then
not rendered into the compose file.
Persona contract opt-outs¶
The shared biber and administrator persona journeys are opted
out via PERSONA_BIBER_BLOCKED / PERSONA_ADMINISTRATOR_BLOCKED in
templates/playwright.env.j2. As soon as an IAM source is wired, the
role sets GITEA__service__DISABLE_REGISTRATION
(templates/env.j2, driven by GITEA_IAM_ENABLED in
vars/main.yml), and tasks/setup/oidc.yml registers only the auth
source — never a Gitea user — so a Keycloak identity without a
pre-linked Gitea account is bounced back to /user/login. The opt-out
lapses once the deploy provisions (or auto-links) Gitea accounts for the
persona identities.
Credits¶
Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).