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
|
class: invert
|
||||||
paginate: true
|
paginate: true
|
||||||
title: How to implement a TAPPaaS module
|
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>
|
<style>
|
||||||
|
|
@ -27,7 +27,7 @@ section.diagram h2 { margin-bottom: 0.1em; }
|
||||||
|
|
||||||
# Getting a module onto TAPPaaS
|
# Getting a module onto TAPPaaS
|
||||||
|
|
||||||
Live build of a **Podman** container host
|
Live build of a **Portainer** container console
|
||||||
|
|
||||||
OrangeMaker · 24 August
|
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
|
1. **What TAPPaaS is** — a few diagrams, ten minutes, no deeper
|
||||||
2. **What a module actually is** — a json contract and three scripts
|
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
|
4. **Prove it works** — tests, then the web console
|
||||||
5. **You get a login** — your own identity on this system
|
5. **You get a login** — your own identity on this system
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ flowchart LR
|
||||||
ha["home-assistant"]
|
ha["home-assistant"]
|
||||||
end
|
end
|
||||||
subgraph z2["lab1 · zone lab1"]
|
subgraph z2["lab1 · zone lab1"]
|
||||||
pod["podman — today"]
|
pod["portainer — today"]
|
||||||
end
|
end
|
||||||
sat --> net
|
sat --> net
|
||||||
cicd --> nc
|
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
|
## A module is a contract and three scripts
|
||||||
|
|
||||||
```text
|
```text
|
||||||
podman/
|
portainer/
|
||||||
├── podman.json # the contract — what it is, needs, provides
|
├── portainer.json # the contract — what it is, needs, provides
|
||||||
├── install.sh # put the software in the VM (once)
|
├── install.sh # put the software in the VM (once)
|
||||||
├── update.sh # keep it patched (on schedule)
|
├── update.sh # keep it patched (on schedule)
|
||||||
├── test.sh # prove it still works (gates updates)
|
├── 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
|
```json
|
||||||
{
|
{
|
||||||
"description": "Podman container host — Debian 13 VM with rootless Podman + Cockpit",
|
"description": "Portainer CE — container console with TAPPaaS login",
|
||||||
"vmname": "podman", "vmid": 812,
|
"vmname": "portainer", "vmid": 813,
|
||||||
"dependsOn": ["cluster:vm", "templates:debian", "backup:vm",
|
"dependsOn": ["cluster:vm", "templates:debian", "backup:vm",
|
||||||
"network:proxy", "identity:accessControl"],
|
"network:proxy", "identity:identity"],
|
||||||
|
"identity": { "oidcRedirectPaths": ["/"],
|
||||||
|
"secretsEnv": "/etc/secrets/portainer.env" },
|
||||||
"config": {
|
"config": {
|
||||||
"cluster:vm": { "cores": 2, "memory": "2048", "diskSize": "20G",
|
"cluster:vm": { "cores": 2, "memory": "2048", "diskSize": "32G",
|
||||||
"image": "debian-13-generic-amd64.qcow2" },
|
"image": "debian-13-generic-amd64.qcow2" },
|
||||||
"network:proxy": { "proxyPort": 9090, "proxyUpstreamTls": true,
|
"network:proxy": { "proxyPort": 9443, "proxyUpstreamTls": true }
|
||||||
"proxyUpstreamHttp1": true, "proxyAllowedZones": ["mgmt"] }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -199,11 +200,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
exec "${SCRIPT_DIR}/update.sh" "$@"
|
exec "${SCRIPT_DIR}/update.sh" "$@"
|
||||||
```
|
```
|
||||||
|
|
||||||
By the time it runs, `cluster:vm` has built the VM and `templates:debian` has
|
By the time it runs: `cluster:vm` built the VM, `templates:debian` apt-upgraded it,
|
||||||
apt-upgraded it and installed the guest agent.
|
`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
|
Nothing here is install-only, so **install *is* update**. Five lines is a perfectly
|
||||||
perfectly good `install.sh`.
|
good `install.sh`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -211,15 +213,16 @@ perfectly good `install.sh`.
|
||||||
|
|
||||||
Runs on the **mothership**, not on the VM. It:
|
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
|
1. finds the node hosting the VM via `pvesh`, resolves its IP through the **guest agent**
|
||||||
2. resolves the VM's IP through the **Proxmox guest agent**, then waits for SSH
|
2. `apt install podman`, then enables the **rootful** `podman.socket` — that socket
|
||||||
3. `apt-get install podman podman-compose slirp4netns uidmap cockpit cockpit-podman`
|
*is* the Docker-compatible API Portainer speaks
|
||||||
4. enables the **rootless** `podman.socket`, `loginctl enable-linger`, `cockpit.socket`
|
3. pulls `portainer-ce:lts` and runs it against the socket + a named volume
|
||||||
5. records `/etc/tappaas-podman.version`
|
4. creates a **break-glass admin**, password into `/etc/secrets/portainer-admin`
|
||||||
6. polls `https://localhost:9090` until the console answers, then prints the URL
|
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,
|
Re-runnable throughout: the container is recreated only when the image digest
|
||||||
the sockets are `enable --now`. That is what "idempotent" has to mean in practice.
|
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 |
|
| Check | How |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| VM reachable | IP resolves via the guest agent |
|
| VM reachable | IP resolves via the guest agent |
|
||||||
| podman installed | version marker + `podman --version` actually runs |
|
| container API | rootful `podman.socket` is active |
|
||||||
| plugin present | `dpkg -s cockpit-podman` |
|
| app running | `podman container inspect` says `running` |
|
||||||
| console alive | `:9090` answers `200`, `302`, `401` or `403` |
|
| 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.
|
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
|
||||||
Prints `N passed, M failed` and exits non-zero on any failure. Note `curl -s`, not
|
longer being the login you thought it was.
|
||||||
`--fail`: `--fail` exits 22 on 4xx and would corrupt the `-w '%{http_code}'` we read.
|
|
||||||
|
|
||||||
Run on demand, and again before every update is merged. If `test.sh` is honest,
|
Run on demand, and again before every update is merged. If `test.sh` is honest,
|
||||||
unattended updates are safe — that is the entire deal.
|
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
|
- A VM — `cluster:vm` built it from the Debian 13 cloud image
|
||||||
- OS prep — `templates:debian` did apt + guest agent
|
- OS prep — `templates:debian` did apt + guest agent
|
||||||
- A VLAN, an interface, DHCP, firewall rules — the zone came with the environment
|
- A VLAN, an interface, DHCP, firewall rules — the zone came with the environment
|
||||||
- `https://podman.lab1.makerfloss.eu` with a real certificate — `network:proxy`
|
- `https://portainer.lab1.makerfloss.eu` with a real certificate — `network:proxy`
|
||||||
- An access gate — `identity:accessControl`: only the bound groups reach the URL
|
- An OIDC application, a group binding and client credentials — `identity:identity`
|
||||||
- Nightly backup to PBS, scheduled updates, health reporting
|
- Nightly backup to PBS, scheduled updates, health reporting
|
||||||
|
|
||||||
A few lines of json bought all of it.
|
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
|
## Demo 2 — install the module
|
||||||
|
|
||||||
```bash
|
```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 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
|
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
|
## Demo 3 — prove it, then look at it
|
||||||
|
|
||||||
```bash
|
```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.
|
- No login form. You are already you — Authentik, via OIDC.
|
||||||
- Authentik gates the URL: no `devops` group, no console. Then Cockpit asks *again* —
|
- Not in `devops`? No console. The group binding **is** the access gate.
|
||||||
it is not an OIDC client, and a local Unix account is not optional for it.
|
- Other machines in `lab1` join as Environments: one agent container on `:9001`.
|
||||||
- Two logins, honestly. `DESIGN.md` in the repo says what it would take to fix.
|
|
||||||
|
Create a container. Start it. Read its logs. From a browser, as yourself.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Where this module lives
|
## Where this module lives
|
||||||
|
|
||||||
```text
|
```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:
|
Three homes for a module, all first-class:
|
||||||
|
|
||||||
| Home | For |
|
| Home | For |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **TAPPaaS** repo, by pull request | modules the whole project should carry |
|
| **TAPPaaS** repo, by pull request | modules the whole project should carry |
|
||||||
| **Community** repo | yours, shared, no gatekeeping — today's podman |
|
| **Community** repo | yours, shared, no gatekeeping |
|
||||||
| **Private** repo — e.g. `forgejo.makerfloss.eu` | yours, not shared |
|
| **Private** repo — `forgejo.makerfloss.eu/TAPPaaS/makerfloss` | ours, today's portainer |
|
||||||
|
|
||||||
Adding a repository is one `site-manager repository add`.
|
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.
|
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