Compare commits

..
12 Commits
Author SHA1 Message Date
grm-ci-bot 62b37d045e release: v0.18.0 [skip ci] 2026-07-12 01:14:36 +00:00
emil 390fcb9d4b GRM-144: feat(runner): enable IPv6 in rootless Docker via pasta network driver
Post-merge / detect-type (push) Waiting to run
Post-merge / configure-repo (push) Waiting to run
Post-merge / release (push) Waiting to run
Post-merge / validate-commit-msg (push) Waiting to run
Post-merge / badges (push) Waiting to run
Post-merge / sync-wiki (push) Waiting to run
Post-merge / vikunja (push) Waiting to run
Post-merge / publish (push) Waiting to run
2026-07-12 01:12:43 +00:00
gitea-actions-bot 9f8adf14bc chore: update badge URLs to commit 625e2176 [skip ci] 2026-07-11 23:58:43 +00:00
grm-ci-bot 92e4d2fd2b release: v0.17.2 [skip ci] 2026-07-11 23:57:11 +00:00
emil f30764d60f GRM-145: refactor: adopt devx v0.40.0
Post-merge / publish (push) Waiting to run
Post-merge / detect-type (push) Waiting to run
Post-merge / validate-commit-msg (push) Waiting to run
Post-merge / release (push) Waiting to run
Post-merge / sync-wiki (push) Waiting to run
Post-merge / vikunja (push) Waiting to run
Post-merge / configure-repo (push) Waiting to run
Post-merge / badges (push) Waiting to run
2026-07-11 23:55:15 +00:00
gitea-actions-bot a0e3fc09c9 chore: update badge URLs to commit c25e7760 [skip ci] 2026-07-09 11:57:18 +00:00
grm-ci-bot bb9e8e6c4b release: v0.17.1 [skip ci] 2026-07-09 11:56:36 +00:00
emil d8312ff62c GRM-143: fix: disable IPv6 in rootless Docker daemon on runners
Post-merge / detect-type (push) Waiting to run
Post-merge / publish (push) Waiting to run
Post-merge / configure-repo (push) Waiting to run
Post-merge / sync-wiki (push) Waiting to run
Post-merge / vikunja (push) Waiting to run
Post-merge / validate-commit-msg (push) Waiting to run
Post-merge / release (push) Waiting to run
Post-merge / badges (push) Waiting to run
2026-07-09 11:54:18 +00:00
gitea-actions-bot 39a8f20df2 chore: update badge URLs to commit feb0219b [skip ci] 2026-07-08 21:15:24 +00:00
grm-ci-bot 240f08fff4 release: v0.17.0 [skip ci] 2026-07-08 21:14:47 +00:00
emil d38a64b0e1 GRM-142: feat: bump devx to 0.38.0 and migrate to role-based Gitea tokens
Post-merge / publish (push) Waiting to run
Post-merge / detect-type (push) Waiting to run
Post-merge / configure-repo (push) Waiting to run
Post-merge / release (push) Waiting to run
Post-merge / sync-wiki (push) Waiting to run
Post-merge / validate-commit-msg (push) Waiting to run
Post-merge / badges (push) Waiting to run
Post-merge / vikunja (push) Waiting to run
2026-07-08 21:12:29 +00:00
gitea-actions-bot 89bd40c4a8 chore: update badge URLs to commit b9122602 [skip ci] 2026-07-07 22:17:56 +00:00
16 changed files with 218 additions and 78 deletions
+12 -1
View File
@@ -15,7 +15,8 @@ GITEA_REGISTRATION_TOKEN=your-registration-token
# If set, API checks are performed as a bonus but do NOT affect pass/fail.
# Required scopes: read:user, read:repository, read:admin (or just "admin")
# Generate token at: Settings → Applications → Generate New Token
# CI_GITEA_TOKEN=your-admin-api-token
# CI_GITEA_API_TOKEN=your-admin-api-token
# Legacy CI_GITEA_TOKEN is also accepted.
# Integration test API retries (optional, default: 3).
# Number of times to retry API checks waiting for runner to appear.
@@ -50,6 +51,16 @@ GITEA_REGISTRATION_TOKEN=your-registration-token
# Used by PIP_INSTALL to configure PIP_EXTRA_INDEX_URL
CI_GITEA_USERNAME=your-gitea-username
# Role-based Gitea API tokens (devx 0.40.0+)
# DEVELOPER_GITEA_API_TOKEN is used by local `grm trigger-workflow` and `make create-pr`.
# CI_GITEA_API_TOKEN is used by CI workflows (and accepted as a fallback for local tools).
# REVIEWER_GITEA_API_TOKEN is used by CI to post APPROVE reviews; it must belong to a
# different user than the PR author.
# Legacy CI_GITEA_TOKEN and REVIEW_GITEA_TOKEN are accepted as fallbacks.
# DEVELOPER_GITEA_API_TOKEN=your-developer-token
# CI_GITEA_API_TOKEN=your-ci-token
# REVIEWER_GITEA_API_TOKEN=your-reviewer-token
# Vikunja API token (required for `make create-task` dev workflow)
# Generate at: Vikunja → Settings → API Tokens
# VIKUNJA_TOKEN=your-vikunja-api-token
+18 -16
View File
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
jobs:
@@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci,lint
- name: Lint all
@@ -80,7 +80,7 @@ jobs:
fetch-depth: 0
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci,lint
- name: Release dry-run validation
@@ -106,7 +106,7 @@ jobs:
fetch-depth: 0
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Detect changed paths
@@ -135,7 +135,7 @@ jobs:
run: make setup-image EXTRAS=ci
- name: Validate auto-merge preconditions
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
DEVX_TASK_PREFIX: GRM
DEVX_VIKUNJA_PROJECT_ID: 6
@@ -165,13 +165,13 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Discover available runners
id: discover
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
MOLECULE_RUNNERS: ${{ vars.MOLECULE_RUNNERS }}
PYTHONPATH: src
run: |
@@ -196,7 +196,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci,molecule
- name: Install Ansible collections
@@ -223,12 +223,14 @@ jobs:
echo "Docker not available in CI container — skipping molecule tests"
exit 0
fi
echo "$CI_GITEA_TOKEN" | docker login git.oblachno.oblachno.fyi -u "$CI_GITEA_USERNAME" --password-stdin
_TOKEN="$CI_GITEA_API_TOKEN"; [ -z "$_TOKEN" ] && _TOKEN="$CI_GITEA_TOKEN"
[ -z "$_TOKEN" ] && { echo "Gitea API token not set — skipping Docker login"; exit 0; }
echo "$_TOKEN" | docker login git.oblachno.oblachno.fyi -u "$CI_GITEA_USERNAME" --password-stdin
# shellcheck disable=SC2086 # intentional word splitting for argument expansion
python3 -m devx.molecule.molecule_ci_guard $TEST_PAIRS
env:
GITEA_URL: ${{ github.server_url }}
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
RUN_ID: ${{ github.run_id }}
ANSIBLE_INJECT_INVOCATION: "1"
@@ -250,12 +252,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Run automated PR review
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
set -euo pipefail
@@ -288,15 +290,15 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.CI_GITEA_TOKEN }}
token: ${{ secrets.CI_GITEA_API_TOKEN }}
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Post approval review
env:
CI_GITEA_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
REVIEWER_GITEA_API_TOKEN: ${{ secrets.REVIEWER_GITEA_API_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
REPOSITORY: ${{ github.repository }}
PYTHONPATH: src
@@ -311,7 +313,7 @@ jobs:
--body "Auto-approved: all CI checks passed (quality, molecule, pr-review, pre-merge-check)."
- name: Squash merge with task ID
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
PYTHONPATH: src
DEVX_TASK_PREFIX: GRM
+20 -20
View File
@@ -32,7 +32,7 @@ on:
workflow_dispatch:
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
jobs:
@@ -48,7 +48,7 @@ jobs:
fetch-depth: 1
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Check if this is a release commit
@@ -71,7 +71,7 @@ jobs:
fetch-depth: 1
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Validate latest commit message
@@ -96,10 +96,10 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.CI_GITEA_TOKEN }}
token: ${{ secrets.CI_GITEA_API_TOKEN }}
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci,lint
- name: Configure git
@@ -120,7 +120,7 @@ jobs:
- name: Notify on failure
if: failure()
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate 2>/dev/null || true
@@ -144,12 +144,12 @@ jobs:
ref: ${{ needs.release.outputs.tag }}
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci,lint
- name: Build and publish release
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate 2>/dev/null || true
@@ -160,7 +160,7 @@ jobs:
- name: Notify on failure
if: failure()
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate 2>/dev/null || true
@@ -186,12 +186,12 @@ jobs:
fetch-depth: 0
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Sync documentation to wiki
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate 2>/dev/null || true
@@ -199,7 +199,7 @@ jobs:
- name: Notify on failure
if: failure()
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
export PATH="$HOME/.local/bin:$PATH"
@@ -220,14 +220,14 @@ jobs:
with:
fetch-depth: 0
ref: master
token: ${{ secrets.CI_GITEA_TOKEN }}
token: ${{ secrets.CI_GITEA_API_TOKEN }}
- name: Fetch latest master
run: |
git fetch origin master
git reset --hard origin/master
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=lint
- name: Generate and push badges
@@ -239,7 +239,7 @@ jobs:
- name: Notify on failure
if: failure()
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
export PATH="$HOME/.local/bin:$PATH"
@@ -261,7 +261,7 @@ jobs:
fetch-depth: 0
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Update Vikunja task
@@ -276,7 +276,7 @@ jobs:
- name: Notify on failure
if: failure()
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
export PATH="$HOME/.local/bin:$PATH"
@@ -296,12 +296,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: make setup-image EXTRAS=ci
- name: Ensure branch protection and labels
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
DEVX_REPO_NAME: grm
DEVX_REPO_OWNER: oblachno-oss
@@ -312,7 +312,7 @@ jobs:
- name: Notify on failure
if: failure()
env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
export PATH="$HOME/.local/bin:$PATH"
+7 -1
View File
@@ -57,7 +57,7 @@ CI also runs a best-effort `make workflow-dryrun` step (skipped if act_runner is
## Architecture
- **Python CLI** (`src/grm/`) — Click-based CLI that delegates to Ansible
- **Ansible Role** (`ansible/roles/gitea-runner/`) — Idempotent role for rootless Docker runner setup
- **Ansible Role** (`ansible/roles/gitea-runner/`) — Idempotent role for rootless Docker runner setup with pasta networking (IPv6 support)
- **devx package** (installed from git) — Reusable CI/CD tools: auto-merge, post-merge, release, publishing, molecule distribution, PR reviews, failure notifications
- **Versioning** (`cliff.toml`) — git-cliff configuration for automated semver versioning from conventional commits
@@ -84,6 +84,12 @@ as a defense-in-depth measure, but branch protection is the primary gate.
### 1. Create Vikunja Task
Create a task in Vikunja project 6 via `make create-task -- --title "Task title" --description "<h2>...</h2>"` (requires `VIKUNJA_TOKEN` in `.env`). This prints the `GRM-N` identifier and next-step instructions.
**IMPORTANT:** The task title must NOT include the `GRM-N:` prefix.
The `make create-pr` and `check_auto_merge_ready` commands automatically
prepend `GRM-N: ` to the Vikunja task title when forming the PR title.
If the Vikunja task title already includes the prefix, the PR title will
have a double prefix and auto-merge validation will fail.
### 2. Create Branch
```bash
git checkout master && git pull
+24
View File
@@ -2,6 +2,30 @@
All notable changes to this project will be documented in this file.
## [0.18.0] - 2026-07-12
### Features
- *(runner)* Enable IPv6 in rootless Docker via pasta network driver
## [0.17.2] - 2026-07-11
### Refactor
- Adopt devx v0.40.0
## [0.17.1] - 2026-07-09
### Bug Fixes
- Disable IPv6 in rootless Docker daemon on runners
## [0.17.0] - 2026-07-08
### Features
- Bump devx to 0.38.0 and migrate to role-based Gitea tokens
## [0.16.0] - 2026-07-07
### Features
+5 -5
View File
@@ -86,7 +86,8 @@ setup-release: $(VENV)/bin/activate .env configure-gitea-pypi
# the venv symlink first, then installs the project.
setup-image:
@if [ -d /opt/venv ]; then ln -sf /opt/venv .venv; . .venv/bin/activate; \
if [ -n "$$CI_GITEA_TOKEN" ]; then export PIP_EXTRA_INDEX_URL="https://$$CI_GITEA_USERNAME:$$CI_GITEA_TOKEN@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"; fi; \
_TOKEN="$$CI_GITEA_API_TOKEN"; [ -z "$$_TOKEN" ] && _TOKEN="$$DEVELOPER_GITEA_API_TOKEN"; [ -z "$$_TOKEN" ] && _TOKEN="$$CI_GITEA_TOKEN"; \
if [ -n "$$_TOKEN" ]; then export PIP_EXTRA_INDEX_URL="https://$$CI_GITEA_USERNAME:$${_TOKEN}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"; fi; \
pip install -e .$(if $(EXTRAS),[$(EXTRAS)],); \
else echo "[setup-image] /opt/venv not found — falling back to setup-ci"; $(MAKE) setup-ci; fi
@@ -159,10 +160,9 @@ workflow-dryrun: devx-workflow-dryrun
workflow-check: devx-workflow-check
configure-gitea-pypi:
@if [ -z "$$CI_GITEA_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
CI_GITEA_TOKEN="$$CI_GITEA_TOKEN"; \
if [ -z "$$CI_GITEA_TOKEN" ]; then echo "[configure-gitea-pypi] CI_GITEA_TOKEN not set — skipping (devx must be on public PyPI)"; exit 0; fi; \
echo "[configure-gitea-pypi] Gitea PyPI registry configured (CI_GITEA_TOKEN present)."
_TOKEN="$$CI_GITEA_API_TOKEN"; [ -z "$$_TOKEN" ] && _TOKEN="$$DEVELOPER_GITEA_API_TOKEN"; [ -z "$$_TOKEN" ] && _TOKEN="$$CI_GITEA_TOKEN"; \
if [ -z "$$_TOKEN" ]; then echo "[configure-gitea-pypi] Gitea API token not set — skipping (devx must be on public PyPI)"; exit 0; fi; \
echo "[configure-gitea-pypi] Gitea PyPI registry configured (token present)."
ansible-lint:
PATH="$(PWD)/$(BIN):$$PATH" $(BIN)/ansible-lint ansible/
+6 -6
View File
@@ -8,12 +8,12 @@ Each runner runs in an isolated **rootless Docker** environment under a dedicate
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/python.svg)](https://www.python.org/downloads/)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/python.svg)](https://www.python.org/downloads/)
## Why GRM?
@@ -53,3 +53,10 @@ docker_apt_source_line: >-
{{ ansible_facts['distribution_release'] }} stable
# Set to false in CI/molecule to skip rootless daemon startup (needs kernel userns)
docker_rootless_setup: true
# Rootless Docker network driver: "pasta" (IPv6 support) or "slirp4netns" (IPv4 only)
# pasta has proper outgoing IPv6 support; slirp4netns does not (known limitation).
docker_rootless_net_driver: "pasta"
# IPv6 subnet for rootless Docker containers (ULA range, not routable on internet)
docker_ipv6_cidr: "fd00:dead:beef::/48"
@@ -35,6 +35,7 @@
name:
- uidmap
- slirp4netns
- passt
- fuse-overlayfs
- docker-ce
- docker-ce-cli
@@ -57,6 +58,7 @@
- docker
- docker-compose
- slirp4netns
- passt
- fuse-overlayfs
- rsync
state: present
@@ -97,6 +99,75 @@
changed_when: true
when: docker_rootless_setup
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: "{{ gitea_runner_home }}/.config/docker"
state: directory
mode: "0755"
owner: "{{ gitea_runner_service_user }}"
group: "{{ gitea_runner_service_user }}"
when: docker_rootless_setup
- name: Ensure systemd user override directory exists
ansible.builtin.file:
path: "{{ gitea_runner_home }}/.config/systemd/user/docker.service.d"
state: directory
mode: "0755"
owner: "{{ gitea_runner_service_user }}"
group: "{{ gitea_runner_service_user }}"
when: docker_rootless_setup
- name: Configure rootless Docker to use pasta with IPv6
ansible.builtin.copy:
dest: "{{ gitea_runner_home }}/.config/systemd/user/docker.service.d/override.conf"
content: |
[Service]
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET={{ docker_rootless_net_driver }}"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
mode: "0644"
owner: "{{ gitea_runner_service_user }}"
group: "{{ gitea_runner_service_user }}"
register: docker_network_override
when: docker_rootless_setup
- name: Reload systemd user daemon if network config changed
ansible.builtin.command: systemctl --user daemon-reload
become: true
become_user: "{{ gitea_runner_service_user }}"
environment:
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
changed_when: true
when:
- docker_rootless_setup
- docker_network_override is changed
- name: Configure rootless Docker daemon with IPv6 enabled
ansible.builtin.copy:
dest: "{{ gitea_runner_home }}/.config/docker/daemon.json"
content: |
{
"ipv6": true,
"ip6tables": true,
"fixed-cidr-v6": "{{ docker_ipv6_cidr }}"
}
mode: "0644"
owner: "{{ gitea_runner_service_user }}"
group: "{{ gitea_runner_service_user }}"
register: docker_ipv6_config
when: docker_rootless_setup
- name: Restart rootless Docker if config changed
ansible.builtin.command: systemctl --user restart docker
become: true
become_user: "{{ gitea_runner_service_user }}"
environment:
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
changed_when: true
when:
- docker_rootless_setup
- docker_ipv6_config is changed or docker_network_override is changed
- name: Wait for rootless Docker daemon to be ready
ansible.builtin.command: docker version
become: true
+6 -6
View File
@@ -8,12 +8,12 @@ Each runner runs in an isolated **rootless Docker** environment under a dedicate
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/f0f26310e77e1a999dd6f23376d5a1617ea46c67/python.svg)](https://www.python.org/downloads/)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/625e21765e37af0c66c56e2424254607583dab76/python.svg)](https://www.python.org/downloads/)
## Overview
+13 -1
View File
@@ -173,9 +173,21 @@ Each runner operates under a dedicated unprivileged system user. The Docker daem
- User namespace mapping via `/etc/subuid` and `/etc/subgid` (range: 100000-165535)
- Rootless Docker socket at `/run/user/<UID>/docker.sock`
- `slirp4netns` for user-mode networking
- `pasta` for user-mode networking with IPv6 support (replaces `slirp4netns`, which lacks outgoing IPv6)
- `fuse-overlayfs` for rootless container storage
The rootless Docker daemon is configured via a systemd user override
(`docker.service.d/override.conf`) that sets:
- `DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta` — use pasta as the network driver
- `DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit` — pasta's native port forwarding
- `DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6` — enable IPv6 routing
The daemon.json enables IPv6 with a ULA subnet (`fd00:dead:beef::/48`)
for container addressing. This ensures runner containers can reach
both IPv4 and IPv6 services (e.g., the Gitea registry) without
per-workaround DNS hacks.
Containers launched by the runner never have root access to the host. The rootless Docker daemon is started as a systemd user service and persists via lingering.
### Secret handling
+2 -2
View File
@@ -34,7 +34,7 @@ ci = [
"build==1.5.0",
"twine==6.2.0",
# Reusable CI/CD and dev tools (auto-merge, pr-review, pre-push checks, etc.)
"devx==0.37.0",
"devx==0.40.0",
]
# Lint and type-checking tools (quality job)
lint = [
@@ -54,7 +54,7 @@ molecule = [
dev = [
"grm[ci,lint,molecule]",
# Reusable CI/CD and dev tools (pre-push hooks, create-task, create-pr)
"devx==0.37.0",
"devx==0.40.0",
# Non-Python dev dependency: checkmake (Makefile linter)
# Install via: go install github.com/checkmake/checkmake/cmd/checkmake@latest
]
+1 -1
View File
@@ -1,3 +1,3 @@
"""Gitea Runner Manager — lean CLI for managing Gitea Actions runners."""
__version__ = "0.16.0"
__version__ = "0.18.0"
+6 -4
View File
@@ -125,8 +125,8 @@ def cli(ctx: click.Context, become_password_file: str | None, verbose: bool) ->
@click.option(
"--admin-token",
"-a",
default=lambda: os.getenv("CI_GITEA_TOKEN"),
help=_("Gitea admin API token for integration test (env: CI_GITEA_TOKEN)"),
default=lambda: os.getenv("CI_GITEA_API_TOKEN") or os.getenv("CI_GITEA_TOKEN"),
help=_("Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)"),
)
@click.option(
"--integration-retries",
@@ -528,8 +528,10 @@ def list_runners(ask_become_pass: bool, no_status: bool) -> None:
)
@click.option(
"--token",
default=lambda: os.getenv("CI_GITEA_TOKEN"),
help=_("Gitea API token (env: CI_GITEA_TOKEN)"),
default=lambda: (
os.getenv("DEVELOPER_GITEA_API_TOKEN") or os.getenv("CI_GITEA_API_TOKEN") or os.getenv("CI_GITEA_TOKEN")
),
help=_("Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)"),
)
@click.option(
"--list",
+14 -14
View File
@@ -175,13 +175,13 @@
"ru": "URL Gitea (env: GITEA_URL)",
"zh": "Gitea URL(环境变量: GITEA_URL"
},
"Gitea admin API token for integration test (env: CI_GITEA_TOKEN)": {
"bg": "Gitea admin API токен за интеграционен тест (env: CI_GITEA_TOKEN)",
"de": "Gitea-Admin-API-Token für Integrationstest (env: CI_GITEA_TOKEN)",
"en": "Gitea admin API token for integration test (env: CI_GITEA_TOKEN)",
"pl": "Token API administratora Gitea do testów integracyjnych (env: CI_GITEA_TOKEN)",
"ru": "Токен админ API Gitea для интеграционного теста (env: CI_GITEA_TOKEN)",
"zh": "Gitea 管理员 API 令牌,用于集成测试(环境变量: CI_GITEA_TOKEN"
"Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)": {
"bg": "Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)",
"de": "Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)",
"en": "Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)",
"pl": "Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)",
"ru": "Gitea admin API token for integration test (env: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN)",
"zh": "Gitea admin API token for integration test(环境变量: CI_GITEA_API_TOKEN or CI_GITEA_TOKEN"
},
"HEALTHY": {
"bg": "ЗДРАВ",
@@ -703,13 +703,13 @@
"ru": "Git ref для запуска workflow (по умолчанию: master)",
"zh": "运行工作流的 Git ref(默认:master"
},
"Gitea API token (env: CI_GITEA_TOKEN)": {
"bg": "Gitea API токен (env: CI_GITEA_TOKEN)",
"de": "Gitea API-Token (env: CI_GITEA_TOKEN)",
"en": "Gitea API token (env: CI_GITEA_TOKEN)",
"pl": "Token API Gitea (env: CI_GITEA_TOKEN)",
"ru": "Токен API Gitea (env: CI_GITEA_TOKEN)",
"zh": "Gitea API 令牌(环境变量:CI_GITEA_TOKEN"
"Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)": {
"bg": "Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)",
"de": "Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)",
"en": "Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)",
"pl": "Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)",
"ru": "Gitea API token (env: DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN)",
"zh": "Gitea API token(环境变量:DEVELOPER_GITEA_API_TOKEN, CI_GITEA_API_TOKEN, or CI_GITEA_TOKEN"
},
"List available workflows instead of triggering one": {
"bg": "Списък на наличните работни процеси вместо изпълнение",
+6 -1
View File
@@ -9,7 +9,12 @@ from click.testing import CliRunner
from grm import __version__
from grm.cli import cli
_TEST_ENV = {"GITEA_URL": "https://git.example.com", "CI_GITEA_TOKEN": ""}
_TEST_ENV = {
"GITEA_URL": "https://git.example.com",
"DEVELOPER_GITEA_API_TOKEN": "",
"CI_GITEA_API_TOKEN": "",
"CI_GITEA_TOKEN": "",
}
class TestCLI: