Files
hermes-skills/devops/proxmox-ve-administration/references/gitea-api-and-skills-versioning-2026-07.md
Debian 01bd921ced feat: add home-assistant-dashboard-conventions skill + update multiple skills
- New: smart-home/home-assistant-dashboard-conventions (Mushroom cards, view tabs, no Bubble Cards)
- Updated: rke2, ceph, galera, proxmox, brainstorming, compound-learning, 1password-cli, smart-home-automation skills
- New references: ceph-cluster-administration, docker-volume-forensics, ceph-crush-weight, ceph-ec-mixed-size
2026-07-14 18:35:16 +00:00

12 KiB

Gitea API, Project Boards & Skills Versioning

Always use git.familie-schoen.com for user-facing links in chat responses. Never paste the internal IP 10.0.30.105:3000 as a clickable link to the user.

Context Format Example
User-facing links in chat git.familie-schoen.com http://git.familie-schoen.com/dominik/iac-homelab/issues/14
API calls (curl) 10.0.30.105:3000 curl http://10.0.30.105:3000/api/v1/repos/...
SSH to Gitea host proxmox6 IP 10.0.20.60 ssh root@10.0.20.60 'pct exec 108 -- ...'

The domain git.familie-schoen.com resolves externally; the internal IP is needed for API calls from within the network. When referencing Gitea issues, PRs, or boards in any user-visible output, always use the domain form.

Gitea Server Access

  • Internal URL (API only): http://10.0.30.105:3000 (CT108 on proxmox6)
  • External URL (user-facing links): http://git.familie-schoen.com
  • User: dominik
  • Token: Generate via CLI on CT108 (1Password op may fail on VM200):
    ssh -i ~/.ssh/id_ed25519_proxmox root@10.0.30.105 \
      "su -s /bin/sh gitea -c 'gitea admin user generate-access-token -u dominik -t <name> --scopes all --raw --config /etc/gitea/app.ini'"
    
  • Existing repos: dominik/iac-homelab (IaC), dominik/hermes-skills (skills)

Gitea API Patterns

TOKEN="<generated-token>"
BASE="http://10.0.30.105:3000/api/v1"
REPO="dominik/iac-homelab"

# Create issue
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
  -d '{"title":"Issue title","body":"Description","labels":[1,5],"assignees":["dominik"]}' \
  "$BASE/repos/$REPO/issues"

# Comment on issue
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
  -d '{"body":"Comment text"}' \
  "$BASE/repos/$REPO/issues/ISSUE_NUMBER/comments"

# List all issues
curl -s -H "Authorization: token $TOKEN" "$BASE/repos/$REPO/issues?limit=50&type=issues"

# Close issue
curl -s -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
  -d '{"state":"closed"}' "$BASE/repos/$REPO/issues/ISSUE_NUMBER"

# Create labels (returns ID needed for issue creation)
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"priority:high","color":"#b60205"}' "$BASE/repos/$REPO/labels"

Pitfall: Gitea 1.25.5 Project Board API Missing

Gitea 1.25.5 (dev build go1.25.8, bindata/sqlite) has project tables in DB and repo unit enabled, but REST API routes for projects are not registered. All project endpoints return 404:

  • /repos/{owner}/{repo}/projects
  • /repos/{owner}/{repo}/projects/ (trailing slash doesn't help)
  • Org-level (/orgs/{org}/projects) and user-level project endpoints

Accessing CT108 (Gitea)

CT108 runs on proxmox6 (10.0.20.60), NOT directly reachable at 10.0.30.105 for SSH. The 10.0.30.105 address is the CT's network IP for HTTP/API only.

# CORRECT: SSH to proxmox6, then pct exec
ssh -i ~/.ssh/id_ed25519_proxmox root@10.0.20.60 'pct exec 108 -- bash -c "..."'

# WRONG: SSH to 10.0.30.105 (this is the CT's DHCP IP, not a hypervisor)
# ssh root@10.0.30.105 → fails or goes to wrong host

Generating a Gitea Token

The gitea binary is at /usr/local/bin/gitea — NOT in the default PATH for the gitea system user. Must use the full path:

# CORRECT (note full binary path):
ssh -i ~/.ssh/id_ed25519_proxmox root@10.0.20.60 'pct exec 108 -- bash -c "
  su -s /bin/sh gitea -c \"/usr/local/bin/gitea admin user generate-access-token -u dominik -t <name> --scopes all --raw --config /etc/gitea/app.ini\"
"'

# WRONG (gitea not in PATH):
# su -s /bin/sh gitea -c 'gitea admin ...' → "gitea: not found" (exit 127)

Workaround: Direct SQLite manipulation

# SSH to proxmox6, then pct exec into CT108
ssh -i ~/.ssh/id_ed25519_proxmox root@10.0.20.60

# Enter CT108
pct exec 108 -- bash

# Create project board directly in SQLite
sqlite3 /var/lib/gitea/data/gitea.db \
  "INSERT INTO project (title, description, repo_id, type, board_type, card_type, creator_id, is_closed, created_unix, updated_unix) \
   VALUES ('Board Title', 'Description', REPO_ID, 2, 0, 0, 1, 0, strftime('%s','now'), strftime('%s','now'));"
# type=2 for repo-level project, board_type=0 for kanban
# Get the inserted project ID: SELECT last_insert_rowid();

# Create board columns (Backlog, Todo, In Progress, Done)
sqlite3 /var/lib/gitea/data/gitea.db << 'SQL'
INSERT INTO project_board (title, sorting, project_id, creator_id, created_unix, updated_unix) VALUES
('Backlog', 0, PROJECT_ID, 1, strftime('%s','now'), strftime('%s','now')),
('Todo', 1, PROJECT_ID, 1, strftime('%s','now'), strftime('%s','now')),
('In Progress', 2, PROJECT_ID, 1, strftime('%s','now'), strftime('%s','now')),
('Done', 3, PROJECT_ID, 1, strftime('%s','now'), strftime('%s','now'));
SQL

# Map issues to board columns
sqlite3 /var/lib/gitea/data/gitea.db << 'SQL'
INSERT INTO project_issue (issue_id, project_id, project_board_id, sorting) VALUES
(ISSUE_DB_ID, PROJECT_ID, BOARD_COLUMN_ID, SORT_ORDER);
SQL

⚠️ SQLite Schema Quirks

  1. index is a SQLite reserved word — the issue table has a column named index (the issue number displayed in UI). Must always quote it:

    SELECT id, "index", name FROM issue WHERE repo_id=34;  -- CORRECT
    SELECT id, index, name FROM issue WHERE repo_id=34;    -- SYNTAX ERROR
    
  2. project_issue table has NO timestamp columns — unlike most Gitea tables, it only has: id, issue_id, project_id, project_board_id, sorting. Do NOT include created_unix/updated_unix in INSERTs.

  3. Issue idindex — the API returns number (which maps to index in DB), but project_issue references the DB primary key id. Always look up both: SELECT id, "index", name FROM issue WHERE repo_id=N;

Label Color Format

Gitea API rejects colors with # prefix. Use bare hex:

# CORRECT:
curl -d '{"name":"ceph","color":"7050ff"}'  # → 200 OK

# WRONG:
curl -d '{"name":"ceph","color":"#7050ff"}' # → 404 / error

Label IDs are numeric in API

When creating issues via API, the labels field takes numeric IDs (from label creation response), not label names. Passing names silently ignores them. Look up IDs first: GET /repos/{owner}/{repo}/labels → use id field.

Complete PM Setup Recipe (End-to-End)

TOKEN="<generated-token>"
BASE="http://10.0.30.105:3000/api/v1"
AUTH="Authorization: token $TOKEN"

# 1. Create org
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/orgs" -d '{"username":"pm-infra","description":"...","visibility":"private"}'

# 2. Create repo under org
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/orgs/pm-infra/repos" \
  -d '{"name":"homelab-board","private":true,"has_issues":true,"has_projects":true,"default_branch":"main"}'

# 3. Create labels (colors WITHOUT #)
for label in "ceph:7050ff:Ceph storage" "k8s:326ce5:Kubernetes" ...; do
  IFS=: read name color desc <<< "$label"
  curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
    "$BASE/repos/pm-infra/homelab-board/labels" \
    -d "{\"name\":\"$name\",\"color\":\"$color\",\"description\":\"$desc\"}"
done

# 4. Create milestones
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/repos/pm-infra/homelab-board/milestones" \
  -d '{"title":"Infra Audit & Cleanup","description":"..."}'

# 5. Create issues (labels=numeric IDs, milestone=numeric ID)
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/repos/pm-infra/homelab-board/issues" \
  -d '{"title":"...","body":"...","labels":[8,17],"milestone":1}'

# 6. Close done issues
curl -s -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/repos/pm-infra/homelab-board/issues/3" -d '{"state":"closed"}'

# 7. Create project board via SQLite (API returns 404)
#    → See "Direct SQLite manipulation" section above

# 8. Map issues to board columns via SQLite
#    → See "SQLite Schema Quirks" for gotchas

⚠️ execute_code Blocked by Cron Approval Mode

execute_code may be blocked with "cron approval mode" error even in interactive sessions. When it fails, use terminal + curl + python3 -c instead. Avoid execute_code for Gitea API scripting — use inline Python in terminal commands for JSON construction.

Skills Versioning + Gitea Sync (2026-07-12)

Setup

cd ~/.hermes/skills/

# .gitignore (exclude archives + bundled manifest)
cat > .gitignore << 'EOF'
.archive/
.bundled_manifest
*.pyc
__pycache__/
EOF

# Init + commit + push
git init
git add -A
git commit -m "Initial commit: Hermes Agent Skills collection"
git branch -M main  # rename master → main
git remote add origin http://10.0.30.105:3000/dominik/hermes-skills.git
git push -u "http://dominik:<token>@10.0.30.105:3000/dominik/hermes-skills.git" main

# Clean token from stored remote URL
git remote set-url origin http://10.0.30.105:3000/dominik/hermes-skills.git

Result

Pitfalls

  1. Default branch is mastergit push main fails with "src refspec main does not match any". Fix: git branch -M main before pushing.
  2. Token in remote URLgit push needs auth. Embed token in push URL: http://dominik:<token>@host/.... After successful push, reset URL without token: git remote set-url origin http://host/... to avoid storing credentials in .git/config.
  3. Submodule warnings — Some skill directories (e.g. productivity/noris-pptx) may be git submodules. git add -A warns but still commits them as regular files. Check git rm --cached if needed.
  4. 1Password CLI may fail on VM200op item get "Gitea Token" --vault "Kubernetes ESO" --reveal fails intermittently. Fall back to generating a fresh token via gitea admin user generate-access-token on CT108 directly.

PM Tool Decision: Gitea over Telegram Topics (2026-07-12)

User explicitly chose Gitea Issues + Project Boards over Telegram Topics as the PM tool for infrastructure work. The Telegram Topics approach (below) was abandoned because:

  • Bots cannot create supergroups (requires manual user action)
  • Telegram topics lack structured state management (labels, milestones, assignees, dependencies)
  • Gitea integrates with the existing IaC repo workflow

The complete Gitea PM setup (org → repo → labels → milestones → issues → project board via SQLite) is documented in the sections above and was fully implemented on 2026-07-12. The board lives at: http://10.0.30.105:3000/pm-infra/homelab-board

Telegram Topics as PM Tool (ABANDONED — kept for reference)

Limitation: Bots cannot create supergroups

Telegram Bot API does not allow bots to create groups or supergroups. The user must manually:

  1. Create a new group in Telegram
  2. Add @hermesdominik_bot as Admin (with "Add New Members" + "Manage Topics" rights)
  3. Enable Topics in group settings ("Topics" toggle)
  4. Share the Chat ID with the agent

Once the bot is admin in a topics-enabled supergroup, the agent can create topic threads via the Bot API:

# Create a topic thread (requires forum_topic icon color)
curl -s -X POST "https://api.telegram.org/bot<TOKEN>/createForumTopic" \
  -d "chat_id=-100xxxx&name=Ceph Crisis&icon_color=0xff6b6b"

# Send message to a specific topic
curl -s -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
  -d "chat_id=-100xxxx&message_thread_id=TOPIC_ID&text=Status update"

Bot Token Location

  • Stored in ~/.hermes/.env as TELEGRAM_BOT_TOKEN
  • Source with: source ~/.hermes/.env && export TELEGRAM_BOT_TOKEN
  • Bot: @hermesdominik_bot (ID: 8482479728)
  • Home chat: 223926918 (DM, not a group)