266 lines
8.0 KiB
Markdown
266 lines
8.0 KiB
Markdown
# Server Infrastructure — Implementation Guide
|
|
|
|
Ansible-based provisioning and deployment for homelab web servers.
|
|
|
|
## Architecture
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
subgraph provision ["Provisioning (manual / rare)"]
|
|
Desktop1["Ubuntu Desktop\n(control node)"]
|
|
Desktop1 -->|ansible-playbook site.yml| Adama
|
|
Desktop1 -->|ansible-playbook site.yml| Roslin
|
|
Desktop1 -->|ansible-playbook site.yml| DesktopTarget
|
|
end
|
|
|
|
subgraph cicd ["CI/CD (every merge to master)"]
|
|
Gitea["Gitea push"]
|
|
Gitea --> Tests["Act: unit tests"]
|
|
Tests --> Deploy["Act: deploy job"]
|
|
Deploy --> AnsibleDeploy["ansible-playbook deploy-apps.yml"]
|
|
AnsibleDeploy --> Adama2["adama"]
|
|
AnsibleDeploy --> Roslin2["roslin"]
|
|
AnsibleDeploy --> Desktop2["desktop"]
|
|
end
|
|
```
|
|
|
|
| Pipeline | When | Playbook | Where it runs |
|
|
|----------|------|----------|---------------|
|
|
| **Provision** | New VM, OS change, firewall, Docker install | `site.yml` | Desktop — run manually |
|
|
| **Deploy** | Green unit tests on `master` | `deploy-apps.yml` | Gitea Act runner on desktop |
|
|
|
|
Both pipelines share the same inventory (`inventory/hosts.yml`).
|
|
|
|
## Servers
|
|
|
|
| Name | IP | Role |
|
|
|------|-----|------|
|
|
| adama | 10.0.0.77 | Ubuntu Server VM (Proxmox) |
|
|
| roslin | 10.0.0.176 | Ubuntu Server VM (Proxmox) |
|
|
| desktop | *see `host_vars/desktop.yml`* | Ubuntu Desktop — control node + deployment target |
|
|
|
|
Hostname on this machine: `ryan-development-1`
|
|
|
|
## Repo Layout
|
|
|
|
```
|
|
server-infra/
|
|
├── IMPLEMENTATION.md # This file
|
|
├── README.md # Quick start
|
|
├── ansible.cfg
|
|
├── requirements.yml # Ansible Galaxy collections
|
|
├── inventory/
|
|
│ ├── hosts.yml
|
|
│ ├── group_vars/
|
|
│ │ ├── all.yml
|
|
│ │ └── webservers.yml
|
|
│ └── host_vars/
|
|
│ └── desktop.yml
|
|
├── playbooks/
|
|
│ ├── site.yml # Phase 1: provision
|
|
│ └── deploy-apps.yml # Phase 2: CI deploy (stub)
|
|
├── roles/
|
|
│ ├── common/ # Base packages
|
|
│ ├── ufw/ # Firewall
|
|
│ ├── docker/ # Docker CE + compose plugin
|
|
│ └── app-deploy/ # App deploy (stub for Phase 2)
|
|
└── scripts/
|
|
├── provision.sh # Wrapper with --limit support
|
|
└── deploy.sh # Wrapper for deploy playbook
|
|
```
|
|
|
|
## Prerequisites (One-Time Bootstrap)
|
|
|
|
Ansible needs SSH + sudo on each target before playbooks work.
|
|
|
|
1. Create `westfarn` on each VM with sudo membership.
|
|
2. Copy your SSH public key from the desktop:
|
|
```bash
|
|
ssh-copy-id westfarn@10.0.0.77
|
|
ssh-copy-id westfarn@10.0.0.176
|
|
```
|
|
3. Confirm passwordless SSH:
|
|
```bash
|
|
ssh westfarn@10.0.0.77
|
|
ssh westfarn@10.0.0.176
|
|
```
|
|
4. On the desktop (control node), install Ansible:
|
|
```bash
|
|
sudo apt update && sudo apt install -y ansible
|
|
# or: pip install ansible
|
|
```
|
|
5. Install Galaxy collections:
|
|
```bash
|
|
cd ~/Documents/repos/server-infra
|
|
ansible-galaxy collection install -r requirements.yml
|
|
```
|
|
6. Update `inventory/host_vars/desktop.yml` with this machine's LAN IP.
|
|
|
|
## Testing on a Single Server
|
|
|
|
Use `--limit` to target one host without touching the others. Helper scripts wrap this.
|
|
|
|
### Ping one host
|
|
|
|
```bash
|
|
./scripts/provision.sh adama --check # dry run
|
|
ansible adama -m ping
|
|
```
|
|
|
|
### Provision one host
|
|
|
|
```bash
|
|
# Dry run (no changes)
|
|
./scripts/provision.sh adama --check
|
|
|
|
# Apply for real
|
|
./scripts/provision.sh adama
|
|
|
|
# Same for other hosts
|
|
./scripts/provision.sh roslin
|
|
./scripts/provision.sh desktop
|
|
```
|
|
|
|
### Provision all hosts
|
|
|
|
```bash
|
|
./scripts/provision.sh
|
|
```
|
|
|
|
### Deploy to one host (Phase 2)
|
|
|
|
```bash
|
|
./scripts/deploy.sh adama
|
|
./scripts/deploy.sh --check roslin
|
|
```
|
|
|
|
Under the hood, scripts pass `--limit <hostname>` to `ansible-playbook`.
|
|
|
|
## Phase 1: Provision (`site.yml`)
|
|
|
|
Applies roles in order to the `webservers` group:
|
|
|
|
| Role | Purpose |
|
|
|------|---------|
|
|
| `common` | apt update, git, python3, pip, curl, ca-certificates |
|
|
| `ufw` | Firewall: SSH from LAN only, HTTP/HTTPS public |
|
|
| `docker` | Docker CE, compose plugin, add `westfarn` to `docker` group |
|
|
|
|
### UFW rules
|
|
|
|
| Port | Source | Purpose |
|
|
|------|--------|---------|
|
|
| 22 | `10.0.0.0/24` | SSH (LAN only) |
|
|
| 80 | anywhere | HTTP |
|
|
| 443 | anywhere | HTTPS |
|
|
| default | deny incoming | Block everything else |
|
|
|
|
**Warning:** Test UFW on one host first (`./scripts/provision.sh adama`). Keep a Proxmox console session open in case SSH rules lock you out.
|
|
|
|
After Docker install, re-SSH so the `docker` group membership takes effect.
|
|
|
|
## Phase 2: CI Deploy (`deploy-apps.yml`)
|
|
|
|
Not fully implemented yet. Planned flow:
|
|
|
|
1. Gitea push triggers unit tests.
|
|
2. On success, Act runner on desktop runs `deploy-apps.yml`.
|
|
3. Ansible fans out to all `webservers` hosts.
|
|
|
|
### Planned `company_site` workflow change
|
|
|
|
```yaml
|
|
# company_site/.gitea/workflows/deploy.yml (future)
|
|
jobs:
|
|
deploy:
|
|
runs-on: self-hosted
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ gitea.event.workflow_run.head_sha }}
|
|
|
|
- name: Deploy to all webservers
|
|
run: |
|
|
~/Documents/repos/server-infra/scripts/deploy.sh \
|
|
--extra-vars "app_ref=${{ gitea.event.workflow_run.head_sha }}"
|
|
```
|
|
|
|
### Planned `app-deploy` role (post-dockerize)
|
|
|
|
Per host:
|
|
|
|
1. Clone or pull app repo at pinned SHA.
|
|
2. `docker compose pull && docker compose up -d`.
|
|
3. Optional health check.
|
|
|
|
Pre-dockerize interim: role can rsync/systemd like current `company_site/scripts/deploy.sh`.
|
|
|
|
## Gitea Act Runner
|
|
|
|
**Recommended:** Single self-hosted runner on the desktop.
|
|
|
|
- One build artifact, one orchestration point.
|
|
- VMs only run containers; no runner needed on them for deploy fan-out.
|
|
- Runner needs: Ansible, this repo checked out, SSH key to all hosts, vault password (later).
|
|
|
|
### Runner requirements on desktop
|
|
|
|
| Requirement | Why |
|
|
|-------------|-----|
|
|
| Ansible | Run `deploy-apps.yml` |
|
|
| `server-infra` checkout | Playbooks + inventory |
|
|
| SSH key to all hosts | Including loopback to desktop |
|
|
| Docker | Build images before push to hosts (Phase 2) |
|
|
|
|
## SSH Keys for CI Deploy
|
|
|
|
| Key | Used by | Purpose |
|
|
|-----|---------|---------|
|
|
| Personal key | You | Manual provisioning |
|
|
| Deploy key (runner) | Act → Ansible → hosts | Automated deploy |
|
|
|
|
Consider a dedicated `deploy` user with limited sudo (docker only) — future hardening step.
|
|
|
|
## Secrets (Phase 2)
|
|
|
|
Use Ansible Vault for production secrets. Do not commit plaintext.
|
|
|
|
```bash
|
|
ansible-vault create inventory/group_vars/webservers/vault.yml
|
|
ansible-playbook playbooks/site.yml --ask-vault-pass
|
|
```
|
|
|
|
Store vault password for CI in a file readable only by the Act runner (e.g. `~/.ansible-vault-pass`, mode 600).
|
|
|
|
## Implementation Order
|
|
|
|
| # | Task | Status |
|
|
|---|------|--------|
|
|
| 1 | Create `server-infra` repo | Done |
|
|
| 2 | Inventory with all 3 hosts | Done — update desktop IP |
|
|
| 3 | Bootstrap SSH to adama + roslin | Manual |
|
|
| 4 | `site.yml` → common, ufw, docker | Done |
|
|
| 5 | Verify `ansible webservers -m ping` | Manual |
|
|
| 6 | Test on single server: `./scripts/provision.sh adama` | Manual |
|
|
| 7 | Provision all: `./scripts/provision.sh` | Manual |
|
|
| 8 | Deploy SSH key for Act runner | Future |
|
|
| 9 | Stub `deploy-apps.yml` + update `company_site` workflow | Future |
|
|
| 10 | Dockerize `company_site` | Future (separate ticket) |
|
|
| 11 | Gitea container registry (optional) | Future |
|
|
|
|
## Open Decisions
|
|
|
|
1. **Desktop LAN IP** — set in `inventory/host_vars/desktop.yml`.
|
|
2. **Same app on all three?** — prod mirror vs adama=prod / roslin=staging / desktop=dev.
|
|
3. **Deploy user** — `westfarn` vs dedicated `deploy` for CI.
|
|
4. **Gitea URL** — for clone URLs in `app-deploy` role.
|
|
5. **Reverse proxy** — Caddy/nginx on host before containers? Affects Phase 2.
|
|
|
|
## Adding a New VM
|
|
|
|
1. Add host to `inventory/hosts.yml` under `webservers`.
|
|
2. Bootstrap SSH: `ssh-copy-id westfarn@<new-ip>`.
|
|
3. Test: `./scripts/provision.sh <hostname> --check`.
|
|
4. Provision: `./scripts/provision.sh <hostname>`.
|
|
5. Deploys automatically include new host once in `webservers` group.
|