# Gitea CI/CD Deployment Guide

This document describes how to **self-host Gitea** on an external server with **Gitea Actions** CI/CD (GitHub Actions-compatible).

---

## Why Gitea?

- Lightweight, self-hosted Git forge (single binary or Docker image)
- Built-in CI/CD via **Gitea Actions** (GitHub Actions-compatible YAML)
- Built-in migration tool imports repos, issues, and PRs from other forges
- Low resource usage

---

## 1. Server Prerequisites

```bash
# Ubuntu/Debian
sudo apt update && sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER   # log out/in after this
```

---

## 2. Gitea Server Setup (Docker Compose)

Create a directory on your server:

```bash
mkdir -p ~/gitea && cd ~/gitea
```

Create `docker-compose.yml`:

```yaml
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: always
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=gitea-db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=<CHANGE_ME>
      - GITEA__server__ROOT_URL=https://git.yourdomain.com/
      - GITEA__server__SSH_DOMAIN=git.yourdomain.com
      - GITEA__server__DOMAIN=git.yourdomain.com
      - GITEA__actions__ENABLED=true
    volumes:
      - gitea-data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"   # Web UI
      - "2222:22"     # SSH (Git over SSH)
    depends_on:
      gitea-db:
        condition: service_healthy

  gitea-db:
    image: postgres:15
    container_name: gitea-db
    restart: always
    environment:
      POSTGRES_DB: gitea
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: <CHANGE_ME>
    volumes:
      - gitea-db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U gitea"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Gitea Actions runner — executes CI workflows
  gitea-runner:
    image: gitea/act_runner:latest
    container_name: gitea-runner
    restart: always
    environment:
      GITEA_INSTANCE_URL: http://gitea:3000
      GITEA_RUNNER_REGISTRATION_TOKEN: <RUNNER_TOKEN>
      GITEA_RUNNER_NAME: default-runner
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - gitea-runner-data:/data
    depends_on:
      - gitea

volumes:
  gitea-data:
  gitea-db-data:
  gitea-runner-data:
```

!!! warning "Replace placeholders"
    Replace `<CHANGE_ME>` with a strong database password and `<RUNNER_TOKEN>` with the token from step 4.

Start Gitea and the database first:

```bash
docker compose up -d gitea gitea-db
```

Visit `http://your-server-ip:3000` and complete the initial setup wizard.

---

## 3. Reverse Proxy with HTTPS (Nginx + Let's Encrypt)

```bash
sudo apt install -y nginx certbot python3-certbot-nginx
```

Create `/etc/nginx/sites-available/gitea`:

```nginx
server {
    server_name git.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        client_max_body_size 100M;
    }
}
```

Enable and get a certificate:

```bash
sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
sudo certbot --nginx -d git.yourdomain.com
sudo systemctl reload nginx
```

---

## 4. Enable Actions & Register the Runner

1. Go to **Site Administration > Runners** in the Gitea web UI.
2. Click **Create new Runner** and copy the registration token.
3. Paste the token into `docker-compose.yml` as `GITEA_RUNNER_REGISTRATION_TOKEN`.
4. Start the runner:

```bash
docker compose up -d gitea-runner
```

Verify the runner appears as **Online** in the admin panel.

---

## 5. Migrate Your Repository

### Option A: Git push (code only)

```bash
# On your local machine
cd /path/to/letzshop-product-import
git remote add gitea ssh://git@git.yourdomain.com:2222/your-username/letzshop-product-import.git
git push gitea --all
git push gitea --tags
```

### Option B: Gitea built-in migration (code + issues + PRs)

1. In Gitea, click **+** > **New Migration**.
2. Select the source forge (GitHub, GitLab, etc.).
3. Enter the source URL and a Personal Access Token.
4. Gitea will import the repository, issues, labels, milestones, and pull/merge requests.

---

## 6. CI/CD — Gitea Actions

The workflow file lives in `.gitea/workflows/ci.yml` (already created in this repository). Gitea Actions uses GitHub Actions-compatible YAML syntax.
| `artifacts: paths:` | `actions/upload-artifact@v4` (not supported on Gitea GHES) |
| `cache: paths:` | `actions/cache@v4` |
| `coverage: '/regex/'` | Use coverage action or parse in step |
| CI/CD Variables (UI) | Repository **Settings > Secrets** |

---

## 7. CI/CD Secrets

Configure these in your Gitea repository under **Settings > Actions > Secrets**:

| Secret | Description | Used by |
|--------|-------------|---------|
| `DEPLOY_SSH_KEY` | Ed25519 private key for deployment | Deploy job |
| `DEPLOY_HOST` | Docker bridge gateway IP: `172.17.0.1` (see note below) | Deploy job |
| `DEPLOY_USER` | SSH user on production server (e.g. `samir`) | Deploy job |
| `DEPLOY_PATH` | App directory on server (e.g. `/home/samir/apps/orion`) | Deploy job |

!!! important "DEPLOY_HOST must be `172.17.0.1`, not `127.0.0.1`"
    The Gitea Actions runner executes CI jobs inside Docker containers. From inside the container, `127.0.0.1` refers to the container itself, not the host machine. Use `172.17.0.1` (the Docker bridge gateway) so the SSH action can reach the host's SSH daemon. When Gitea and Orion are split onto separate servers, update this to the Orion server's real IP.

---

## 8. Pipeline Overview

The CI pipeline (`.gitea/workflows/ci.yml`) runs:

```
push/PR to master
├── ruff          (lint)
├── pytest        (tests + PostgreSQL service)
├── validate      (all 4 validators: architecture, security, performance, audit)
├── dependency-scanning  (pip-audit, non-blocking)
├── docs          (mkdocs build, master-only, after lint+test+validate pass)
└── deploy        (SSH deploy, master-only, after lint+test+validate pass)
```

All jobs run in parallel except `docs` and `deploy`, which wait for `ruff`, `pytest`, and `validate` to pass. The `deploy` job only runs on push (not PRs).

---

## 9. Deploy Job (Continuous Deployment)

The CI pipeline includes an automated deploy job that runs on every successful push to master. It SSHes to the production server and runs a version-controlled deploy script:

```yaml
  deploy:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/master'
    needs: [ruff, pytest, validate]
    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          port: 22
          command_timeout: 10m
          script: cd ${{ secrets.DEPLOY_PATH }} && bash scripts/deploy.sh
```

The `scripts/deploy.sh` script handles the full deploy lifecycle:

1. Stash local changes (preserves `.env` and other server-side edits)
2. Pull latest code (`--ff-only`)
3. Pop stash to restore local changes
4. Rebuild and restart Docker containers (`docker compose --profile full up -d --build`)
5. Run database migrations (`alembic upgrade heads`)
6. Health check `http://localhost:8001/health` with retries

See [Hetzner Server Setup — Step 16](hetzner-server-setup.md#step-16-continuous-deployment) for the full setup guide including SSH key generation and Gitea secrets configuration.

---

## 10. Firewall Configuration

```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
# Allow CI containers (Docker bridge) to SSH to the host for deployment
sudo ufw allow from 172.17.0.0/16 to any port 22
sudo ufw enable
```

---

## 11. Maintenance

```bash
# View Gitea logs
docker compose -f ~/gitea/docker-compose.yml logs -f gitea

# View runner logs
docker compose -f ~/gitea/docker-compose.yml logs -f gitea-runner

# Update Gitea
cd ~/gitea
docker compose pull
docker compose up -d

# Backup Gitea data
docker run --rm -v gitea-data:/data -v $(pwd):/backup alpine \
    tar czf /backup/gitea-backup-$(date +%Y%m%d).tar.gz /data
```

---
