AXP Magisk resurrection track: Speckit 009 port AXP/LOS22 to LOS23.

Ship Magisk like GOLDEN; KernelSU optional recycle only; parallel to sunfish-a16-rom standby.
This commit is contained in:
sunfish-admin committed 2026-09-16 21:56:22 +00:00
commit 12d2179c20
150 files changed
+26263

No files matched your search

@@ -0,0 +1,9 @@
# Contracts: N/A for 001
Feature `001-development-environment` is host/Git/service bootstrap.
It does **not** expose a project-owned HTTP API, CLI contract for external
consumers, or IPC schema that needs `contracts/` files.
Service access is operational (SSH tunnel to Gitea/Semaphore UIs) and is
documented in `plan.md` / `quickstart.md` instead.
@@ -0,0 +1,87 @@
# Data model (operational): 001-development-environment
This is **not** an application domain model. It records host/Git/service
entities for planning clarity. No ORM, no API schemas.
## Entities
### Host (`rom-dev01`)
| Field | Notes |
|-------|-------|
| OS | Ubuntu 24.04 LTS (precondition) |
| Capacity | ~6 vCPU / 52 GB / 2 TB / 32 GB swap |
| Network | Public IPv4 NIC; no required internal NIC |
| Management | External Salt `saltenv=base` + project config |
### Account `android-build`
| Field | Notes |
|-------|-------|
| Type | Unprivileged login user |
| Owns | `/srv/rom/{src,references,tooling,ccache,artifacts,logs}` (as documented) |
| Must not | Need root to compile; own Gitea/Semaphore system dirs |
### Meta working tree
| Path | Purpose |
|------|---------|
| `/home/android-build/sunfish-a16-rom` | Spec Kit + specs + docs (Cursor Remote SSH) |
### Path root `/srv/rom`
| Child | Purpose | Populated in 001? |
|-------|---------|-------------------|
| `src/` | Future Android trees | Empty (no sync) |
| `references/` | Future AXP/reference | Empty |
| `tooling/` | Project tooling helpers (not Spec Kit checkout) | Partial OK |
| `ccache/` | CCACHE_DIR | Yes (dir + env) |
| `artifacts/` | Laptop handoff | Dummy file for smoke |
| `logs/` | Bootstrap/preflight logs | Yes |
| `secrets/` | Restricted placeholder | Empty; no keygen |
### GiteaInstance
| Field | Notes |
|-------|-------|
| Bind | Loopback / non-public |
| Repos | `sunfish-a16-rom`, `sunfish-rom-manifest`, `sunfish-rom-build`, `sunfish-rom-patches` |
| Canonical | Yes after first successful push of meta repo |
### GitMirror (off-VM)
| Field | Notes |
|-------|-------|
| Location | Laptop bare mirrors (initial) |
| Direction | Laptop pulls (`clone --mirror` / `remote update`); no VM→laptop SSH |
| Scope | All four project repos |
| Success | At least one verified mirror cycle |
### AnsibleController / SemaphoreInstance
| Field | Notes |
|-------|-------|
| Host | `rom-dev01` |
| First job | Environment smoke playbook |
| Exposure | Same private access model as Gitea |
## Relationships
```text
AdminSSH ──manages──► Host packages/systemd/firewall
android-build ──writes──► /srv/rom (build-related trees)
GiteaInstance ──hosts──► four Repositories
Laptop ──mirrors──► Repositories (bare)
SemaphoreInstance ──runs──► Ansible smoke playbook on Host
Laptop Cursor ──RemoteSSH──► Host workspace
Laptop ──rsync──► /srv/rom/artifacts (pull)
```
## State transitions (meta Git)
1. `laptop-staging-only`
2. `copied-to-host` (pre-Gitea)
3. `gitea-canonical` (push complete)
4. `mirrored-off-vm` (SC-007)
Do not destroy state (1) until (3) and (4) succeed.
+476
View File
@@ -0,0 +1,476 @@
# Implementation Plan: Development environment on rom-dev01
**Branch**: `001-development-environment` | **Date**: 2026-08-09 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/001-development-environment/spec.md`
**Mode**: Documentation/design only until `rom-dev01` exists with working SSH.
No implementation, host mutation, or Android sync/build in this planning phase.
## Summary
Feature `001` prepares an already-existing Ubuntu 24.04 host (`rom-dev01`) as the
ROM development environment: unprivileged `android-build` account, `/srv/rom`
layout, Lineage/AOSP host toolchain + ccache, Spec Kit meta-repo bootstrap,
private Gitea (four repos) with off-VM Git history mirror, Ansible + Semaphore
smoke orchestration, thin-client laptop workflow, and guest isolation posture.
It does **not** create the Proxmox VM, sync LineageOS, build Android, or touch
the Pixel. Those belong to human Salt/PVE prep (`rom-dev01` prerequisite) and
features `002`/`003`+.
**Unlocks**: after P1, host is usable for work; after P2, Gitea is canonical and
orchestration smoke works → ready for `002` then `003`.
## Technical Context
**Language/Version**: Bash / Ansible YAML; Ubuntu Server 24.04 LTS on host;
Spec Kit docs in Markdown (English).
**Primary Dependencies**: apt packages per current LineageOS/AOSP host build
docs at implementation time; `repo`; `git` + `git-lfs`; `ccache`; Gitea;
Ansible; Semaphore UI; OpenSSH.
**Storage**: Host filesystem paths under `/srv/rom/` (no project-owned LVM/fstab
changes). Secrets directory restricted/empty in `001`.
**Testing**: Observable shell/service checks (clean-login tool smoke, systemd
active, local curl, rsync SHA256, restart survival). No Android instrumentation.
**Target Platform**: Single VM `rom-dev01` (~6 vCPU / 52 GB RAM / 2 TB /
32 GB swap) + thin laptop (Cursor Remote SSH, adb/fastboot, rsync).
**Project Type**: Infrastructure / host bootstrap for Android ROM development.
**Performance Goals**: Toolchain available in a clean `android-build` login;
services survive restart; no public exposure of Gitea/Semaphore. Build `-j`
tuning is **out of scope** (owned by `003`).
**Constraints**:
- VM/PVE/IP/Salt `saltenv=base` provisioning is external; do not fight managed
config silently.
- Never build Android as root; never `chmod 777`.
- Never flush firewall / risk active SSH path.
- Do not bind Gitea/Semaphore to `0.0.0.0` without an explicit protected design
(default: loopback / non-public + SSH tunnel).
- No secrets in Git or build logs.
- Do not delete laptop staging tree before Gitea + mirror verified.
**Scale/Scope**: One developer; four Gitea repos; one smoke Ansible playbook;
no distributed CI / registry / Kubernetes.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Gate | Status | Notes |
|------|--------|-------|
| Independence from SSYSTEMS governance | PASS | No Redmine/GitLab/company MR workflow; no Salt VM creation from this repo |
| Spec-as-source | PASS | Plan derived from `001/spec.md` + constitution `0.1.5` |
| Parity/hardening docs non-normative | PASS | Not used as requirements for `001` |
| Host boundary: VM is prerequisite | PASS | Preconditions assume host exists; non-goals forbid PVE/qm/Salt states |
| Isolation posture (guest) | PASS | Public-NIC workflow; UIs not world-exposed; SSH preserved |
| No Android trees on laptop | PASS | Thin-client + `/srv/rom` on host |
| Secrets not in Git | PASS | Explicit secrets handling |
| Magisk/KSU/microG/signing out of `001` | PASS | Listed in DO NOT |
| Capacity honesty | PASS | Plan around 6/52/2T/32; parallelism deferred to `003` |
**Post-design re-check**: PASS — artifacts (`research.md`, `quickstart.md`,
operational `data-model.md`) stay inside `001` scope; no invented API contracts.
## Project Structure
### Documentation (this feature)
```text
specs/001-development-environment/
├── plan.md # This file
├── research.md # Phase 0
├── data-model.md # Operational entities only (no app domain model)
├── quickstart.md # Validation / cold-start guide
├── contracts/ # N/A for 001 (see contracts/README.md)
└── tasks.md # Created later by /speckit-tasks — NOT this command
```
### Host layout (target on rom-dev01)
```text
/srv/rom/
├── src/ # future Android trees (empty in 001; no repo sync)
├── references/ # future AXP/reference captures (empty OK)
├── tooling/ # project scripts / ansible / helper tooling
├── ccache/ # CCACHE_DIR for android-build
├── artifacts/ # handoff to laptop (dummy file for smoke)
├── logs/ # install/bootstrap logs
└── secrets/ # restricted; empty; no key generation in 001
# service data (exact paths chosen at implement time; document them)
# e.g. /var/lib/gitea, /etc/gitea, semaphore data dir
# Canonical meta Spec Kit working tree (Cursor Remote SSH edits here):
/home/android-build/sunfish-a16-rom
# /srv/rom/tooling/ = project tooling only (scripts helpers, etc.)
# — NOT the Spec Kit meta checkout
```
### Repository roles (Gitea)
| Repo | Contents |
|------|----------|
| `sunfish-a16-rom` | Spec Kit + specs + docs (meta) |
| `sunfish-rom-build` | Scripts + Ansible + Semaphore/build automation |
| `sunfish-rom-manifest` | Repo manifests (empty at first) |
| `sunfish-rom-patches` | Patchsets / tracking (empty at first) |
Do **not** mix executable automation into the Spec Kit meta repo.
### Meta repository content (laptop staging → Gitea)
```text
/home/android-build/sunfish-a16-rom/ # Spec Kit + docs only
├── .specify/
├── specs/
├── docs/
├── AGENTS.md
└── …
```
**Structure Decision**: Infrastructure feature — host paths + Git/service
bootstrap. No application `src/` tree inside Spec Kit for `001`.
## Complexity Tracking
> No constitution violations requiring justification.
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|--------------------------------------|
| — | — | — |
---
## 1. Summary (expanded)
`001` makes `rom-dev01` a usable ROM workstation and private Git home without
performing ROM builds. Completion means: clean `android-build` toolchain smoke
passes; `/srv/rom` owned correctly; Spec Kit tree lives on host; Gitea holds
four repos and is canonical; laptop has an off-VM bare mirror; Ansible+Semaphore
run one smoke playbook; thin-client + isolation docs/checks pass; no PVE/Salt
fight, no Pixel/LOS work.
## 2. Constitution check / source of truth
Authoritative for this plan:
1. `.specify/memory/constitution.md` **v0.1.5**
2. `specs/001-development-environment/spec.md`
3. `AGENTS.md`
Non-normative (do not treat as `001` requirements):
`docs/axp-pro-parity.md`, `docs/hardening-backlog.md`.
## 3. Preconditions
Assume already true before `/speckit-implement`:
- `rom-dev01` exists (human + SSYSTEMS Salt/PVE)
- Ubuntu Server 24.04 LTS
- One reachable public IPv4 (public NIC only)
- SSH administration works
- `saltenv=base` already applied
- Capacity approximately: 6 vCPU / 52 GB RAM / 2 TB disk / 32 GB swap
Do **not** create, resize, or re-IP the VM from this project.
## 4. Non-goals / boundaries
**DO NOT** (plan / tasks / implement):
- create or configure the Proxmox VM; emit `qm`/PVE provisioning;
- choose or hard-code the public IPv4;
- write SSYSTEMS Salt states; modify `pm-test01`;
- assume company internal DNS/VPN/LDAP/GitLab/NFS/Zabbix is required;
- start LineageOS `repo sync` or any Android build;
- touch/backup/flash the Pixel (NeoBackup belongs to `002`);
- introduce microG, Phonesky, KernelSU, Magisk, SUSFS, or hardening;
- generate ROM/AVB/Phonesky signing keys;
- put `.repo`, `out/`, or ccache on the laptop;
- require Ansible on the laptop;
- expose Gitea or Semaphore to the public Internet;
- design GitLab/Jenkins/Kubernetes/registry/distributed runners;
- turn Semaphore into the Android build pipeline yet;
- invent an API/data model merely to satisfy a template;
- hard-code facts that must be measured on `rom-dev01`;
- duplicate requirements owned by `002`/`003`+.
**Implement-time DO NOT** (operational):
- run Android builds as root; `chmod 777` project trees;
- blindly dist-upgrade; replace apt sources without need;
- overwrite Salt/base-managed config silently;
- flush firewall/nftables; risk active SSH management path;
- bind Gitea/Semaphore to `0.0.0.0` without explicit protected design;
- commit passwords/tokens/private keys; plaintext secrets in Ansible in Git;
- delete laptop staging before Gitea + mirror verified;
- delete/overwrite working data only to fake idempotency;
- download full Lineage tree; compile Lineage; connect/flash Pixel;
- automate ROM builds through Semaphore yet.
**Conflict rule**: If base-managed configuration conflicts with project needs,
**stop and report** — do not silently replace or fight it; do not “fix”
unrelated infrastructure opportunistically.
## 5. Host preflight (before modifying the host)
First implement steps are **observational**. Capture and store under
`/srv/rom/logs/preflight/` (or create layout first then log):
- Ubuntu version / kernel
- CPU / RAM / swap
- filesystem free space (esp. for `/` and future `/srv/rom`)
- network interfaces / default route (confirm no required internal NIC)
- SSH reachability
- existing users / groups
- existing firewall (nft/ufw/iptables) summary — **do not flush**
- listening ports / services
- apt sources summary
- hints of Salt/base-managed files if obvious
Produce a short baseline note in the meta repo docs or `/srv/rom/logs` for later
diff. Especially important because `saltenv=base` already touched the machine.
## 6. Account and permission model
| Actor | Role |
|-------|------|
| Existing admin SSH user | packages, systemd, system config, firewall changes (carefully) |
| `android-build` | Android sources/builds (later), ccache, artifacts, build tooling; SSH pubkey login from laptop; Cursor Remote SSH user; **never** build as root; **no sudo required to compile** |
| `gitea` (service user) | Gitea data/config ownership |
| `semaphore` (service user/context) | Semaphore data/config ownership |
Rules:
- No `chmod -R 777 /srv/rom`
- Group memberships only as needed (e.g. `android-build` owns `/srv/rom` trees
it writes; services own their state dirs)
- Admin elevates only for system packaging/services
## 7. `/srv/rom` filesystem layout
Create with explicit owners/modes (exact modes chosen at implement; document):
```text
/srv/rom/
├── src/
├── references/
├── tooling/
├── ccache/
├── artifacts/
├── logs/
└── secrets/ # empty, restricted; do not generate keys in 001
```
- Do **not** manage partitions/LVM/fstab from this project.
- Do not populate `references/` (that is `002`) or generate secrets.
- `artifacts/` must be writable by `android-build` for dummy + later builds.
## 8. Android host toolchain (no sync/build)
- Install host packages using **current primary LineageOS/AOSP build
documentation at implementation time** (do not freeze a stale 50-package list
in this plan).
- Record what was installed (log + short note in meta repo / `/srv/rom/logs`).
- Ensure `git`, `git-lfs`, `repo`, `python3`, and Java/toolchain prerequisites
required by that documentation.
- Configure `ccache`: location (`/srv/rom/ccache`), ownership (`android-build`),
size cap, environment (`CCACHE_DIR`, etc.). **Not** tied to inventing `-j`
from vCPU/RAM here.
- FR-007: document capacity (6/52/32) and that **conservative `-j` / OOM tuning
belongs to `003`**, with a one-line recommended starting point only if needed
for docs — no build execution in `001`.
- Smoke from a **clean login**: primary check is **direct SSH from the laptop as
`android-build`** (after pubkey setup) → verify tools. Optional local admin
check: `sudo -iu android-build` on the host if needed. **No `repo sync`**.
## 9. Spec Kit staging bootstrap (before Gitea)
Egg/chicken sequence (mandatory):
```text
laptop staging:
/home/aventura/Workspaces/sunfish-a16-rom
↓ rsync/scp/git bundle (preserve .git)
rom-dev01 working copy
↓
install Gitea
↓
create empty Gitea repo sunfish-a16-rom
↓
push meta repo
↓
create sunfish-rom-manifest, sunfish-rom-build, sunfish-rom-patches
↓
Gitea becomes canonical remote
↓
host working tree at /home/android-build/sunfish-a16-rom
uses Gitea as origin (after push)
```
Do **not** assume `git clone …gitea…` before Gitea exists.
Do **not** delete laptop staging until Gitea push + off-VM mirror verified.
## 10. Gitea
- Install on `rom-dev01` with persistent data dirs and systemd unit.
- Default **UI** access: **loopback / non-public bind + SSH tunnel** from laptop
(decision fixed for `001` unless implement discovers a hard conflict — then
stop/report).
- **Git transport**: document the concrete mechanism at implement time after
inspecting the host (HTTP via the same SSH tunnel and/or Git-over-SSH also
tunnelled are valid). Do **not** blindly modify OpenSSH managed by
`saltenv=base`, and do **not** assume or open a second public SSH port
(e.g. `:2222`) for Gitea.
- Create admin credentials with restricted local storage; never commit secrets.
- Verify: service active; local curl succeeds; direct public Internet access to
UI **not** required / not open.
## 11. Canonical Git transition
Repos:
1. `sunfish-a16-rom` — Spec Kit + specs + docs; push existing history
2. `sunfish-rom-manifest` — empty
3. `sunfish-rom-build` — scripts + Ansible + Semaphore/build automation (seed
minimal structure when ready)
4. `sunfish-rom-patches` — empty
Document remotes for host + laptop clones. Company GitLab must not be used.
## 12. Off-VM Git mirror (P2)
First implementation (sufficient for `001` SC-007): **laptop-initiated pull**
of bare mirrors. The laptop opens the connection (via SSH tunnel / documented
Git URL); `rom-dev01` must **not** need inbound SSH to the laptop.
```text
# On laptop (conceptual):
git clone --mirror <gitea-repo-url-via-tunnel>
cd <repo>.git && git remote update
```
Borg / full Gitea DR is **later**. Requirement is Git history redundancy.
## 13. Ansible bootstrap (P2)
- Install Ansible on `rom-dev01` for **project environment** automation.
- Keep playbooks and automation in **`sunfish-rom-build`** (not in the Spec Kit
meta repo `sunfish-a16-rom`).
- No plaintext secrets in Git.
## 14. Semaphore bootstrap (P2)
- Install Semaphore as UI over Ansible.
- Same exposure model as Gitea (loopback/tunnel).
- First integration: **unprivileged** environment smoke playbook only — e.g.
gather facts + assert `/srv/rom`, `android-build`, and toolchain checks.
**No sudo password / privileged secrets in Semaphore for this smoke.**
Privileged automation is a later explicit decision.
- **Not** an Android build pipeline.
## 15. Isolation / firewall / service exposure
Keep guest isolation posture from constitution/`001` spec:
- Public NIC only for project workflow
- Gitea/Semaphore not world-exposed
- Document firewall/DNS expectations
Implementation safety:
- never flush/replace existing firewall blindly;
- never close current SSH path before a tested replacement;
- keep a rollback path;
- prefer additive allow rules.
## 16. Laptop thin-client workflow
Document and smoke:
1. Direct SSH from laptop as `android-build` (pubkey); Cursor Remote SSH →
`/home/android-build/sunfish-a16-rom` with `User android-build`
2. Dummy artifact in `/srv/rom/artifacts` → `rsync` to laptop → SHA256 match
3. adb/fastboot remain on laptop; Pixel USB on laptop; no USB passthrough design
## 17. Verification matrix (FR/SC → evidence)
| ID | Implementation focus | Observable verification |
|----|----------------------|-------------------------|
| FR-001 | Create `android-build` + home/workspace docs + SSH pubkey | `id android-build`; `ssh android-build@rom-dev01` from laptop |
| FR-002 | LOS/AOSP host deps + ccache | clean SSH login as `android-build`: `git`, `repo`, `ccache -s`, etc. |
| FR-003 | `/srv/rom` tree + ownership | `namei -l` / `stat` shows owners; writable artifacts |
| FR-004 | Gitea + four repos | local UI/API; four repos listed |
| FR-005 | Ansible + Semaphore smoke | Semaphore runs smoke playbook successfully |
| FR-006 | Thin-client docs | doc exists; Remote SSH + rsync smoke done |
| FR-007 | Capacity / defer `-j` to `003` | short doc records 6/52/32 and defers build jobs |
| FR-008 | Spec Kit on host | tree present; after P2, cloned/pushed via Gitea |
| FR-009 | Isolation posture | checklist: NIC, binds, firewall notes; SSH still works |
| FR-010 | Off-VM Git mirror | laptop bare mirror of four repos once |
| SC-001 | Toolchain smoke | clean `android-build` session has tools for later `003` |
| SC-002 | Artifacts handoff | dummy file rsync SHA256 match |
| SC-003 | Four Gitea repos | listed on Gitea; not company GitLab |
| SC-004 | Cold-start workflow doc | quickstart path SSH→workspace→(later build)→rsync→flash |
| SC-005 | No PVE management | no qm/PVE playbooks in repo/tasks evidence |
| SC-006 | Isolation checklist | passes without requiring VPN/internal DNS |
| SC-007 | Mirror demonstrated | off-VM mirror exists and was verified once |
## 18. Handoff
**`001` complete** means environment ready for `002` (phone capture) and later
`003` (LOS vanilla build) — **not** “start building Lineage now”.
Do not begin `/speckit-plan` for `002`/`003` until `001` doc cycle + implement
are done (avoid document saturation).
## P1 / P2 sequencing
### P1 — host usable without Gitea
1. Host preflight baseline
2. Create `android-build`
3. Configure developer SSH public-key access for `android-build`
4. Verify direct SSH login from the laptop as `android-build`
5. Create `/srv/rom` + permissions
6. Install build prerequisites (current LOS/AOSP docs)
7. `repo` / `git` / `git-lfs` / ccache config
8. Copy laptop staging meta repo to host path `/home/android-build/sunfish-a16-rom` (preserve `.git`)
9. Clean-shell toolchain smoke via SSH as `android-build`
10. Cursor Remote SSH smoke (`User android-build`)
11. Dummy artifact → rsync → laptop (SHA256)
### P2 — reproducible / comfortable
1. Install Gitea (loopback/tunnel)
2. Create four repos
3. Push meta repo; set Gitea canonical
4. Off-VM bare mirrors on laptop
5. Install Ansible
6. First smoke playbook
7. Install Semaphore
8. Run smoke playbook from Semaphore
9. Isolation / access / firewall validation (SSH-safe)
10. Service restart (+ reboot survival where practical)
Finish P2 before formally starting `002`.
## Phase outputs
| Artifact | Role |
|----------|------|
| [research.md](./research.md) | Decisions: Gitea/Semaphore access, mirror, deps source, ccache |
| [data-model.md](./data-model.md) | Operational entities only |
| [quickstart.md](./quickstart.md) | Cold-start validation |
| [contracts/](./contracts/) | N/A |
Next command after human review of these files: `/speckit-tasks` (still no VM).
Then `/speckit-analyze`. Stop before `/speckit-implement` until SSH to
`rom-dev01` works.
@@ -0,0 +1,132 @@
# Quickstart / validation: 001-development-environment
Use after `/speckit-implement` on a real `rom-dev01`. Until the VM exists, treat
this as the acceptance script shape.
## Prerequisites
- Host: `a15.ssystems.de` (`49.13.104.83`), SSH port **37037**, guest name `rom-dev01`
- Laptop key: `~/.ssh/id_ed25519_rom_dev01` + Host aliases in
`misc/config.d/a15-rom-dev01` (see `docs/001-host-runbook.md`)
- Laptop has `rsync`, `ssh`, Cursor Remote SSH, `adb`/`fastboot`
## P1 validation path
### 1. Preflight captured
```bash
ssh rom-dev01-root 'less /srv/rom/logs/preflight/preflight-latest.txt'
```
### 2. Build user workspace
```bash
ssh rom-dev01 'whoami; hostname; test -f ~/sunfish-a16-rom/specs/001-development-environment/spec.md && echo SPEC_OK'
```
### 3. Toolchain smoke (clean shell)
```bash
ssh rom-dev01 'bash -l -c "git --version; git lfs version; repo --version; python3 --version; ccache -s"'
```
Expected: all commands succeed without sudo. See `/srv/rom/logs/toolchain-smoke.txt`.
### 4. Layout
```bash
namei -l /srv/rom /srv/rom/artifacts /srv/rom/ccache
touch /srv/rom/artifacts/.write_test && rm /srv/rom/artifacts/.write_test
```
### 5. Meta repo present on host (pre-Gitea OK)
```bash
test -d /home/android-build/sunfish-a16-rom/.git
test -f /home/android-build/sunfish-a16-rom/specs/001-development-environment/spec.md
```
### 6. Cursor Remote SSH
From laptop: open host workspace; edit a trivial file; confirm it is on the VM
disk (not only local).
### 7. Artifact rsync smoke
On host:
```bash
echo '001-smoke' > /srv/rom/artifacts/dummy-001.txt
sha256sum /srv/rom/artifacts/dummy-001.txt
```
On laptop:
```bash
rsync -avz USER@ROM_DEV01:/srv/rom/artifacts/dummy-001.txt ./
sha256sum ./dummy-001.txt # must match
```
## P2 validation path
### 8. Gitea private access
```bash
ssh -L 3000:127.0.0.1:3000 rom-dev01-root
# browser: http://127.0.0.1:3000/ (user sunfish-admin; password in host /srv/rom/secrets/)
```
Gitea binds **only** `127.0.0.1:3000`. Git over HTTP to that URL (no public `:2222`).
### 9. Four repos
In Gitea: `sunfish-a16-rom`, `sunfish-rom-manifest`, `sunfish-rom-build`,
`sunfish-rom-patches` (under `sunfish-admin/`).
### 10. Ansible CLI smoke
```bash
ssh rom-dev01 'cd ~/sunfish-rom-build && ansible-playbook -i ansible/inventory.ini ansible/smoke.yml'
```
### 11. Semaphore smoke (unprivileged)
```bash
ssh -L 3001:127.0.0.1:3001 rom-dev01-root
# http://127.0.0.1:3001/ project sunfish-rom / template env-smoke
```
Semaphore clones canonical `sunfish-rom-build` from Gitea and runs the smoke
playbook without sudo. Evidence: `/srv/rom/logs/semaphore-smoke.txt`.
### 12. Off-VM mirrors
Laptop path: `/home/aventura/Archives/sunfish-rom-mirrors/*.git` (rsync from
`/srv/rom/artifacts/gitea-mirrors/` on host).
```bash
# refresh mirrors (host builds mirrors; laptop pulls via rsync — no token on laptop)
rsync -a android-build@a15.ssystems.de:/srv/rom/artifacts/gitea-mirrors/ \
~/Archives/sunfish-rom-mirrors/
git -C ~/Archives/sunfish-rom-mirrors/sunfish-a16-rom.git fsck
```
### 13. Restart survival
```bash
ssh rom-dev01-root 'systemctl restart gitea semaphore; curl -fsS -o /dev/null http://127.0.0.1:3000/; curl -fsS -o /dev/null http://127.0.0.1:3001/'
```
Evidence: `/srv/rom/logs/restart-smoke.txt`.
### 14. Isolation checklist
- No required internal NIC for workflow
- Gitea/Semaphore binds only `127.0.0.1`
- SSH still works after service restarts
- Day-to-day docs do not require company VPN/internal DNS/LDAP/GitLab
## Done means
`001` complete → start **`002`** (AXP capture).
Do **not** start Lineage `repo sync` / build as part of `001` success.
@@ -0,0 +1,110 @@
# Research: 001-development-environment
Phase 0 decisions for planning. Implementation may refine versions using live
primary docs on `rom-dev01`.
## R1 — Gitea access model
**Decision**: Bind Gitea to loopback (or other non-public address) and access via
SSH local port forward from the laptop. Do not require public HTTPS exposure.
**Rationale**: Constitution/`001` require private project Git without unnecessary
Internet exposure; single developer; SSH already required for admin/Remote SSH.
**Alternatives considered**:
- Public HTTPS + firewall allowlist — rejected for `001` (more moving parts;
accidental exposure risk).
- VPN-only bind — rejected as a *required* path (may exist externally; project
must not depend on company VPN).
## R2 — Semaphore access model
**Decision**: Same as Gitea — loopback/non-public + SSH tunnel. First job is an
**unprivileged** localhost/environment smoke playbook only (facts + path/tool
asserts). No sudo credentials in Semaphore for `001` smoke.
**Rationale**: Semaphore is orchestration UI, not a public portal; Android build
pipeline is explicitly out of scope for `001`; avoid baking privileged secrets
into the first integration.
**Alternatives considered**: Public Semaphore — rejected. Full ROM pipeline in
Semaphore — deferred past `003` manual first build. Privileged playbooks —
later explicit decision.
## R3 — Off-VM Git history backup
**Decision**: Laptop-initiated **bare mirrors** (`git clone --mirror` /
`git remote update`) of the four Gitea repos for `001` (SC-007). Laptop pulls;
VM does not SSH into the laptop. Borg/full Gitea DR later.
**Rationale**: Spec requires off-VM copy of project Git history, not a complete
backup platform. Pull-from-laptop avoids opening inbound access on the laptop.
**Alternatives considered**: Borg-only — optional later. Push-from-VM to laptop
— rejected (would require laptop inbound SSH).
## R4 — Lineage/AOSP host dependencies
**Decision**: At implement time, follow **current primary LineageOS / AOSP host
build documentation** for Ubuntu; record installed package set in logs. Do not
freeze a long package list in the plan.
**Rationale**: Dependency lists drift; stale pins cause false confidence.
**Alternatives considered**: Copy a historical package list from blogs —
rejected.
## R5 — `repo` installation source
**Decision**: Install Google's `repo` launcher via the method recommended by
current Lineage/AOSP docs (typically curl to `~/bin/repo` or distro package if
still adequate); verify `repo --version` as `android-build`.
**Rationale**: Align with upstream bring-up docs used for `003`.
## R6 — ccache policy vs build parallelism
**Decision**: `001` configures `CCACHE_DIR=/srv/rom/ccache` (or equivalent),
ownership for `android-build`, and a disk size cap. Document host capacity
(6 vCPU / 52 GB / 32 GB swap). **Defer `-j` / OOM tuning to `003`.**
**Rationale**: ccache is storage/env; parallel link/compile jobs belong to the
first real build feature.
**Alternatives considered**: Encoding `-j$(nproc)` defaults in `001` — rejected
as premature and confusing with FR-007 (document capacity; don't run builds).
## R7 — Meta-repo bootstrap before Gitea
**Decision**: Copy laptop staging Git tree to host path
`/home/android-build/sunfish-a16-rom` first (rsync/scp/bundle preserving
`.git`), install Gitea, create repos, push, then mark Gitea canonical. Keep
laptop staging until mirror verified. Ansible/automation lives in
`sunfish-rom-build`, not in the meta Spec Kit tree.
**Rationale**: Avoid chicken/egg `git clone` against non-existent Gitea; keep
Cursor edit path single and clear.
## R7b — Gitea Git transport vs OpenSSH base
**Decision**: UI via loopback + SSH tunnel. Exact Git clone/push transport
chosen at implement after host inspection (tunneled HTTP and/or tunneled Git
SSH are valid). Do not assume a public secondary SSH port for Gitea; do not
blindly edit `saltenv=base`-managed sshd.
## R8 — Coexistence with `saltenv=base`
**Decision**: Preflight + stop/report on conflicts with base-managed config.
Project Ansible may manage **project** paths/services; must not silently rewrite
Salt-owned system policy.
**Rationale**: Host is dual-touched (SSYSTEMS base then project). Fighting Salt
causes flapping.
## R9 — data-model / contracts
**Decision**: No application domain model or HTTP API contracts for `001`.
Operational entities only in `data-model.md`. `contracts/` marked N/A.
**Rationale**: Infrastructure bootstrap feature; inventing APIs would be template
noise.
+230
View File
@@ -0,0 +1,230 @@
# Feature Specification: Development environment on rom-dev01
**Feature Branch**: `001-development-environment`
**Created**: 2026-08-09
**Status**: Draft
**Input**: Provision the ROM development environment on an already-existing
`rom-dev01` host and establish the laptop ↔ VM ↔ Pixel workflow defined by the
project constitution.
## Context / non-goals
**Assumed already true before this feature starts:**
- `rom-dev01` exists (Ubuntu Server 24.04 LTS);
- one reachable public IPv4 address (public NIC only);
- SSH administration works;
- SSYSTEMS base provisioning (`saltenv=base`) is complete.
**Explicitly out of scope:**
- Proxmox / `pm-test01` VM creation;
- public subnet/IP selection;
- company Salt states, Redmine, or SSYSTEMS governance;
- LineageOS source sync or first ROM build (see `003`);
- AXP capture (see `002`).
**Isolation posture (in scope for guest config/docs):**
- Keep the host usable without SSYSTEMS internal network services.
- Do not add an internal NIC from this feature.
- Bind Gitea/Semaphore so they need not be world-exposed.
- Document guest firewall / egress expectations (block or avoid unnecessary
traffic to SSYSTEMS internal ranges; SSH from laptop must work).
- Sharing the public VLAN with lab/gateway VMs is acceptable.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Reachable ROM build host workspace (Priority: P1)
As the developer, I can SSH from my laptop into `rom-dev01` and use a dedicated
`android-build` account with a documented home/workspace layout for Android
work.
**Why this priority**: Without a stable host account and layout, no later build
feature can proceed.
**Independent Test**: SSH as the project build user, create a file under the
documented workspace, confirm ownership and paths.
**Acceptance Scenarios**:
1. **Given** `rom-dev01` is up, **When** I connect via SSH from the laptop,
**Then** authentication succeeds without requiring this project to change
PVE/network provisioning.
2. **Given** SSH works, **When** the `android-build` account is provisioned,
**Then** it exists with a documented home and build-related directories.
3. **Given** the account exists, **When** I inspect `/srv/rom` (or the agreed
layout), **Then** directories for sources, artifacts, logs, and tooling are
present with clear ownership.
---
### User Story 2 - Android build toolchain prerequisites (Priority: P1)
As the developer, the host has the packages and tools needed to sync and build
LineageOS for sunfish (repo, git, git-lfs, JDK, build deps, ccache), sized for
6 vCPU / 52 GB RAM / 32 GB swap.
**Why this priority**: Vanilla LOS build (`003`) depends on this.
**Independent Test**: `repo`/`git`/`ccache` respond; documented package set is
installed; ccache directory exists and is writable by `android-build`.
**Acceptance Scenarios**:
1. **Given** `android-build` session, **When** I run `repo --version` and
`git --version`, **Then** both succeed.
2. **Given** build dependencies are installed, **When** I check ccache config
and cache dir, **Then** ccache is usable by `android-build`.
3. **Given** constrained host resources, **When** build parallelism defaults are
documented, **Then** they are conservative (not assuming 16 vCPU / 64 GB).
---
### User Story 3 - Project Git and orchestration services (Priority: P2)
As the developer, private Gitea and Semaphore (over Ansible) run on `rom-dev01`
for project repos and ROM workflow orchestration, without exposing those UIs
unnecessarily to the public Internet. Project Git history must not exist only
on the build VM.
**Why this priority**: Needed before multi-repo ROM automation; can follow basic
toolchain if sequenced. Losing `out/` is fine; losing specs/patches/history is
not.
**Independent Test**: Gitea serves locally/over SSH tunnel; initial repos
`sunfish-a16-rom`, `sunfish-rom-manifest`, `sunfish-rom-build`,
`sunfish-rom-patches` exist or are documented to create; Semaphore can run a
trivial Ansible playbook; off-VM backup/mirror procedure documented and proven
once.
**Acceptance Scenarios**:
1. **Given** Gitea is installed, **When** I access it via the approved path
(SSH tunnel or existing access policy), **Then** I can create/browse private
project repositories.
2. **Given** Ansible is installed, **When** Semaphore is configured, **Then** it
drives Ansible without hiding undocumented build steps.
3. **Given** public IP exists, **When** reviewing listening services, **Then**
Gitea/Semaphore are not required to be open to the whole Internet for normal
operation.
4. **Given** the four project repositories, **When** backup/mirror is configured,
**Then** an off-VM copy exists (laptop clone, Borg, or other agreed target)
so `rom-dev01` is not the only copy of project history.
---
### User Story 4 - Guest isolation from SSYSTEMS internals (Priority: P2)
As the developer, normal ROM work on `rom-dev01` does not require company
internal DNS, VPN, LDAP, GitLab, NFS, or Zabbix, and project UIs are not
exposed more than necessary.
**Why this priority**: Limits blast radius and keeps the personal project
logically separate even though the hypervisor is shared lab hardware.
**Independent Test**: From the VM, confirm no required route/NIC into internal
SSYSTEMS networks for the workflow; Gitea/Semaphore reachable via approved
non-public path; checklist documents firewall/DNS posture.
**Acceptance Scenarios**:
1. **Given** the provisioned guest, **When** I inspect network interfaces,
**Then** only the public NIC is required for this project's workflow.
2. **Given** Gitea and Semaphore are installed, **When** I review listen
addresses / access docs, **Then** they are not required to be open to the
whole Internet.
3. **Given** guest firewall/DNS docs, **When** I follow them, **Then** day-to-day
ROM work does not depend on SSYSTEMS internal resolvers or VPN.
---
### User Story 5 - Laptop remains a thin client (Priority: P2)
As the developer, Cursor Remote SSH uses `rom-dev01` as the heavy workspace;
the Pixel stays USB-attached to the laptop; artifacts are `rsync`'d for
flashing.
**Why this priority**: Enforces the disk/space strategy and flashing model.
**Independent Test**: Documented Remote SSH workflow; sample artifact path
`/srv/rom/artifacts` syncs to the laptop with `rsync`.
**Acceptance Scenarios**:
1. **Given** Cursor on the laptop, **When** I open the project via Remote SSH,
**Then** I edit/build on `rom-dev01` without keeping `.repo`/`out` on the
laptop.
2. **Given** a dummy artifact in `/srv/rom/artifacts`, **When** I `rsync` it to
the laptop, **Then** the file arrives for local `adb`/`fastboot` use.
3. **Given** the Pixel is attached to the laptop, **When** following the
workflow docs, **Then** no USB passthrough to the VM is required.
---
### Edge Cases
- Host reboots: services and mount/layout expectations are documented.
- Disk pressure on 2 TB: cleanup conventions for `out/` and old artifacts.
- SSH admin user ≠ `android-build`: project must not hard-depend on laptop
usernames (`aventura` / `ubuntu`).
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: Provide dedicated `android-build` account and documented workspace.
- **FR-002**: Install Android/LineageOS build prerequisites and ccache.
- **FR-003**: Create `/srv/rom` layout (sources, artifacts, logs, tooling) or
document an equivalent and use it consistently.
- **FR-004**: Install and configure private Gitea for project repositories,
including meta repo `sunfish-a16-rom` plus `sunfish-rom-manifest`,
`sunfish-rom-build`, and `sunfish-rom-patches`.
- **FR-005**: Install Ansible; configure Semaphore as UI over Ansible for ROM
workflow (initially may only prove a smoke playbook).
- **FR-006**: Document Cursor Remote SSH + `rsync` artifact + laptop
adb/fastboot workflow.
- **FR-007**: Document conservative build parallelism defaults for 6 vCPU /
52 GB / swap 32 GB.
- **FR-008**: Ensure Spec Kit project for `sunfish-a16-rom` is available on the
host (push/clone the meta repo from this staging tree or Gitea once published).
- **FR-009**: Document and apply guest isolation posture: public-NIC-only
workflow; no required dependency on SSYSTEMS internal DNS/VPN/LDAP/GitLab/NFS/
Zabbix; Gitea/Semaphore not world-exposed by default; guest firewall guidance
to avoid unnecessary traffic to internal SSYSTEMS ranges (without encoding
concrete company IPs as project secrets/config in Git unless necessary).
- **FR-010**: Provide an off-VM backup or mirror of project-owned Gitea
repositories so the build VM is not the only copy of project history
(mechanism may be laptop clones, Borg, or another agreed target).
### Key Entities
- **rom-dev01**: pre-existing Ubuntu 24.04 build host.
- **android-build**: project build account.
- **/srv/rom**: canonical on-host data layout.
- **Gitea / Semaphore / Ansible**: project-owned services on the VM.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: From a clean SSH session as `android-build`, toolchain commands
required for `003` are available without further ad-hoc package hunting.
- **SC-002**: `/srv/rom/artifacts` (or documented equivalent) is writable and
used as the artifact handoff point to the laptop.
- **SC-003**: Gitea hosts (or is ready to host) the **four** initial project
repos (`sunfish-a16-rom`, `sunfish-rom-manifest`, `sunfish-rom-build`,
`sunfish-rom-patches`) without using company GitLab.
- **SC-004**: Written workflow doc allows a cold start: SSH → workspace →
(later) build → rsync → flash on laptop-attached Pixel.
- **SC-005**: No playbook or doc in this feature creates or reconfigures the
Proxmox VM itself.
- **SC-006**: Isolation checklist passes: no internal NIC required; project UIs
accessible without public Internet exposure; workflow docs do not require
SSYSTEMS VPN/internal DNS for normal use.
- **SC-007**: Off-VM backup/mirror of the four Gitea project repos is documented
and demonstrated at least once.
+208
View File
@@ -0,0 +1,208 @@
# Tasks: Development environment on rom-dev01
**Input**: Design documents from `/specs/001-development-environment/`
**Prerequisites**: plan.md (required), spec.md, research.md, data-model.md,
quickstart.md, contracts/ (N/A)
**Tests**: Observational verify steps embedded in tasks (no separate TDD suite).
**Organization**: Setup → Foundational (preflight) → User stories by priority →
Polish. **Do not run these tasks until `rom-dev01` SSH works.** Generating this
file is documentation-only.
**Canonical paths**:
- Meta Spec Kit tree: `/home/android-build/sunfish-a16-rom`
- Data root: `/srv/rom/`
- Automation repo (after Gitea): `sunfish-rom-build` (not the meta repo)
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no incomplete dependencies)
- **[Story]**: US1…US5 from spec.md
- Every task includes a path and an observable verify hint
## Path Conventions
Host paths are absolute on `rom-dev01`. Docs/logs also land under
`/home/android-build/sunfish-a16-rom/docs/` or `/srv/rom/logs/` as noted.
---
## Phase 1: Setup (docs readiness — can be done on laptop before VM)
- [x] T001 Confirm active feature is `specs/001-development-environment` via `.specify/feature.json` and that constitution is `0.1.7` in `.specify/memory/constitution.md`
- [x] T002 [P] Create host-facing doc stub `docs/001-host-runbook.md` in meta repo (preflight results, paths, Gitea tunnel notes — fill during implement)
- [x] T003 [P] Create FR/SC evidence checklist stub `docs/001-verification-matrix.md` mapping each FR-*/SC-* from `specs/001-development-environment/spec.md` to blank evidence fields
---
## Phase 2: Foundational (blocking — host preflight before stories)
**Goal**: Observe `rom-dev01` without fighting `saltenv=base`; abort on conflict.
- [x] T004 On `rom-dev01` as admin, capture preflight baseline into `/srv/rom/logs/preflight/` (create `/srv/rom/logs` with safe perms if needed): Ubuntu/kernel, CPU/RAM/swap, disk free, interfaces/routes, users, firewall summary (**do not flush**), listening ports, apt sources → also summarize in `docs/001-host-runbook.md`
- [x] T005 Record coexistence rule in `docs/001-host-runbook.md`: if base-managed config conflicts with project needs, **stop and report**; do not silently replace Salt-managed settings
- [x] T006 Verify SSH admin path still works after any read-only inspection; note rollback contacts/commands in `docs/001-host-runbook.md`
---
## Phase 3: User Story 1 — Reachable build workspace (P1)
**Story goal**: `android-build` exists with SSH key login from laptop; `/srv/rom` owned correctly; meta tree at fixed path.
**Independent test**: `ssh android-build@rom-dev01` from laptop; `/srv/rom/artifacts` writable; `/home/android-build/sunfish-a16-rom` contains Spec Kit tree (after T013).
### Implementation
- [x] T007 [US1] Create unprivileged user `android-build` with home `/home/android-build` (admin); document uid/gid in `docs/001-host-runbook.md`
- [x] T008 [US1] Configure SSH public-key access for `android-build` using the developer's existing public key only (do not copy private keys; do not modify global sshd/`saltenv=base` policy unless required). Verify from the laptop that `ssh android-build@rom-dev01` succeeds. Document Cursor `Host` entry (`User android-build`) in `docs/001-host-runbook.md`
- [x] T009 [US1] Create `/srv/rom/{src,references,tooling,ccache,artifacts,logs,secrets}` with ownership for `android-build` on writable trees; `secrets/` restricted/empty — document modes in `docs/001-host-runbook.md`
- [x] T010 [US1] Verify: `namei -l /srv/rom /srv/rom/artifacts` and `sudo -iu android-build touch /srv/rom/artifacts/.write_test` succeeds; no `chmod 777`
- [x] T011 [US1] Document account model (admin vs `android-build` vs service users) in `docs/001-host-runbook.md` — never build Android as root
- [x] T012 [US1] Copy laptop staging Git tree to `/home/android-build/sunfish-a16-rom` preserving `.git` (rsync/scp/bundle); verify `test -f /home/android-build/sunfish-a16-rom/specs/001-development-environment/spec.md`
- [x] T013 [US1] Ensure `/srv/rom/tooling` is **not** used as Spec Kit checkout; note distinction in `docs/001-host-runbook.md`
---
## Phase 4: User Story 2 — Android host toolchain (P1)
**Story goal**: Clean-login toolchain ready for later `003` (no `repo sync` / build).
**Independent test**: `ssh android-build@rom-dev01` → `git`, `git lfs`, `repo`, `python3`, `ccache -s`, and LOS-doc Java/prereqs succeed.
### Implementation
- [x] T014 [US2] As admin, install host packages per **current** primary LineageOS/AOSP Ubuntu build documentation; record exact package list in `/srv/rom/logs/toolchain-packages.txt`
- [x] T015 [US2] Install/configure `repo` for `android-build` per current docs; verify `ssh android-build@rom-dev01 'repo --version'`
- [x] T016 [US2] Ensure `git` + `git-lfs` for `android-build`; verify `ssh android-build@rom-dev01 'git lfs version'`
- [x] T017 [US2] Configure ccache: `CCACHE_DIR=/srv/rom/ccache`, ownership `android-build`, size cap; document env in `/home/android-build/.bashrc` or `/home/android-build/.profile` and `docs/001-host-runbook.md`
- [x] T018 [US2] Verify: `ssh android-build@rom-dev01 'ccache -s'` shows configured cache dir
- [x] T019 [US2] Document FR-007 capacity (6 vCPU / 52 GB / 32 GB swap) and **defer `-j`/OOM tuning to `003`** in `docs/001-host-runbook.md` — do not run a Lineage build
- [x] T020 [US2] Clean-shell toolchain smoke checklist executed; paste command outputs into `/srv/rom/logs/toolchain-smoke.txt` and tick FR-002/SC-001 in `docs/001-verification-matrix.md`
---
## Phase 5: User Story 5 — Laptop thin client (P1/P2 overlap — do after US1–US2)
**Story goal**: Cursor Remote SSH as `android-build` + artifact rsync proven; Pixel stays on laptop USB.
**Independent test**: Remote SSH edit on host as `android-build`; dummy artifact SHA256 matches after rsync; laptop has `adb`/`fastboot` binaries.
### Implementation
- [x] T021 [US5] Document Cursor Remote SSH → `/home/android-build/sunfish-a16-rom` with `User android-build` in `docs/001-host-runbook.md` and update `specs/001-development-environment/quickstart.md` if needed
- [x] T022 [US5] Create `/srv/rom/artifacts/dummy-001.txt` as `android-build`; record `sha256sum` in `/srv/rom/logs/rsync-smoke.txt`
- [x] T023 [US5] From laptop, `rsync` dummy artifact; verify SHA256 match; record laptop path in `/srv/rom/logs/rsync-smoke.txt` and `docs/001-verification-matrix.md` (SC-002)
- [x] T024 [US5] On laptop only: run `adb --version` and `fastboot --version` (do **not** connect or flash the Pixel); record versions in `docs/001-host-runbook.md`. Document adb/fastboot-on-laptop / no USB passthrough rule (SC-004 partial)
---
## Phase 6: User Story 3 — Gitea, Ansible, Semaphore, off-VM mirror (P2)
**Story goal**: Private Gitea with four repos; canonical meta remote; `sunfish-rom-build` committed+pushed; laptop pull mirrors; unprivileged Semaphore smoke from canonical repo.
**Independent test**: Four repos on Gitea; meta history pushed; `sunfish-rom-build` fetch shows smoke playbook commit; laptop `git clone --mirror` works; Semaphore runs unprivileged smoke.
### Implementation
- [x] T025 [US3] Install Gitea on `rom-dev01` with systemd + persistent data; bind UI to loopback/non-public; document unit/paths in `docs/001-host-runbook.md`
- [x] T026 [US3] Store Gitea admin credentials only in restricted host location under `/srv/rom/secrets/` or service-native secret path — **never** commit to Git; document location (not secrets) in `docs/001-host-runbook.md`
- [x] T027 [US3] Verify: local `curl` to Gitea UI succeeds; confirm not relying on public Internet exposure; record bind address in `docs/001-host-runbook.md`
- [x] T028 [US3] Document Git transport chosen after host inspection (HTTP via SSH tunnel and/or tunneled Git SSH); **do not** open public `:2222` or blindly edit `saltenv=base` sshd — write choice in `docs/001-host-runbook.md`
- [x] T029 [US3] Create Gitea repos: `sunfish-a16-rom`, `sunfish-rom-manifest`, `sunfish-rom-build`, `sunfish-rom-patches`
- [x] T030 [US3] Push `/home/android-build/sunfish-a16-rom` history to Gitea `sunfish-a16-rom`; set `origin` to Gitea; verify remote in that directory
- [x] T031 [US3] Seed minimal structure in a working tree of `sunfish-rom-build` for Ansible (e.g. `ansible/` playbooks path) — **not** inside Spec Kit meta repo
- [x] T032 [US3] Install Ansible on `rom-dev01`; document version in `docs/001-host-runbook.md`
- [x] T033 [US3] Add unprivileged smoke playbook under `sunfish-rom-build` (gather_facts + asserts for `/srv/rom`, `android-build`, toolchain); **no become/sudo secrets**
- [x] T034 [US3] Run smoke playbook successfully via `ansible-playbook` CLI; log output to `/srv/rom/logs/ansible-smoke.txt`
- [x] T035 [US3] Commit and push the initial `sunfish-rom-build` structure and smoke playbook to its canonical Gitea repository; verify the pushed commit is visible from a fresh fetch/clone (document clone URL/path in `docs/001-host-runbook.md`)
- [x] T036 [US3] Install Semaphore; bind like Gitea (loopback/tunnel); no public exposure; document in `docs/001-host-runbook.md`
- [x] T037 [US3] Wire Semaphore so it executes the smoke playbook **originating from the canonical `sunfish-rom-build` repository**, using the simplest private mechanism supported by the actual installation (token/HTTP/SSH — decide at implement). Run once successfully; evidence in `/srv/rom/logs/semaphore-smoke.txt` (FR-005). Still unprivileged / no sudo password in Semaphore
- [x] T038 [US3] From **laptop**, create bare mirrors with `git clone --mirror` for all four repos (laptop initiates connection); run `git remote update` + `git fsck`; document mirror paths in `docs/001-host-runbook.md` (FR-010/SC-007)
- [x] T039 [US3] Confirm laptop staging `/home/aventura/Workspaces/sunfish-a16-rom` is **retained** until T030+T035+T038 verified; only then optionally mark staging as non-canonical in `docs/001-host-runbook.md`
---
## Phase 7: User Story 4 — Guest isolation (P2)
**Story goal**: Isolation checklist passes without breaking SSH.
**Independent test**: Public-NIC-only workflow; UIs non-public; SSH still works; no VPN/internal DNS required in docs.
### Implementation
- [x] T040 [US4] Document interfaces/routes: project workflow requires only public NIC — `docs/001-host-runbook.md` + evidence in `docs/001-verification-matrix.md` (SC-006)
- [x] T041 [US4] Apply isolation/firewall guidance **additively** if needed: never flush nftables/iptables; never close active SSH before tested alternative; record before/after in `/srv/rom/logs/firewall-notes.txt`
- [x] T042 [US4] Re-verify Gitea/Semaphore binds remain non-public; SSH admin + `android-build` + tunnel still work — update `docs/001-verification-matrix.md` FR-009/SC-006
- [x] T043 [US4] Confirm day-to-day runbook does not require SSYSTEMS VPN/internal DNS/LDAP/GitLab/NFS/Zabbix — `docs/001-host-runbook.md`
---
## Phase 8: Polish & cross-cutting
- [x] T044 [P] Fill all FR-*/SC-* evidence rows in `docs/001-verification-matrix.md` (including SC-005: no PVE/qm/Salt VM tasks were performed)
- [x] T045 [P] Align `specs/001-development-environment/quickstart.md` with final ports/paths/tunnel commands actually used
- [x] T046 Restart Gitea (+ Semaphore) via systemd; confirm recovery; optional reboot when safe; note results in `/srv/rom/logs/restart-smoke.txt`
- [x] T047 Declare handoff in `docs/001-host-runbook.md`: **`001` complete → proceed to `002`**, do not start Lineage sync/build
- [x] T048 Verify that no credentials/tokens/private keys are present in the **current trees OR committed history** of `sunfish-a16-rom` and `sunfish-rom-build`. Review current files plus reachable Git history; if a secret was ever committed, remove it from history and rotate/revoke it before closing `001`. Method may be manual/`git log -p`/`git rev-list` review (no mandatory Gitleaks/TruffleHog)
---
## Dependencies & story order
```text
Phase 1 (laptop docs)
↓
Phase 2 preflight (host)
↓
US1 user + SSH key + /srv/rom + meta copy
↓
US2 toolchain ──────────────┐
↓ │
US5 thin-client smokes ←───┘ (needs artifacts + meta path + android-build SSH)
↓
US3 Gitea → repos → Ansible in sunfish-rom-build → commit/push → Semaphore → laptop mirrors
↓
US4 isolation hardening/validation
↓
Polish / handoff → STOP until implement; next feature 002 after 001 done
```
**MVP (minimum useful host)**: complete through US2 + US5 (T007–T024) even if Gitea delayed — but **formal `001` done** requires P2 (US3+US4+polish) per plan.
## Parallel opportunities
- T002/T003 on laptop before VM
- After T009: T011 docs can parallel T012 copy if disk ready
- T014–T016 package/`repo`/`git-lfs` partly serial (apt) then T017 ccache
- T044/T045 polish docs in parallel after evidence exists
## FR/SC coverage map
| ID | Tasks |
|----|-------|
| FR-001 | T007–T011, T008 |
| FR-002 | T014–T018, T020 |
| FR-003 | T009–T010, T013 |
| FR-004 | T025–T030 |
| FR-005 | T031–T037 |
| FR-006 | T021–T024 |
| FR-007 | T019 |
| FR-008 | T012, T030 |
| FR-009 | T040–T043 |
| FR-010 | T038–T039 |
| SC-001 | T020 |
| SC-002 | T022–T023 |
| SC-003 | T029–T030 |
| SC-004 | T021, T024, T047 |
| SC-005 | T005, T044 |
| SC-006 | T040–T043 |
| SC-007 | T038 |
## Implementation strategy
1. Do **not** execute host tasks until SSH to `rom-dev01` works.
2. After analyze approval: STOP until VM exists, then `/speckit-implement`.
3. Follow P1 then P2; stop/report on Salt conflicts.
4. Do not plan `002` until `001` implement + verification complete.