diff --git a/ansible/roles/forgejo/tasks/main.yml b/ansible/roles/forgejo/tasks/main.yml index 84eab20..68dc2ce 100644 --- a/ansible/roles/forgejo/tasks/main.yml +++ b/ansible/roles/forgejo/tasks/main.yml @@ -166,3 +166,18 @@ chdir: "{{ forgejo_data_dir }}" register: runner_started changed_when: "'Starting' in runner_started.stdout or 'Creating' in runner_started.stdout" + +- name: Deploy registry cleanup script + ansible.builtin.template: + src: cleanup-registry.sh.j2 + dest: "{{ forgejo_data_dir }}/cleanup-registry.sh" + owner: root + group: root + mode: '0755' + +- name: Schedule registry cleanup cron job + ansible.builtin.cron: + name: forgejo-registry-cleanup + job: "{{ forgejo_data_dir }}/cleanup-registry.sh >/dev/null 2>&1" + hour: "*/6" + minute: "0" diff --git a/ansible/roles/forgejo/templates/cleanup-registry.sh.j2 b/ansible/roles/forgejo/templates/cleanup-registry.sh.j2 new file mode 100644 index 0000000..dc6746c --- /dev/null +++ b/ansible/roles/forgejo/templates/cleanup-registry.sh.j2 @@ -0,0 +1,65 @@ +#!/bin/bash +# Forgejo container registry cleanup: keep only the latest 2 image tags per repo +set -euo pipefail + +TOKEN="{{ forgejo_admin_password }}" +BASE="http://127.0.0.1:3000" +USER="{{ forgejo_admin_user }}" + +# Get a real API token +API_TOKEN=$(curl -s -X POST "$BASE/api/v1/users/$USER/tokens" \ + -u "$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"cleanup-'"$(date +%s)"'","scopes":["all"]}' \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha1',''))" 2>/dev/null) + +if [ -z "$API_TOKEN" ]; then + echo "Failed to get API token" + exit 1 +fi + +# List all repos +repos=$(curl -s -H "Authorization: token $API_TOKEN" "$BASE/api/v1/user/repos?limit=100" \ + | python3 -c "import sys,json; [print(r['name']) for r in json.load(sys.stdin)]" 2>/dev/null) + +for repo in $repos; do + # List docker packages in the repo + pkgs=$(curl -s -H "Authorization: token $API_TOKEN" \ + "$BASE/api/v1/packages/$USER/$repo?type=docker" 2>/dev/null \ + | python3 -c "import sys,json;d=json.load(sys.stdin);[print(p['id'],p['name']) for p in d]" 2>/dev/null) + + while IFS= read -r line; do + pkg_id=$(echo "$line" | awk '{print $1}') + pkg_name=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ //') + [ -z "$pkg_id" ] && continue + + # Get all versions of this package + versions=$(curl -s -H "Authorization: token $API_TOKEN" \ + "$BASE/api/v1/packages/$USER/$repo/$pkg_id/versions?page=-1" 2>/dev/null \ + | python3 -c " +import sys,json +d=json.load(sys.stdin) +if isinstance(d,list): + for v in sorted(d, key=lambda x: x.get('created_at',0), reverse=True): + print(v['id'], v['version']) +" 2>/dev/null) + + count=$(echo "$versions" | wc -l) + if [ "$count" -le 2 ]; then + continue + fi + + # Keep the first 2 (latest), delete the rest + echo "$versions" | tail -n +3 | while IFS= read -r vline; do + ver_id=$(echo "$vline" | awk '{print $1}') + echo "Deleting $pkg_name version $ver_id (keeping latest 2 of $count)" + curl -s -X DELETE -H "Authorization: token $API_TOKEN" \ + "$BASE/api/v1/packages/$USER/$repo/$pkg_id/versions/$ver_id" -o /dev/null -w '%{http_code}' + echo "" + done + done <<< "$pkgs" +done + +# Clean up the temporary token +curl -s -X DELETE -H "Authorization: token $API_TOKEN" \ + "$BASE/api/v1/users/$USER/tokens/cleanup-$(date +%s)" -o /dev/null 2>/dev/null || true diff --git a/ansible/roles/forgejo/templates/docker-compose.yml.j2 b/ansible/roles/forgejo/templates/docker-compose.yml.j2 index 1eda400..3d6e62e 100644 --- a/ansible/roles/forgejo/templates/docker-compose.yml.j2 +++ b/ansible/roles/forgejo/templates/docker-compose.yml.j2 @@ -26,6 +26,9 @@ services: - FORGEJO__service__DISABLE_REGISTRATION=true - FORGEJO__repository__ENABLE_PUSH_CREATE_USER=true - FORGEJO__repository__DEFAULT_PUSH_CREATE_PRIVATE=true + - FORGEJO__cron__cleanup_packages__ENABLED=true + - FORGEJO__cron__cleanup_packages__INTERVAL=6h + - FORGEJO__cron__cleanup_packages__CLEANUP_RULES=[{"type":"docker","keep_latest":2}] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000"] interval: 10s