From d072390e56804568f1ce97742c9035ee350b2933 Mon Sep 17 00:00:00 2001 From: MrSphay Date: Sat, 16 May 2026 21:22:36 +0200 Subject: [PATCH] ci: align gitea workflows with agent kit --- .gitea/workflows/build.yml | 22 ++- .gitea/workflows/dependency-check.yml | 114 +++++++++++++++ .gitea/workflows/release-dry-run.yml | 133 +++++++++++++++++ .gitea/workflows/repo-cleanup.yml | 139 ++++++++++++++++++ .gitea/workflows/security-scan.yml | 174 +++++++++++++++++++++++ .gitea/workflows/template-compliance.yml | 109 ++++++++++++++ README.md | 2 + 7 files changed, 687 insertions(+), 6 deletions(-) create mode 100644 .gitea/workflows/dependency-check.yml create mode 100644 .gitea/workflows/release-dry-run.yml create mode 100644 .gitea/workflows/repo-cleanup.yml create mode 100644 .gitea/workflows/security-scan.yml create mode 100644 .gitea/workflows/template-compliance.yml diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 188020f..441f29d 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -11,6 +11,11 @@ on: jobs: test-and-image: runs-on: ubuntu-latest + env: + REGISTRY_HOST: git.wilkensxl.de + REGISTRY_NAMESPACE: mrsphay + IMAGE_NAME: intelligence-terminal + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} steps: - name: Checkout uses: actions/checkout@v4 @@ -31,12 +36,17 @@ jobs: run: docker compose config - name: Build Docker image - run: docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:${{ github.sha }} . + run: docker build -t "${REGISTRY_HOST}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}:${GITHUB_SHA}" . - name: Publish Docker image - if: ${{ secrets.REGISTRY_TOKEN != '' }} + if: ${{ env.REGISTRY_TOKEN != '' }} + shell: bash run: | - echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.wilkensxl.de -u "${{ github.repository_owner }}" --password-stdin - docker tag git.wilkensxl.de/mrsphay/intelligence-terminal:${{ github.sha }} git.wilkensxl.de/mrsphay/intelligence-terminal:latest - docker push git.wilkensxl.de/mrsphay/intelligence-terminal:${{ github.sha }} - docker push git.wilkensxl.de/mrsphay/intelligence-terminal:latest + image="${REGISTRY_HOST}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}" + date_tag="$(date -u +%Y%m%d)" + echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY_HOST}" -u "${REGISTRY_NAMESPACE}" --password-stdin + docker tag "${image}:${GITHUB_SHA}" "${image}:latest" + docker tag "${image}:${GITHUB_SHA}" "${image}:${date_tag}" + docker push "${image}:${GITHUB_SHA}" + docker push "${image}:latest" + docker push "${image}:${date_tag}" diff --git a/.gitea/workflows/dependency-check.yml b/.gitea/workflows/dependency-check.yml new file mode 100644 index 0000000..f601304 --- /dev/null +++ b/.gitea/workflows/dependency-check.yml @@ -0,0 +1,114 @@ +name: Scheduled Dependency Check + +on: + schedule: + - cron: "29 3 * * 2" + workflow_dispatch: + +jobs: + dependency-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + { [ -f Dockerfile ] || [ -f compose.yml ] || [ -f docker-compose.yml ]; } && stacks="${stacks} docker" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node dependency report + if: contains(steps.detect.outputs.stacks, 'node') + shell: bash + run: | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + npm ci + else + npm install --package-lock-only --ignore-scripts + fi + + echo "Security audit:" + npm audit --omit=dev --audit-level=high + + echo + echo "Outdated dependencies:" + npm outdated || true + + - name: Python dependency report + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip pip-audit + + echo "Security audit:" + if [ -f requirements.txt ]; then + pip-audit -r requirements.txt + else + pip-audit + fi + + echo + echo "Outdated packages:" + python -m pip list --outdated || true + + - name: Rust dependency report + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo install cargo-audit cargo-outdated --locked + + echo "Security audit:" + cargo audit + + echo + echo "Outdated crates:" + cargo outdated || true + + - name: Go dependency report + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + + echo "Security audit:" + govulncheck ./... + + echo + echo "Available dependency updates:" + go list -u -m all || true + + - name: Docker base image report + if: contains(steps.detect.outputs.stacks, 'docker') + shell: bash + run: | + echo "Docker image references:" + grep -RInE --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build '^\s*FROM\s+' Dockerfile* . 2>/dev/null || true + + echo + echo "Review Docker base images manually for pinned versions, official sources, and current security status." + + - name: Dependency guidance + shell: bash + run: | + cat <<'EOF' + Dependency check completed. + + This workflow reports vulnerabilities and available updates. It does + not modify dependency files, create pull requests, or publish packages. + + Recommended manual follow-up: + - update dependencies in a focused branch, + - run the project test/build commands, + - review lockfile diffs carefully, + - document intentionally held versions. + EOF diff --git a/.gitea/workflows/release-dry-run.yml b/.gitea/workflows/release-dry-run.yml new file mode 100644 index 0000000..894cb86 --- /dev/null +++ b/.gitea/workflows/release-dry-run.yml @@ -0,0 +1,133 @@ +name: Release Dry Run + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + release-dry-run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Inspect release metadata + shell: bash + run: | + missing=0 + + required_docs=( + "README.md" + "CHANGELOG.md" + "SECURITY.md" + "docs/release-checklist.md" + ) + + for file in "${required_docs[@]}"; do + if [ ! -f "$file" ]; then + echo "Missing release document: $file" + missing=1 + fi + done + + placeholder_paths=(README.md AGENTS.md .codex docs .gitea) + placeholder_pattern='PROJECT_NAME|PROJECT_DESCRIPTION|REPOSITORY_OWNER|REPOSITORY_NAME|PACKAGE_NAME|ARTIFACT_NAME|ARTIFACT_OUTPUT_DIRECTORY|DOWNLOAD_URL|CI_URL|RELEASES_URL|BUILD_COMMAND|TEST_COMMAND|LINT_COMMAND|AUDIT_COMMAND|COMMIT_OR_VERSION' + + for path in "${placeholder_paths[@]}"; do + [ -e "$path" ] || continue + if grep -RInE --exclude-dir=.git "$placeholder_pattern" "$path"; then + echo "Unresolved template placeholders found." + missing=1 + fi + done + + if [ "$missing" -eq 1 ]; then + exit 1 + fi + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node release checks + if: contains(steps.detect.outputs.stacks, 'node') + shell: bash + run: | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + npm ci + else + npm install + fi + + node -e "const p=require('./package.json'); if(!p.name||!p.version){throw new Error('package.json needs name and version')}; console.log(p.name+'@'+p.version)" + + npm run lint --if-present + npm test --if-present + npm run build --if-present + npm run release:check --if-present + + - name: Python release checks + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip + + if [ -f requirements.txt ]; then + python -m pip install -r requirements.txt + fi + + if [ -f pyproject.toml ]; then + python -m pip install build + python -m build + else + echo "No pyproject.toml found; skipped Python package build." + fi + + - name: Rust release checks + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo test + cargo build --release + + - name: Go release checks + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go test ./... + go build ./... + + - name: Artifact report + shell: bash + run: | + echo "Potential release artifacts:" + find . \ + -path ./.git -prune -o \ + -path ./node_modules -prune -o \ + -path './dist/*' -type f -print -o \ + -path './build/*' -type f -print -o \ + -path './release/*' -type f -print -o \ + -path './target/release/*' -type f -print \ + | sed 's#^\./##' \ + | head -200 + + cat <<'EOF' + + Release dry run completed. + + This workflow verifies release readiness. It does not create tags, + releases, packages, or upload artifacts. + EOF diff --git a/.gitea/workflows/repo-cleanup.yml b/.gitea/workflows/repo-cleanup.yml new file mode 100644 index 0000000..a4b7156 --- /dev/null +++ b/.gitea/workflows/repo-cleanup.yml @@ -0,0 +1,139 @@ +name: Scheduled Repository Cleanup Check + +on: + schedule: + - cron: "43 3 * * 1" + workflow_dispatch: + +jobs: + cleanup-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check ignored and untracked generated files + shell: bash + run: | + echo "Ignored files that would be skipped by git:" + git status --ignored --short || true + + echo + echo "Tracked generated files check:" + generated_patterns=( + '(^|/)node_modules/' + '(^|/)dist/' + '(^|/)build/' + '(^|/)out/' + '(^|/)release/' + '(^|/)target/' + '(^|/)coverage/' + '\.log$' + '\.tmp$' + '\.temp$' + ) + + found=0 + tracked_files="$(git ls-files)" + for pattern in "${generated_patterns[@]}"; do + if echo "$tracked_files" | grep -Ei "$pattern"; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Generated files appear to be tracked. Review .gitignore and remove generated outputs from version control if appropriate." + exit 1 + fi + + - name: Check large tracked files + shell: bash + run: | + limit_bytes="${LARGE_FILE_LIMIT_BYTES:-5242880}" + found=0 + + while IFS= read -r file; do + [ -f "$file" ] || continue + size="$(wc -c < "$file")" + if [ "$size" -gt "$limit_bytes" ]; then + echo "${file} is ${size} bytes, above limit ${limit_bytes}." + found=1 + fi + done < <(git ls-files) + + if [ "$found" -eq 1 ]; then + echo "Large tracked files found. Move release artifacts to packages/releases or document why they belong in git." + exit 1 + fi + + - name: Check local config and secret-prone files + shell: bash + run: | + found=0 + + risky_patterns=( + '^\.env$' + '^\.env\.' + '\.pfx$' + '\.p12$' + '\.pem$' + '\.key$' + '\.token$' + '(^|/)secrets/' + ) + + tracked_files="$(git ls-files)" + for pattern in "${risky_patterns[@]}"; do + if echo "$tracked_files" | grep -Ei "$pattern" | grep -vE '^\.env\.example$'; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Secret-prone local config files are tracked. Review immediately." + exit 1 + fi + + - name: Check stale branches + shell: bash + run: | + git fetch --all --prune + + protected='^(main|master|develop|dev|release|staging|production)$' + cutoff="$(date -u -d '90 days ago' +%s)" + found=0 + + while IFS='|' read -r branch timestamp; do + branch="${branch#origin/}" + [ "$branch" = "HEAD" ] && continue + echo "$branch" | grep -Eq "$protected" && continue + + if [ "$timestamp" -lt "$cutoff" ]; then + echo "Stale remote branch candidate: ${branch}" + found=1 + fi + done < <(git for-each-ref refs/remotes/origin --format='%(refname:short)|%(committerdate:unix)') + + if [ "$found" -eq 1 ]; then + echo "Stale branch candidates found. Review manually before deleting anything." + exit 1 + fi + + - name: Cleanup guidance + shell: bash + run: | + cat <<'EOF' + Repository cleanup check completed. + + This workflow reports cleanup candidates. It does not delete branches, + packages, releases, or files automatically. + + Recommended manual follow-up: + - remove generated files from git, + - update .gitignore, + - move large artifacts to releases or package registry, + - review stale branches, + - document intentional exceptions. + EOF diff --git a/.gitea/workflows/security-scan.yml b/.gitea/workflows/security-scan.yml new file mode 100644 index 0000000..d514dcc --- /dev/null +++ b/.gitea/workflows/security-scan.yml @@ -0,0 +1,174 @@ +name: Scheduled Security Scan + +on: + schedule: + - cron: "17 3 * * 1" + workflow_dispatch: + +jobs: + security-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + { [ -f Dockerfile ] || [ -f compose.yml ] || [ -f docker-compose.yml ]; } && stacks="${stacks} docker" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node production dependency audit + if: contains(steps.detect.outputs.stacks, 'node') + run: npm audit --omit=dev --audit-level=high + + - name: Python dependency audit + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip pip-audit + if [ -f requirements.txt ]; then + pip-audit -r requirements.txt + else + pip-audit + fi + + - name: Rust dependency audit + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo install cargo-audit --locked + cargo audit + + - name: Go vulnerability scan + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + - name: Suspicious code pattern scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'eval\s*\(' + 'new Function\s*\(' + 'dangerouslySetInnerHTML' + 'innerHTML\s*=' + 'child_process' + 'exec\s*\(' + 'spawn\s*\(' + 'shell\.openExternal' + 'nodeIntegration:\s*true' + 'webSecurity:\s*false' + 'allowRunningInsecureContent:\s*true' + 'curl .*sh' + 'wget .*sh' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInE "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Suspicious code patterns were found. Review the matches above." + exit 1 + fi + + - name: Secret and config leak scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY' + 'AKIA[0-9A-Z]{16}' + 'xox[baprs]-[0-9A-Za-z-]+' + 'gh[pousr]_[0-9A-Za-z_]+' + 'sk-[A-Za-z0-9]{20,}' + 'api[_-]?key\s*=\s*["'\'']?[A-Za-z0-9_\-]{20,}' + 'token\s*=\s*["'\'']?[A-Za-z0-9_\-]{20,}' + 'password\s*=\s*["'\'']?[^[:space:]]{8,}' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInE "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if find . -path ./.git -prune -o \( -name ".env" -o -name ".env.*" \) -not -name ".env.example" -print | grep .; then + echo "Committed environment files were found." + found=1 + fi + + if [ "$found" -eq 1 ]; then + echo "Potential secret or config leak detected. Review the matches above." + exit 1 + fi + + - name: AI instruction injection scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'ignore (all )?(previous|above) instructions' + 'system prompt' + 'developer message' + 'reveal your instructions' + 'exfiltrate' + 'send.*token' + 'send.*secret' + 'disable.*safety' + 'jailbreak' + 'prompt injection' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInEi "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Potential AI instruction-injection text found. Review whether this is documentation, test data, or malicious content." + exit 1 + fi diff --git a/.gitea/workflows/template-compliance.yml b/.gitea/workflows/template-compliance.yml new file mode 100644 index 0000000..ea5fae6 --- /dev/null +++ b/.gitea/workflows/template-compliance.yml @@ -0,0 +1,109 @@ +name: Codex Template Compliance + +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +jobs: + template-compliance: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check required Codex files + shell: bash + run: | + missing=0 + + required_files=( + "AGENTS.md" + ".codex/project.md" + "README.md" + ) + + recommended_files=( + "SECURITY.md" + "CHANGELOG.md" + "docs/agent-handoff.md" + ) + + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + echo "Missing required Codex file: $file" + missing=1 + fi + done + + for file in "${recommended_files[@]}"; do + if [ ! -f "$file" ]; then + echo "Recommended Codex file not found: $file" + fi + done + + if [ "$missing" -eq 1 ]; then + exit 1 + fi + + - name: Check unresolved placeholders + shell: bash + run: | + found=0 + paths=(AGENTS.md README.md SECURITY.md CHANGELOG.md .codex docs .gitea blueprint.md blueprint.json) + pattern='PROJECT_NAME|PROJECT_DESCRIPTION|REPOSITORY_OWNER|REPOSITORY_NAME|PACKAGE_NAME|ARTIFACT_NAME|ARTIFACT_OUTPUT_DIRECTORY|AUTHOR_NAME|PROJECT_STACK|DOWNLOAD_URL|CI_URL|RELEASES_URL|BUILD_COMMAND|TEST_COMMAND|LINT_COMMAND|AUDIT_COMMAND|README_COMMAND|INSTALL_COMMAND|DEV_COMMAND|PACKAGE_MANAGER|PROJECT_VERSION|COMMIT_OR_VERSION' + + for path in "${paths[@]}"; do + [ -e "$path" ] || continue + if grep -RInE --exclude-dir=.git "$pattern" "$path"; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Unresolved template placeholders found. Replace real values or mark genuinely unknown values as PENDING." + exit 1 + fi + + - name: Check README divider convention + shell: bash + run: | + if [ -f blueprint.md ] || [ -f blueprint.json ]; then + if ! grep -q 'template:section-line' blueprint.md 2>/dev/null; then + echo "README blueprint exists but does not use {{ template:section-line }}." + exit 1 + fi + fi + + - name: Check workflow baseline + shell: bash + run: | + echo "Detected Gitea workflows:" + find .gitea/workflows -maxdepth 1 -type f -name '*.yml' -print 2>/dev/null || true + + if [ ! -f ".gitea/workflows/security-scan.yml" ]; then + echo "Recommended workflow missing: .gitea/workflows/security-scan.yml" + fi + + if [ ! -f ".gitea/workflows/repo-cleanup.yml" ]; then + echo "Recommended workflow missing: .gitea/workflows/repo-cleanup.yml" + fi + + - name: Compliance guidance + shell: bash + run: | + cat <<'EOF' + Codex template compliance check completed. + + This workflow verifies agent context and template hygiene. It does + not change files automatically. + + Recommended manual follow-up: + - add missing required Codex context files, + - replace unresolved placeholders, + - keep README blueprint and README output aligned, + - document intentional exceptions in .codex/project.md. + EOF diff --git a/README.md b/README.md index 16d5db8..8b4d102 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,8 @@ docker push git.wilkensxl.de/mrsphay/intelligence-terminal:latest docker push git.wilkensxl.de/mrsphay/intelligence-terminal:20260516 ``` +Gitea Actions publishes the same image automatically when the repository secret `REGISTRY_TOKEN` is set with package read/write permissions. The workflow tags images as `latest`, the commit SHA, and a UTC `YYYYMMDD` release tag. + --- ## What You Get