docs: migrate Authentik (SSO keystone) to edge2 CT 105
- Authentik -> edge2 CT 105 (Postgres pg_dump/restore; SECRET_KEY carried verbatim; zero-downtime until ~2s cutover) - Multi-block Caddy cutover: auth.echo6.co + notes.echo6.co outpost/forward_auth -> 100.64.0.36:9000 - runbook: add reboot tailscale-before-docker gotcha; clarify dnsmasq must NOT be repointed (points at Caddy host) - source left stopped + intact on Contabo as cold rollback Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
30f70793f8
commit
44f0257376
140 changed files with 4013 additions and 24 deletions
180
vault/runbooks/expose-service-edge2.md
Normal file
180
vault/runbooks/expose-service-edge2.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# Expose Service on edge2 (Contabo Cloud VPS)
|
||||
|
||||
## Context
|
||||
|
||||
edge2 is a Proxmox VE 8 node (184.174.35.153 / 100.64.0.26) running LXC containers on an internal bridge (`vmbr0`, subnet `10.10.10.0/24`, gateway `10.10.10.1`). Services run inside unprivileged LXC containers. Caddy on the edge2 host terminates TLS and reverse-proxies to the container's internal IP.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- SSH access to edge2: `ssh edge2` (admin@100.64.0.26, key auth, passwordless sudo)
|
||||
- Debian 13 CT template cached: `local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst`
|
||||
- DNS provider access (Cloudflare, GoDaddy, etc.)
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Create the LXC container
|
||||
|
||||
Pick the next available CTID (current: 100=pdm, 101=wordpress). All CTs use static IPs on the `10.10.10.0/24` subnet.
|
||||
|
||||
```bash
|
||||
ssh edge2
|
||||
|
||||
sudo pct create <CTID> local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
|
||||
--hostname <hostname> \
|
||||
--rootfs local:<DISK_GB> \
|
||||
--memory <RAM_MB> \
|
||||
--swap 512 \
|
||||
--cores <CORES> \
|
||||
--net0 name=eth0,bridge=vmbr0,ip=10.10.10.<X>/24,gw=10.10.10.1 \
|
||||
--nameserver 1.1.1.1 \
|
||||
--unprivileged 1 \
|
||||
--features nesting=1 \
|
||||
--onboot 1 \
|
||||
--start 1
|
||||
|
||||
# Verify
|
||||
sudo pct list
|
||||
sudo pct exec <CTID> -- ping -c1 1.1.1.1
|
||||
```
|
||||
|
||||
### 2. Install the service inside the container
|
||||
|
||||
```bash
|
||||
sudo pct exec <CTID> -- bash
|
||||
# ... install service, bind to port on 0.0.0.0 or 10.10.10.<X>
|
||||
# ... the host will reach the CT via the internal bridge
|
||||
```
|
||||
|
||||
For file transfer into the CT:
|
||||
```bash
|
||||
# From edge2 host
|
||||
sudo pct push <CTID> /path/on/host /path/in/ct
|
||||
```
|
||||
|
||||
### 3. Add Caddy site block on edge2 host
|
||||
|
||||
Caddy runs on the edge2 host and terminates TLS.
|
||||
|
||||
**For Cloudflare-proxied domains** (orange cloud / Full SSL mode):
|
||||
```bash
|
||||
# tls internal generates a self-signed cert — Cloudflare "Full" mode
|
||||
# encrypts transit without needing a publicly-trusted cert
|
||||
sudo tee -a /etc/caddy/Caddyfile << 'EOF'
|
||||
|
||||
<domain> {
|
||||
tls internal
|
||||
reverse_proxy 10.10.10.<X>:<PORT>
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo caddy validate --config /etc/caddy/Caddyfile
|
||||
sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
**For non-Cloudflare domains** (needs real cert):
|
||||
```bash
|
||||
# Caddy will auto-provision a Let's Encrypt cert
|
||||
sudo tee -a /etc/caddy/Caddyfile << 'EOF'
|
||||
|
||||
<domain> {
|
||||
reverse_proxy 10.10.10.<X>:<PORT>
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo caddy validate --config /etc/caddy/Caddyfile
|
||||
sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
### 4. Configure DNS
|
||||
|
||||
**GoDaddy domains:**
|
||||
```bash
|
||||
# On cortex/toc
|
||||
source /home/zvx/projects/.ref/credentials
|
||||
godaddy-dns.py add-a <zone> <subdomain> 184.174.35.153
|
||||
```
|
||||
|
||||
**Cloudflare domains:**
|
||||
Manual via Cloudflare dashboard (no API token):
|
||||
1. A record → 184.174.35.153 (proxied / orange cloud)
|
||||
2. SSL/TLS mode → Full (not Strict — since `tls internal` uses self-signed)
|
||||
3. Optional: www CNAME → domain (proxied)
|
||||
|
||||
### 5. Verify
|
||||
|
||||
```bash
|
||||
# From edge2 (proper SNI required for tls internal)
|
||||
curl -sk --resolve <domain>:443:127.0.0.1 -o /dev/null -w "%{http_code}\n" https://<domain>/
|
||||
|
||||
# From cortex (direct to origin)
|
||||
curl -sk --resolve <domain>:443:184.174.35.153 -o /dev/null -w "%{http_code}\n" https://<domain>/
|
||||
|
||||
# Via DNS (after propagation)
|
||||
curl -I https://<domain>/
|
||||
```
|
||||
|
||||
### 6. Update docs
|
||||
|
||||
- Add CT to `environment.md` LXC Containers table
|
||||
- Add credentials to `.ref/credentials`
|
||||
- Add Tailscale IP if Tailscale is installed in the CT
|
||||
|
||||
## Checklist
|
||||
|
||||
```
|
||||
□ CT created, started, networking verified (ping 1.1.1.1)
|
||||
□ Service installed and running inside CT
|
||||
□ Caddy site block added and reloaded on edge2 host
|
||||
□ DNS record pointing to 184.174.35.153
|
||||
□ SSL mode correct (Full for Cloudflare, auto for others)
|
||||
□ HTTPS verified from edge2 + externally
|
||||
□ Docs updated (environment.md, credentials)
|
||||
```
|
||||
|
||||
## Network Reference
|
||||
|
||||
| Resource | Value |
|
||||
|----------|-------|
|
||||
| edge2 public IP | 184.174.35.153 |
|
||||
| edge2 Tailscale | 100.64.0.26 |
|
||||
| Internal bridge | vmbr0, 10.10.10.0/24 |
|
||||
| Gateway | 10.10.10.1 (edge2 host) |
|
||||
| DNS in CTs | 1.1.1.1 |
|
||||
| CT IP range | 10.10.10.10+ (10=pdm, 11=wordpress) |
|
||||
|
||||
## CT Creation via PVE API (alternative)
|
||||
|
||||
When SSH is unavailable, CTs can be created via the PVE REST API:
|
||||
|
||||
```python
|
||||
import urllib.request, urllib.parse, json, ssl
|
||||
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
base = "https://100.64.0.26:8006/api2/json"
|
||||
|
||||
# Authenticate
|
||||
data = urllib.parse.urlencode({"username": "root@pam", "password": "<EDGE2_ROOT_PASSWORD>"}).encode()
|
||||
req = urllib.request.Request(f"{base}/access/ticket", data=data)
|
||||
resp = urllib.request.urlopen(req, context=ctx)
|
||||
auth = json.loads(resp.read())["data"]
|
||||
ticket, csrf = auth["ticket"], auth["CSRFPreventionToken"]
|
||||
|
||||
# Create CT
|
||||
params = urllib.parse.urlencode({
|
||||
"vmid": <CTID>, "hostname": "<hostname>",
|
||||
"ostemplate": "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst",
|
||||
"cores": 2, "memory": 2048, "swap": 512, "rootfs": "local:16",
|
||||
"net0": "name=eth0,bridge=vmbr0,ip=10.10.10.<X>/24,gw=10.10.10.1",
|
||||
"nameserver": "1.1.1.1", "unprivileged": 1, "features": "nesting=1",
|
||||
"onboot": 1, "start": 1,
|
||||
}).encode()
|
||||
req = urllib.request.Request(f"{base}/nodes/edge2/lxc", data=params, method="POST")
|
||||
req.add_header("Cookie", f"PVEAuthCookie={ticket}")
|
||||
req.add_header("CSRFPreventionToken", csrf)
|
||||
resp = urllib.request.urlopen(req, context=ctx)
|
||||
print(json.loads(resp.read())["data"]) # UPID of creation task
|
||||
```
|
||||
|
||||
Note: The PVE API uses `root@pam` auth. This is separate from the system SSH user (`admin`). See credentials file for the PVE password.
|
||||
Loading…
Add table
Add a link
Reference in a new issue