Commit Graph

8 Commits

Author SHA1 Message Date
Julien Gautier 05faeca05d docs(setup): drop deprecated apt packages on Debian 13 trixie
CI / commits (pull_request) Successful in 3m31s
CI / scan (pull_request) Successful in 3m40s
CI / check (pull_request) Successful in 3m54s
CI / a11y (pull_request) Successful in 3m26s
Docs site / build (pull_request) Successful in 3m32s
CI / perf (pull_request) Successful in 7m23s
10-base-packages.sh was failing on a fresh Trixie VM with
`E: Impossible de trouver le paquet software-properties-common`.

The package isn't shipped on Trixie anymore, but it was also never
needed: 50-docker.sh writes /etc/apt/sources.list.d/docker.list
manually and never invokes add-apt-repository. apt-transport-https
is a transitional no-op since apt 1.5 (2018, native HTTPS). lsb-release
isn't called anywhere either - the codename detection in 50-docker.sh
reads /etc/os-release directly.

Drop all three from the install list. The minimal set is now what's
actually consumed downstream: curl wget git ca-certificates gnupg
build-essential pkg-config. Doc table descriptor updated accordingly.

Inline comment explains why each kept package is there and why the
three dropped ones were not just removed but deliberately excluded -
so a future drive-by addition doesn't reintroduce them.
2026-05-24 20:58:38 +02:00
julien d99254a280 docs(setup): make fail2ban opt-in in 70-hardening.sh (#222)
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m14s
CI / scan (push) Successful in 3m19s
CI / a11y (push) Successful in 3m59s
CI / perf (push) Successful in 5m48s
Docs site / build (push) Successful in 5m57s
## Summary

Follow-up on [#220](#220) / [#221](#221). Makes fail2ban **opt-in** in [`70-hardening.sh`](docs/setup/scripts/70-hardening.sh) instead of installing it unconditionally.

Reasoning: some corp environments already ship brute-force protection at the network layer (ACL / corp firewall / appliance) — fail2ban on the host then becomes redundant and can be the wrong layer to debug from when a rule misfires. The other three hardening steps (UFW enable, sshd lockdown) were already prompt-gated; fail2ban was the odd one out.

## What lands

| File | Change |
| --- | --- |
| `docs/setup/scripts/70-hardening.sh` | fail2ban block restructured into three branches: (1) already running → skip; (2) installed but stopped → prompt to enable+start; (3) not installed → prompt to install+enable+start. Each "no" path logs `↪ skip (user choice)` so re-runs don't repeatedly nag if the dev has already declined. |
| `docs/setup/01-dev-debian-vm-setup.md` | Table row for `70-hardening.sh` clarified — each sub-step's prompt posture is now visible: UFW prompts before enabling, fail2ban prompts before installing, sshd hardening prompts before applying. `unattended-upgrades` is the only one applied unconditionally. |
| `docs/setup/README.md` | Same descriptor adjustment. |

## Test plan

- [ ] On a fresh Debian VM with no fail2ban installed, run `70-hardening.sh`, decline the fail2ban prompt → script continues, fail2ban not installed, no service started.
- [ ] On the same VM, re-run `70-hardening.sh` → the fail2ban branch prompts again (the dev may have changed their mind); declining again produces the same `↪ skip (user choice)` result.
- [ ] On a VM where fail2ban is pre-installed but stopped (rare, but possible if infra rolled it back), the script offers to start it without re-installing.
- [x] `pnpm exec prettier --check` clean on the touched files.
- [x] `bash -n docs/setup/scripts/70-hardening.sh` (syntax check) clean.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #222
2026-05-24 20:04:16 +02:00
julien c77313c693 docs(setup): use ~/.bashrc hand-off instead of chsh for zsh switch (#221)
CI / commits (push) Has been skipped
CI / check (push) Successful in 1m50s
CI / scan (push) Successful in 3m11s
CI / a11y (push) Successful in 3m28s
CI / perf (push) Successful in 6m7s
Docs site / build (push) Successful in 4m29s
## Summary

Follow-up fix on [#220](#220). The corp infra locks the default shell at user-provisioning time on the dev VM (`10.100.201.21`) — `chsh` is denied at the PAM level, so the `sudo chsh -s` block in [`20-zsh.sh`](docs/setup/scripts/20-zsh.sh) fails on every fresh VM.

Switch to the `~/.bashrc` exec-zsh hand-off — the same proven pattern used in the legacy [`02-wsl-terminal-setup.md`](docs/setup/02-wsl-terminal-setup.md). UX-identical for the dev, zero infra escalation required.

## What lands

| File | Change |
| --- | --- |
| `docs/setup/scripts/20-zsh.sh` | Drop the `sudo chsh -s` block. Append a guarded `exec zsh -l` block to `~/.bashrc` instead, marked with a managed-by comment for idempotency on re-runs. |
| `docs/setup/01-dev-debian-vm-setup.md` | Table row for `20-zsh.sh` updated — "set as default shell" → "`~/.bashrc` (exec zsh on interactive shells — `chsh` is blocked on the corp VM)". |
| `docs/setup/README.md` | Same descriptor adjustment. |

## The hand-off block

```bash
# Managed by docs/setup/scripts/20-zsh.sh — apf-portal zsh hand-off.
# Hand off to zsh on interactive shells. The `case $-` guard avoids
# breaking non-interactive bash (scp, rsync, cron, …), and the
# ZSH_VERSION check prevents an infinite re-exec loop.
case $- in
  *i*)
    if command -v zsh >/dev/null 2>&1 && [ -z "${ZSH_VERSION-}" ]; then
      exec zsh -l
    fi
    ;;
esac
```

Two safety nets in the block:

- **`case $- in *i*)`** — only runs the hand-off when the shell flag set contains `i` (interactive). `scp` / `rsync` / non-interactive ssh executions go through bash and are not hijacked.
- **`[ -z "${ZSH_VERSION-}" ]`** — `ZSH_VERSION` is set by zsh itself; if it's already set we're already in zsh and a re-exec would loop.

## Test plan

- [ ] On the dev VM, run `20-zsh.sh` on a fresh state: bash, no existing `~/.bashrc` zsh hand-off → script exits without `chsh` error, `~/.bashrc` gets the block appended, `exit` + reconnect lands in zsh.
- [ ] Re-run `20-zsh.sh` → reports `↪ skip zsh hand-off already in ~/.bashrc` (idempotency).
- [ ] `scp some-file vm-dev:/tmp/` from the workstation still works (non-interactive bash not hijacked).
- [x] `pnpm exec prettier --check` clean on the touched markdown files.
- [x] `bash -n docs/setup/scripts/20-zsh.sh` (syntax check) clean.

## Why not amend #220

#220 has already merged. Squash-merge collapsed it to `8a04540` on main; a force-push to amend would rewrite that commit and break anyone who's pulled it. The fix lands as a separate commit on the same setup-doc family.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #221
2026-05-24 19:55:32 +02:00
julien 8a04540410 docs(setup): add Debian 13 dev-VM setup procedure + scripts + devcontainer (#220)
CI / check (push) Successful in 3m7s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 3m3s
CI / a11y (push) Successful in 3m41s
CI / perf (push) Successful in 5m24s
Docs site / build (push) Successful in 5m8s
## Summary

Adds a full Debian 13 dev-VM setup procedure ([docs/setup/01-dev-debian-vm-setup.md](docs/setup/01-dev-debian-vm-setup.md)) + 10 modular idempotent setup scripts + a systemd template + a `.devcontainer/` spec, in preparation for the new dev VM (`10.100.201.21`) replacing the WSL-based workflow. Both IDE flows (VSCode Remote-SSH + Devcontainer) and both Node toolchains (nvm on host + devcontainer image) are available — devs pick per task.

Adjacent context (does not ship here, planned follow-up):

- **Preview infra on the GitLab VM (`10.100.201.10`)** — same `dev.compose.yml`, deployed by CI on `main`. Doc placeholder in §8.6.
- **GitLab Runner migration** (act_runner Gitea → GitLab Runner Docker executor) — bundled with the Gitea → GitLab cutover.
- **Private dotfiles repo** (`apf/dotfiles`) — `~/.zshrc`, `~/.p10k.zsh`, `~/.tmux.conf` versioned. `80-dotfiles.sh` is already structured to fall back to a `~/.dotfiles/` clone when present.

No application-code changes. No CI gate impact (doc + scripts + devcontainer spec only).

## What lands

| Path | Change |
| --- | --- |
| `docs/setup/01-dev-debian-vm-setup.md` | **New.** Step-by-step doc: workstation prep (SSH agent + VSCode Remote-SSH + fonts), bootstrap orchestrator, per-script effects, project clone, infra boot, IDE flow A / B / C, apf-ai-service .NET appendix, troubleshooting. |
| `docs/setup/02-wsl-terminal-setup.md` | Renamed from `01-wsl-terminal-setup.md`. No content change. |
| `docs/setup/03-dev-web-stack.md` | Renamed from `02-dev-web-stack.md`. No content change. |
| `docs/setup/04-angular-nx-monorepo.md` | Renamed from `03-angular-nx-monorepo.md`. No content change. |
| `docs/setup/README.md` | **New.** Index of the `docs/setup/` folder. |
| `docs/setup/scripts/lib.sh` | **New.** Shared helpers — colour-coded log/ok/warn/err/skip, `apt_install` skipping already-installed, `ensure_line` idempotent append, `confirm` prompt. |
| `docs/setup/scripts/bootstrap.sh` | **New.** Orchestrator running scripts 10..80 in order with confirmation prompts. |
| `docs/setup/scripts/10-base-packages.sh` | **New.** apt update + base packages (curl, wget, git, build-essential, …). |
| `docs/setup/scripts/20-zsh.sh` | **New.** zsh + Oh My Zsh (RUNZSH=no, no shell hijack) + Powerlevel10k + `zsh-autosuggestions` + `zsh-syntax-highlighting`. Patches `~/.zshrc` (theme, plugins, fzf hook). |
| `docs/setup/scripts/30-cli-tools.sh` | **New.** `bat eza fd-find ripgrep fzf zoxide ncdu keychain` + `jq yq httpie make tree htop tmux direnv dnsutils unzip rsync`. Symlinks Debian-renamed binaries (`batcat`→`bat`, `fdfind`→`fd`) into `~/.local/bin` so notes/aliases.zsh works as-is. |
| `docs/setup/scripts/40-node.sh` | **New.** nvm v0.40.1 + Node from `.nvmrc` (currently 24) + corepack enable + pnpm warmed from `package.json#packageManager`. |
| `docs/setup/scripts/50-docker.sh` | **New.** Docker CE + compose plugin from docker.com apt repo, user added to `docker` group, `docker.service` enabled at boot. Pinned GPG key + repo line for Debian Trixie. |
| `docs/setup/scripts/60-tuning.sh` | **New.** `fs.inotify.max_user_watches=524288` (Vite/Nx watch ceiling), optional 4 GB swapfile, optional hostname rename (only prompts on generic hostnames like `debian13`). |
| `docs/setup/scripts/70-hardening.sh` | **New.** Best-effort UFW (`allow OpenSSH` only) + unattended-upgrades on security channel + fail2ban + sshd drop-in (`PermitRootLogin no`, `PasswordAuthentication no`, `AllowAgentForwarding yes`). Probes before applying, validates `sshd -t` before reloading, skips cleanly if infra already locked the box down. |
| `docs/setup/scripts/80-dotfiles.sh` | **New.** Symlinks `notes/aliases.zsh` → `~/.oh-my-zsh/custom/aliases.zsh` (backs up an existing target). Copies `notes/gitconfig.txt` to `~/.gitconfig`, prompts for identity, applies via `git config --global user.name/email`. |
| `docs/setup/systemd/apf-portal-infra@.service` | **New.** Template systemd unit auto-starting `./infra/local/dev.sh up` at boot. Install: `enable apf-portal-infra@$USER.service`. |
| `.devcontainer/devcontainer.json` | **New.** VSCode Dev Container spec on `mcr.microsoft.com/devcontainers/typescript-node:1-24-bookworm`. `docker-outside-of-docker` feature + `--network=apf-portal-dev` so DNS to `postgres`/`redis`/`otel-collector` works. `initializeCommand` fails fast if `dev.sh up` hasn't been run yet. Forwarded ports labelled. Six dev extensions pre-installed. |
| `.devcontainer/post-create.sh` | **New.** `corepack enable && pnpm install --frozen-lockfile`. |
| `CLAUDE.md` | "Environment conventions" rewritten: documents the two envs (`local` / `development`) + hybrid sub-mode + the two IDE flows (Remote-SSH / Devcontainer), points at the new VM setup doc and the legacy WSL doc. |

## Key choices

- **Idempotent scripts, individually runnable.** Each script probes before doing anything (apt package already installed? plugin already cloned? UFW already active? sshd drop-in already present?). Bootstrap is the orchestrator; each script also runs standalone. Re-running after a partial setup is safe and reports `↪ skip` for the no-op cases.
- **No private key on the VM — SSH agent forwarding instead.** `~/.ssh/config` on the workstation carries `ForwardAgent yes`; the VM never holds long-lived secrets. Same future pattern for GPG signing (covered in §8.5 as appendix). `keychain` is installed by `30-cli-tools.sh` as a fallback for scenarios where agent forwarding is not available (CI runners, scripts).
- **Hardening is "best-effort, probe-first".** Some infra teams ship pre-hardened VMs; this script doesn't fight that. UFW already active? Print rules and skip. unattended-upgrades already on? Skip. SSH already locked down? Skip. Each section validates before reloading so a misconfig can't take SSH offline.
- **Devcontainer assumes infra-on-host.** The container runs on the VM but talks to postgres / redis / otel **on the same VM's host docker daemon** through the shared `apf-portal-dev` Compose network. `initializeCommand` fails fast with a clear message if `./infra/local/dev.sh up` hasn't been run yet — better than puzzling `ECONNREFUSED` errors at runtime.
- **`60-tuning.sh` raises inotify to 524288.** Default Debian limit is 8K; Vite/Nx in this monorepo blow past that. The setting is persisted in `/etc/sysctl.d/99-apf-portal.conf` so it survives reboot.
- **Hybrid mode (workstation IDE + VM infra) is a documented sub-mode.** SSH `LocalForward` directives on 5432/6379/4317/4318 expose the VM's infra services as `localhost:*` on the workstation. Latency cost: 5-15 ms per query, fine for daily work; for long-running flows, wrap in `tmux` or use `autossh`.

## Notes for the reviewer

- **Renaming the existing setup docs (01→02, 02→03, 03→04) is the only "destructive" change.** `git log --follow` still works because of `git mv`. Diff shows up as renames, not delete-and-add.
- **The brief asked for `bat eza fd-find ripgrep fzf zoxide docker keychain ncdu git`.** `git` is installed by `10-base-packages.sh` (every other script needs it). Everything else lives in `30-cli-tools.sh` + `50-docker.sh`. The "fullstack-dev extras" (`jq yq httpie make tree htop tmux direnv dnsutils unzip rsync`) are additions I proposed in the lock-in question and that you greenlit (full scope) — easy to trim if any of them turn out to be unwanted.
- **Both Node toolchains in parallel** — nvm on the VM (via `40-node.sh`) **and** devcontainer in the repo. Devs can use either; both read the same `.nvmrc` + `packageManager` pin so the version stays consistent.
- **The systemd unit is a TEMPLATE** (`apf-portal-infra@.service`) — install once, enable per-user (`enable apf-portal-infra@$USER.service`). This is the right shape for a shared VM with multiple devs eventually, even if today only one user uses it.
- **No PR-body Co-Authored-By trailer, no Generated-with-Claude footer**, per the project rule.

## Test plan

Manual (no automated test exists for this kind of setup work):

- [ ] On a fresh Debian 13 VM: `git clone …`, `./docs/setup/scripts/bootstrap.sh`, answer prompts, end up with zsh + Powerlevel10k + all the requested CLI tools + Node 24 + pnpm 10.33.4 + Docker on PATH.
- [ ] `./infra/local/dev.sh up` boots successfully against the VM's local docker daemon.
- [ ] `pnpm install` + `pnpm exec nx run-many -t lint test --parallel=3` passes on the VM.
- [ ] VSCode Remote-SSH from a workstation: connect, open `~/Works/apf_portal`, run `pnpm exec nx serve portal-bff`, confirm reaches `postgres:5432`.
- [ ] VSCode Dev Containers from the same workstation: `Reopen in Container`, image builds, `postCreateCommand` runs `pnpm install`, dev server reaches postgres through the `apf-portal-dev` network.
- [ ] Hybrid mode: SSH tunnel from workstation, `pnpm exec nx serve portal-bff` locally, confirm postgres reachable via `localhost:5432`.
- [x] `pnpm exec prettier --check` clean on the touched markdown files.
- [x] Scripts pass `bash -n` (syntax check) — verified during writing.

## What's next

- Validate by walking through this doc on the actual VM `10.100.201.21`. Any friction surfaced becomes a follow-up PR (`docs(setup): ...`).
- Once the dev VM is operational, return to **ADR-0027 Implementation PR 1** (Region / Delegation / Structure Prisma schema + inline reference-data migration) — paused since the start of this PR.
- Set up the **private dotfiles repo** (`apf/dotfiles`) as a small follow-up, then teach `80-dotfiles.sh` to prefer the dotfiles repo over `notes/`.
- When migrating to GitLab: PR pair — (a) `git remote set-url` doc updates here, (b) `infra/gitlab-runners/` replacing `infra/ci-runners.compose.yml`.
- Future: deploy the **shared preview infra** on `vm-gitlab` (10.100.201.10) — CI-driven, separate PR.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #220
2026-05-24 18:40:07 +02:00
Julien Gautier 0e58e32d29 chore: relocate ADRs from decisions/ to docs/decisions/ to consolidate documentation
Move the ADR folder under docs/ alongside the rest of the project
documentation. Convention (flat folder, globally-sequential 4-digit
numbering, tags-based categorization, MADR 4.0.0 format) is unchanged
- only the path moved.

- git mv decisions docs/decisions preserves history for all 18 ADRs +
  README + template (19 files renamed in this commit).
- ADR-0001 amended in-place with a dated note documenting the
  relocation. Status remains 'accepted' - the location detail
  changed, the decision did not.
- All cross-references updated:
  - CLAUDE.md (~17 ADR links + 3 mentions of decisions/ in the Project
    rules section)
  - docs/README.md (now references decisions/ as a sibling under docs/)
  - docs/setup/03-angular-nx-monorepo.md (paths shortened from
    ../../decisions/ to ../decisions/, since setup/ and decisions/ are
    now both inside docs/)
  - docs/decisions/0003 ../CLAUDE.md adjusted to ../../CLAUDE.md
    (one extra level of nesting)
  - docs/decisions/template.md mention of the README path
  - notes/asvs-level-decision-briefing-rssi.md mention of the index

Sanity verified: every ADR link in CLAUDE.md, docs/setup/03, and
docs/decisions/0001 resolves to an existing file. pnpm nx run-many
-t lint passes on 8 projects.
2026-04-30 18:57:59 +02:00
Julien Gautier 880c7ded6b chore: rename project from adastra_portal to apf_portal
The host organisation - APF France Handicap - was confirmed on
2026-04-30. Update all in-repo references to use apf_portal
(snake_case in prose) and apf-portal (kebab-case workspace name and
repo URL). Touched: CLAUDE.md, ADRs 0001/0002/0003/0015, and the Nx
bootstrap setup guide.

The historical name is preserved as a single sentence in ADR-0003 as
a self-validating example of the function-prefixed naming convention
designed exactly for this scenario - the apps (portal-shell,
portal-bff) and the lib conventions (feature-<name>, shared-<scope>)
were unaffected by the rename, which was the explicit point of
ADR-0003.

Memory state aligned out-of-band: project_adastra.md retired,
project_apf_portal.md created with the expanded APF context (host
org, health + financial data scope, ASVS L3 pending RSSI input, UI
stack decision spartan-ng + CDK + Tailwind, expanded phase-3 status).

Pending follow-ups (user-side, not in this commit):
- rename the Gitea repo julien/adastra_portal -> julien/apf_portal
- git remote set-url origin gitea@git.unespace.com:julien/apf_portal.git
- optionally rename the local working directory ~/Works/adastra_portal/
  -> ~/Works/apf_portal/
2026-04-30 13:31:38 +02:00
Julien Gautier 084ff5c3bf docs: add ADR-0007 for pre-commit hooks and align documentation references
- decisions/0007-pre-commit-hooks-and-conventional-commits.md formalizes
  Husky + lint-staged + commitlint with Conventional Commits as the local
  quality-gate baseline.
- decisions/README.md index updated.
- docs/setup/03 section 8 rewritten to reference the ADR and document the
  full hook setup (pre-commit, commit-msg, commitlint config).
- docs/setup/03 future-work table 'ADR(s)' column removed; future ADR
  numbers are now assigned at the moment each ADR is written, not
  pre-reserved.
- CLAUDE.md aligned: pre-allocated phase-2 ADR numbers replaced by phase
  references; a pointer to ADR-0007 added under 'Local quality gates'.
2026-04-29 21:01:49 +02:00
Julien Gautier 79eee77594 chore: initialize repository with project rules, docs, and phase-1 ADRs
Set up the foundation for the adastra-portal project:

- CLAUDE.md captures durable project rules (quality bar, security/perf/a11y
  as first-class, language, commit conventions, ADR proactivity).
- docs/ and decisions/ scaffolding with maintained indexes (docs/README.md
  and decisions/README.md), MADR 4.0.0 template, and tag vocabulary.
- Phase-1 ADRs (0001-0006) lock structural choices: ADR usage, Nx monorepo
  with the apps preset, naming convention (adastra-portal / portal-shell /
  portal-bff), Angular CSR/zoneless/Signals/Vitest, NestJS over Express,
  PostgreSQL with Prisma.
- docs/setup/ guides translated to English.
- .gitignore covers Node/Nx artifacts and the personal notes/ scratchpad.

The Nx workspace itself is not yet bootstrapped; that step is gated on a
revised setup guide aligned with the ADRs.
2026-04-29 20:43:00 +02:00