Add Gitea Actions workflows, CI config, and docs
Some checks failed
Docker / docker (push) Successful in 6s
Security / security (push) Successful in 6s
Deploy / deploy-local-runner (push) Has been cancelled
CI / ci (push) Successful in 1m42s
Deploy / deploy-ssh (push) Successful in 7s

This commit is contained in:
2026-02-28 20:40:14 +01:00
parent 3b48b39561
commit 8cadb2d216
35 changed files with 3216 additions and 0 deletions

295
docs/DEPLOY.md Normal file
View File

@@ -0,0 +1,295 @@
# Deploy Workflow — ${REPO_NAME}
## Overview
The deploy workflow (`.gitea/workflows/deploy.yml`) automates deployment to a
VPS after a push to the default branch. It supports two modes and three
deployment strategies.
**Disabled by default.** Set `ENABLE_DEPLOY=true` in `.ci/config.env` to enable.
## Deployment Modes
### Mode A: Local Runner (Recommended)
The deploy job runs **directly on the VPS** via a self-hosted `act_runner`.
No SSH keys or network credentials needed — the runner already has local access.
```
┌──────────┐ push ┌──────────┐ runs-on: deploy-ovh ┌───────────┐
│ Developer │ ────────────► │ Gitea │ ────────────────────────► │ VPS │
│ │ │ Server │ │ act_runner│
└──────────┘ └──────────┘ │ (local) │
└───────────┘
```
**Pros:**
- No SSH secrets to manage
- No network exposure (runner is already on the VPS)
- Simpler setup, fewer moving parts
- Runner can access local Docker socket, systemd, files directly
**Cons:**
- Requires installing `act_runner` on the VPS
- One runner per VPS (or one runner with multiple labels)
### Mode B: SSH (Fallback)
The deploy job runs on **any runner** (e.g., shared ubuntu-latest) and SSHs
into the VPS to execute deploy commands remotely.
```
┌──────────┐ push ┌──────────┐ runs job ┌────────┐ SSH ┌─────┐
│ Developer │ ────────────► │ Gitea │ ──────────────► │ Runner │ ────────► │ VPS │
└──────────┘ └──────────┘ │(shared)│ └─────┘
└────────┘
```
**Pros:**
- Works without installing anything on the VPS
- Can deploy to VPSes you don't fully control
**Cons:**
- Requires SSH secrets (private key, host, user)
- VPS SSH port must be reachable from the runner
- More moving parts, more to go wrong
## Deploy Strategies
Regardless of mode, the workflow supports three strategies for what actually
happens on the VPS:
### `compose` (default)
```bash
cd /opt/myapp
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d
```
Best for: Docker Compose-based projects. Pulls latest images and recreates
containers with zero-downtime (depends on your compose config).
### `systemd`
```bash
sudo systemctl restart my-service
```
Best for: Applications managed as systemd services (e.g., a Go binary,
Python app with gunicorn, etc.).
### `script`
```bash
./scripts/deploy.sh /opt/myapp
```
Best for: Custom deploy logic that doesn't fit compose or systemd.
The script receives `DEPLOY_WORKDIR` as `$1`.
## Setup Guide
### Local Runner Mode
#### Step 1: Install act_runner on the VPS
```bash
# Download the latest act_runner binary
wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x act_runner-linux-amd64
sudo mv act_runner-linux-amd64 /usr/local/bin/act_runner
```
#### Step 2: Register the runner with your label
```bash
# Register with Gitea (get the token from: Site Admin → Runners → Create)
act_runner register \
--instance https://git.hiddenden.cafe \
--token YOUR_RUNNER_TOKEN \
--labels deploy-ovh
# Or for repo-level: Repository Settings → Actions → Runners → Create
```
#### Step 3: Configure runner labels
The runner config file (usually `~/.config/act_runner/config.yaml` or next to
the binary) contains the labels:
```yaml
runner:
labels:
- "deploy-ovh:host"
```
The `:host` suffix means the job runs directly on the host (not in a container).
This is important for accessing Docker, systemd, and local files.
**After changing labels, restart the runner:**
```bash
sudo systemctl restart act_runner
```
#### Step 4: Update workflow runs-on
In `.gitea/workflows/deploy.yml`, the `deploy-local-runner` job has:
```yaml
runs-on: deploy-ovh
```
If you changed `DEPLOY_RUNNER_LABEL` in config.env, you **must also** update
this `runs-on` value in the workflow file to match. Gitea Actions does not
support dynamic `runs-on` from environment variables.
#### Step 5: Enable deploy
In `.ci/config.env`:
```env
ENABLE_DEPLOY=true
DEPLOY_MODE=local-runner
DEPLOY_RUNNER_LABEL=deploy-ovh
DEPLOY_WORKDIR=/opt/myapp
DEPLOY_STRATEGY=compose
```
#### Step 6: Verify VPS prerequisites
For `compose` strategy:
```bash
# Docker and compose plugin must be installed
docker --version
docker compose version
# The deploy workdir must exist with a docker-compose.yml
ls /opt/myapp/docker-compose.yml
```
For `systemd` strategy:
```bash
# The service must exist
systemctl cat my-service
```
For `script` strategy:
```bash
# The script must be in the repo and executable
```
### SSH Mode
#### Step 1: Create an SSH key
```bash
ssh-keygen -t ed25519 -C "deploy@gitea" -f deploy_key -N ""
```
#### Step 2: Add the public key to the VPS
```bash
ssh-copy-id -i deploy_key.pub user@your-vps
```
#### Step 3: Add secrets to Gitea
Go to **Repository Settings → Actions → Secrets** and add:
| Secret | Value |
|--------|-------|
| `DEPLOY_SSH_KEY` | Contents of `deploy_key` (private key) |
| `DEPLOY_HOST` | VPS IP or hostname |
| `DEPLOY_USER` | SSH username |
| `DEPLOY_KNOWN_HOSTS` | Output of `ssh-keyscan your-vps` (recommended) |
#### Step 4: Enable deploy
In `.ci/config.env`:
```env
ENABLE_DEPLOY=true
DEPLOY_MODE=ssh
DEPLOY_WORKDIR=/opt/myapp
DEPLOY_STRATEGY=compose
```
## Configuration Reference
| Variable | Default | Description |
|----------|---------|-------------|
| `ENABLE_DEPLOY` | `false` | Master switch. Deploy never runs unless `true`. |
| `DEPLOY_MODE` | `local-runner` | `local-runner` or `ssh` |
| `DEPLOY_RUNNER_LABEL` | `deploy-ovh` | Runner label for local-runner mode |
| `DEPLOY_WORKDIR` | `/opt/${REPO_NAME}` | Working directory on the VPS |
| `DEPLOY_STRATEGY` | `compose` | `compose`, `systemd`, or `script` |
| `DEPLOY_COMPOSE_FILE` | `docker-compose.yml` | Compose file path (relative to workdir) |
| `DEPLOY_SYSTEMD_SERVICE` | _(empty)_ | Systemd service name (required for systemd strategy) |
| `DEPLOY_SCRIPT` | `scripts/deploy.sh` | Deploy script path (relative to repo root) |
| `DEPLOY_ON_TAG` | `false` | Also deploy on `v*` tag pushes |
## Safety
### Branch Protection
The workflow only deploys from the `DEFAULT_BRANCH` (main). This is enforced
in two places:
1. **Trigger filter**: `on.push.branches: [main]`
2. **Branch guard step**: Runtime check comparing the actual ref to `DEFAULT_BRANCH`
### Never on Pull Requests
The `pull_request` event is intentionally absent from the trigger list.
Deploy cannot run on PRs.
### Protected Branches
For maximum safety, enable **branch protection** on `main` in Gitea:
1. Go to **Repository Settings → Branches → Branch Protection**
2. Enable for `main`
3. Require: status checks to pass, review approval, no force push
4. This ensures only reviewed, passing code reaches main → gets deployed
### Secrets
- **Local-runner mode**: No secrets needed. The runner has local access.
- **SSH mode**: Private key is written to a temp file, used once, then deleted.
It is never echoed to logs.
## Runner Label Examples
| Label | Description |
|-------|-------------|
| `deploy-ovh` | OVH VPS runner |
| `vps-prod` | Production VPS |
| `deploy-hetzner` | Hetzner VPS runner |
| `staging-runner` | Staging environment |
## Troubleshooting
### Local Runner Mode
- [ ] **Runner registered?** Check Gitea Admin → Runners or Repo Settings → Runners
- [ ] **Label matches?** `runs-on` in workflow must match the runner's label exactly
- [ ] **Runner online?** Check runner status in Gitea; restart if offline
- [ ] **Label suffix correct?** Use `:host` for direct host execution (not container)
- [ ] **Docker accessible?** Runner user must be in the `docker` group
- [ ] **Workdir exists?** `DEPLOY_WORKDIR` must exist and be readable
- [ ] **Compose file present?** Check `docker-compose.yml` exists in the workdir
- [ ] **Permissions?** For systemd: runner user needs sudo for systemctl
### SSH Mode
- [ ] **Secrets set?** Check all four secrets are in repo settings
- [ ] **Key format?** Private key must include `-----BEGIN` header
- [ ] **SSH port open?** VPS firewall must allow port 22 (or custom port) from runner
- [ ] **User permissions?** SSH user needs access to Docker/systemd/workdir
- [ ] **Known hosts?** Set `DEPLOY_KNOWN_HOSTS` for production (avoids MITM risk)
- [ ] **Key not passphrase-protected?** CI keys must have no passphrase
### General
- [ ] **ENABLE_DEPLOY=true?** Check `.ci/config.env`
- [ ] **Pushing to main?** Deploy only triggers on `DEFAULT_BRANCH`
- [ ] **Workflow file correct?** YAML syntax errors prevent the workflow from running
- [ ] **Check Actions tab** in Gitea for workflow run logs