slides(tappaas): the session builds the portainer module
Retargets the deck from podman to portainer: the contract, the three script slides, both demo slides and the repo-location slide. Demo 3 is now an actual single sign-on — 'no login form, you are already you' — because Portainer is an OIDC client and Cockpit never could be. The test.sh slide keeps the check worth stealing: /api/settings/public reporting method 3, which catches identity silently falling back to local accounts.
This commit is contained in:
parent
33e783efb5
commit
87d6d83ff3
1 changed files with 52 additions and 45 deletions
|
|
@ -4,7 +4,7 @@ theme: gaia
|
|||
class: invert
|
||||
paginate: true
|
||||
title: How to implement a TAPPaaS module
|
||||
description: Live build of a Podman module — OrangeMaker, 24 August
|
||||
description: Live build of a Portainer module — OrangeMaker, 24 August
|
||||
---
|
||||
|
||||
<style>
|
||||
|
|
@ -27,7 +27,7 @@ section.diagram h2 { margin-bottom: 0.1em; }
|
|||
|
||||
# Getting a module onto TAPPaaS
|
||||
|
||||
Live build of a **Podman** container host
|
||||
Live build of a **Portainer** container console
|
||||
|
||||
OrangeMaker · 24 August
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ counterpart — keep the deck moving and spend the time in the shell.
|
|||
|
||||
1. **What TAPPaaS is** — a few diagrams, ten minutes, no deeper
|
||||
2. **What a module actually is** — a json contract and three scripts
|
||||
3. **Build one live** — the `lab1` environment, then `podman` into it
|
||||
3. **Build one live** — the `lab1` environment, then `portainer` into it
|
||||
4. **Prove it works** — tests, then the web console
|
||||
5. **You get a login** — your own identity on this system
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ flowchart LR
|
|||
ha["home-assistant"]
|
||||
end
|
||||
subgraph z2["lab1 · zone lab1"]
|
||||
pod["podman — today"]
|
||||
pod["portainer — today"]
|
||||
end
|
||||
sat --> net
|
||||
cicd --> nc
|
||||
|
|
@ -156,8 +156,8 @@ We track **`main`**, not `stable` — this is a lab, we want the new things.
|
|||
## A module is a contract and three scripts
|
||||
|
||||
```text
|
||||
podman/
|
||||
├── podman.json # the contract — what it is, needs, provides
|
||||
portainer/
|
||||
├── portainer.json # the contract — what it is, needs, provides
|
||||
├── install.sh # put the software in the VM (once)
|
||||
├── update.sh # keep it patched (on schedule)
|
||||
├── test.sh # prove it still works (gates updates)
|
||||
|
|
@ -173,15 +173,16 @@ That is the whole surface. Everything else is the platform's job.
|
|||
|
||||
```json
|
||||
{
|
||||
"description": "Podman container host — Debian 13 VM with rootless Podman + Cockpit",
|
||||
"vmname": "podman", "vmid": 812,
|
||||
"description": "Portainer CE — container console with TAPPaaS login",
|
||||
"vmname": "portainer", "vmid": 813,
|
||||
"dependsOn": ["cluster:vm", "templates:debian", "backup:vm",
|
||||
"network:proxy", "identity:accessControl"],
|
||||
"network:proxy", "identity:identity"],
|
||||
"identity": { "oidcRedirectPaths": ["/"],
|
||||
"secretsEnv": "/etc/secrets/portainer.env" },
|
||||
"config": {
|
||||
"cluster:vm": { "cores": 2, "memory": "2048", "diskSize": "20G",
|
||||
"cluster:vm": { "cores": 2, "memory": "2048", "diskSize": "32G",
|
||||
"image": "debian-13-generic-amd64.qcow2" },
|
||||
"network:proxy": { "proxyPort": 9090, "proxyUpstreamTls": true,
|
||||
"proxyUpstreamHttp1": true, "proxyAllowedZones": ["mgmt"] }
|
||||
"network:proxy": { "proxyPort": 9443, "proxyUpstreamTls": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -199,11 +200,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||
exec "${SCRIPT_DIR}/update.sh" "$@"
|
||||
```
|
||||
|
||||
By the time it runs, `cluster:vm` has built the VM and `templates:debian` has
|
||||
apt-upgraded it and installed the guest agent.
|
||||
By the time it runs: `cluster:vm` built the VM, `templates:debian` apt-upgraded it,
|
||||
`network:proxy` published the name — and `identity:identity` already created the
|
||||
OIDC application and dropped its client credentials on the VM.
|
||||
|
||||
For podman there is nothing install-only — **install *is* update**. Five lines is a
|
||||
perfectly good `install.sh`.
|
||||
Nothing here is install-only, so **install *is* update**. Five lines is a perfectly
|
||||
good `install.sh`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -211,15 +213,16 @@ perfectly good `install.sh`.
|
|||
|
||||
Runs on the **mothership**, not on the VM. It:
|
||||
|
||||
1. finds the node hosting the VM via `pvesh` — HA-safe, the VM may have moved
|
||||
2. resolves the VM's IP through the **Proxmox guest agent**, then waits for SSH
|
||||
3. `apt-get install podman podman-compose slirp4netns uidmap cockpit cockpit-podman`
|
||||
4. enables the **rootless** `podman.socket`, `loginctl enable-linger`, `cockpit.socket`
|
||||
5. records `/etc/tappaas-podman.version`
|
||||
6. polls `https://localhost:9090` until the console answers, then prints the URL
|
||||
1. finds the node hosting the VM via `pvesh`, resolves its IP through the **guest agent**
|
||||
2. `apt install podman`, then enables the **rootful** `podman.socket` — that socket
|
||||
*is* the Docker-compatible API Portainer speaks
|
||||
3. pulls `portainer-ce:lts` and runs it against the socket + a named volume
|
||||
4. creates a **break-glass admin**, password into `/etc/secrets/portainer-admin`
|
||||
5. reads `/etc/secrets/portainer.env`, fetches the OIDC **discovery document**,
|
||||
and `PUT`s the three endpoints into `/api/settings` → login becomes OAuth
|
||||
|
||||
Every step is re-runnable: apt is a no-op when current, the marker is overwritten,
|
||||
the sockets are `enable --now`. That is what "idempotent" has to mean in practice.
|
||||
Re-runnable throughout: the container is recreated only when the image digest
|
||||
actually changed, and an existing admin password is never regenerated.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -228,14 +231,14 @@ the sockets are `enable --now`. That is what "idempotent" has to mean in practic
|
|||
| Check | How |
|
||||
| --- | --- |
|
||||
| VM reachable | IP resolves via the guest agent |
|
||||
| podman installed | version marker + `podman --version` actually runs |
|
||||
| plugin present | `dpkg -s cockpit-podman` |
|
||||
| console alive | `:9090` answers `200`, `302`, `401` or `403` |
|
||||
| container API | rootful `podman.socket` is active |
|
||||
| app running | `podman container inspect` says `running` |
|
||||
| API alive | `/api/status` returns a version |
|
||||
| **identity intact** | `/api/settings/public` reports method `3` = OAuth |
|
||||
|
||||
A login page **is** a pass — pre-auth, `401` is the healthy answer.
|
||||
|
||||
Prints `N passed, M failed` and exits non-zero on any failure. Note `curl -s`, not
|
||||
`--fail`: `--fail` exits 22 on 4xx and would corrupt the `-w '%{http_code}'` we read.
|
||||
That last one is the check worth stealing. It catches the failure nobody notices:
|
||||
identity quietly falling back to local accounts, so the app still *works* while no
|
||||
longer being the login you thought it was.
|
||||
|
||||
Run on demand, and again before every update is merged. If `test.sh` is honest,
|
||||
unattended updates are safe — that is the entire deal.
|
||||
|
|
@ -247,8 +250,8 @@ unattended updates are safe — that is the entire deal.
|
|||
- A VM — `cluster:vm` built it from the Debian 13 cloud image
|
||||
- OS prep — `templates:debian` did apt + guest agent
|
||||
- A VLAN, an interface, DHCP, firewall rules — the zone came with the environment
|
||||
- `https://podman.lab1.makerfloss.eu` with a real certificate — `network:proxy`
|
||||
- An access gate — `identity:accessControl`: only the bound groups reach the URL
|
||||
- `https://portainer.lab1.makerfloss.eu` with a real certificate — `network:proxy`
|
||||
- An OIDC application, a group binding and client credentials — `identity:identity`
|
||||
- Nightly backup to PBS, scheduled updates, health reporting
|
||||
|
||||
A few lines of json bought all of it.
|
||||
|
|
@ -279,10 +282,10 @@ A tenant with its own VLAN, firewall posture and DNS names —
|
|||
## Demo 2 — install the module
|
||||
|
||||
```bash
|
||||
module-manager module add podman --environment lab1
|
||||
module-manager module add portainer --environment lab1
|
||||
|
||||
module-manager module list # what is deployed
|
||||
module-manager module show podman # the resolved config, cascade applied
|
||||
module-manager module show portainer # the resolved config, cascade applied
|
||||
```
|
||||
|
||||
Watch the order: dependencies resolve first, then the VM, then the network, then
|
||||
|
|
@ -296,31 +299,35 @@ maintains a list.
|
|||
## Demo 3 — prove it, then look at it
|
||||
|
||||
```bash
|
||||
module-manager module test podman
|
||||
module-manager module test portainer
|
||||
```
|
||||
|
||||
Then open **`https://podman.lab1.makerfloss.eu`** — Cockpit, with the Podman page.
|
||||
Then open **`https://portainer.lab1.makerfloss.eu`** and press **Sign in**.
|
||||
|
||||
- Reachable from the `mgmt` zone only. Not from the internet, by declaration.
|
||||
- Authentik gates the URL: no `devops` group, no console. Then Cockpit asks *again* —
|
||||
it is not an OIDC client, and a local Unix account is not optional for it.
|
||||
- Two logins, honestly. `DESIGN.md` in the repo says what it would take to fix.
|
||||
- No login form. You are already you — Authentik, via OIDC.
|
||||
- Not in `devops`? No console. The group binding **is** the access gate.
|
||||
- Other machines in `lab1` join as Environments: one agent container on `:9001`.
|
||||
|
||||
Create a container. Start it. Read its logs. From a browser, as yourself.
|
||||
|
||||
---
|
||||
|
||||
## Where this module lives
|
||||
|
||||
```text
|
||||
Community/src/larsrossen/containers/podman/
|
||||
makerfloss/src/containers/portainer/
|
||||
```
|
||||
|
||||
Two siblings next to it: `podman/` (one host, local login) and `komodo/` (GPL,
|
||||
scripts still to write). Same job, three answers.
|
||||
|
||||
Three homes for a module, all first-class:
|
||||
|
||||
| Home | For |
|
||||
| --- | --- |
|
||||
| **TAPPaaS** repo, by pull request | modules the whole project should carry |
|
||||
| **Community** repo | yours, shared, no gatekeeping — today's podman |
|
||||
| **Private** repo — e.g. `forgejo.makerfloss.eu` | yours, not shared |
|
||||
| **Community** repo | yours, shared, no gatekeeping |
|
||||
| **Private** repo — `forgejo.makerfloss.eu/TAPPaaS/makerfloss` | ours, today's portainer |
|
||||
|
||||
Adding a repository is one `site-manager repository add`.
|
||||
|
||||
|
|
@ -339,7 +346,7 @@ authentik-manager user-recovery-link <you> # one-time URL: set your own passwo
|
|||
|
||||
One login per person, roles via groups. Nobody types a password into a chat window.
|
||||
|
||||
<!-- Do these live, one per participant, while the podman VM builds. -->
|
||||
<!-- Do these live, one per participant, while the portainer VM builds. -->
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue