MakerFLOSS/slides/tappaas/how-to/new-module/index.md
Lars Rossen f1c1c79b63
All checks were successful
Build docs site / build (push) Successful in 44s
Build slides / build (push) Successful in 1m2s
slides(tappaas): write the OrangeMaker podman session deck
Sixteen slides built around the live demo: two orientation diagrams (site
shape with environments/zones/satellite, and the pull-based GitOps repo
topology), a condensed module anatomy anchored on the real podman.json from
Community/src/larsrossen/containers/podman, the three demo beats (test
environment, module add, prove it), and the identity hand-out.

Mermaid subgraph titles collide with the node row under flowchart TB, so both
diagrams use LR; _class directives repeat 'invert' because a local _class
replaces the deck-level one rather than adding to it.
2026-08-22 16:47:53 +02:00

8.2 KiB

marp theme class paginate title description
true gaia invert true How to implement a TAPPaaS module Live build of a Podman module — OrangeMaker session

Getting a module onto TAPPaaS

Live build of a Podman container host

OrangeMaker · TODO: date


The plan

  1. What TAPPaaS is — two diagrams, ten minutes, no deeper
  2. What a module actually is — a json contract and three scripts
  3. Build one live — a test environment, then podman into it
  4. Prove it works — tests, then the web console
  5. You get a login — your own identity on this system

You leave with: a mental model, a module you watched get built, and an account.


The shape of a TAPPaaS site

flowchart LR
  sat["satellite VPS<br/>public IP · ingress<br/>off-site backup"]
  subgraph z0["mgmt · zone mgmt"]
    cicd["tappaas-cicd"]
    net["network"]
    clu["cluster"]
    bak["backup"]
    idp["identity"]
  end
  subgraph z1["default · zone srv"]
    nc["nextcloud"]
    ha["home-assistant"]
  end
  subgraph z2["test · zone hacklab"]
    pod["podman — today"]
  end
  sat --> net
  cicd --> nc
  cicd --> pod

The words we will use

Word Meaning
Module The smallest deployable unit — its own VM, its own json contract. Foundation and apps are modules.
Environment A tenant. mgmt and one named after the site always exist; add more freely.
Zone A VLAN with a firewall policy. Modules land in their environment's zone.
Mothership tappaas-cicd — where the managers run and where you type.
Satellite Optional VPS, for sites with no usable public IP.

Everything is a module. There is one model to learn, not eight.


How code gets in: pull-based GitOps

flowchart RL
  subgraph t["TAPPaaS repository"]
    stable["stable"]
    main["main"]
  end
  subgraph c["Community repository"]
    cm["main"]
  end
  subgraph inst["Your TAPPaaS · tappaas-cicd"]
    lt["clone of TAPPaaS"]
    lc["clone of Community"]
  end
  lt -->|pull| stable
  lc -->|pull| cm

Nothing pushes into your site. It pulls, on a schedule, and reconciles.


Managers: the verbs you will watch me type

module-manager       module add|test|reconcile|modify|delete
environment-manager  add|modify|reconcile
network-manager      zone add|merge|reconcile
people-manager       user add|modify · reconcile --apply

Admins drive verbs, never hand-edited JSON. Each manager owns one slice of desired state; controllers push it into OPNsense, Proxmox, the switch, Authentik.


A module is a contract and three scripts

podman/
├── podman.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)
├── README.md       # what it is
└── INSTALL.md      # what automation cannot do for you

That is the whole surface. Everything else is the platform's job.


The contract, for real

{
  "description": "Podman container host — Debian 13 VM with rootless Podman + Cockpit",
  "vmname": "podman", "vmid": 812,
  "dependsOn": ["cluster:vm", "templates:debian", "backup:vm", "network:proxy"],
  "config": {
    "cluster:vm": { "cores": 2, "memory": "2048", "diskSize": "20G",
                    "image": "debian-13-generic-amd64.qcow2" },
    "network:proxy": { "proxyPort": 9090, "proxyUpstreamTls": true,
                       "proxyUpstreamHttp1": true, "proxyAllowedZones": ["mgmt"] }
  }
}

dependsOn is the whole trick: four services, declared — not scripted.


The three scripts, honestly

Script Called Does
install.sh once, at add For podman: exec update.sh — install is update here
update.sh every schedule apt: podman, podman-compose, cockpit, cockpit-podman; enable the rootless socket
test.sh on demand + before every update merge VM reachable · podman present · plugin present · console answers on :9090

If test.sh is honest, unattended updates are safe. That is the entire deal.


What you did not have to write

  • 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.<domain> with a real certificate — network:proxy, one dependency
  • Nightly backup to PBS — backup:vm
  • Scheduled updates and health reporting — the mothership

Six lines of json bought all of it.


Demo 1 — an environment of our own

# srvTest ships in the zone template; a fresh one keeps the demo self-contained
network-manager zone add hacklab --from-zone srv

environment-manager add test \
    --display "Hackerspace test" \
    --zone hacklab \
    --domain test.<our-domain>

A tenant with its own VLAN, its own firewall posture, its own DNS names — and nothing in it can touch production.

TODO: our domain — and do we build the zone live or pre-bake it?


Demo 2 — install the module

module-manager module add podman --environment test

Watch the order: dependencies resolve first, then the VM, then the network, then install.sh. Install order is computed from every module's dependsOn — nobody maintains a list.


Demo 3 — prove it, then look at it

module-manager module test podman

Then open https://podman.<domain> — Cockpit, with the Podman page.

  • Reachable from the mgmt zone only. Not from the internet, by declaration.
  • Cockpit authenticates against Linux accounts on the VM — the cloud-init tappaas user has no password until someone sets one.

TODO: pre-create the demo login, or set the password live?


Where this module lives

Community/src/larsrossen/containers/podman/

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 yours, not shared

Adding a repository is one site-manager repository add.


Your turn — identity on this TAPPaaS

people-manager user add <you> --email <you>@example.org \
    --roles user --groups orangemaker__users

people-manager reconcile          # preview
people-manager reconcile --apply  # push to Authentik

One login per person, roles via groups. TODO: which groups/roles do participants get?


Take it further

  • Develop a module — tappaas.org/generated/develop-a-module
  • Every json field — tappaas.org/generated/schemas
  • Zones and the access model — tappaas.org/generated/zones
  • Repository topology — tappaas.org/generated/design/cicd-git
  • This deck — slides.makerfloss.eu/tappaas/how-to/new-module

Pick an app you want on a sovereign box. Open an issue first — someone may already be packaging it.