- Document DEFAULT_ACTIONS_URL=self and REQUIRE_SIGNIN_VIEW=false - Correct runner setup: internal URL, config.yaml mount, no .netrc - Add shared composite actions table (calver, gitea-tag, discord-notify) - Document action reference rules (short form local, full URL GitHub) - Add auth troubleshooting entry - Update reference implementations with all 5 projects - Replace semver references with CalVer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
492 lines
14 KiB
Markdown
492 lines
14 KiB
Markdown
# 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)
|
|
|