Compare commits

...
12 Commits
11 changed files with 205 additions and 232 deletions
+4
View File
@@ -37,6 +37,10 @@ GITEA_REGISTRATION_TOKEN=your-registration-token
# Supported: en, bg, de, ru, zh # Supported: en, bg, de, ru, zh
# GRM_LANG=en # GRM_LANG=en
# Gitea PyPI registry username (for private package access)
# Used by PIP_INSTALL to configure PIP_EXTRA_INDEX_URL
GITEA_REGISTRY_USERNAME=emil
# devx configuration (GRM-specific overrides) # devx configuration (GRM-specific overrides)
# Task prefix for Vikunja task IDs # Task prefix for Vikunja task IDs
DEVX_TASK_PREFIX=GRM DEVX_TASK_PREFIX=GRM
+10 -12
View File
@@ -175,18 +175,17 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install dependencies - name: Set up environment
run: | env:
. .env 2>/dev/null || true REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" run: make setup-ci
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"
- name: Run automated PR review - name: Run automated PR review
env: env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }} REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src PYTHONPATH: src
run: | run: |
set -euo pipefail set -euo pipefail
. .venv/bin/activate
python3 -m devx.ci.pr_review \ python3 -m devx.ci.pr_review \
"${{ github.event.number }}" \ "${{ github.event.number }}" \
"${{ github.repository }}" "${{ github.repository }}"
@@ -210,12 +209,10 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.REPO_TOKEN }} token: ${{ secrets.REPO_TOKEN }}
- name: Install dependencies - name: Set up environment
run: | env:
. .env 2>/dev/null || true REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" run: make setup-ci
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"
- name: Squash merge with task ID - name: Squash merge with task ID
env: env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }} REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
@@ -228,6 +225,7 @@ jobs:
REPOSITORY: ${{ github.repository }} REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.number }} PR_NUMBER: ${{ github.event.number }}
run: | run: |
. .venv/bin/activate
python3 -m devx.ci.auto_merge \ python3 -m devx.ci.auto_merge \
"$HEAD_REF" \ "$HEAD_REF" \
"$PR_TITLE" \ "$PR_TITLE" \
+90 -55
View File
@@ -1,30 +1,30 @@
name: Post-merge name: Post-merge
# Runs on every push to master. A single workflow with conditional jobs # Runs on every push to master. A single workflow with conditional jobs
# replaces the previous 4 separate workflows (release.yml, post-merge.yml, # for release, publish, wiki sync, badges, and Vikunja task updates.
# sync-wiki.yml, and the badges job from ci.yml).
# #
# Job dependency graph: # Job dependency graph:
# #
# detect-type ──┬── release (skip if release commit) # detect-type ──┬── validate-commit-msg (skip if release commit)
# ├── release (skip if release commit)
# │ └── publish (needs release — builds & publishes to PyPI)
# ├── badges (ALWAYS runs — even on release commits) # ├── badges (ALWAYS runs — even on release commits)
# ├── configure-repo (independent — skip if release commit) # ├── configure-repo (independent — skip if release commit)
# ├── sync-wiki (needs release — skip if release commit/fails) # ├── sync-wiki (skip if release commit — runs for ALL merges)
# └── vikunja (needs release — skip if release commit/fails) # └── vikunja (skip if release commit — runs for ALL merges)
# #
# sync-wiki and vikunja depend on release succeeding so that the wiki # sync-wiki and vikunja run for ALL non-release commits, not just when
# and task tracker are only updated when the code is actually released. # release succeeds. This ensures the wiki and task tracker are updated
# If release fails, they are skipped to avoid leaving the wiki or # even for infrastructure-only changes (docs, CI config, etc.).
# Vikunja in an inconsistent state with the codebase on master.
# #
# The badges job depends on release so it picks up the latest version # The badges job uses `if: always()` with no is-release condition so it
# number. It uses `if: always()` with no is-release condition so it # runs on every push to master, including release commits. This ensures
# runs on every push to master, including release commits. This # badges (tests, coverage, version, etc.) are always current.
# ensures badges (tests, coverage, version, etc.) are always current.
# #
# When release.py creates a "release: vX.Y.Z" commit, the release # When release creates a "release: vX.Y.Z" commit and tag, the publish
# commit's post-merge run still updates badges (version badge picks # job (which depends on release) builds and publishes the package to the
# up the new version). Other jobs skip. The tag push triggers publish.yml. # Gitea PyPI registry. The release commit's post-merge run still updates
# badges (version badge picks up the new version). Other jobs skip.
on: on:
push: push:
@@ -40,15 +40,17 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Install dependencies - name: Set up environment
run: | env:
. .env 2>/dev/null || true REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" run: make setup-ci
- name: Check if this is a release commit - name: Check if this is a release commit
id: check id: check
env: env:
PYTHONPATH: src PYTHONPATH: src
run: python3 -m devx.ci.detect_release_commit run: |
. .venv/bin/activate
python3 -m devx.ci.detect_release_commit
validate-commit-msg: validate-commit-msg:
needs: [detect-type] needs: [detect-type]
@@ -59,15 +61,16 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Install dependencies - name: Set up environment
run: | env:
. .env 2>/dev/null || true REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" run: make setup-ci
- name: Validate latest commit message - name: Validate latest commit message
env: env:
PYTHONPATH: src PYTHONPATH: src
DEVX_TASK_PREFIX: GRM DEVX_TASK_PREFIX: GRM
run: | run: |
. .venv/bin/activate
git log -1 --format=%B > commit-msg.txt git log -1 --format=%B > commit-msg.txt
python3 -m devx.ci.validate_commit_msg commit-msg.txt --branch master python3 -m devx.ci.validate_commit_msg commit-msg.txt --branch master
rm -f commit-msg.txt rm -f commit-msg.txt
@@ -77,6 +80,8 @@ jobs:
if: needs.detect-type.outputs.is-release == 'false' if: needs.detect-type.outputs.is-release == 'false'
runs-on: docker runs-on: docker
timeout-minutes: 15 timeout-minutes: 15
outputs:
tag: ${{ steps.release-tag.outputs.tag }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -91,6 +96,7 @@ jobs:
git config user.name "grm-ci-bot" git config user.name "grm-ci-bot"
git config user.email "grm-ci-bot@oblachno.fyi" git config user.email "grm-ci-bot@oblachno.fyi"
- name: Run release - name: Run release
id: release-tag
env: env:
PYTHONPATH: src PYTHONPATH: src
DEVX_VERSION_FILE: src/gitea_runner_manager/__init__.py DEVX_VERSION_FILE: src/gitea_runner_manager/__init__.py
@@ -100,20 +106,13 @@ jobs:
. .venv/bin/activate . .venv/bin/activate
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.release python3 -m devx.ci.release
- name: Publish release - name: Extract tag (fallback if GITHUB_OUTPUT not set)
env: if: steps.release-tag.outputs.tag == ''
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
run: | run: |
. .venv/bin/activate tag=$(git describe --tags --abbrev=0 2>/dev/null || true)
export PATH="$HOME/.local/bin:$PATH" if [ -n "$tag" ]; then
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") echo "tag=$tag" >> "$GITHUB_OUTPUT"
if [ -z "$TAG" ]; then
echo "No tag found — skipping publish"
exit 0
fi fi
echo "Publishing release $TAG (idempotent — skips if already published)..."
python3 -m devx.ci.publish "$TAG" "${{ github.repository }}"
- name: Notify on failure - name: Notify on failure
if: failure() if: failure()
env: env:
@@ -122,15 +121,51 @@ jobs:
run: | run: |
. .venv/bin/activate 2>/dev/null || true . .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.tools.install_tools --tool tea
python3 -m devx.ci.notify_failure --auto-login \ python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \ --repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \ --run-id "${{ github.run_id }}" \
--workflow "post-merge/release" \ --workflow "post-merge/release" \
--commit "${{ github.sha }}" --commit "${{ github.sha }}"
publish:
needs: [release]
if: needs.release.outputs.tag != ''
runs-on: docker
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up environment
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
run: make setup-release
- name: Build and publish release
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.publish \
"${{ needs.release.outputs.tag }}" \
"${{ github.repository }}"
- name: Notify on failure
if: failure()
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "post-merge/publish" \
--commit "${{ github.sha }}"
sync-wiki: sync-wiki:
needs: [detect-type, release] needs: [detect-type]
if: needs.detect-type.outputs.is-release == 'false' if: needs.detect-type.outputs.is-release == 'false'
runs-on: docker runs-on: docker
timeout-minutes: 10 timeout-minutes: 10
@@ -156,7 +191,6 @@ jobs:
PYTHONPATH: src PYTHONPATH: src
run: | run: |
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.tools.install_tools --tool tea
python3 -m devx.ci.notify_failure --auto-login \ python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \ --repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \ --run-id "${{ github.run_id }}" \
@@ -164,7 +198,7 @@ jobs:
--commit "${{ github.sha }}" --commit "${{ github.sha }}"
badges: badges:
needs: [detect-type, release] needs: [detect-type]
if: always() if: always()
runs-on: docker runs-on: docker
timeout-minutes: 10 timeout-minutes: 10
@@ -195,7 +229,6 @@ jobs:
PYTHONPATH: src PYTHONPATH: src
run: | run: |
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.tools.install_tools --tool tea
python3 -m devx.ci.notify_failure --auto-login \ python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \ --repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \ --run-id "${{ github.run_id }}" \
@@ -203,7 +236,7 @@ jobs:
--commit "${{ github.sha }}" --commit "${{ github.sha }}"
vikunja: vikunja:
needs: [detect-type, release] needs: [detect-type]
if: needs.detect-type.outputs.is-release == 'false' if: needs.detect-type.outputs.is-release == 'false'
runs-on: docker runs-on: docker
timeout-minutes: 10 timeout-minutes: 10
@@ -211,17 +244,19 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install dependencies - name: Set up environment
run: | env:
. .env 2>/dev/null || true REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" run: make setup-ci
- name: Update Vikunja task - name: Update Vikunja task
env: env:
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }} VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
PYTHONPATH: src PYTHONPATH: src
DEVX_TASK_PREFIX: GRM DEVX_TASK_PREFIX: GRM
DEVX_VIKUNJA_PROJECT_ID: 6 DEVX_VIKUNJA_PROJECT_ID: 6
run: python3 -m devx.ci.post_merge --git-sha "${{ github.sha }}" run: |
. .venv/bin/activate
python3 -m devx.ci.post_merge --git-sha "${{ github.sha }}"
- name: Notify on failure - name: Notify on failure
if: failure() if: failure()
env: env:
@@ -229,7 +264,6 @@ jobs:
PYTHONPATH: src PYTHONPATH: src
run: | run: |
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.tools.install_tools --tool tea
python3 -m devx.ci.notify_failure --auto-login \ python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \ --repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \ --run-id "${{ github.run_id }}" \
@@ -243,10 +277,10 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install dependencies - name: Set up environment
run: | env:
. .env 2>/dev/null || true REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
python3 -m pip install --break-system-packages --target=src "devx==0.14.1" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" run: make setup-ci
- name: Ensure branch protection and labels - name: Ensure branch protection and labels
env: env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }} REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
@@ -254,7 +288,9 @@ jobs:
DEVX_REPO_NAME: grm DEVX_REPO_NAME: grm
DEVX_REPO_OWNER: oblachno-oss DEVX_REPO_OWNER: oblachno-oss
DEVX_STATUS_CHECKS: "CI / quality (pull_request),CI / molecule-tests (1) (pull_request),CI / molecule-tests (2) (pull_request),CI / molecule-tests (3) (pull_request)" DEVX_STATUS_CHECKS: "CI / quality (pull_request),CI / molecule-tests (1) (pull_request),CI / molecule-tests (2) (pull_request),CI / molecule-tests (3) (pull_request)"
run: python3 -m devx.tools.configure_repo run: |
. .venv/bin/activate
python3 -m devx.tools.configure_repo
- name: Notify on failure - name: Notify on failure
if: failure() if: failure()
env: env:
@@ -262,7 +298,6 @@ jobs:
PYTHONPATH: src PYTHONPATH: src
run: | run: |
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.tools.install_tools --tool tea
python3 -m devx.ci.notify_failure --auto-login \ python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \ --repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \ --run-id "${{ github.run_id }}" \
-56
View File
@@ -1,56 +0,0 @@
name: Publish Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to publish (e.g. v0.7.0)'
required: true
type: string
jobs:
publish:
runs-on: docker
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install CI tools
run: |
. .env 2>/dev/null || true
python3 -m pip install --break-system-packages --target=src "devx==0.12.0" --extra-index-url "https://emil:${{ secrets.REPO_TOKEN }}@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"
python3 -m devx.tools.install_tools --tool git-cliff --tool tea
- name: Install build tools
run: python3 -m pip install --break-system-packages build twine
- name: Configure tea login
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
run: |
export PATH="$HOME/.local/bin:$PATH"
tea login add --name grm --url "${{ github.server_url }}" --token "$REPO_TOKEN" || true
tea login default grm || true
- name: Build and publish release
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
run: |
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.publish \
"${{ github.event.inputs.tag || github.ref_name }}" \
"${{ github.repository }}"
- name: Notify on failure
if: failure()
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
run: |
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure --auto-login \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "publish" \
--commit "${{ github.sha }}"
+16 -11
View File
@@ -221,17 +221,22 @@ Vikunja task updates:
- `--skip-tests` flag bypasses test verification (emergency use only, not recommended) - `--skip-tests` flag bypasses test verification (emergency use only, not recommended)
- Loops are prevented by `has_unreleased_changes` — after a release commit is tagged, the next run finds no unreleased changes and exits - Loops are prevented by `has_unreleased_changes` — after a release commit is tagged, the next run finds no unreleased changes and exits
3. **sync-wiki** — Syncs documentation to the Gitea wiki. 3. **sync-wiki** — Syncs documentation to the Gitea wiki. Runs for ALL
non-release commits (not just when release succeeds), so docs-only
changes still update the wiki.
4. **badges** — Generates and pushes quality badge SVGs to the `badges` branch. 4. **badges** — Generates and pushes quality badge SVGs to the `badges` branch.
Runs **after** the release job (even if release fails or is skipped) so the Uses `if: always()` so it runs on every push, including release commits.
version badge always reflects the latest state. The script fetches the The script fetches the latest master before generating badges to pick up
latest master before generating badges to pick up any release commits. any release commits.
5. **vikunja** — Marks the corresponding Vikunja task as done. 5. **vikunja** — Marks the corresponding Vikunja task as done. Runs for ALL
non-release commits (not just when release succeeds), so infrastructure-only
changes still update the task tracker.
The tag push triggers the **publish workflow** (`.gitea/workflows/publish.yml`) 6. **publish** — Runs after release succeeds (needs: release). Builds and
which builds and publishes the package to PyPI. publishes the package to the Gitea PyPI registry. Gets the tag from the
release job's `tag` output.
### Smart CI: User-Facing vs Workflow-Only Changes ### Smart CI: User-Facing vs Workflow-Only Changes
@@ -352,11 +357,11 @@ platform matrix. Both `devx.molecule.distribute_molecule` (CI) and
`devx.molecule.molecule_all` (dev tool) import `PLATFORMS` from it — this `devx.molecule.molecule_all` (dev tool) import `PLATFORMS` from it — this
avoids dev tools importing directly from CI modules. avoids dev tools importing directly from CI modules.
2. **Publish workflow** (`.gitea/workflows/publish.yml`): 2. **Publish job** (in `post-merge.yml`, needs: release):
- Triggers on tag push (`v*`) - Runs after the release job creates a tag
- Validates `PYPI_TOKEN` is set (warns if missing) - Gets the tag from `needs.release.outputs.tag`
- Builds the Python package - Builds the Python package
- Optionally publishes to PyPI (if `PYPI_TOKEN` is set) - Publishes to the Gitea PyPI registry
- Creates a Gitea release with git-cliff-generated release notes - Creates a Gitea release with git-cliff-generated release notes
- On failure, creates a Gitea issue via `devx.ci.notify_failure` - On failure, creates a Gitea issue via `devx.ci.notify_failure`
+11
View File
@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.10.1] - 2026-06-27
### Bug Fixes
- Set PYTHONPATH=src in publish Install CI tools step
### Refactor
- Replace duplicated Makefile targets with devx.mak aliases
- Consolidate publish.yml into post-merge.yml
## [0.10.0] - 2026-06-26 ## [0.10.0] - 2026-06-26
### Features ### Features
+49 -85
View File
@@ -1,4 +1,4 @@
.PHONY: all setup setup-ci setup-quality setup-molecule setup-release install update lint ansible-lint makefile-lint lint-all test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools .PHONY: all setup setup-ci setup-quality setup-molecule setup-release install update lint ansible-lint makefile-lint lint-all lint-ruff lint-format lint-bandit lint-deps typecheck checkmake install-hooks test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools
.PHONY: configure-gitea-pypi .PHONY: configure-gitea-pypi
.PHONY: create-task create-pr push-with-pr git-push .PHONY: create-task create-pr push-with-pr git-push
@@ -9,20 +9,21 @@ CHECKMAKE := $(shell command -v checkmake 2>/dev/null || echo $(HOME)/go/bin/che
all: setup all: setup
# Helper: run pip install with Gitea registry configured # --- devx.mak include (shared Makefile targets) -------------------------------
# Usage: $(PIP_INSTALL) install -e '.[ci,lint]' # Set DEVX_PYTHON before including devx.mak so it uses the venv Python.
PIP_INSTALL := if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \ DEVX_PYTHON := $(BIN)/python
REPO_TOKEN="$${REPO_TOKEN:-$$GITEA_REGISTRY_TOKEN}"; \ DEVX_VENV := $(VENV)
if [ -n "$$REPO_TOKEN" ]; then export PIP_EXTRA_INDEX_URL="https://emil:$$REPO_TOKEN@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"; fi; \ DEVX_BIN := $(BIN)
$(BIN)/pip DEVX_COV_PKG := src/gitea_runner_manager
# Configure Gitea private PyPI registry so pip can find devx and other # Include shared targets from devx package (create-task, create-pr, push-with-pr,
# private packages. In CI, REPO_TOKEN is set as a secret. Locally, it's in .env. # check-config, workflow-lint, lint-ruff, clean, venv, .env, activate-scripts,
configure-gitea-pypi: # install-hooks, install-tools, configure-gitea-pypi, checkmake, etc.)
@if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \ # Silent if devx not installed yet — run 'make setup' first.
REPO_TOKEN="$${REPO_TOKEN:-$$GITEA_REGISTRY_TOKEN}"; \ DEVX_MAK := $(shell $(BIN)/python -c \
if [ -z "$$REPO_TOKEN" ]; then echo "[configure-gitea-pypi] REPO_TOKEN not set — skipping (devx must be on public PyPI)"; exit 0; fi; \ "from pathlib import Path; import devx; print(Path(devx.__file__).parent / 'make' / 'devx.mak')" \
echo "[configure-gitea-pypi] Gitea PyPI registry configured (REPO_TOKEN present)." 2>/dev/null)
-include $(DEVX_MAK)
# Full setup for local development (all deps, tools, collections, hooks) # Full setup for local development (all deps, tools, collections, hooks)
# devx is installed via pip install -e .[dev] (devx is in dev extra) # devx is installed via pip install -e .[dev] (devx is in dev extra)
@@ -61,33 +62,30 @@ setup-release: $(VENV)/bin/activate .env configure-gitea-pypi
@export PATH="$(HOME)/.local/bin:$$PATH"; \ @export PATH="$(HOME)/.local/bin:$$PATH"; \
$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install --no-ansible-collections --no-pre-commit $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install --no-ansible-collections --no-pre-commit
.env: # Helper: run pip install with Gitea registry configured
@if [ ! -f .env ]; then \ # Usage: $(PIP_INSTALL) install -e '.[ci,lint]'
cp .env.example .env; \ PIP_INSTALL := if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
echo "Created .env from .env.example — please edit it with your credentials."; \ REPO_TOKEN="$${REPO_TOKEN:-$$GITEA_REGISTRY_TOKEN}"; \
fi _PYPI_USER="$${GITEA_REGISTRY_USERNAME:-emil}"; \
if [ -n "$$REPO_TOKEN" ]; then export PIP_EXTRA_INDEX_URL="https://$$_PYPI_USER:$$REPO_TOKEN@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"; fi; \
$(BIN)/pip
$(VENV)/bin/activate: $(VENV)/bin/activate:
@python3 -c "import sys; v=sys.version_info; assert v >= (3, 12), f'Python 3.12+ required, found {v.major}.{v.minor}'; print(f'Python {v.major}.{v.minor}.{v.micro} OK')" @python3 -c "import sys; v=sys.version_info; assert v >= (3, 12), f'Python 3.12+ required, found {v.major}.{v.minor}'; print(f'Python {v.major}.{v.minor}.{v.micro} OK')"
$(PYTHON) -m venv $(VENV) $(PYTHON) -m venv $(VENV)
$(BIN)/pip install --upgrade pip setuptools wheel $(BIN)/pip install --upgrade pip setuptools wheel
.env:
@if [ ! -f .env ]; then \
cp .env.example .env; \
echo "Created .env from .env.example — please edit it with your credentials."; \
fi
activate-scripts: $(VENV)/bin/activate activate-scripts: $(VENV)/bin/activate
@test -f activate.sh || (echo '#!/usr/bin/env bash' > activate.sh && echo 'source "$$(cd "$$(dirname "$${BASH_SOURCE[0]}")" && pwd)/.venv/bin/activate"' >> activate.sh && chmod +x activate.sh) @test -f activate.sh || (echo '#!/usr/bin/env bash' > activate.sh && echo 'source "$$(cd "$$(dirname "$${BASH_SOURCE[0]}")" && pwd)/.venv/bin/activate"' >> activate.sh && chmod +x activate.sh)
@test -f activate.fish || (echo '#!/usr/bin/env fish' > activate.fish && echo 'set -l script_dir (dirname (status --current-filename))' >> activate.fish && echo 'source "$$script_dir/.venv/bin/activate.fish"' >> activate.fish && chmod +x activate.fish) @test -f activate.fish || (echo '#!/usr/bin/env fish' > activate.fish && echo 'set -l script_dir (dirname (status --current-filename))' >> activate.fish && echo 'source "$$script_dir/.venv/bin/activate.fish"' >> activate.fish && chmod +x activate.fish)
@test -f activate.zsh || (echo '#!/usr/bin/env zsh' > activate.zsh && echo '0="$${ZERO:-$${0:#$$ZSH_ARGZERO}}"' >> activate.zsh && echo '0="$${$${(M)0:#/*}:-$$PWD/$$0}"' >> activate.zsh && echo 'source "$${0:A:h}/.venv/bin/activate"' >> activate.zsh && chmod +x activate.zsh) @test -f activate.zsh || (echo '#!/usr/bin/env zsh' > activate.zsh && echo '0="$${ZERO:-$${0:#$$ZSH_ARGZERO}}"' >> activate.zsh && echo '0="$${$${(M)0:#/*}:-$$PWD/$$0}"' >> activate.zsh && echo 'source "$${0:A:h}/.venv/bin/activate"' >> activate.zsh && chmod +x activate.zsh)
install-hooks:
@git config core.hooksPath hooks
@chmod +x hooks/pre-commit hooks/pre-push 2>/dev/null || true
@echo "core.hooksPath set to hooks/ — tracked hooks are now live."
checkmake:
@$(BIN)/python -m devx.tools.install_checkmake
install-tools:
@$(BIN)/python -m devx.tools.install_tools
install: install:
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make install HOST=192.168.1.10"; exit 1; fi @if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make install HOST=192.168.1.10"; exit 1; fi
$(BIN)/grm install $(HOST) $(if $(USER),--user $(USER),) $(if $(KEY),--key $(KEY),) $(if $(NAME),--name $(NAME),) $(if $(TOKEN),--token $(TOKEN),) $(if $(ASK_BECOME_PASS),--ask-become-pass,) $(BIN)/grm install $(HOST) $(if $(USER),--user $(USER),) $(if $(KEY),--key $(KEY),) $(if $(NAME),--name $(NAME),) $(if $(TOKEN),--token $(TOKEN),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
@@ -120,25 +118,28 @@ remove:
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make remove HOST=192.168.1.10"; exit 1; fi @if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make remove HOST=192.168.1.10"; exit 1; fi
$(BIN)/grm remove $(NAME) $(if $(HOST),--host $(HOST),) $(if $(USER),--user $(USER),) $(if $(TOKEN),--token $(TOKEN),) $(if $(ASK_BECOME_PASS),--ask-become-pass,) $(BIN)/grm remove $(NAME) $(if $(HOST),--host $(HOST),) $(if $(USER),--user $(USER),) $(if $(TOKEN),--token $(TOKEN),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
lint-ruff: # --- Aliases to devx.mak targets ----------------------------------------------
$(BIN)/ruff check src/ tests/ lint-ruff: devx-lint-ruff
lint-format: devx-lint-format
typecheck: devx-typecheck
lint-bandit: devx-lint-bandit
lint-deps: devx-lint-deps
lint: devx-lint
checkmake: devx-checkmake
install-tools: devx-install-tools
install-hooks: devx-install-hooks
clean: devx-clean
test-unit: devx-test-unit
pytest-cov: devx-pytest-cov
workflow-lint: devx-workflow-lint
workflow-dryrun: devx-workflow-dryrun
workflow-check: devx-workflow-check
lint-format: configure-gitea-pypi:
$(BIN)/ruff format --check src/ tests/ @if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
REPO_TOKEN="$${REPO_TOKEN:-$$GITEA_REGISTRY_TOKEN}"; \
typecheck: if [ -z "$$REPO_TOKEN" ]; then echo "[configure-gitea-pypi] REPO_TOKEN not set — skipping (devx must be on public PyPI)"; exit 0; fi; \
$(BIN)/pyright echo "[configure-gitea-pypi] Gitea PyPI registry configured (REPO_TOKEN present)."
lint: lint-ruff lint-format typecheck lint-bandit
lint-bandit:
$(BIN)/bandit -r src/
lint-deps:
@echo "Checking dependencies for known vulnerabilities..."
@.venv/bin/python -m ensurepip 2>/dev/null || true
@PIPAPI_PYTHON_LOCATION=$$(pwd)/.venv/bin/python \
.venv/bin/pip-audit --desc --skip-editable 2>&1 || true
ansible-lint: ansible-lint:
PATH="$(PWD)/$(BIN):$$PATH" $(BIN)/ansible-lint ansible/ PATH="$(PWD)/$(BIN):$$PATH" $(BIN)/ansible-lint ansible/
@@ -152,30 +153,9 @@ makefile-lint:
lint-all: lint ansible-lint makefile-lint workflow-lint lint-all: lint ansible-lint makefile-lint workflow-lint
workflow-lint:
@command -v actionlint >/dev/null 2>&1 || { \
echo "actionlint not found. Install: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)"; \
exit 1; \
}
actionlint -config-file .gitea/actionlint.yaml .gitea/workflows/*.yml
workflow-dryrun:
@command -v act_runner >/dev/null 2>&1 || { echo "act_runner not found. Install: https://gitea.com/gitea/act_runner/releases"; exit 1; }
@echo "Dry-running all workflows (no Docker containers started)..."
act_runner exec --dryrun -W .gitea/workflows/ 2>&1 | grep -E 'DRYRUN|ERROR|FAIL|Job'
workflow-check: workflow-lint workflow-dryrun
@echo "Workflow checks passed (static lint + dry-run)."
test-unit:
$(BIN)/pytest tests/unit/ -v --no-cov
test-integration: test-integration:
$(BIN)/pytest tests/integration/ -v --no-cov $(BIN)/pytest tests/integration/ -v --no-cov
pytest-cov:
$(BIN)/pytest tests/ -v --cov=src/gitea_runner_manager --cov-report=term-missing --cov-fail-under=100
MOLECULE := $(realpath $(BIN))/molecule MOLECULE := $(realpath $(BIN))/molecule
MOLECULE_BASE := cd $(CURDIR)/ansible/roles/gitea-runner && ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true ANSIBLE_INJECT_INVOCATION=1 $(MOLECULE) MOLECULE_BASE := cd $(CURDIR)/ansible/roles/gitea-runner && ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true ANSIBLE_INJECT_INVOCATION=1 $(MOLECULE)
@@ -191,23 +171,7 @@ test: test-all
test-all: pytest-cov molecule test-all: pytest-cov molecule
clean:
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
find . -type f -name "*.pyc" -delete 2>/dev/null || true
rm -rf .coverage htmlcov/ .molecule/
# --- Vikunja task and PR management (via devx.mak fragment) ------------------- # --- Vikunja task and PR management (via devx.mak fragment) -------------------
# Project config (task prefix, Vikunja project ID, repo owner/name) is read
# from [tool.devx] in pyproject.toml by devx.config — no Makefile variables needed.
DEVX_PYTHON := $(BIN)/python
# Include shared targets from devx package (create-task, create-pr, push-with-pr, check-config)
# Silent if devx not installed yet — run 'make setup' first.
DEVX_MAK := $(shell $(BIN)/python -c \
"from pathlib import Path; import devx; print(Path(devx.__file__).parent / 'make' / 'devx.mak')" \
2>/dev/null)
-include $(DEVX_MAK)
# Aliases for project-specific target names # Aliases for project-specific target names
create-task: devx-create-task create-task: devx-create-task
create-pr: devx-create-pr create-pr: devx-create-pr
+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) [![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) [![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/47c7e239b2bb0f47217277a6498e53069aab94f3/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions) [![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions) [![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki) [![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions) [![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases) [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/python.svg)](https://www.python.org/downloads/) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/python.svg)](https://www.python.org/downloads/)
## Why GRM? ## Why GRM?
+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) [![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) [![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/47c7e239b2bb0f47217277a6498e53069aab94f3/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions) [![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions) [![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki) [![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions) [![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases) [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/47c7e239b2bb0f47217277a6498e53069aab94f3/python.svg)](https://www.python.org/downloads/) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/raw/commit/182f11ca856746999110f1c3fbfee87fbf000b8a/python.svg)](https://www.python.org/downloads/)
## Overview ## Overview
+12
View File
@@ -102,6 +102,18 @@ vikunja_project_id = 6
repo_owner = "oblachno-oss" repo_owner = "oblachno-oss"
repo_name = "gitea-runner-manager" repo_name = "gitea-runner-manager"
# Molecule test weights for LPT scheduling.
# GRM has a single role (gitea-runner) with 7 scenarios.
# Weights are estimates — recalibrate from CI logs after next run.
[tool.devx.molecule.weights]
"multi-instance" = 8
"lifecycle" = 6
"update" = 5
"default" = 4
"deregister" = 3
"remove" = 3
"template-content" = 2
# lint config, etc.) that applies to any Python project. We only specify # lint config, etc.) that applies to any Python project. We only specify
# what's different about GRM. # what's different about GRM.
[tool.devx.classify] [tool.devx.classify]
+1 -1
View File
@@ -1,3 +1,3 @@
"""Gitea Runner Manager — lean CLI for managing Gitea Actions runners.""" """Gitea Runner Manager — lean CLI for managing Gitea Actions runners."""
__version__ = "0.10.0" __version__ = "0.10.1"