--- title: "Gitea Server Setup and Config" description: "Complete setup and configuration reference for the self-hosted Gitea instance on LXC 225 (10.10.0.225). Covers service management, Gitea Actions CI/CD runner setup, shared composite actions, branch protection, backup/restore, and troubleshooting." type: reference domain: server-configs tags: [gitea, git, ci-cd, gitea-actions, lxc, postgresql, docker-runner, branch-protection] --- # Gitea - Self-Hosted Git Server **LXC 225** | **10.10.0.225** | **git.manticorum.com** Self-hosted Git server with web UI, Git LFS support, and Gitea Actions for CI/CD pipelines. ## Quick Info | Property | Value | |----------|-------| | **Type** | LXC Container (Proxmox) | | **OS** | Ubuntu 20.04 LTS | | **IP** | 10.10.0.225 | | **Public URL** | https://git.manticorum.com | | **Gitea Version** | 1.22.6 | | **Database** | PostgreSQL 12 | | **Reverse Proxy** | Nginx Proxy Manager (10.10.0.16) | ## Container Specs - **VMID**: 225 - **CPU**: 2 cores - **RAM**: 2GB - **Disk**: 20GB - **Features**: Nesting enabled (for future Docker runner support) ## Services ### Gitea Web - **Port**: 3000 (internal) - **Service**: `gitea.service` - **User**: `git` - **Work Dir**: `/var/lib/gitea` - **Config**: `/etc/gitea/app.ini` - **Data**: `/var/lib/gitea/data` - **Logs**: `/var/lib/gitea/log` ### PostgreSQL - **Version**: 12 - **Port**: 5432 (localhost only) - **Database**: `gitea` - **User**: `gitea` - **Service**: `postgresql` ## Management ### Access Container ```bash ssh root@10.10.0.225 # or via Proxmox pct enter 225 ``` ### Service Management ```bash # Status systemctl status gitea systemctl status postgresql # Restart systemctl restart gitea # Logs journalctl -u gitea -f ``` ### Database Access ```bash # As postgres user sudo -u postgres psql -d gitea # As gitea user (from container) PGPASSWORD=gitea123 psql -U gitea -d gitea -h 127.0.0.1 ``` ## Configuration ### Main Config File `/etc/gitea/app.ini` contains all Gitea settings: - Database connection - Server domain and URLs - SSH settings - LFS configuration - OAuth2/JWT secrets - Actions enabled **Permissions**: - Owner: `root:git` - Mode: `640` - Directory: `750` on `/etc/gitea` ### Admin Account - **Username**: `cal` - **Password**: Set during initial setup (change immediately!) - **Email**: `cal@manticorum.com` ### Features Enabled - ✅ **Gitea Actions** - Built-in CI/CD (GitHub Actions compatible) - ✅ **Git LFS** - Large file storage support - ✅ **SSH Access** - Git over SSH on port 22 - ✅ **Web UI** - Repository browser and management - ✅ **Organizations** - Multi-user repository groups - ✅ **Webhooks** - Integration with external services ### Key app.ini Settings (Actions) ```ini [actions] DEFAULT_ACTIONS_URL = self # Short-form actions resolve against local Gitea instance [service] REQUIRE_SIGNIN_VIEW = false # Public repos (like gitea-actions) cloneable without auth ``` - `DEFAULT_ACTIONS_URL=self` means `uses: cal/gitea-actions/calver@main` resolves locally - `REQUIRE_SIGNIN_VIEW=false` only exposes PUBLIC repos — all private repos remain locked down - The only public repo is `cal/gitea-actions` (reusable CI composite actions, no sensitive code) ## Backup ### What to Backup 1. **PostgreSQL database**: `gitea` database 2. **Repository data**: `/var/lib/gitea/data/gitea-repositories` 3. **Configuration**: `/etc/gitea/app.ini` 4. **Custom files**: `/var/lib/gitea/custom` (if any) ### Backup Commands ```bash # Database dump sudo -u postgres pg_dump gitea > gitea-backup-$(date +%Y%m%d).sql # Full data directory tar -czf gitea-data-$(date +%Y%m%d).tar.gz /var/lib/gitea # Config only cp /etc/gitea/app.ini gitea-app-$(date +%Y%m%d).ini ``` ### Restore ```bash # Restore database sudo -u postgres psql -d gitea < gitea-backup.sql # Restore data tar -xzf gitea-data.tar.gz -C / chown -R git:git /var/lib/gitea ``` ## Upgrades ### Upgrade Gitea ```bash # Stop service systemctl stop gitea # Backup current binary cp /usr/local/bin/gitea /usr/local/bin/gitea.backup # Download new version wget -O /usr/local/bin/gitea https://dl.gitea.com/gitea/VERSION/gitea-VERSION-linux-amd64 # Set permissions chmod +x /usr/local/bin/gitea # Start service (will auto-migrate database) systemctl start gitea # Check logs journalctl -u gitea -f ``` ### Check Version ```bash /usr/local/bin/gitea --version ``` ## Setting Up CI/CD with Gitea Actions Gitea Actions are enabled and the runner is deployed on the same LXC. ### Runner Container ```bash # Current production runner setup docker run -d \ --name gitea-runner \ --restart unless-stopped \ -e GITEA_RUNNER_REGISTRATION_TOKEN= \ -e GITEA_INSTANCE_URL=http://10.10.0.225:3000 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v gitea-runner-data:/data \ -v /etc/gitea/runner-config.yaml:/config.yaml:ro \ gitea/act_runner:latest ``` **CRITICAL**: `GITEA_INSTANCE_URL` must be the **internal** URL (`http://10.10.0.225:3000`), not the public domain. The runner uses this for API communication and auth token matching. ### Runner Config (`/etc/gitea/runner-config.yaml`) Key settings: - `container.options: --add-host=git.manticorum.com:host-gateway` — lets job containers resolve the public domain - `container.force_pull: true` — always pulls latest runner images - Labels: `ubuntu-latest`, `ubuntu-22.04`, `ubuntu-20.04` ### Shared Composite Actions (`cal/gitea-actions`) Reusable CI/CD actions shared across all projects. This repo is **public** (required for runner auth). | Action | Purpose | |--------|---------| | `cal/gitea-actions/calver@main` | Generate CalVer version (YYYY.MM.BUILD) from git tags | | `cal/gitea-actions/gitea-tag@main` | Create git tag via Gitea API | | `cal/gitea-actions/discord-notify@main` | Send Discord embed notification via webhook | ### Action Reference Rules Because `DEFAULT_ACTIONS_URL=self` is set: ```yaml # Local actions (cal/gitea-actions) — use SHORT FORM uses: cal/gitea-actions/calver@main # GitHub actions — use FULL URL (otherwise they'd resolve against local Gitea) uses: https://github.com/actions/checkout@v4 uses: https://github.com/docker/setup-buildx-action@v3 uses: https://github.com/docker/login-action@v3 uses: https://github.com/docker/build-push-action@v5 ``` ### Creating a New Workflow 1. Copy an existing workflow as a template (e.g., from major-domo-database) 2. Use short form for `cal/gitea-actions/*` references 3. Use `https://github.com/` prefix for all GitHub action references 4. Required secrets: `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN`, `DISCORD_WEBHOOK` ## Adding Repositories ### Via Web UI 1. Go to https://git.manticorum.com 2. Click "+" → "New Repository" 3. Fill in details and create ### Via Command Line ```bash # Add remote git remote add homelab git@git.manticorum.com:cal/repo-name.git # Or HTTPS git remote add homelab https://git.manticorum.com/cal/repo-name.git # Push git push homelab main ``` ### Migrate from GitHub Gitea has built-in migration: 1. New Repository → "Migrate from GitHub" 2. Enter GitHub URL and token 3. Gitea will clone all commits, branches, tags ## Integration with NPM Reverse proxy is configured on NPM (10.10.0.16): - **Domain**: git.manticorum.com - **Forward to**: 10.10.0.225:3000 - **SSL**: Let's Encrypt - **Websockets**: Enabled ## Troubleshooting ### Gitea won't start ```bash # Check logs journalctl -u gitea -n 50 # Common issues: # - Permission on /etc/gitea/app.ini (should be 640, root:git) # - PostgreSQL not running # - Port 3000 already in use ``` ### Can't connect to database ```bash # Check PostgreSQL is running systemctl status postgresql # Test connection PGPASSWORD=gitea123 psql -U gitea -d gitea -h 127.0.0.1 -c "SELECT 1;" # Check pg_hba.conf allows md5 auth cat /etc/postgresql/12/main/pg_hba.conf | grep md5 ``` ### 502 Bad Gateway on web ```bash # Check Gitea is listening ss -tlnp | grep 3000 # Check NPM can reach container curl http://10.10.0.225:3000 # Verify firewall rules (should allow from 10.10.0.0/24) ``` ### Actions runner not working - Ensure runner is registered in Gitea Admin → Actions → Runners - Check runner logs: `docker logs gitea-runner` - Verify `GITEA_INSTANCE_URL` uses internal URL (`http://10.10.0.225:3000`), NOT the public domain - Ensure runner has network access to Gitea ### Actions can't clone composite actions ("authentication required") - Verify `REQUIRE_SIGNIN_VIEW = false` in app.ini (allows public repo clone without auth) - Verify `DEFAULT_ACTIONS_URL = self` in app.ini - Use short-form references for local actions (`cal/gitea-actions/calver@main`) - Use full GitHub URLs for GitHub actions (`https://github.com/actions/checkout@v4`) - The `cal/gitea-actions` repo must be set to **public** visibility ## Security Notes - Database password is stored in `/etc/gitea/app.ini` (secured with 640 permissions) - SSH keys for Git access are stored per-user in Gitea database - JWT secrets are auto-generated and stored in config - LXC is unprivileged for better isolation - PostgreSQL only listens on localhost - `REQUIRE_SIGNIN_VIEW=false` — only public repos are accessible without login; all private repos remain fully protected - The only public repo is `cal/gitea-actions` (audited — contains no secrets or sensitive code) ## Related Documentation - [Official Gitea Docs](https://docs.gitea.io/) - [Gitea Actions](https://docs.gitea.io/en-us/usage/actions/overview/) - [Proxmox LXC Config](../proxmox/lxc/225.conf) - [Networking Setup](../../networking/CONTEXT.md) ## Deployment Date **Created**: 2026-02-03 **By**: Claude Code (Proxmox Skill) **Initial Version**: Gitea 1.22.6 on Ubuntu 20.04 ## Git Remotes This repository is mirrored on both GitHub and Gitea for redundancy: - **GitHub**: https://github.com/calcorum/claude-home - **Gitea**: https://git.manticorum.com/cal/claude-home --- ## Branch Protection Manager Automated script to apply consistent branch protection rules across all your Gitea repositories. ### Features - Applies branch protection rules to all repositories for a user/organization - Supports dry-run mode to preview changes - Configurable protection rules via environment variables - Handles updating existing protection rules - Clear success/error reporting ### Requirements ```bash pip install requests ``` ### Configuration The script (`apply_branch_protection.py`) uses these settings: - ✅ Disable direct pushes (force PR workflow) - ✅ Require 1 approval before merge - ✅ Restrict approvals to whitelisted users - ✅ Dismiss stale approvals when new commits are pushed - ✅ Enable status checks (for CI/CD) - ✅ Restrict merging to whitelisted users - ✅ Block merge on rejected reviews - ✅ Block merge if pull request is outdated (critical for DB migrations) ### Usage #### 1. Create a Gitea API Token 1. Go to https://git.manticorum.com/user/settings/applications 2. Click "Generate New Token" 3. Give it a name (e.g., "Branch Protection Script") 4. Select permissions: `repo` (full control) 5. Click "Generate Token" 6. Copy the token (you won't see it again!) #### 2. Set Environment Variables ```bash export GITEA_TOKEN='your-api-token-here' export GITEA_URL='https://git.manticorum.com' # Optional, defaults to this export GITEA_OWNER='cal' # Optional, defaults to cal ``` #### 3. Run the Script **Dry run (preview changes without applying):** ```bash cd /mnt/NV2/Development/claude-home/server-configs/gitea python apply_branch_protection.py --dry-run ``` **Apply to all repositories:** ```bash python apply_branch_protection.py ``` ### Customizing Protection Rules Edit the `BranchProtectionConfig` section in `main()` to customize the rules: ```python config = BranchProtectionConfig( branch_name="main", # Branch to protect enable_push=False, # Disable direct pushes required_approvals=1, # Number of required approvals enable_approvals_whitelist=True, # Restrict who can approve approvals_whitelist_usernames=[GITEA_OWNER], dismiss_stale_approvals=True, # Dismiss approvals on new commits enable_status_check=True, # Require status checks to pass enable_merge_whitelist=True, # Restrict who can merge merge_whitelist_usernames=[GITEA_OWNER], block_on_rejected_reviews=True, # Block merge if reviews are rejected block_on_outdated_branch=True, # Block merge if branch is outdated require_signed_commits=False, # Require GPG signatures ) ``` ### Example Output ``` ============================================================ Gitea Branch Protection Configuration ============================================================ Gitea URL: https://git.manticorum.com Owner: cal Branch: main ============================================================ Protection Rules: • Direct pushes: Disabled • Required approvals: 1 • Approvals whitelist: cal • Dismiss stale approvals: True • Status checks enabled: True • Merge whitelist: cal • Block on rejected reviews: True • Block on outdated branch: True • Require signed commits: False ============================================================ Applying branch protection to 5 repositories... 📦 major-domo-database 🔄 Updating existing protection... ✅ Successfully applied branch protection 📦 major-domo-bot ✅ Successfully applied branch protection ... ``` ### Troubleshooting Branch Protection Script **"GITEA_TOKEN environment variable is required"** - You need to set your API token. See usage step 2 above. **"Error fetching repositories"** - Check that your `GITEA_URL` is correct - Verify your API token has `repo` permissions - Ensure the `GITEA_OWNER` username is correct **"Could not delete existing protection"** - The script will still try to create the new protection - If this fails, manually delete the old protection rule from Gitea web UI **"Error: 422 Unprocessable Entity"** This usually means: - The branch doesn't exist in the repository - Invalid username in whitelist - Conflicting protection rule settings ### API References This script uses the Gitea API branch protection endpoints: - [Gitea API Documentation](https://docs.gitea.com/api/) - [Protected Branches](https://docs.gitea.com/usage/access-control/protected-branches) - [Create Branch Protection Endpoint](https://share.apidog.com/apidoc/docs-site/346218/api-3521477)