claude-home/server-configs/gitea/README.md
Cal Corum 4b7eca8a46
All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 3s
docs: add YAML frontmatter to all 151 markdown files
Adds title, description, type, domain, and tags frontmatter to every
doc for improved KB semantic search. The description field is prepended
to every search chunk, and domain/type/tags enable filtered queries.

Type values: context, guide, runbook, reference, troubleshooting
Domain values match directory structure (networking, docker, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:00:44 -05:00

500 lines
14 KiB
Markdown

---
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=<token-from-gitea-admin> \
-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)