7 Commits

Author SHA1 Message Date
7e4177c6c0 Merge pull request 'ci(docs): auto-sync PROJECT_TREE.md to the docs repo on push to main' (#16) from chore/sync-project-tree-ci into main
All checks were successful
Release sidecar / release (push) Successful in 6s
sync-project-tree / sync (push) Successful in 13s
SonarQube / analysis (push) Successful in 47s
Reviewed-on: #16
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-22 21:20:43 +00:00
ecdaf9171b ci(docs): auto-sync PROJECT_TREE.md to the docs repo on push to main
Add a sync-project-tree workflow that regenerates this repo's tracked-file
tree and opens (or force-updates) a PR against RunicGateway/docs whenever the
layout on main changes. Never writes to the docs repo's main directly. Reuses
the existing REGISTRY_USER / REGISTRY_TOKEN secrets. Tree rendering lives in
.gitea/scripts/gen_tree.py (deterministic, dirs-first ordering).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 16:19:43 -05:00
f23b9030ed Merge pull request 'ci(sonarqube): correct project key to Runic-Gateway-link' (#15) from ci/sonarqube-fix-project-key into main
All checks were successful
Release sidecar / release (push) Successful in 8s
SonarQube / analysis (push) Successful in 2m9s
Reviewed-on: #15
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-21 04:23:45 +00:00
31497c38b2 ci(sonarqube): correct project key to Runic-Gateway-link
The SonarQube server already has a project keyed Runic-Gateway-link and
refuses to create a case-variant duplicate, so the scan (added in #13)
failed to auto-create runic-gateway-link. Match the existing key.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 23:22:58 -05:00
21b9920f80 Merge pull request 'ci(sonarqube): add non-blocking SonarQube analysis on push to main' (#13) from ci/sonarqube-analysis into main
Some checks failed
Release sidecar / release (push) Successful in 15s
SonarQube / analysis (push) Failing after 55s
Reviewed-on: #13
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-21 04:15:14 +00:00
7224b9b834 ci(sonarqube): add non-blocking SonarQube analysis on push to main
Mirrors the website repo's setup: a source-based scan of sidecar/src that
reports to the self-hosted SonarQube server after merge, never gating PRs.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 23:13:28 -05:00
45bb8b0de4 Merge pull request 'chore: add open-source governance files (GPLv3 + contributing docs)' (#12) from chore/open-source-governance into main
All checks were successful
Release sidecar / release (push) Successful in 24s
Reviewed-on: #12
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-19 00:30:33 +00:00
4 changed files with 242 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Render an ASCII tree of tracked files, read from stdin (one path per line).
Used by the `sync-project-tree` workflow to regenerate this repo's PROJECT_TREE.md
snapshot in the RunicGateway/docs repo. Feed it `git ls-files`:
git ls-files | python3 .gitea/scripts/gen_tree.py <root-label>
Deterministic ordering: directories before files, each group sorted
case-insensitively with the raw name as a tiebreak. Output uses the classic
`tree(1)` box-drawing style so the result is stable across runs and platforms.
"""
import sys
def build(paths):
root = {}
for p in paths:
p = p.strip().replace("\\", "/")
if not p:
continue
node = root
for part in p.split("/"):
node = node.setdefault(part, {})
return root
def render(node, prefix, lines):
entries = list(node.items())
# directories (non-empty children dict) before files, then case-insensitive name
entries.sort(key=lambda kv: (0 if kv[1] else 1, kv[0].lower(), kv[0]))
for i, (name, child) in enumerate(entries):
last = i == len(entries) - 1
branch = "└── " if last else "├── "
suffix = "/" if child else ""
lines.append(f"{prefix}{branch}{name}{suffix}")
if child:
render(child, prefix + (" " if last else ""), lines)
def main():
try:
sys.stdout.reconfigure(encoding="utf-8", newline="\n")
except AttributeError:
pass
root_label = sys.argv[1] if len(sys.argv) > 1 else "."
tree = build(sys.stdin.read().splitlines())
lines = [f"{root_label}/"]
render(tree, "", lines)
sys.stdout.write("\n".join(lines) + "\n")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,52 @@
# Run SonarQube static analysis against the code that just landed on `main` and
# report the results to the self-hosted SonarQube server for review. This is
# intentionally NON-BLOCKING: it triggers on push to main (i.e. AFTER merge),
# not on pull_request, so it never gates a PR. It complements release.yml
# (which builds + cuts releases) — this one only feeds the dashboard.
#
# Prerequisites (one-time, in the Gitea UI — Repo → Settings → Actions):
# • Secret SONAR_TOKEN — a SonarQube "Analysis" token generated at
# My Account → Security in SonarQube for the
# Runic-Gateway-link project (or a global one).
# • Variable SONAR_HOST_URL — the SonarQube base URL on your LAN, e.g.
# http://192.168.0.56:9000
# (kept as a variable, not committed, so the internal address stays out of git.)
#
# The runner (self-hosted `ubuntu-latest`, same as release.yml) must be able to
# reach SONAR_HOST_URL on your network. Nothing here waits on the SonarQube
# Quality Gate, so a failing gate does not fail this job — check the dashboard
# when you want to.
#
# Scope: this analyses the Rust source directly (the Sonar scanner reads
# sonar-project.properties). It does NOT build the crate or run Clippy — see the
# "Optional enrichment" note in sonar-project.properties for wiring in a Clippy
# report if your SonarQube edition supports Rust lint import.
name: SonarQube
on:
push:
branches: [main]
# Allow re-running the analysis on demand from the Actions tab.
workflow_dispatch: {}
concurrency:
group: sonarqube-${{ github.ref }}
cancel-in-progress: true
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- name: Check out (full history for accurate new-code + blame)
uses: actions/checkout@v4
with:
# SonarQube uses git history to attribute issues to authors and to
# compute "new code". A shallow clone degrades both.
fetch-depth: 0
- name: Run SonarQube scan
uses: sonarsource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}

View File

@@ -0,0 +1,111 @@
name: sync-project-tree
# Keeps this repo's file-layout snapshot (docs/link/PROJECT_TREE.md in the
# RunicGateway/docs repo) current. On every push to `main` it regenerates the
# tree from tracked files and, if it changed, opens (or force-updates) a pull
# request against the docs repo. It never writes to the docs repo's `main`
# directly. Auth reuses the same REGISTRY_USER / REGISTRY_TOKEN secrets the
# other workflows use (the token needs repo read/write on RunicGateway/docs).
on:
push:
branches: [main]
workflow_dispatch: {}
concurrency:
group: sync-project-tree
cancel-in-progress: true
env:
GITEA_HOST: gitea.whitlocktech.com
DOCS_REPO: RunicGateway/docs
SELF_REPO: RunicGateway/link
DOCS_PATH: link/PROJECT_TREE.md
TREE_TITLE: uo-link
ROOT_LABEL: link
PR_BRANCH: chore/sync-link-tree
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Check out this repo
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Ensure python3 is available
run: |
set -euo pipefail
command -v python3 >/dev/null 2>&1 || { sudo apt-get update -qq && sudo apt-get install -y -qq python3; }
- name: Render PROJECT_TREE.md from tracked files
run: |
set -euo pipefail
mkdir -p _sync
{
printf '# %s — Project Tree\n\n' "${TREE_TITLE}"
printf '> **Auto-generated.** This file is maintained by the `sync-project-tree` CI workflow in\n'
printf '> the [`%s`](https://%s/%s) repository, which\n' "${SELF_REPO}" "${GITEA_HOST}" "${SELF_REPO}"
printf '> opens a pull request here whenever the tracked file layout on `main` changes. Do not edit\n'
printf '> by hand — changes will be overwritten by the next sync.\n\n'
printf 'A snapshot of the tracked files in the repository (build output, dependencies, and other\n'
printf 'git-ignored paths are excluded).\n\n'
printf '```text\n'
git ls-files | python3 .gitea/scripts/gen_tree.py "${ROOT_LABEL}"
printf '```\n'
} > _sync/PROJECT_TREE.md
echo "----- generated ${DOCS_PATH} -----"
cat _sync/PROJECT_TREE.md
- name: Open or update the docs PR if the tree changed
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
# Secrets can carry a trailing CR/LF depending on how they were pasted;
# strip line breaks before they land in a URL or Authorization header.
CI_USER="$(printf '%s' "${REGISTRY_USER}" | tr -d '\r\n')"
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN}" | tr -d '\r\n')"
API="https://${GITEA_HOST}/api/v1/repos/${DOCS_REPO}"
REMOTE="https://${CI_USER}:${CI_TOKEN}@${GITEA_HOST}/${DOCS_REPO}.git"
git clone --depth 1 "${REMOTE}" docs_repo
cd docs_repo
git config user.name "runic-docs-bot"
git config user.email "ci@whitlocktech.com"
mkdir -p "$(dirname "${DOCS_PATH}")"
cp ../_sync/PROJECT_TREE.md "${DOCS_PATH}"
git add "${DOCS_PATH}"
if git diff --cached --quiet; then
echo "PROJECT_TREE.md already up to date — nothing to sync."
exit 0
fi
SHORT_SHA="$(echo "${GITHUB_SHA:-local}" | cut -c1-7)"
git checkout -B "${PR_BRANCH}"
git commit -m "docs(tree): sync ${DOCS_PATH} from ${SELF_REPO}@${SHORT_SHA} [skip ci]"
git push --force "${REMOTE}" "HEAD:${PR_BRANCH}"
# Open a PR only if one isn't already open for this branch (a force-push
# to an existing open PR's head updates it in place).
OPEN="$(curl -sSf -H "Authorization: token ${CI_TOKEN}" \
"${API}/pulls?state=open&limit=50" \
| jq --arg b "${PR_BRANCH}" '[.[] | select(.head.ref == $b)] | length')"
if [ "${OPEN}" = "0" ]; then
curl -sSf -X POST "${API}/pulls" \
-H "Authorization: token ${CI_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg head "${PR_BRANCH}" \
--arg base "main" \
--arg title "docs(tree): sync ${DOCS_PATH}" \
--arg body "Automated project-tree sync from [\`${SELF_REPO}\`](https://${GITEA_HOST}/${SELF_REPO}), regenerated from tracked files on \`main\`. Merge once the layout looks right; the workflow will keep this branch current until then." \
'{head: $head, base: $base, title: $title, body: $body}')" \
>/dev/null
echo "Opened a new docs PR for ${PR_BRANCH}."
else
echo "Existing open docs PR for ${PR_BRANCH} was updated via force-push."
fi

25
sonar-project.properties Normal file
View File

@@ -0,0 +1,25 @@
# SonarQube analysis config for the link (uo-link sidecar) repo.
# Consumed by the scanner in .gitea/workflows/sonarqube.yml on push to main.
# The project key must match the one created in SonarQube (dashboard URL
# ?id=Runic-Gateway-link).
sonar.projectKey=Runic-Gateway-link
sonar.projectName=runic gateway link
# Analysed application code. The Rust sidecar crate lives under sidecar/src.
# Rust unit tests live inline (#[cfg(test)] modules) rather than in a separate
# tree, so there is no distinct sonar.tests path to declare.
sonar.sources=sidecar/src
# Never analyse build output, the vendored lockfile, or generated config.
sonar.exclusions=**/target/**,**/*.lock
sonar.sourceEncoding=UTF-8
# ── Optional enrichment (enable if your SonarQube edition/version supports it) ──
# SonarQube imports Clippy findings when given a JSON report. To turn this on:
# 1. In sonarqube.yml, add a step before the scan that runs:
# cargo clippy --message-format=json > sidecar/clippy-report.json
# (needs the Rust toolchain + `rustup component add clippy` on the runner).
# 2. Uncomment the line below.
# sonar.rust.clippy.reportPaths=sidecar/clippy-report.json