Add Gitea Actions workflows, CI config, and docs
This commit is contained in:
295
docs/DEPLOY.md
Normal file
295
docs/DEPLOY.md
Normal 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
|
||||
Reference in New Issue
Block a user