reorg: split into infra/ services/ history/ ideas/
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
# Dev Environment — CT 111 (dev.nuclide.systems)
|
||||
|
||||
CT 111 hosts the self-hosted development platform: **Coder** (dev workspaces) + **Gitea** (internal repos).
|
||||
|
||||
## Services
|
||||
|
||||
| Service | URL | Port |
|
||||
|---|---|---|
|
||||
| Coder | https://dev.nuclide.systems | 7080 |
|
||||
| Gitea | https://git.nuclide.systems | 3000 |
|
||||
|
||||
Both use **Pocket-ID OIDC** (`https://id.nuclide.systems`) for SSO.
|
||||
|
||||
---
|
||||
|
||||
## Coder — Dev Workspaces
|
||||
|
||||
### What it is
|
||||
Coder provisions isolated Docker-based dev environments (workspaces) on CT 111. Each workspace has:
|
||||
- A full Linux environment with your tools
|
||||
- Persistent home dir on UNAS (`/mnt/pve/unas/services/coder/`)
|
||||
- Intel Arc GPU renderD128 available
|
||||
- VS Code Server (browser or desktop SSH tunnel)
|
||||
|
||||
### First-time setup
|
||||
1. Go to `https://dev.nuclide.systems` → log in via Pocket-ID
|
||||
2. Create a workspace from a template (admin must create templates first)
|
||||
3. Connect via VS Code: install **Coder extension** → sign in → open workspace
|
||||
|
||||
### VS Code connection
|
||||
```bash
|
||||
# Install Coder CLI on your local machine
|
||||
curl -fsSL https://coder.com/install.sh | sh
|
||||
|
||||
# Authenticate
|
||||
coder login https://dev.nuclide.systems
|
||||
|
||||
# Open workspace in VS Code
|
||||
coder open <workspace-name>
|
||||
```
|
||||
Or use the Coder VS Code extension directly from the marketplace (`coder.coder-remote`).
|
||||
|
||||
### Claude Code inside a workspace
|
||||
```bash
|
||||
# Inside the workspace terminal
|
||||
npm install -g @anthropic/claude-code
|
||||
claude
|
||||
```
|
||||
Claude Code runs inside the workspace container — same environment, same files, same GPU.
|
||||
|
||||
### Coder MCP (AI agent sandbox execution)
|
||||
Add to Claude Code's MCP config (`~/.claude/claude_desktop_config.json` or via `/mcp add`):
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"coder": {
|
||||
"command": "coder",
|
||||
"args": ["mcp", "server"],
|
||||
"env": {
|
||||
"CODER_URL": "https://dev.nuclide.systems",
|
||||
"CODER_TOKEN": "<your-api-token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Claude can then create workspaces, execute code, and read output via MCP tools:
|
||||
- `coder_list_workspaces`
|
||||
- `coder_create_workspace`
|
||||
- `coder_execute_command` ← sandbox code execution
|
||||
- `coder_start_workspace` / `coder_stop_workspace`
|
||||
|
||||
Get your API token: `coder tokens create`
|
||||
|
||||
### Creating workspace templates
|
||||
Templates are Terraform configs stored in Gitea. Basic Docker template:
|
||||
```bash
|
||||
coder templates push <template-name> --directory ./template/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gitea — Internal Repos
|
||||
|
||||
### What it is
|
||||
Self-hosted Git for internal infrastructure: compose files, CT configs, dotfiles, Coder templates.
|
||||
**Not** the primary remote for Claude Code collaboration — use GitHub for that.
|
||||
|
||||
### First-time setup (admin)
|
||||
1. Go to `https://git.nuclide.systems` → complete installation wizard
|
||||
2. Set admin account, confirm DB settings (pre-filled from env)
|
||||
3. Add OIDC provider: Admin → Site Administration → Authentication Sources
|
||||
- Auth type: OAuth2
|
||||
- Provider: OpenID Connect
|
||||
- Discovery URL: `https://id.nuclide.systems/.well-known/openid-configuration`
|
||||
- Client ID/Secret: create a new client in Pocket-ID for Gitea
|
||||
|
||||
### SSH access
|
||||
```bash
|
||||
# Gitea SSH runs on port 222
|
||||
git clone ssh://git@git.nuclide.systems:222/<user>/<repo>.git
|
||||
|
||||
# Or add to ~/.ssh/config:
|
||||
Host git.nuclide.systems
|
||||
Port 222
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
### Recommended repos to create
|
||||
| Repo | Contents |
|
||||
|---|---|
|
||||
| `infra/proxmox` | `/etc/pve/` snapshots, CT configs |
|
||||
| `infra/stacks` | Compose files from CT 104/101/111 |
|
||||
| `infra/docs` | Mirror of `/docs/` on Proxmox host |
|
||||
| `dev/templates` | Coder workspace Terraform templates |
|
||||
|
||||
---
|
||||
|
||||
## Storage layout (UNAS)
|
||||
|
||||
```
|
||||
/mnt/pve/unas/services/
|
||||
├── coder/ # Coder workspace home dirs (persistent)
|
||||
└── gitea/ # Gitea repos + data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CT 111 specs
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| IP | 192.168.1.42 |
|
||||
| Cores | 12 |
|
||||
| RAM | 32GB |
|
||||
| Rootfs | 60GB local-zfs (Docker image cache) |
|
||||
| UNAS | /mnt/pve/unas (mp0) |
|
||||
| GPU | renderD128 (Intel Arc Xe, by-path) |
|
||||
|
||||
---
|
||||
|
||||
## OIDC clients in Pocket-ID
|
||||
|
||||
| Client | Callback URL |
|
||||
|---|---|
|
||||
| Coder | `https://dev.nuclide.systems/api/v2/users/oidc/callback` |
|
||||
| Gitea | Add via Gitea admin UI (see above) |
|
||||
|
||||
To create new OIDC clients programmatically, see `/docs/proxmox-optimizations.md` § OIDC client creation via SQLite.
|
||||
|
||||
---
|
||||
|
||||
## Auth lockdown (SSO-only)
|
||||
|
||||
Both Gitea and Coder are locked to **Pocket-ID OIDC only**. Local password and GitHub login are disabled. Passkey/WebAuthn login *to Gitea* remains available because it's tied to OIDC accounts, not to a separate password.
|
||||
|
||||
### Compose env flags
|
||||
|
||||
**Coder** (`/opt/stacks/coder/compose.yaml`):
|
||||
```yaml
|
||||
CODER_OIDC_ISSUER_URL: "https://id.nuclide.systems"
|
||||
CODER_OIDC_CLIENT_ID: "${CODER_OIDC_CLIENT_ID}"
|
||||
CODER_OIDC_CLIENT_SECRET: "${CODER_OIDC_CLIENT_SECRET}"
|
||||
CODER_OIDC_ALLOW_SIGNUPS: "true"
|
||||
CODER_OIDC_EMAIL_DOMAIN: "nucli.de"
|
||||
CODER_DISABLE_PASSWORD_AUTH: "true"
|
||||
CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE: "false"
|
||||
```
|
||||
Note: the bundled "GitHub external auth provider" log line is for *workspaces* cloning from GitHub, not for login — leaving it on is fine.
|
||||
|
||||
**Gitea** (`/opt/stacks/gitea/compose.yaml`):
|
||||
```yaml
|
||||
GITEA__oauth2__ENABLED: "true"
|
||||
GITEA__openid__ENABLE_OPENID_SIGNIN: "false" # legacy OpenID 2.0 button off
|
||||
GITEA__openid__ENABLE_OPENID_SIGNUP: "false"
|
||||
GITEA__service__ENABLE_PASSWORD_SIGNIN_FORM: "false" # local username/password form off
|
||||
GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
|
||||
GITEA__oauth2_client__ACCOUNT_LINKING: "auto"
|
||||
GITEA__oauth2_client__USERNAME: "preferred_username"
|
||||
GITEA__oauth2_client__UPDATE_AVATAR: "true"
|
||||
```
|
||||
Note: `ENABLE_OPENID_SIGNIN` lives in `[openid]`, *not* `[service]`. Putting it under `GITEA__service__` is a no-op and leaves the legacy button visible.
|
||||
|
||||
Gitea login source (DB row in `login_source`):
|
||||
- `name = "pocket-id"` (case-sensitive — becomes part of the callback URL)
|
||||
- `Scopes = ["openid","profile","email"]`
|
||||
- `two_factor_policy = "skip"` (Pocket-ID passkey already enforces 2FA)
|
||||
|
||||
### Pocket-ID client secret pitfall (important)
|
||||
|
||||
Pocket-ID 2.7.0 verifies client secrets with **bcrypt** (`bcrypt.CompareHashAndPassword`). The stored `oidc_clients.secret` column must be a 60-char bcrypt hash like `$2a$10$...` or `$2b$10$...`.
|
||||
|
||||
A raw 64-char hex SHA-256 in that column **silently fails every token exchange** with `invalid client secret`. The Gitea and Coder clients on this host were initially provisioned that way and had to be regenerated.
|
||||
|
||||
To programmatically add or rotate an OIDC client secret in Pocket-ID:
|
||||
|
||||
```bash
|
||||
# On Proxmox host (has python3-bcrypt installed)
|
||||
python3 <<'EOF'
|
||||
import bcrypt, secrets, string
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
plain = "".join(secrets.choice(alphabet) for _ in range(40))
|
||||
hashed = bcrypt.hashpw(plain.encode(), bcrypt.gensalt(rounds=10)).decode()
|
||||
print("PLAINTEXT (give to client app):", plain)
|
||||
print("HASH (store in oidc_clients.secret):", hashed)
|
||||
EOF
|
||||
```
|
||||
|
||||
Always push the hash to Pocket-ID via a tmp file (`pct push 110 ...`) — never inline the bcrypt hash in a shell command, the `$` chars get expanded.
|
||||
|
||||
To verify a client's secret is the right format:
|
||||
```bash
|
||||
pct exec 110 -- sqlite3 /opt/stacks/pocketid/data/pocket-id.db \
|
||||
"SELECT name, length(secret), substr(secret,1,7) FROM oidc_clients;"
|
||||
# Working clients: length=60, prefix "$2a$10$" or "$2b$10$"
|
||||
# Broken clients : length=64, prefix is hex (e.g. "1180664")
|
||||
```
|
||||
|
||||
### Break-glass recovery (if SSO is broken)
|
||||
|
||||
You have host SSH access, so you're never locked out — but the UI will be unusable until you re-enable a local login path. From the Proxmox host:
|
||||
|
||||
**Gitea — re-enable local login + reset password:**
|
||||
```bash
|
||||
# 1. Temporarily put the form back
|
||||
ssh nuc
|
||||
python3 -c "
|
||||
p='/rpool/data/subvol-111-disk-0/opt/stacks/gitea/compose.yaml'
|
||||
s=open(p).read().replace('ENABLE_PASSWORD_SIGNIN_FORM: \"false\"','ENABLE_PASSWORD_SIGNIN_FORM: \"true\"')
|
||||
open(p,'w').write(s)"
|
||||
pct exec 111 -- bash -c 'cd /opt/stacks/gitea && docker compose up -d --force-recreate gitea'
|
||||
|
||||
# 2. Reset admin password
|
||||
pct exec 111 -- docker exec -u git gitea gitea -c /data/gitea/conf/app.ini admin user change-password -u fkrebs -p 'temp-strong-pass'
|
||||
```
|
||||
|
||||
**Coder — flip user back to password login + re-enable:**
|
||||
```bash
|
||||
ssh nuc
|
||||
# 1. Flip login_type back
|
||||
pct exec 111 -- docker exec coder-db psql -U coder -d coder -c \
|
||||
"UPDATE users SET login_type='password' WHERE username='fkrebs';"
|
||||
|
||||
# 2. Re-enable password auth in compose
|
||||
python3 -c "
|
||||
p='/rpool/data/subvol-111-disk-0/opt/stacks/coder/compose.yaml'
|
||||
s=open(p).read().replace('CODER_DISABLE_PASSWORD_AUTH: \"true\"','CODER_DISABLE_PASSWORD_AUTH: \"false\"')
|
||||
open(p,'w').write(s)"
|
||||
pct exec 111 -- bash -c 'cd /opt/stacks/coder && docker compose up -d --force-recreate coder'
|
||||
|
||||
# 3. Reset password (interactive)
|
||||
pct exec 111 -- docker exec -it coder coder reset-password fkrebs
|
||||
```
|
||||
|
||||
**Pocket-ID — bootstrap a one-time access token if you're locked out of *Pocket-ID itself*:**
|
||||
```bash
|
||||
pct exec 110 -- docker exec pocket-id pocket-id-cli one-time-access-token \
|
||||
--email fkrebs@nucli.de --duration 1h
|
||||
# Open the printed URL in a browser to log in once and register a new passkey.
|
||||
```
|
||||
|
||||
After SSO is fixed: undo each of the above steps (re-disable password auth, flip `login_type` back to `oidc`).
|
||||
Reference in New Issue
Block a user