added gitea for PoC

This commit is contained in:
Prox 2026-03-06 17:11:28 +02:00
parent ded80f5a4b
commit 53433dc3de
8 changed files with 235 additions and 65 deletions

View File

@ -5,12 +5,13 @@ testing of the NetBird GitOps reconciler.
## Stack overview ## Stack overview
| Component | Purpose | | Component | Purpose |
| ---------- | ------------------------------------------- | | --------------- | ------------------------------------------- |
| Caddy | TLS termination, reverse proxy | | Caddy | TLS termination, reverse proxy |
| NetBird | Management, Signal, Relay, Dashboard, TURN | | NetBird | Management, Signal, Relay, Dashboard, TURN |
| Reconciler | Declarative config → NetBird API reconciler | | Reconciler | Declarative config → NetBird API reconciler |
| Gitea | Local Git server (optional, off by default) | | Gitea | Git server for GitOps source-of-truth |
| Gitea Runner | Executes CI workflows (Actions) |
All services run as Docker containers on a single VPS, connected via a `netbird` All services run as Docker containers on a single VPS, connected via a `netbird`
Docker bridge network. Caddy handles ACME certificates automatically. Docker bridge network. Caddy handles ACME certificates automatically.
@ -18,7 +19,9 @@ Docker bridge network. Caddy handles ACME certificates automatically.
## Prerequisites ## Prerequisites
- SSH access to `46.225.220.61` (root, key-based) - SSH access to `46.225.220.61` (root, key-based)
- DNS A record: `vps-a.networkmonitor.cc``46.225.220.61` - DNS A records:
- `vps-a.networkmonitor.cc``46.225.220.61`
- `gitea.vps-a.networkmonitor.cc``46.225.220.61`
- `rsync` installed locally (used to sync reconciler source) - `rsync` installed locally (used to sync reconciler source)
- Ansible 2.15+ with `community.general` and `ansible.posix` collections - Ansible 2.15+ with `community.general` and `ansible.posix` collections
@ -28,22 +31,16 @@ Install collections if needed:
ansible-galaxy collection install community.general ansible.posix ansible-galaxy collection install community.general ansible.posix
``` ```
## Setup ## Deployment (multi-phase)
### 1. Create vault file The deployment is intentionally multi-phase because some tokens can only be
obtained after services are running.
### Phase 1: Initial deploy
```bash ```bash
cd poc/ansible cd poc/ansible
cp group_vars/all/vault.yml.example group_vars/all/vault.yml cp group_vars/all/vault.yml.example group_vars/all/vault.yml
```
For the first deploy, leave all values as empty strings — the playbook
auto-generates NetBird secrets and a reconciler token.
### 2. Deploy
```bash
cd poc/ansible
ansible-playbook -i inventory.yml playbook.yml ansible-playbook -i inventory.yml playbook.yml
``` ```
@ -51,48 +48,61 @@ The playbook will:
1. Generate secrets (encryption key, TURN password, relay secret, reconciler 1. Generate secrets (encryption key, TURN password, relay secret, reconciler
token) token)
2. Install Docker if not present 2. Install Docker, configure UFW
3. Configure UFW firewall 3. Rsync the reconciler source code and build the Docker image
4. Rsync the reconciler source code to VPS-A 4. Template configs and start all services
5. Template all config files 5. Skip the Gitea Actions runner (no token yet)
6. Build the reconciler Docker image on VPS-A 6. Print a summary with generated secrets
7. Pull NetBird/Gitea images and start all services
8. Run health checks and print a summary with generated secrets
**Save the generated secrets** printed at the end into `vault.yml` so subsequent **Save the generated secrets** into `vault.yml` so subsequent runs are
runs are idempotent. idempotent.
### 3. Create NetBird admin + API token ### Phase 2: Create NetBird admin + API token
1. Open `https://vps-a.networkmonitor.cc` in a browser 1. Open `https://vps-a.networkmonitor.cc` in a browser
2. Create the first admin account (embedded IdP — no external OAuth) 2. Create the first admin account (embedded IdP — no external OAuth)
3. Go to **Settings → Personal Access Tokens → Generate** 3. Go to **Settings → Personal Access Tokens → Generate**
4. Copy the token into `vault.yml` as `vault_netbird_api_token` 4. Copy the token into `vault.yml` as `vault_netbird_api_token`
5. Re-run the playbook:
### Phase 3: Set up Gitea
1. Open `https://gitea.vps-a.networkmonitor.cc` and complete the install wizard
2. Create an admin account (user: `blastpilot`)
3. Create org `BlastPilot` and repo `netbird-gitops`
4. Generate a Gitea API token (**Settings → Applications**) → `vault_gitea_token`
5. Go to **Site Administration → Actions → Runners** → copy runner registration
token → `vault_gitea_admin_password` and `vault_gitea_runner_token`
### Phase 4: Re-deploy with all tokens
```bash ```bash
ansible-playbook -i inventory.yml playbook.yml ansible-playbook -i inventory.yml playbook.yml
``` ```
The reconciler will now start successfully with a valid API token. This run will:
- Start the reconciler with a valid NetBird API token
- Register and start the Gitea Actions runner
- Wire the reconciler to poll Gitea for `netbird.json` changes
### 4. (Optional) Enable Gitea ### Phase 5: Push code and test CI
To enable Gitea-backed GitOps polling: ```bash
cd /path/to/netbird-gitops
git remote add poc git@gitea.vps-a.networkmonitor.cc:BlastPilot/netbird-gitops.git
git push poc main
```
1. Open `http://vps-a.networkmonitor.cc:3000` and complete the install wizard Then configure Gitea repo secrets (Settings → Actions → Secrets):
2. Create an admin account (user: `blastpilot`) - `RECONCILER_TOKEN` — the reconciler bearer token
3. Create org `BlastPilot` and repo `netbird-gitops` - `RECONCILER_URL``https://vps-a.networkmonitor.cc/reconciler`
4. Push `netbird.json` to the repo - `GITEA_TOKEN` — same Gitea API token
5. Generate a Gitea API token (Settings → Applications)
6. In `vars.yml`, set `gitea_enabled: "true"` Create a branch, modify `netbird.json`, open a PR — the dry-run workflow should
7. In `vault.yml`, fill in `vault_gitea_token` and `vault_gitea_admin_password` post a plan as a PR comment.
8. Re-run the playbook
## Testing ## Testing
All commands below assume you have the reconciler token. Replace `<TOKEN>` with Replace `<TOKEN>` with `vault_reconciler_token`.
the value of `vault_reconciler_token`.
### Health check ### Health check
@ -127,14 +137,6 @@ curl -H "Authorization: Bearer <TOKEN>" \
'https://vps-a.networkmonitor.cc/reconciler/export' 'https://vps-a.networkmonitor.cc/reconciler/export'
``` ```
### Enroll a peer
Use a setup key from the reconcile response (`created_keys` field):
```bash
sudo netbird up --management-url https://vps-a.networkmonitor.cc --setup-key <KEY>
```
## Teardown ## Teardown
Remove all containers and volumes: Remove all containers and volumes:
@ -143,6 +145,12 @@ Remove all containers and volumes:
ssh root@46.225.220.61 "cd /opt/netbird-poc && docker compose down -v" ssh root@46.225.220.61 "cd /opt/netbird-poc && docker compose down -v"
``` ```
Stop the runner:
```bash
ssh root@46.225.220.61 "systemctl stop gitea-runner && systemctl disable gitea-runner"
```
## File structure ## File structure
``` ```
@ -158,7 +166,7 @@ poc/
templates/ templates/
docker-compose.yml.j2 # All services (NetBird + Gitea + Reconciler) docker-compose.yml.j2 # All services (NetBird + Gitea + Reconciler)
management.json.j2 # NetBird management config management.json.j2 # NetBird management config
Caddyfile.j2 # Caddy reverse proxy with reconciler route Caddyfile.j2 # Caddy reverse proxy (NetBird + Gitea)
dashboard.env.j2 # NetBird dashboard env dashboard.env.j2 # NetBird dashboard env
relay.env.j2 # NetBird relay env relay.env.j2 # NetBird relay env
turnserver.conf.j2 # TURN server config turnserver.conf.j2 # TURN server config

View File

@ -12,15 +12,21 @@ coturn_version: "4.8.0-r0"
# --- Reconciler --- # --- Reconciler ---
reconciler_port: 8080 reconciler_port: 8080
# --- Gitea (standalone mode by default) --- # --- Gitea ---
# String "false" because it goes into an env var verbatim. gitea_enabled: "true"
gitea_enabled: "false"
gitea_version: "1.23" gitea_version: "1.23"
gitea_domain: "gitea.vps-a.networkmonitor.cc"
gitea_http_port: 3000 gitea_http_port: 3000
gitea_ssh_port: 2222 gitea_ssh_port: 2222
gitea_admin_user: "blastpilot" gitea_admin_user: "blastpilot"
gitea_org_name: "BlastPilot" gitea_org_name: "BlastPilot"
gitea_repo_name: "netbird-gitops" gitea_repo_name: "netbird-gitops"
# --- Gitea Actions Runner ---
gitea_runner_version: "0.2.11"
gitea_runner_dir: "/opt/gitea-runner"
gitea_runner_name: "poc-runner"
gitea_runner_labels: "ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://ubuntu:22.04"
# --- Paths --- # --- Paths ---
base_dir: /opt/netbird-poc base_dir: /opt/netbird-poc

View File

@ -18,3 +18,7 @@ vault_netbird_api_token: ""
# Gitea API token (created via Gitea UI after first deploy): # Gitea API token (created via Gitea UI after first deploy):
vault_gitea_token: "" vault_gitea_token: ""
# Gitea Actions runner registration token
# (get from Gitea: Site Administration → Actions → Runners):
vault_gitea_runner_token: ""

View File

@ -166,10 +166,10 @@
port: "3478" port: "3478"
proto: tcp proto: tcp
- name: Allow Gitea HTTP - name: Allow Gitea SSH
community.general.ufw: community.general.ufw:
rule: allow rule: allow
port: "{{ gitea_http_port | string }}" port: "{{ gitea_ssh_port | string }}"
proto: tcp proto: tcp
- name: Enable UFW (default deny incoming) - name: Enable UFW (default deny incoming)
@ -327,7 +327,88 @@
changed_when: false changed_when: false
# ========================================================================= # =========================================================================
# 9. Summary # 9. Gitea Actions Runner
# =========================================================================
# The runner needs Gitea to be up and a registration token.
# On first deploy, skip this (vault_gitea_runner_token is empty).
# After Gitea is running, get the token from Site Administration →
# Actions → Runners, add it to vault.yml, and re-run.
- name: Create runner directory
ansible.builtin.file:
path: "{{ gitea_runner_dir }}"
state: directory
mode: "0755"
when: vault_gitea_runner_token | default('') | length > 0
- name: Download act_runner binary
ansible.builtin.get_url:
url: "https://gitea.com/gitea/act_runner/releases/download/v{{ gitea_runner_version }}/act_runner-{{ gitea_runner_version }}-linux-amd64"
dest: "{{ gitea_runner_dir }}/act_runner"
mode: "0755"
when: vault_gitea_runner_token | default('') | length > 0
- name: Check if runner is already registered
ansible.builtin.stat:
path: "{{ gitea_runner_dir }}/.runner"
register: _runner_config
when: vault_gitea_runner_token | default('') | length > 0
- name: Register runner with Gitea
ansible.builtin.command:
cmd: >-
{{ gitea_runner_dir }}/act_runner register
--instance https://{{ gitea_domain }}
--token {{ vault_gitea_runner_token }}
--name {{ gitea_runner_name }}
--labels {{ gitea_runner_labels }}
--no-interactive
chdir: "{{ gitea_runner_dir }}"
when:
- vault_gitea_runner_token | default('') | length > 0
- not (_runner_config.stat.exists | default(false))
- name: Create systemd service for runner
ansible.builtin.copy:
dest: /etc/systemd/system/gitea-runner.service
mode: "0644"
content: |
[Unit]
Description=Gitea Actions Runner
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
User=root
WorkingDirectory={{ gitea_runner_dir }}
ExecStart={{ gitea_runner_dir }}/act_runner daemon
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
when: vault_gitea_runner_token | default('') | length > 0
- name: Start and enable runner service
ansible.builtin.systemd:
name: gitea-runner
daemon_reload: true
state: started
enabled: true
when: vault_gitea_runner_token | default('') | length > 0
- name: Skip runner (no token provided)
ansible.builtin.debug:
msg: >-
Skipping Gitea Actions runner — vault_gitea_runner_token is empty.
After Gitea is running, get the token from
https://{{ gitea_domain }}/-/admin/actions/runners
and add it to vault.yml.
when: vault_gitea_runner_token | default('') | length == 0
# =========================================================================
# 10. Summary
# ========================================================================= # =========================================================================
- name: Note about NetBird API token - name: Note about NetBird API token
@ -343,11 +424,12 @@
ansible.builtin.debug: ansible.builtin.debug:
msg: | msg: |
============================================================ ============================================================
NetBird + Reconciler PoC deployed on VPS-A NetBird + Reconciler + Gitea PoC deployed on VPS-A
============================================================ ============================================================
Dashboard: https://{{ netbird_domain }} Dashboard: https://{{ netbird_domain }}
Gitea: http://{{ netbird_domain }}:{{ gitea_http_port }} Gitea: https://{{ gitea_domain }}
Gitea SSH: ssh://git@{{ gitea_domain }}:{{ gitea_ssh_port }}
Reconciler: https://{{ netbird_domain }}/reconciler/health Reconciler: https://{{ netbird_domain }}/reconciler/health
Reconciler status: {{ 'healthy' if (_reconciler_check.status | default(0)) == 200 else 'NOT YET READY (see note above)' }} Reconciler status: {{ 'healthy' if (_reconciler_check.status | default(0)) == 200 else 'NOT YET READY (see note above)' }}
@ -362,5 +444,8 @@
1. Open the dashboard and create an admin account 1. Open the dashboard and create an admin account
2. Go to Settings > API > generate a Personal Access Token 2. Go to Settings > API > generate a Personal Access Token
3. Put the token in vault.yml as vault_netbird_api_token 3. Put the token in vault.yml as vault_netbird_api_token
4. Re-run: ansible-playbook -i inventory.yml playbook.yml 4. Open Gitea, complete install wizard, create org + repo
5. Go to Site Administration > Actions > Runners, copy token
6. Put the token in vault.yml as vault_gitea_runner_token
7. Re-run: ansible-playbook -i inventory.yml playbook.yml
============================================================ ============================================================

View File

@ -44,3 +44,11 @@
# NetBird Dashboard (catch-all must be last) # NetBird Dashboard (catch-all must be last)
reverse_proxy /* dashboard:80 reverse_proxy /* dashboard:80
} }
# =============================================================================
# Gitea
# =============================================================================
{{ gitea_domain }} {
import security_headers
reverse_proxy gitea:{{ gitea_http_port }}
}

View File

@ -96,13 +96,14 @@ services:
networks: networks:
- netbird - netbird
environment: environment:
- GITEA__server__DOMAIN={{ netbird_domain }} - GITEA__server__DOMAIN={{ gitea_domain }}
- GITEA__server__ROOT_URL=http://{{ netbird_domain }}:{{ gitea_http_port }} - GITEA__server__ROOT_URL=https://{{ gitea_domain }}
- GITEA__server__SSH_DOMAIN={{ gitea_domain }}
- GITEA__database__DB_TYPE=sqlite3 - GITEA__database__DB_TYPE=sqlite3
- GITEA__actions__ENABLED=true
volumes: volumes:
- gitea_data:/data - gitea_data:/data
ports: ports:
- "{{ gitea_http_port }}:3000"
- "{{ gitea_ssh_port }}:22" - "{{ gitea_ssh_port }}:22"
logging: logging:
driver: json-file driver: json-file

View File

@ -2,11 +2,9 @@ NETBIRD_API_URL=http://management:80/api
NETBIRD_API_TOKEN={{ vault_netbird_api_token }} NETBIRD_API_TOKEN={{ vault_netbird_api_token }}
RECONCILER_TOKEN={{ vault_reconciler_token }} RECONCILER_TOKEN={{ vault_reconciler_token }}
GITEA_ENABLED={{ gitea_enabled }} GITEA_ENABLED={{ gitea_enabled }}
{% if gitea_enabled == "true" %}
GITEA_URL=http://gitea:{{ gitea_http_port }} GITEA_URL=http://gitea:{{ gitea_http_port }}
GITEA_TOKEN={{ vault_gitea_token }} GITEA_TOKEN={{ vault_gitea_token }}
GITEA_REPO={{ gitea_org_name }}/{{ gitea_repo_name }} GITEA_REPO={{ gitea_org_name }}/{{ gitea_repo_name }}
{% endif %}
POLL_INTERVAL_SECONDS=30 POLL_INTERVAL_SECONDS=30
PORT={{ reconciler_port }} PORT={{ reconciler_port }}
DATA_DIR=/data DATA_DIR=/data

60
state/test.json Normal file
View File

@ -0,0 +1,60 @@
{
"groups": {
"ground-stations": {
"peers": []
},
"pilots": {
"peers": []
}
},
"setup_keys": {
"GS-TestHawk-1": {
"type": "one-off",
"expires_in": 604800,
"usage_limit": 1,
"auto_groups": [
"ground-stations"
],
"enrolled": false
},
"Pilot-TestHawk-1": {
"type": "one-off",
"expires_in": 604800,
"usage_limit": 1,
"auto_groups": [
"pilots"
],
"enrolled": false
}
},
"policies": {
"pilots-to-gs": {
"description": "",
"enabled": true,
"sources": [
"pilots"
],
"destinations": [
"ground-stations"
],
"bidirectional": true,
"protocol": "all",
"action": "accept",
"source_posture_checks": []
}
},
"posture_checks": {},
"networks": {},
"peers": {},
"users": {
"admin@example.com": {
"name": "admin",
"role": "owner",
"auto_groups": []
}
},
"routes": {},
"dns": {
"nameserver_groups": {}
}
}