initial commit
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
name: gitea
|
||||
|
||||
services:
|
||||
# Gitea itself — git hosting, the Actions controller, and the OCI package
|
||||
# registry, all in one process. SQLite: a personal forge never has the write
|
||||
# concurrency that would justify a second container for Postgres.
|
||||
gitea:
|
||||
image: gitea/gitea:1.27.3-rootless
|
||||
container_name: gitea
|
||||
user: $PUID:$PGID
|
||||
restart: unless-stopped
|
||||
cpu_shares: 70
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
environment:
|
||||
TZ: $TZ
|
||||
# Every URL Gitea hands out — clone URLs, the registry host, and the
|
||||
# server_url the CI runner passes to jobs — is derived from ROOT_URL, so
|
||||
# it has to be the address that works from outside as well as inside.
|
||||
GITEA__server__ROOT_URL: https://gitea-${APP_DOMAIN}/
|
||||
GITEA__server__DOMAIN: gitea-${APP_DOMAIN}
|
||||
GITEA__server__HTTP_PORT: "3000"
|
||||
GITEA__server__PROTOCOL: http
|
||||
# The address Gitea's own CLI uses to reach the running server's internal
|
||||
# API. It defaults to localhost, which is right for the server itself and
|
||||
# useless from the init container that runs
|
||||
# `gitea actions generate-runner-token`. It has to be set here rather than
|
||||
# there: the CLI reads it out of the shared app.ini, and only this
|
||||
# container's entrypoint renders environment into app.ini.
|
||||
GITEA__server__LOCAL_ROOT_URL: http://gitea:3000/
|
||||
# Git over SSH would need a published host port of its own. Everything
|
||||
# here — clone, push, CI, registry — goes over HTTPS instead.
|
||||
GITEA__server__DISABLE_SSH: "true"
|
||||
GITEA__server__START_SSH_SERVER: "false"
|
||||
# Skips the first-run install wizard: every value it would ask for is set
|
||||
# here, and the admin account is created by an init step below.
|
||||
GITEA__security__INSTALL_LOCK: "true"
|
||||
GITEA__database__DB_TYPE: sqlite3
|
||||
GITEA__database__PATH: /var/lib/gitea/data/gitea.db
|
||||
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||
GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
|
||||
GITEA__actions__ENABLED: "true"
|
||||
# Where a bare `uses: actions/checkout@v4` is resolved from.
|
||||
GITEA__actions__DEFAULT_ACTIONS_URL: github
|
||||
GITEA__packages__ENABLED: "true"
|
||||
# Lets a pull mirror name a repository on this server's own Docker
|
||||
# network — a Radicle node's git endpoint, for instance. See rationale.md.
|
||||
GITEA__migrations__ALLOW_LOCALNETWORKS: "true"
|
||||
# How often a pull mirror is re-fetched. The sync emits the same push
|
||||
# event a real push would, so this interval is also how quickly CI
|
||||
# reacts to a commit that arrives from the mirrored source.
|
||||
GITEA__mirror__DEFAULT_INTERVAL: 10m
|
||||
GITEA__mirror__MIN_INTERVAL: 10m
|
||||
volumes:
|
||||
- ${DATA_ROOT:-/DATA}/AppData/$AppID/data:/var/lib/gitea
|
||||
- ${DATA_ROOT:-/DATA}/AppData/$AppID/config:/etc/gitea
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/healthz"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
expose:
|
||||
- 3000
|
||||
labels:
|
||||
caddy_0: gitea-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 3000}}"
|
||||
caddy_1: gitea-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 3000}}"
|
||||
caddy_2: gitea-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 3000}}"
|
||||
networks:
|
||||
- pcs
|
||||
- gitea-internal
|
||||
|
||||
# The CI runner: act_runner with its own Docker daemon inside it (dind), so
|
||||
# workflow jobs and the images they build never touch the host's daemon.
|
||||
# See rationale.md for what `privileged` buys and what it costs.
|
||||
gitea-runner:
|
||||
image: gitea/act_runner:0.6.1-dind
|
||||
container_name: gitea-runner
|
||||
restart: unless-stopped
|
||||
privileged: true
|
||||
cpu_shares: 20
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
environment:
|
||||
TZ: $TZ
|
||||
# This address is not just how the runner daemon reaches Gitea — it is
|
||||
# handed to every job as GITHUB_SERVER_URL, and a job runs inside the
|
||||
# runner's *nested* daemon, on a network where `gitea` does not resolve.
|
||||
# So it has to be an address that works from the outside: checkout and
|
||||
# `docker login` from a workflow both go out and back over HTTPS.
|
||||
GITEA_INSTANCE_URL: https://gitea-${APP_DOMAIN}
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN_FILE: /data/token
|
||||
GITEA_RUNNER_NAME: gitea-runner
|
||||
# Registration-time labels. runner-config/config.yaml repeats them, and
|
||||
# that copy wins on every start, so an installed runner follows store updates.
|
||||
GITEA_RUNNER_LABELS: ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04,ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04,docker:docker://docker:28.5-cli
|
||||
CONFIG_FILE: /config/config.yaml
|
||||
# DOCKER_TLS_CERTDIR is deliberately left at the image's default. Emptying
|
||||
# it does not turn the nested daemon's TCP listener off — it turns the
|
||||
# listener's TLS off, and 2375 then answers unauthenticated to anything
|
||||
# that can reach this container.
|
||||
volumes:
|
||||
# .runner (the registration this runner is identified by) and the
|
||||
# registration token the init step leaves for it.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/$AppID/runner:/data
|
||||
- ${DATA_ROOT:-/DATA}/AppData/$AppID/runner-config:/config:ro
|
||||
# Replaces the image's own start-up wait — see the comments in the
|
||||
# script. It is declared under x-compose-app.files rather than left to
|
||||
# the seed tree, because it has to arrive executable.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/$AppID/runner-config/act_runner-run:/etc/s6/act_runner/run:ro
|
||||
# The inner daemon's image store. Without this every restart re-pulls
|
||||
# every base image a workflow uses.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/$AppID/runner-docker:/var/lib/docker
|
||||
networks:
|
||||
- gitea-internal
|
||||
|
||||
networks:
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
# The runner has no business being reachable from other apps — it only ever
|
||||
# talks to gitea and to the internet.
|
||||
gitea-internal:
|
||||
driver: bridge
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
main: gitea
|
||||
developer: Gitea
|
||||
category: Developer
|
||||
tags: ["developer", "git", "ci"]
|
||||
description:
|
||||
en_us: |
|
||||
Gitea — a git forge with CI and a container registry
|
||||
|
||||
Gitea is a lightweight, self-contained software forge: repositories, issues, pull requests, releases, a CI engine and an OCI container registry in a single service that is happy on a small machine.
|
||||
|
||||
What it gives you here:
|
||||
|
||||
• Git hosting over HTTPS, with issues, pull requests, releases and wikis
|
||||
• Actions — CI workflows in GitHub Actions syntax, so existing workflow files usually run unchanged
|
||||
• A built-in container registry, so a workflow can build an image and push it next to the code it came from
|
||||
• Pull mirrors — point a repository at any git URL and it is re-fetched on a schedule, and each sync starts the same workflows a push would
|
||||
|
||||
That last pair is what makes it useful in front of a git source that has no webhooks of its own: mirror the repository, and the mirror's own updates drive the build.
|
||||
|
||||
A runner ships with the app, with its own Docker daemon inside it, so workflows can build container images out of the box.
|
||||
fr_fr: |
|
||||
Gitea — une forge git avec CI et registre de conteneurs
|
||||
|
||||
Gitea est une forge logicielle légère et autonome : dépôts, tickets, pull requests, versions, moteur d'intégration continue et registre de conteneurs OCI dans un seul service, à l'aise sur une petite machine.
|
||||
|
||||
Ce que vous obtenez ici :
|
||||
|
||||
• Hébergement git en HTTPS, avec tickets, pull requests, versions et wikis
|
||||
• Actions — des workflows d'intégration continue en syntaxe GitHub Actions, si bien qu'un fichier de workflow existant tourne généralement tel quel
|
||||
• Un registre de conteneurs intégré : un workflow peut construire une image et la publier à côté du code dont elle provient
|
||||
• Miroirs — pointez un dépôt vers n'importe quelle URL git, il est re-synchronisé périodiquement, et chaque synchronisation déclenche les mêmes workflows qu'un push
|
||||
|
||||
Ces deux derniers points sont ce qui rend Gitea utile devant une source git dépourvue de webhooks : mettez le dépôt en miroir, et ce sont les mises à jour du miroir qui pilotent la construction.
|
||||
|
||||
Un runner est fourni avec l'application, avec son propre démon Docker embarqué, afin que les workflows puissent construire des images de conteneurs dès l'installation.
|
||||
zh_cn: |
|
||||
Gitea — 带 CI 与容器仓库的 Git 代码托管
|
||||
|
||||
Gitea 是轻量、自足的软件协作平台:仓库、议题、合并请求、发布、CI 引擎与 OCI 容器仓库集于一个服务,在小型机器上也能流畅运行。
|
||||
|
||||
它在这里提供:
|
||||
|
||||
• 通过 HTTPS 托管 Git,附带议题、合并请求、发布与百科
|
||||
• Actions —— 采用 GitHub Actions 语法的 CI 工作流,现有的工作流文件通常无需修改即可运行
|
||||
• 内置容器仓库,工作流可以构建镜像并发布在源代码旁边
|
||||
• 拉取镜像同步 —— 将仓库指向任意 Git URL,它会按计划重新拉取,每次同步都会触发与推送相同的工作流
|
||||
|
||||
后两项让 Gitea 特别适合放在没有 webhook 的 Git 源前面:把仓库设为镜像,镜像的更新就会驱动构建。
|
||||
|
||||
应用自带一个运行器,内含独立的 Docker 守护进程,因此工作流开箱即可构建容器镜像。
|
||||
icon: icon.png
|
||||
screenshot_link:
|
||||
- screenshot-1.png
|
||||
- screenshot-2.png
|
||||
- screenshot-3.png
|
||||
thumbnail: thumbnail.png
|
||||
tagline:
|
||||
en_us: A git forge with CI and a container registry, on your own domain.
|
||||
fr_fr: Une forge git avec intégration continue et registre de conteneurs, sur votre domaine.
|
||||
zh_cn: 运行在你自己域名下的 Git 托管,内建 CI 与容器仓库。
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
Signing in
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Address | `https://gitea-$APP_DOMAIN` |
|
||||
| User | `gitea_admin` |
|
||||
| Password | your server's default app password |
|
||||
|
||||
Gitea uses its own account system. Self-registration is off, so
|
||||
`gitea_admin` is the only account until you create more under
|
||||
Site Administration.
|
||||
|
||||
Building from a repository that has no webhooks
|
||||
|
||||
New Migration → check **This repository will be a mirror**, and give it
|
||||
the git URL. Gitea re-fetches on a schedule, and each sync raises the
|
||||
same `push` event a real push would — so a workflow with `on: push`
|
||||
runs. Commit the workflow to `.gitea/workflows/` in the mirrored
|
||||
repository itself (`.github/workflows/` also works; `.forgejo/` does
|
||||
not).
|
||||
|
||||
One thing to change by hand: a migrated repository arrives with
|
||||
**Actions switched off**. Repository → Settings → Advanced Settings →
|
||||
tick **Actions**, or nothing will ever run, however correct the
|
||||
workflow is.
|
||||
|
||||
Publishing images
|
||||
|
||||
The registry lives at the same address as the web UI. In a workflow,
|
||||
log in to `gitea-$APP_DOMAIN` and push to
|
||||
`gitea-$APP_DOMAIN/<user>/<image>:<tag>`. Use an access token with the
|
||||
`write:package` scope, kept as a repository secret — the token Actions
|
||||
injects as `secrets.GITHUB_TOKEN` is rejected by the registry.
|
||||
|
||||
The CI runner
|
||||
|
||||
The runner holds a Docker daemon of its own and therefore runs
|
||||
privileged. It is what lets a workflow build container images. If you
|
||||
do not intend to run CI, stop the `gitea-runner` container — Gitea
|
||||
itself is unaffected.
|
||||
fr_fr: |
|
||||
Connexion
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Adresse | `https://gitea-$APP_DOMAIN` |
|
||||
| Utilisateur | `gitea_admin` |
|
||||
| Mot de passe | le mot de passe par défaut de vos applications |
|
||||
|
||||
Gitea gère ses propres comptes. L'auto-inscription est désactivée :
|
||||
`gitea_admin` est donc le seul compte tant que vous n'en créez pas
|
||||
d'autres depuis l'administration du site.
|
||||
|
||||
Construire depuis un dépôt sans webhooks
|
||||
|
||||
Nouvelle migration → cochez **Ce dépôt sera un miroir** et indiquez
|
||||
l'URL git. Gitea re-synchronise périodiquement, et chaque
|
||||
synchronisation lève l'événement `push` qu'un vrai push aurait levé —
|
||||
un workflow `on: push` se déclenche donc. Le fichier de workflow se
|
||||
place dans `.gitea/workflows/` du dépôt mis en miroir
|
||||
(`.github/workflows/` fonctionne aussi ; `.forgejo/` non).
|
||||
|
||||
Une chose à changer à la main : un dépôt migré arrive avec **Actions
|
||||
désactivé**. Dépôt → Paramètres → Paramètres avancés → cochez
|
||||
**Actions**, sinon rien ne se déclenchera, si correct que soit le
|
||||
workflow.
|
||||
|
||||
Publier des images
|
||||
|
||||
Le registre est à la même adresse que l'interface web. Dans un
|
||||
workflow, connectez-vous à `gitea-$APP_DOMAIN` puis publiez vers
|
||||
`gitea-$APP_DOMAIN/<utilisateur>/<image>:<tag>`. Utilisez un jeton
|
||||
d'accès de portée `write:package`, conservé en secret de dépôt : le
|
||||
jeton fourni par Actions (`secrets.GITHUB_TOKEN`) est refusé par le
|
||||
registre.
|
||||
|
||||
Le runner d'intégration continue
|
||||
|
||||
Le runner embarque son propre démon Docker et tourne donc en mode
|
||||
privilégié. C'est ce qui permet à un workflow de construire des images
|
||||
de conteneurs. Si vous ne comptez pas utiliser l'intégration continue,
|
||||
arrêtez le conteneur `gitea-runner` — Gitea lui-même n'en dépend pas.
|
||||
zh_cn: |
|
||||
登录
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| 地址 | `https://gitea-$APP_DOMAIN` |
|
||||
| 用户 | `gitea_admin` |
|
||||
| 密码 | 服务器的默认应用密码 |
|
||||
|
||||
Gitea 使用自己的账户体系。自助注册已关闭,因此在你于站点管理中创建更多账户之前,
|
||||
`gitea_admin` 是唯一的账户。
|
||||
|
||||
为没有 webhook 的仓库构建
|
||||
|
||||
新建迁移 → 勾选**将此仓库设为镜像**并填入 git URL。Gitea 会按计划重新拉取,
|
||||
每次同步都会触发与真实推送相同的 `push` 事件,因此 `on: push` 工作流会运行。
|
||||
工作流文件请提交到被镜像仓库自身的 `.gitea/workflows/` 目录
|
||||
(`.github/workflows/` 同样有效,`.forgejo/` 无效)。
|
||||
|
||||
有一处需要手动调整:迁移而来的仓库默认**关闭 Actions**。
|
||||
进入 仓库 → 设置 → 高级设置 → 勾选 **Actions**,否则工作流再正确也不会运行。
|
||||
|
||||
发布镜像
|
||||
|
||||
容器仓库与网页界面位于同一地址。在工作流中登录 `gitea-$APP_DOMAIN`,
|
||||
并推送到 `gitea-$APP_DOMAIN/<用户>/<镜像>:<标签>`。请使用具有
|
||||
`write:package` 权限的访问令牌并存为仓库机密 —— Actions 注入的
|
||||
`secrets.GITHUB_TOKEN` 会被容器仓库拒绝。
|
||||
|
||||
CI 运行器
|
||||
|
||||
运行器内含独立的 Docker 守护进程,因此以特权模式运行,这正是工作流能够构建
|
||||
容器镜像的原因。若不打算使用 CI,可停止 `gitea-runner` 容器 —— 这不影响 Gitea 本身。
|
||||
title:
|
||||
en_us: Gitea
|
||||
index: /
|
||||
webui_port: 3000
|
||||
store_app_id: gitea
|
||||
|
||||
x-compose-app:
|
||||
schema_version: 2
|
||||
folders:
|
||||
- path: /DATA/AppData/$AppID/data
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
- path: /DATA/AppData/$AppID/config
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Holds .runner (the runner's registration) and the token file the
|
||||
# init step writes for it.
|
||||
- path: /DATA/AppData/$AppID/runner
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
mode: "0700"
|
||||
- path: /DATA/AppData/$AppID/runner-config
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# The inner Docker daemon's image store. dockerd runs as root inside the
|
||||
# runner and takes ownership of what it creates below this point.
|
||||
- path: /DATA/AppData/$AppID/runner-docker
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
mode: "0710"
|
||||
files:
|
||||
# Declared rather than left to the seed tree so the executable bit is
|
||||
# stated, not inherited: git on a Windows-hosted checkout records every
|
||||
# file 100644, and s6 refuses to spawn a `run` script it cannot execute —
|
||||
# the runner then loops "unable to spawn ./run: Permission denied" and no
|
||||
# workflow ever runs.
|
||||
- path: /DATA/AppData/$AppID/runner-config/act_runner-run
|
||||
from: runner-config/act_runner-run
|
||||
ensure: always
|
||||
mode: "0755"
|
||||
init:
|
||||
# Both steps are the gitea binary acting on its own installation, and both
|
||||
# have to wait for the server: post_up fires as soon as `compose up`
|
||||
# returns, which is when the container has *started*, not when Gitea has
|
||||
# migrated its schema. Run them any sooner and `user create` dies on
|
||||
# "no such table: user".
|
||||
#
|
||||
# The wait is bounded by `timeout` rather than left open: a step that
|
||||
# blocks forever would hold the install open forever.
|
||||
- name: create-admin
|
||||
image: gitea/gitea:1.27.3-rootless
|
||||
user: $PUID:$PGID
|
||||
phase: post_up
|
||||
volumes:
|
||||
- /DATA/AppData/$AppID/data:/var/lib/gitea
|
||||
- /DATA/AppData/$AppID/config:/etc/gitea
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
timeout 240 sh -c 'until gitea admin user list >/dev/null 2>&1; do sleep 3; done'
|
||||
gitea admin user create --admin --username gitea_admin \
|
||||
--password "$APP_DEFAULT_PASSWORD" --email "$APP_EMAIL" \
|
||||
--must-change-password=false \
|
||||
|| echo "admin account already present, leaving it alone"
|
||||
# Re-run rather than remembered: the command is idempotent, and an account
|
||||
# that was deleted by accident comes back on the next start.
|
||||
when: always
|
||||
- name: runner-token
|
||||
image: gitea/gitea:1.27.3-rootless
|
||||
user: $PUID:$PGID
|
||||
phase: post_up
|
||||
# Unlike the account, a runner token is not a database write — it is a
|
||||
# call to the running server's internal API, so this one needs to be able
|
||||
# to reach it.
|
||||
network: $APP_NET
|
||||
volumes:
|
||||
- /DATA/AppData/$AppID/data:/var/lib/gitea
|
||||
- /DATA/AppData/$AppID/config:/etc/gitea
|
||||
- /DATA/AppData/$AppID/runner:/runner
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
timeout 240 sh -c 'until gitea actions generate-runner-token > /runner/token.new 2>/dev/null; do sleep 3; done'
|
||||
mv /runner/token.new /runner/token
|
||||
chmod 600 /runner/token
|
||||
# Only while the runner has no registration of its own. Once it has one
|
||||
# the token is dead weight, and re-minting it on every start would leave
|
||||
# a live credential lying in the app folder for no reason.
|
||||
when: absent:/DATA/AppData/$AppID/runner/.runner
|
||||
webui-host: gitea-${domain}
|
||||
webui-path: /
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
@@ -0,0 +1,86 @@
|
||||
# Gitea — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
1. `gitea-runner` runs with `privileged: true`.
|
||||
2. Gitea is configured with `[migrations] ALLOW_LOCALNETWORKS = true`.
|
||||
3. The admin account is called `gitea_admin`, not the deployment's usual `admin`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
**1. The privileged runner.** The point of shipping a runner at all is that a
|
||||
workflow can build a container image. That needs a Docker daemon, and there are
|
||||
only two ways to give a CI job one:
|
||||
|
||||
- mount the host's `/var/run/docker.sock` into the runner, which hands every
|
||||
workflow — including one that arrives from a mirrored third-party repository —
|
||||
full control of the host's daemon, i.e. root on the PCS; or
|
||||
- give the runner a daemon of its own inside the container (`dind`), which
|
||||
requires `privileged`.
|
||||
|
||||
The second is the smaller exposure: the blast radius is the runner's own
|
||||
container and its own image store, not the host's containers, volumes and
|
||||
socket. It is also what upstream ships the `-dind` tag for.
|
||||
|
||||
The rootless dind variant (`0.6.1-dind-rootless`) would have avoided
|
||||
`privileged` altogether, and was tried first. It does not start on this
|
||||
platform: Ubuntu sets `kernel.apparmor_restrict_unprivileged_userns=1`, and
|
||||
rootlesskit dies with `fork/exec /proc/self/exe: operation not permitted`.
|
||||
Making it work means turning that protection off for the container
|
||||
(`security_opt: apparmor=unconfined`), which weakens the host more than
|
||||
`privileged` on a single container does. Revisit if the platform ever ships
|
||||
that sysctl relaxed, or if upstream's runner learns to use the host's
|
||||
user-namespace remapping.
|
||||
|
||||
**2. `ALLOW_LOCALNETWORKS`.** Gitea refuses by default to migrate or mirror
|
||||
from an address on a private network. The app's most useful configuration —
|
||||
mirroring a repository from another app on the same server, e.g. a Radicle
|
||||
node's git endpoint at `http://radicle-api:8080/<rid>.git` — is exactly that
|
||||
case. Without the flag the URL is rejected before any fetch is attempted.
|
||||
|
||||
**3. The admin username.** `admin` is on Gitea's reserved-name list (it
|
||||
collides with the `/admin` route) and account creation fails with it.
|
||||
|
||||
## Security mitigations in place
|
||||
|
||||
- The runner is not on the shared `pcs` network. It sits on an app-internal
|
||||
bridge with Gitea and nothing else, so no other app can reach it, and it
|
||||
publishes no port.
|
||||
- The runner's daemon is nested: its image store is a directory under the
|
||||
app's own AppData, and the containers it creates are invisible to, and
|
||||
cannot see, the host's daemon.
|
||||
- `capacity: 1` in the runner config and a 2G memory limit bound what a
|
||||
runaway build can consume.
|
||||
- Self-registration is disabled (`DISABLE_REGISTRATION`) and anonymous
|
||||
browsing is off (`REQUIRE_SIGNIN_VIEW`), so the forge starts closed with a
|
||||
single account whose password is the deployment's default app password.
|
||||
- `ALLOW_LOCALNETWORKS` is reachable only by an authenticated user of this
|
||||
Gitea — with registration disabled, that is the owner. It widens what the
|
||||
owner can point a mirror at; it does not expose anything to an anonymous
|
||||
caller.
|
||||
- The runner is severable: stopping the `gitea-runner` container leaves a
|
||||
fully working forge and removes the privileged container from the box. This
|
||||
is called out in `tips.before_install`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
| Alternative | Why not |
|
||||
|---|---|
|
||||
| Host Docker socket in the runner | Any workflow, including one from a mirrored repo, gets root on the host. Strictly worse than a privileged container. |
|
||||
| `0.6.1-dind-rootless` | Does not start under Ubuntu's unprivileged-userns restriction (see above); the workaround disables AppArmor for the container. |
|
||||
| Ship no runner | Gitea Actions is the reason to choose Gitea over a plain git server here, and a runner cannot be added later without the same `privileged` decision. |
|
||||
| Mirror over the public HTTPS address instead of `ALLOW_LOCALNETWORKS` | Works, but routes a same-box fetch out to the gateway and back, and breaks if the deployment has no public address. |
|
||||
|
||||
## Data protection
|
||||
|
||||
Everything that survives a restart is under `/DATA/AppData/gitea/`:
|
||||
repositories and the SQLite database in `data/`, the rendered `app.ini` in
|
||||
`config/`, the runner's registration in `runner/`, and the nested daemon's
|
||||
image store in `runner-docker/`. Nothing is written outside that tree, and no
|
||||
user directory (`/DATA/Documents`, `/DATA/Media`, …) is mounted into any
|
||||
container.
|
||||
|
||||
The only credential the app generates is the runner registration token, which
|
||||
is rewritten on every start and is worthless without network access to Gitea.
|
||||
The admin password is the deployment's own `APP_DEFAULT_PASSWORD`; it is never
|
||||
written to disk by this app beyond Gitea's own salted hash.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Replaces the image's own /etc/s6/act_runner/run. Two waits, both of which the
|
||||
# stock script gets wrong for this deployment.
|
||||
#
|
||||
# 1. The nested Docker daemon. Upstream waits for it through s6's readiness
|
||||
# notification. On a cold start with a warm image store dockerd takes longer
|
||||
# than s6-notifyoncheck's window, the notification never arrives, s6 marks
|
||||
# the service permanently failed, and act_runner sits blocked in
|
||||
# `s6-svwait -U` for two to four minutes before s6 gives up and lets it past
|
||||
# anyway. The runner shows "offline" in the forge for that whole time and
|
||||
# picks up no jobs. Asking the daemon directly is the same wait without the
|
||||
# guesswork.
|
||||
#
|
||||
# 2. The registration token. It is minted by an init step that runs after the
|
||||
# stack is up, so on a first install it does not exist yet when this
|
||||
# container starts — and the stock entrypoint reads the token file exactly
|
||||
# once, before its retry loop, so a token that arrives late is never seen.
|
||||
|
||||
while [ ! -d /etc/s6/docker/supervise ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
while ! docker info >/dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
while [ ! -s "${GITEA_RUNNER_REGISTRATION_TOKEN_FILE:-/data/token}" ] && [ ! -s /data/.runner ]; do
|
||||
echo "waiting for the runner registration token"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
exec run.sh
|
||||
@@ -0,0 +1,40 @@
|
||||
# act_runner configuration. Only the settings that differ from upstream's
|
||||
# defaults are written out — `act_runner generate-config` prints the full set.
|
||||
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
# The file the runner's own registration is kept in, relative to /data.
|
||||
file: .runner
|
||||
# One job at a time. A PCS shares its CPU with everything else installed on
|
||||
# it, and an image build is the least polite thing on the box.
|
||||
capacity: 1
|
||||
timeout: 1h
|
||||
fetch_timeout: 5s
|
||||
fetch_interval: 2s
|
||||
# Overrides the labels stored at registration (.runner). These are upstream
|
||||
# act_runner defaults: Gitea's own job images, which carry node, bash, git
|
||||
# and the docker CLI with buildx, so marketplace actions (actions/checkout,
|
||||
# docker/build-push-action, ...) run as they do on GitHub. The registration
|
||||
# labels pointed at node:22-bookworm, which has no docker CLI.
|
||||
labels:
|
||||
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
|
||||
- "ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04"
|
||||
- "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
|
||||
- "docker:docker://docker:28.5-cli"
|
||||
|
||||
cache:
|
||||
# The cache server binds an address inside the runner container, which the
|
||||
# job containers — created by the Docker daemon nested inside that same
|
||||
# container — cannot route to. Left on, every `actions/cache` step fails
|
||||
# noisily instead of being skipped.
|
||||
enabled: false
|
||||
|
||||
container:
|
||||
# Jobs run on the runner's own nested daemon, on its default bridge: they
|
||||
# reach the internet, and they reach Gitea the way anything outside would,
|
||||
# through its public address.
|
||||
network: bridge
|
||||
privileged: false
|
||||
force_pull: false
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Reference in New Issue
Block a user