Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62b37d045e | ||
|
|
390fcb9d4b | ||
|
|
9f8adf14bc | ||
|
|
92e4d2fd2b | ||
|
|
f30764d60f | ||
|
|
a0e3fc09c9 | ||
|
|
bb9e8e6c4b | ||
|
|
d8312ff62c | ||
|
|
39a8f20df2 | ||
|
|
240f08fff4 | ||
|
|
d38a64b0e1 | ||
|
|
89bd40c4a8 |
+12
-1
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -8,12 +8,12 @@ Each runner runs in an isolated **rootless Docker** environment under a dedicate
|
||||
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](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
@@ -8,12 +8,12 @@ Each runner runs in an isolated **rootless Docker** environment under a dedicate
|
||||
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -1,3 +1,3 @@
|
||||
"""Gitea Runner Manager — lean CLI for managing Gitea Actions runners."""
|
||||
|
||||
__version__ = "0.16.0"
|
||||
__version__ = "0.18.0"
|
||||
|
||||
+6
-4
@@ -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
@@ -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": "Списък на наличните работни процеси вместо изпълнение",
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user