diff --git a/.gitea/scripts/README-update-readme.md b/.gitea/scripts/README-update-readme.md new file mode 100644 index 0000000..4ad30f1 --- /dev/null +++ b/.gitea/scripts/README-update-readme.md @@ -0,0 +1,147 @@ +# Auto-Update README Workflow + +## Overview + +This workflow automatically updates the `README.md` file with the current list of ArgoCD applications based on the directory structure in `k8s/`. + +## How it works + +1. **Trigger**: Workflow runs automatically when changes are pushed to `k8s/**` paths +2. **Scan**: Python script scans `k8s/` directory structure and finds all applications +3. **Generate**: Creates README.md with badges for all found applications +4. **Create PR**: If changes detected, creates a Merge Request for manual review + +## Files + +- `.gitea/workflows/update-readme.yaml` - GitHub Actions workflow +- `.gitea/scripts/generate-readme.py` - Python script for README generation + +## Directory Structure + +The script expects the following k8s directory structure: + +``` +k8s/ +├── core/ # Core infrastructure applications +│ ├── argocd/ +│ ├── authentik/ +│ └── ... +├── apps/ # User applications +│ ├── gitea/ +│ ├── immich/ +│ └── ... +└── games/ # Game servers + ├── minecraft/ + └── ... +``` + +Each subdirectory name becomes an application name in the README. + +## Required Secrets + +The workflow requires the following secrets to be configured in Gitea: + +| Secret | Description | Example | +|--------|-------------|---------| +| `GT_URL` | Gitea instance URL | `https://gt.hexor.cy` | +| `GT_TOKEN` | Gitea API token with repo write access | `glpat-xxxxx...` | +| `GT_OWNER` | Repository owner (username or org) | `ab` | +| `GT_REPO` | Repository name | `homelab` | + +### How to create a Gitea Token + +1. Go to Settings → Applications → Generate New Token +2. Give it a name like "README Update Bot" +3. Select scopes: `repo` (Full control of repositories) +4. Generate and copy the token +5. Add it as a secret in repository settings + +## Badge Format + +Badges are generated using a predictable pattern: + +```markdown +[![app-name](https://ag.hexor.cy/api/badge?name=app-name&revision=true)](https://ag.hexor.cy/applications/argocd/app-name) +``` + +This allows you to immediately see which applications are: +- ✅ Healthy and synced (green badge) +- ⚠️ Degraded or out of sync (yellow badge) +- ❌ Unhealthy or failed (red badge) + +## Manual Trigger + +You can manually trigger the workflow from Gitea: + +1. Go to Actions tab +2. Select "Auto-update README" workflow +3. Click "Run workflow" +4. Select branch and run + +## Example Output + +The generated README will look like: + +```markdown +# homelab + +ArgoCD homelab project + +## ArgoCD Applications Status + +| Application | Status | +| :--- | :---: | +| **argocd** | [![argocd](https://ag.hexor.cy/api/badge?name=argocd&revision=true)](https://ag.hexor.cy/applications/argocd/argocd) | +... +``` + +## Reviewing Pull Requests + +When the workflow creates a PR: + +1. Check the Actions tab for the workflow run details +2. Review the PR in the Pull Requests tab +3. Verify the application list matches your k8s/ structure +4. Merge when ready + +The PR will include: +- Updated application list +- Timestamp of generation +- Automatic commit message + +## Troubleshooting + +### No PR created + +- Check if there are actually changes in README.md +- Verify secrets are configured correctly +- Check workflow logs in Actions tab + +### Wrong applications listed + +- Verify k8s/ directory structure +- Ensure folder names match expected application names +- Check for hidden directories (starting with `.`) + +### Badge not loading + +- Verify ArgoCD badge API is accessible at `https://ag.hexor.cy` +- Check application name matches ArgoCD application name +- Ensure application exists in ArgoCD + +## Maintenance + +### Update badge URL + +If you need to change the badge URL pattern, edit: +- `.gitea/scripts/generate-readme.py` - function `generate_badge_line()` + +### Change workflow trigger + +To modify when the workflow runs, edit: +- `.gitea/workflows/update-readme.yaml` - `on:` section + +### Add new categories + +To add new categories (besides core/apps/games), edit: +- `.gitea/scripts/generate-readme.py` - function `scan_k8s_directory()` and `generate_readme_content()` diff --git a/.gitea/scripts/generate-readme.py b/.gitea/scripts/generate-readme.py new file mode 100644 index 0000000..fac416d --- /dev/null +++ b/.gitea/scripts/generate-readme.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +""" +Generate README.md with ArgoCD application status badges. +Scans k8s/ directory structure to find all applications and generates badges for them. +""" + +import os +import sys +from pathlib import Path +from typing import Dict, List + + +def scan_k8s_directory(k8s_path: str) -> Dict[str, List[str]]: + """ + Scan k8s/ directory and return applications grouped by category. + + Args: + k8s_path: Path to k8s directory + + Returns: + Dictionary with categories as keys and lists of app names as values + """ + apps_by_category = { + 'core': [], + 'apps': [], + 'games': [] + } + + k8s_dir = Path(k8s_path) + + for category in apps_by_category.keys(): + category_path = k8s_dir / category + if category_path.exists() and category_path.is_dir(): + # Get all subdirectories (each subdirectory is an app) + apps = [ + d.name for d in category_path.iterdir() + if d.is_dir() and not d.name.startswith('.') + ] + apps_by_category[category] = sorted(apps) + + return apps_by_category + + +def generate_badge_line(app_name: str) -> str: + """ + Generate markdown line with badge for an application. + + Args: + app_name: Name of the application + + Returns: + Markdown formatted string with badge + """ + badge_url = f"https://ag.hexor.cy/api/badge?name={app_name}&revision=true" + app_url = f"https://ag.hexor.cy/applications/argocd/{app_name}" + return f"| **{app_name}** | [![{app_name}]({badge_url})]({app_url}) |" + + +def generate_readme_content(apps_by_category: Dict[str, List[str]]) -> str: + """ + Generate README.md content with all applications. + + Args: + apps_by_category: Dictionary with apps grouped by category + + Returns: + Complete README.md content + """ + lines = [ + "# homelab", + "", + "ArgoCD homelab project", + "", + "## ArgoCD Applications Status", + "", + "", + "", + "", + "", + "", + "
", + "", + "### Core Applications", + "", + "| Application | Status |", + "| :--- | :---: |" + ] + + # Add core applications + for app in apps_by_category.get('core', []): + lines.append(generate_badge_line(app)) + + lines.extend([ + "", + "### Games", + "", + "| Application | Status |", + "| :--- | :---: |" + ]) + + # Add games + for app in apps_by_category.get('games', []): + lines.append(generate_badge_line(app)) + + lines.extend([ + "", + "", + "### Applications", + "", + "| Application | Status |", + "| :--- | :---: |" + ]) + + # Add applications + for app in apps_by_category.get('apps', []): + lines.append(generate_badge_line(app)) + + lines.extend([ + "", + "
" + ]) + + return '\n'.join(lines) + '\n' + + +def main(): + if len(sys.argv) < 2: + print("Usage: generate-readme.py [output-file]") + print("Example: generate-readme.py k8s/ README.md") + sys.exit(1) + + k8s_path = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else "README.md" + + if not os.path.exists(k8s_path): + print(f"Error: Directory {k8s_path} does not exist") + sys.exit(1) + + print(f"📁 Scanning {k8s_path}...") + apps_by_category = scan_k8s_directory(k8s_path) + + # Print statistics + total_apps = sum(len(apps) for apps in apps_by_category.values()) + print(f"✅ Found {total_apps} applications:") + for category, apps in apps_by_category.items(): + if apps: + print(f" - {category}: {len(apps)} apps") + + print(f"📝 Generating {output_file}...") + readme_content = generate_readme_content(apps_by_category) + + with open(output_file, 'w', encoding='utf-8') as f: + f.write(readme_content) + + print(f"✅ {output_file} generated successfully") + print(f" Total lines: {len(readme_content.splitlines())}") + print(f" File size: {len(readme_content)} bytes") + + +if __name__ == "__main__": + main() diff --git a/.gitea/workflows/update-readme.yaml b/.gitea/workflows/update-readme.yaml new file mode 100644 index 0000000..7e6a1bd --- /dev/null +++ b/.gitea/workflows/update-readme.yaml @@ -0,0 +1,156 @@ +name: 'Auto-update README' + +on: + push: + branches: [ "main" ] + paths: + - 'k8s/**' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-readme: + name: 'Generate README and Create MR' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config --global user.name "Gitea Actions Bot" + git config --global user.email "actions@gitea.local" + + - name: Generate README + run: | + echo "📋 Starting README generation..." + python3 .gitea/scripts/generate-readme.py k8s/ README.md + + if [ -f "README.md" ]; then + echo "✅ README generated successfully" + echo "📄 File size: $(wc -c < README.md) bytes" + echo "📄 Lines: $(wc -l < README.md)" + else + echo "❌ README not generated" + exit 1 + fi + + - name: Check for changes + id: check_changes + run: | + if git diff --quiet README.md; then + echo "No changes detected in README.md" + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "Changes detected in README.md" + echo "has_changes=true" >> $GITHUB_OUTPUT + fi + + - name: Create Pull Request + if: steps.check_changes.outputs.has_changes == 'true' + run: | + # Set variables + GITEA_URL="${{ secrets.GT_URL }}" + GITEA_TOKEN="${{ secrets.GT_TOKEN }}" + GITEA_OWNER="${{ secrets.GT_OWNER }}" + GITEA_REPO="${{ secrets.GT_REPO }}" + BRANCH_NAME="auto-update-readme-$(date +%Y%m%d-%H%M%S)" + + echo "🔍 Configuration:" + echo "GITEA_URL: ${GITEA_URL:-NOT SET}" + echo "GITEA_OWNER: ${GITEA_OWNER:-NOT SET}" + echo "GITEA_REPO: ${GITEA_REPO:-NOT SET}" + echo "BRANCH_NAME: $BRANCH_NAME" + + # Create and push new branch + echo "🌿 Creating branch: $BRANCH_NAME" + git checkout -b "$BRANCH_NAME" + git add README.md + git commit -m "Auto-update README with current k8s applications + +Generated by CI/CD workflow on $(date +%Y-%m-%d\ %H:%M:%S) + +This PR updates the README.md file with the current list of applications +found in the k8s/ directory structure." + + # Push branch to remote + echo "📤 Pushing branch to remote..." + git push origin "$BRANCH_NAME" + + # Create Pull Request using Gitea API + echo "🔀 Creating Pull Request..." + + PR_TITLE="Auto-update README with k8s applications" + PR_BODY="This PR automatically updates README.md based on the current k8s/ directory structure. + +## Changes +- Updated application list in README.md +- Applications are now synced with k8s/ folders + +## Review +Please review and merge if everything looks correct. + +--- +🤖 This PR was automatically generated by CI/CD workflow +⏰ Generated at: $(date +%Y-%m-%d\ %H:%M:%S)" + + # Create PR via API + RESPONSE=$(curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"title\": \"$PR_TITLE\", + \"body\": \"$PR_BODY\", + \"head\": \"$BRANCH_NAME\", + \"base\": \"main\" + }" \ + "$GITEA_URL/api/v1/repos/$GITEA_OWNER/$GITEA_REPO/pulls") + + # Extract PR number and URL from response + PR_NUMBER=$(echo "$RESPONSE" | grep -o '"number":[0-9]*' | head -1 | cut -d':' -f2) + + if [ -n "$PR_NUMBER" ]; then + echo "✅ Pull Request created successfully!" + echo "📝 PR #$PR_NUMBER" + echo "🔗 URL: $GITEA_URL/$GITEA_OWNER/$GITEA_REPO/pulls/$PR_NUMBER" + + # Save PR info for summary + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pr_url=$GITEA_URL/$GITEA_OWNER/$GITEA_REPO/pulls/$PR_NUMBER" >> $GITHUB_OUTPUT + else + echo "⚠️ Failed to create Pull Request" + echo "Response: $RESPONSE" + exit 1 + fi + + - name: Summary + if: always() + run: | + echo "## 📊 README Update Summary" >> $GITHUB_STEP_SUMMARY + + if [ -f "README.md" ]; then + echo "- ✅ README generated successfully" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check_changes.outputs.has_changes }}" = "true" ]; then + echo "- ✅ Changes detected" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Pull Request created" >> $GITHUB_STEP_SUMMARY + + if [ -n "${{ steps.create_pr.outputs.pr_number }}" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "**PR:** [#${{ steps.create_pr.outputs.pr_number }}](${{ steps.create_pr.outputs.pr_url }})" >> $GITHUB_STEP_SUMMARY + fi + else + echo "- ℹ️ No changes detected - README already up to date" >> $GITHUB_STEP_SUMMARY + fi + else + echo "- ❌ README generation failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Generated at:** $(date)" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index c853555..b74dc47 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ crash.*.log *.tfvars.json !*terraform.tfvars +# claude ai +.claude/ # Ignore override files as they are usually used to override resources locally and so # are not checked in override.tf diff --git a/README.md b/README.md index f258df8..8100396 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ ArgoCD homelab project | **cert-manager** | [![cert-manager](https://ag.hexor.cy/api/badge?name=cert-manager&revision=true)](https://ag.hexor.cy/applications/argocd/cert-manager) | | **external-secrets** | [![external-secrets](https://ag.hexor.cy/api/badge?name=external-secrets&revision=true)](https://ag.hexor.cy/applications/argocd/external-secrets) | | **k3s-system-upgrade** | [![k3s-system-upgrade](https://ag.hexor.cy/api/badge?name=k3s-system-upgrade&revision=true)](https://ag.hexor.cy/applications/argocd/k3s-system-upgrade) | +| **kube-system-custom** | [![kube-system-custom](https://ag.hexor.cy/api/badge?name=kube-system-custom&revision=true)](https://ag.hexor.cy/applications/argocd/kube-system-custom) | | **kubernetes-dashboard** | [![kubernetes-dashboard](https://ag.hexor.cy/api/badge?name=kubernetes-dashboard&revision=true)](https://ag.hexor.cy/applications/argocd/kubernetes-dashboard) | | **postgresql** | [![postgresql](https://ag.hexor.cy/api/badge?name=postgresql&revision=true)](https://ag.hexor.cy/applications/argocd/postgresql) | | **prom-stack** | [![prom-stack](https://ag.hexor.cy/api/badge?name=prom-stack&revision=true)](https://ag.hexor.cy/applications/argocd/prom-stack) | @@ -25,7 +26,6 @@ ArgoCD homelab project | Application | Status | | :--- | :---: | -| **beam-ng** | [![beam-ng](https://ag.hexor.cy/api/badge?name=beam-ng&revision=true)](https://ag.hexor.cy/applications/argocd/beam-ng) | | **counter-strike-16** | [![counter-strike-16](https://ag.hexor.cy/api/badge?name=counter-strike-16&revision=true)](https://ag.hexor.cy/applications/argocd/counter-strike-16) | | **minecraft** | [![minecraft](https://ag.hexor.cy/api/badge?name=minecraft&revision=true)](https://ag.hexor.cy/applications/argocd/minecraft) | @@ -36,18 +36,17 @@ ArgoCD homelab project | Application | Status | | :--- | :---: | | **gitea** | [![gitea](https://ag.hexor.cy/api/badge?name=gitea&revision=true)](https://ag.hexor.cy/applications/argocd/gitea) | -| **greece-notifier** | [![greece-notifier](https://ag.hexor.cy/api/badge?name=greece-notifier&revision=true)](https://ag.hexor.cy/applications/argocd/greece-notifier) | | **hexound** | [![hexound](https://ag.hexor.cy/api/badge?name=hexound&revision=true)](https://ag.hexor.cy/applications/argocd/hexound) | | **immich** | [![immich](https://ag.hexor.cy/api/badge?name=immich&revision=true)](https://ag.hexor.cy/applications/argocd/immich) | | **jellyfin** | [![jellyfin](https://ag.hexor.cy/api/badge?name=jellyfin&revision=true)](https://ag.hexor.cy/applications/argocd/jellyfin) | | **k8s-secrets** | [![k8s-secrets](https://ag.hexor.cy/api/badge?name=k8s-secrets&revision=true)](https://ag.hexor.cy/applications/argocd/k8s-secrets) | | **khm** | [![khm](https://ag.hexor.cy/api/badge?name=khm&revision=true)](https://ag.hexor.cy/applications/argocd/khm) | | **paperless** | [![paperless](https://ag.hexor.cy/api/badge?name=paperless&revision=true)](https://ag.hexor.cy/applications/argocd/paperless) | +| **pasarguard** | [![pasarguard](https://ag.hexor.cy/api/badge?name=pasarguard&revision=true)](https://ag.hexor.cy/applications/argocd/pasarguard) | | **qbittorent-nas** | [![qbittorent-nas](https://ag.hexor.cy/api/badge?name=qbittorent-nas&revision=true)](https://ag.hexor.cy/applications/argocd/qbittorent-nas) | | **rustdesk** | [![rustdesk](https://ag.hexor.cy/api/badge?name=rustdesk&revision=true)](https://ag.hexor.cy/applications/argocd/rustdesk) | -| **sonarr-stack** | [![sonarr-stack](https://ag.hexor.cy/api/badge?name=sonarr-stack&revision=true)](https://ag.hexor.cy/applications/argocd/sonarr-stack) | -| **stirling-pdf** | [![stirling-pdf](https://ag.hexor.cy/api/badge?name=stirling-pdf&revision=true)](https://ag.hexor.cy/applications/argocd/stirling-pdf) | | **syncthing** | [![syncthing](https://ag.hexor.cy/api/badge?name=syncthing&revision=true)](https://ag.hexor.cy/applications/argocd/syncthing) | +| **tg-bots** | [![tg-bots](https://ag.hexor.cy/api/badge?name=tg-bots&revision=true)](https://ag.hexor.cy/applications/argocd/tg-bots) | | **vaultwarden** | [![vaultwarden](https://ag.hexor.cy/api/badge?name=vaultwarden&revision=true)](https://ag.hexor.cy/applications/argocd/vaultwarden) | | **vpn** | [![vpn](https://ag.hexor.cy/api/badge?name=vpn&revision=true)](https://ag.hexor.cy/applications/argocd/vpn) |