Files

103 lines
5.4 KiB
Markdown
Raw Permalink Normal View History

# Linux Dev Station - dev-02.lab.alexpires.me
## Overview
Provisioned `dev-02.lab.alexpires.me` (Fedora, `10.19.4.210`) as a remote dev station mirroring the macOS setup. Includes code-server, opencode, and dev toolchain management. Exposed via nginx reverse proxy with TLS termination and OAuth2/OIDC SSO (Gitea as IdP).
## Role: `linux_dev_station`
- **Location:** `ansible/roles/linux_dev_station/`
- **Scope:** Fedora only (`ansible_facts['distribution'] == "Fedora"`)
- **Package manager:** dnf for system packages; tool-managed (uv, rustup, fnm) for dev toolchains
- **Excludes:** podman/container runtime management
### Components
- **SSH hardening:** Public key auth, password auth disabled, root login disabled
- **Users:** `devstation` user with sudo, zsh shell, and required groups
- **Zsh:** Oh My Zsh with agnoster theme, plugins, custom env vars, and zshrc management
- **Tooling:**
- `uv` - Python package manager
- `rustup` - Rust toolchain
- `fnm` + `node` - Node.js version manager
- `code-server` - VS Code remote on `127.0.0.1:3000`
- `opencode` - AI coding assistant (HTTP server on `127.0.0.1:4096`)
- **Firewall:** firewalld with `fw_allowed_ports` (22, 443)
- **Systemd services:** code-server, opencode
### Opencode Details
- **Repo:** `anomalyco/opencode` (not `opencode-ai`)
- **Version:** `1.17.18`
- **Binary:** `opencode-linux-x64.tar.gz`
- **Mode:** HTTP server (`serve`), binds to `127.0.0.1`
- **Install strategy:** Versioned directories (`~/.local/lib/tool-vX.Y.Z`) + symlinks for idempotent upgrades
### Oh My Zsh Plugins (Linux)
git, fzf, common-aliases, sudo, tmux, extract, colored-man-pages, history-substring-search, kubectl, microk8s, docker, docker-compose, ansible, aws, golang, dnf, bgnotify
## macOS Role Updates
- **Location:** `ansible/roles/macos_dev_station/`
- Synced Oh My Zsh fixes: sourcing, theme, plugins, extra env vars
- **Oh My Zsh Plugins (macOS):** git, fzf, common-aliases, sudo, tmux, extract, colored-man-pages, history-substring-search, kubectl, docker, docker-compose, ansible, aws, golang, brew, bgnotify
## TLS + OAuth2/OIDC SSO (`nginx_proxy` role)
- **Location:** `ansible/roles/nginx_proxy/` — standalone, generic role not tied to `linux_dev_station`
- **Playbook:** `ansible/playbook_nginx_proxy.yml` targets `[nginx]` group
- **Architecture:** nginx (:443, TLS) → oauth2-proxy (:4180, session mgmt) → backends (127.0.0.1 only)
- **Certificate:** SAN cert for `ide.lab.alexpires.me` + `agent.lab.alexpires.me` via certbot DNS-01 (cloudns plugin), auto-renewal cron
- **SSO cookie:** Scoped to `.lab.alexpires.me`
- **IdP:** Gitea (`code.alexpires.me`), OAuth2 app `lab-services` (confidential client, scopes: `openid`, `profile`, `email`)
- **oauth2-proxy:** v7.15.3, config format: `oidc_issuer_url`, `http_address`, `upstreams` array, `cookie_domains` array, `proxy_websockets = true`, `reverse_proxy = true`
- **Services exposed:**
- `https://ide.lab.alexpires.me` → code-server (127.0.0.1:3000)
- `https://agent.lab.alexpires.me` → opencode (127.0.0.1:4096)
- **Nginx fixes applied:** `types_hash_bucket_size 64;`, ssl_ciphers single line, http2 separate directive, SAN cert shared via first site directory, POST auth_request body fix (`proxy_set_header Content-Length "";`), proxy_buffer_size for oauth2 callback
### Defaults
- `nginx_proxy_oauth2_proxy_version: "7.15.3"`
- `nginx_proxy_cloudns_nameserver: "109.201.133.111"`
- `nginx_proxy_sites` — list of site configs with server_name, upstream, websocket toggle
### Secrets
- `OAUTH2_PROXY_COOKIE_SECRET` — 32-byte random, stored in vault
- `OAUTH2_PROXY_CLIENT_ID` / `OAUTH2_PROXY_CLIENT_SECRET` — from Gitea OAuth2 app
- `CLOUDNS_AUTH_ID` / `CLOUDNS_PASSWORD` — DNS credentials for certbot
## Infrastructure Changes
- **DNS:**
- Added A record for `dev-02.lab.alexpires.me` (`10.19.4.210`)
- Added A records for `ide` and `agent``10.19.4.210` (in `terraform/apps/dev-01/config.tf`)
- **Inventory:** Added `[nginx]` group with `dev-02.lab.alexpires.me`; dev-02 removed from `[certbot]` group
- **Host vars:** `ansible/host_vars/dev-02.lab.alexpires.me.yml` with `nginx_proxy_sites`, `bind_to_localhost: true`, firewall rules (22, 443), OAuth2 credentials via `lookup('env', ...)`
- **dev-01 cleanup:** Removed `opencode`/`vscode` CNAME records and `proxy_services` entries (no longer proxying to dev-02)
## Service Bindings
| Service | Binding |
|---------|---------|
| code-server | `127.0.0.1:3000` |
| opencode | `127.0.0.1:4096` |
| oauth2-proxy | `127.0.0.1:4180` |
| nginx | `0.0.0.0:443` |
## Firewall
| Port | Access |
|------|--------|
| 22 | `10.19.4.0/24` + VPN (`10.5.5.5/32`) |
| 443 | `10.19.4.0/24` + VPN (`10.5.5.5/32`) |
| 3000/4096 | Closed (nginx handles all external access) |
## SELinux
- http_port_t extended to include 4180 (oauth2-proxy)
- httpd_can_network_connect enabled
- `/etc/letsencrypt` context set to `httpd_cert_t`
## Known Issues & Workarounds
- **ssh-agent systemd:** Removed due to SELinux blocking transitions; managed via `.zshrc` startup instead
- **ACL issue:** `allow_world_readable_tmpfiles = True` required in `ansible.cfg` for macOS→Fedora `become_user` compatibility
## Required Credentials
- `GITEA_APP_TOKEN`
- `OPEN_WEBUI_API_KEY`
- `CLOUDNS_AUTH_ID` / `CLOUDNS_PASSWORD` (for certbot DNS-01)
- `OAUTH2_PROXY_COOKIE_SECRET` / `CLIENT_ID` / `CLIENT_SECRET` (for nginx_proxy role)
## Ansible.cfg Fix
Added `allow_world_readable_tmpfiles = True` under `[defaults]` to fix `become_user` failures on Fedora when running from macOS.