Install Mesopod on your Mac¶
Mesopod runs your apps on a machine you own. The console issues a one-time
install command; you paste it in a Mac terminal; the machine calls home.
Nothing listens for inbound connections from us, and the command needs no
sudo — both Mac targets run as your own user.
This takes a couple of minutes, most of it spent creating a local cluster.
(Running a spare Linux box instead? See the Linux server appendix below — same console flow, different machine. On a Windows 11 PC, the console's Windows tab gives you one PowerShell line: see Windows: one line.)
What you need¶
Mesopod installs on a Mac, a Windows 11 PC or a Linux machine. Here is where each setup stands:
| Machine | Status | Notes |
|---|---|---|
| Mac with Apple silicon, k3d on OrbStack's Docker | Tested | macOS 14 or later. |
| Mac with Apple silicon, k3d on Docker Desktop | Expected to work, in testing | macOS 14 or later. |
| Mac with Apple silicon, OrbStack's own Kubernetes | Tested | macOS 14 or later. Apps open on that Mac only: see Mode 2. |
| Mac with an Intel chip | Expected to work, in testing | macOS 14 or later. |
| Windows 11 Pro 24H2, x64 | Tested | Build 22621 (22H2) or later. The installer sets up WSL itself. |
| Windows 11 Home, any other version from 22H2 on, or an arm64 PC | Expected to work, in testing | Build 22621 (22H2) or later. |
| Windows 10, Windows Server | Not supported | Windows 10 and Server 2022 are older than build 22621, which the installer needs. On Server 2025, WSL's mirrored networking doesn't work, and the install relies on it. |
| Ubuntu 22.04 or 24.04, Debian 12 or 13; amd64 or arm64 | Expected to work, in testing | On a machine of its own or in a VM. It's the same install every Windows PC runs inside WSL2, on Ubuntu 24.04. |
| Raspberry Pi 4 or 5 with 4 GB or more, on a 64-bit OS | Expected to work, in testing | Runs the same arm64 build as Apple silicon Macs. Raspberry Pi OS needs memory cgroups turned on first, and k3s recommends an external SSD over the SD card: see its Raspberry Pi notes. |
| Proxmox VE, in a VM running Ubuntu or Debian | Expected to work, in testing | The same goes for a VM under any other hypervisor. |
| Proxmox VE, in an LXC container | Not supported | k3s runs in LXC only with the container's isolation turned down (privileged, AppArmor unconfined, the host's /dev/kmsg passed in). Use a VM. |
| Synology, Unraid or TrueNAS, on the NAS's own OS | Not supported | These are appliances that own their OS. Unraid has neither systemd nor OpenRC, and k3s's installer needs one of them; a TrueNAS update replaces the whole OS image, and anything installed on it with it. Run Ubuntu or Debian in a VM on the NAS instead. To the installer, that's a Linux machine like any other. |
32-bit ARM (uname -m says armv7l or armv6l), RISC-V, any other architecture |
Not supported | The installer refuses it. Mesopod runs on amd64 and arm64. |
Tested means we've run the install on it end to end. Expected to work, in testing means the installer supports it, nothing we know of stands in the way, and we're working through these runs now. Not supported means we don't support installing there, for the reason in the notes, which also say what to use instead where there's an alternative. Installing onto a Kubernetes cluster you already run needs Kubernetes 1.30 or newer, whatever the machine: see below.
Every machine needs outbound HTTPS: it must reach
console.mesopod.cloud:443 and bridge.mesopod.cloud:443. No inbound ports,
no port forwarding, no public IP.
On a Mac, install these first (curl is already there) to run Mesopod on a
k3d cluster, which the install creates for you:
- A Docker engine: Docker Desktop or OrbStack.
- k3d:
brew install k3d.
If you already run OrbStack with its Kubernetes turned on, you can skip k3d: the install can use OrbStack's cluster instead. Apps then open on that Mac only: see Mode 2.
The Mac should be one you leave on and awake. Your apps are only up while it is.
A Windows PC needs nothing installed first; the console's line sets up WSL
itself (Windows: one line). A Linux machine has a few
requirements of its own, sudo among them, in the
Linux appendix.
1. Add the cluster in the console¶
In the console, open Clusters and use Add cluster. Give it a name you
will recognize — daves-mbp, homelab. That name becomes the cluster's slug,
and the slug shows up in every app URL, so pick it deliberately.
The console answers with the install command and shows the row as waiting for your server to call home.
2. Run the command on your Mac¶
Copy the command and run it in a terminal:
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | MESOPOD_CLAIM=mpc_… sh
The claim (mpc_…) is one-time and expires in 24 hours. Copy it from the
console rather than retyping it — a truncated paste fails with unknown claim.
The claim rides an environment variable, not the command line, because command lines are readable by every user on the machine while the install runs.
This page writes the production names throughout. If you are testing against a
non-production Mesopod, take the origin from your own install command
(console.staging.mesopod.cloud, say) and substitute it everywhere below —
including the apps domain, which on staging is staging.mesopod-internal.app
rather than mesopod.app, so the rebind exception and the dig checks use that
name instead.
The prompt¶
If you didn't pass a mode explicitly, the script asks on the terminal:
Where should Mesopod run?
1) a local k3d cluster (this script creates it)
2) OrbStack's built-in Kubernetes
Choice [k3d]:
The bracketed default names the mode it will take, and is autodetected: if a
kubectl context named orbstack already exists (you've turned on OrbStack
Kubernetes), the prompt reads Choice [orbstack]:; otherwise Choice [k3d]:.
Press Enter to accept it, or answer 1 for k3d / 2 for OrbStack (the words
k3d and orbstack work too).
You are asked this once. The mode you land on is saved next to the token (see
State on your Mac), and a later re-run resumes in it
rather than asking again — so turning OrbStack's Kubernetes on afterwards
cannot quietly move your install. Passing --k3d/--orbstack on a re-run
still overrides, and the script says so when it does.
To skip the question — for a script, or because you already know which you want — pass a flag or set an env var ahead of time:
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | MESOPOD_CLAIM=mpc_… sh -s -- --k3d
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | MESOPOD_CLAIM=mpc_… sh -s -- --orbstack
or MESOPOD_INSTALL_MODE=k3d / MESOPOD_INSTALL_MODE=orbstack in the
environment alongside MESOPOD_CLAIM.
Mode 1: k3d (a local cluster the script creates)¶
Needs docker (OrbStack or Docker Desktop) and k3d (brew install k3d) on
PATH. The script looks for both before it exchanges your claim, so if either
is missing it says exactly that and stops with the claim unspent: install the
missing piece and paste the same command again.
The script creates a cluster named mesopod-<your-slug>, publishing ports 80
and 443 from the cluster onto the Mac (so LAN devices can reach your apps at
the Mac's address — whether DNS actually points there is a separate concern,
out of scope here). Only one cluster can hold those ports, so if something else
already has them — usually a k3d cluster from an earlier install — the script
says so before it spends your claim and asks whether to quit or install
without publishing. --no-port-publish answers that ahead of time, which is how
you run more than one k3d cluster on one Mac; apps then answer only at the
cluster node's own address rather than the Mac's. See
troubleshooting.md.
The cluster is created with your Mac's /Users and /Volumes mounted into
the node at the same paths. A k3d node is a container, and a Mac path exists
inside it only if it was mounted when the cluster was created, so this is what
lets you register host directories (a Plex library under /Users/you/…, a
drive under /Volumes/…) later without touching the cluster again. The
mount is wide on purpose and the roots are narrow: the node can see your whole
home directory, but apps only reach the directories you register as roots,
and the outpost cannot mount anything else (see the host directory design's
§4). It adds no reach the node did not have: the node runs privileged, and
the Docker VM already exposes the whole Mac filesystem to it, so anything
running at node level could read your disk with or without this mount. On
Linux nothing is mounted, since the node is the host. A cluster created by an
earlier version of this script has no such mounts; a re-run of the install
command adds them to the running node in place, with nothing recreated or
restarted (verified on OrbStack). See
troubleshooting.md if every
directory you register fails with doesn't resolve on the server.
Re-running is idempotent: an existing mesopod-<your-slug> cluster is reused,
not recreated.
The resulting kubectl context is k3d-mesopod-<your-slug>.
Mode 2: OrbStack's built-in Kubernetes¶
Needs OrbStack's Kubernetes turned on: OrbStack app → Settings →
Kubernetes. The script looks for a kubectl context literally named
orbstack; if it isn't there, the script says so and stops before spending
your claim.
This mode installs into OrbStack's own cluster — there's nothing to create.
The resulting kubectl context is orbstack.
LAN reachability works differently here: OrbStack routes its Kubernetes LoadBalancer addresses to the Mac itself only, so other devices on your network reaching an app through this mode is out of scope for now.
When the script finishes¶
It prints:
+ outpost installed and dialling home
> return to the console — the cluster row walks: waiting for your server -> setting up -> ready
Go back to the console. The row moves to Setting up on its own — Mesopod installs the routing pieces the first time an outpost connects — and then reads Provisioned. That usually takes under a minute. Once it does, you can deploy to it: first-deploy.md.
What the script actually does¶
In order, so you know what you pasted:
- Reads the machine's architecture — amd64 or arm64 (Apple Silicon Macs report arm64); anything else stops here with a message.
- Resolves the mode — flag, env var, the mode a previous run saved, or the prompt above.
-
Exchanges the claim and checks the Kubernetes version floor, in an order that depends on the mode, since each burns the claim only once the thing it needs to check first actually exists:
- k3d checks that
dockerandk3dare on PATH (looking for a command spends nothing), then exchanges the claim — its response names the slug the cluster gets created with — then creates (or reuses) the cluster, then checks the version floor now that a cluster exists to check. - OrbStack confirms the
orbstackcontext answers, checks the version floor on it, then exchanges the claim (recordingorbstackas this cluster's kind).
Either way, the claim is exchanged before anything that could fail for reasons outside the script's control, and the token is written to
$HOME/.mesopod/outpost-token, mode 0600, alongside$HOME/.mesopod/cluster-slugand$HOME/.mesopod/install-mode, before any further step. That ordering is the point: if a later step fails, the claim is already banked as a durable token, and re-running the script picks up from there (see Re-running below). The version floor itself — Kubernetes 1.30, because the admission policies that fence the Mesopod outpost in need it to exist — should never actually bite on a Mac; both k3d and OrbStack ship well above it. 4. Downloads this cluster's manifests from the platform, authenticated with that token. 5. Downloads the outpost image as a tarball and loads it straight into the cluster's container runtime —docker exec … ctr images importfor k3d,docker loadfor OrbStack (it shares the Mac's docker image store). Mesopod pulls from no registry: your Mac never needs a registry credential, and the image never has to be public. 6. Applies the manifests and waits up to three minutes for the outpost to roll out. On failure it prints the pod state before exiting — see troubleshooting.md. - k3d checks that
The outpost — Mesopod's piece on your machine — runs as an ordinary
in-cluster Deployment (namespace
mesopod-system), with a deliberately small, fenced Kubernetes identity. It is
not a host daemon and it never holds your cluster's admin credentials. Design
detail: docs/features/byo-install-path/design.md,
docs/features/macos-install-modes/design.md.
State on your Mac¶
Both modes keep their state under your home directory, unprivileged:
| File | What it holds |
|---|---|
$HOME/.mesopod/outpost-token (0600) |
This cluster's outpost token. Its presence is what lets a re-run resume without a fresh claim. |
$HOME/.mesopod/cluster-slug |
The slug the platform assigned. k3d mode reads this on a resume to know which cluster to reuse (mesopod-<slug>). |
$HOME/.mesopod/install-mode |
Which mode this Mac installed in — k3d or orbstack. A re-run resumes in it instead of asking again. |
--token-file PATH overrides the location (useful for dev/test setups running
more than one instance side by side); the slug and mode files then live next
to it.
Installing onto a cluster you already run¶
If you already have a Kubernetes context (1.30+) you'd rather use directly —
your own cluster, a cluster in a Linux VM, anything kubectl can already
reach — skip the mode question and point the script at it. Save the script
first rather than piping it, so you can pass flags:
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh -o install.sh
chmod +x install.sh
MESOPOD_CLAIM=mpc_… ./install.sh \
--skip-k3s \
--kube-context my-cluster \
--token-file ~/.mesopod/outpost-token
This path needs no root — only a kubeconfig that can create the objects, which
includes cluster-scoped roles and admission policies. Passing --kube-context
on a Mac also skips the interactive prompt: you've already said where Mesopod
goes. Don't combine it with --k3d/--orbstack (or a leftover
MESOPOD_INSTALL_MODE in your environment) — those name a cluster to find or
create, --kube-context names one that exists, and the script refuses the two
together rather than guessing which you meant.
All flags, optional:
| Flag | What it does |
|---|---|
--claim mpc_… |
Same as MESOPOD_CLAIM. Convenient for scripts; visible in ps. |
--base https://… |
Override the console origin the script talks to. |
--k3d |
Run Mesopod on a local k3d cluster this script creates. |
--orbstack |
Run Mesopod on OrbStack's built-in Kubernetes. |
--skip-k3s |
Don't install or expect k3s (irrelevant on macOS; k3s doesn't run there). |
--skip-image |
Don't download or load the image (it is already present). |
--kube-context CTX |
Use kubectl --context CTX instead of the mode's own cluster/context. |
--import-cmd CMD |
Feed the image tarball to your own import command on stdin. |
--token-file PATH |
Where the token is stored (default $HOME/.mesopod/outpost-token; /etc/mesopod/outpost-token on the Linux k3s path). |
--help, -h |
Print this flag list on the machine and exit. |
Re-running the install¶
Re-running is safe and is the normal fix for anything that failed midway:
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | sh
With no claim, the script resumes from the saved token (and, for k3d, the saved slug) and redoes the rest — manifests, image, apply. You only need a new claim if the token was never written (the failure happened before the claim exchange in step 3) or you revoked it. Generate one in the console with Generate new install command; doing so retires the old one immediately.
If you installed onto a cluster you already run, or with an explicit mode
flag, re-run the saved script with the same flags you used the first time —
--skip-k3s, --kube-context, --k3d/--orbstack, --token-file — or it
will ask the mode question again or look for state where you didn't put it.
Make your app names resolve at home¶
Each cluster gets one wildcard DNS record, *.<cluster-slug>.mesopod.app,
pointing at the server's own IP address — usually a private one like
192.168.1.40. Certificates are real and public; only the address is local.
(This applies to the k3d mode's published ports; OrbStack mode's LoadBalancer
address is reachable from the Mac itself only, per the OrbStack section above.)
Many home routers refuse to pass through a public name that answers with a
private address. The feature is called DNS rebind protection, and the app
will simply not resolve until you make an exception for mesopod.app. Where to
look:
- OpenWrt (dnsmasq): Network → DHCP and DNS → Rebind protection → add
mesopod.appto Domain whitelist. - FRITZ!Box: Home Network → Network → Network Settings → DNS Rebind
Protection → add
mesopod.appunder Hostname exceptions. - pfSense / OPNsense: Services → DNS Resolver → Custom options →
private-domain: "mesopod.app", or turn off DNS Rebind Check under System → Advanced → Networking. - Pi-hole (also dnsmasq): add
rebind-domain-ok=/mesopod.app/to a file under/etc/dnsmasq.d/, then restart the DNS service.
If your router offers no exception, point the affected devices at a resolver that doesn't rebind-protect, or add hosts entries as a stopgap.
Away from home, see remote-access-tailscale.md.
Removing Mesopod¶
Uninstall your apps in the console first — that removes their workloads cleanly and keeps their data.
k3d mode — deleting the cluster removes everything:
k3d cluster delete mesopod-<your-slug>
rm -f "$HOME/.mesopod/outpost-token" "$HOME/.mesopod/cluster-slug" \
"$HOME/.mesopod/install-mode"
OrbStack mode, or any cluster you brought yourself (--kube-context) —
removing the cluster from the console runs the outpost's decommission_cluster
command, which deletes everything in mesopod-system, the cluster-scoped
GatewayClass, and the k3s HelmChartConfig. What remains is cluster-scoped
and the outpost has no permission to delete it (ADR-027) — so the list,
deploy/in-cluster-outpost/manifests.yaml, and render.ClusterDecommission
change together. Use --context orbstack for OrbStack mode, or the context
you passed to --kube-context otherwise:
# The outpost itself lives in mesopod-system — already gone if you removed
# the cluster from the console, since decommission_cluster deletes it. Do it
# first here, so the outpost is not still running while its permissions go.
kubectl --context orbstack delete namespace mesopod-system
kubectl --context orbstack delete clusterrolebinding mesopod-outpost
kubectl --context orbstack delete clusterrole mesopod-outpost-cluster mesopod-outpost-runtime mesopod-outpost-helm
kubectl --context orbstack -n kube-system delete rolebinding mesopod-outpost-helm
kubectl --context orbstack delete validatingadmissionpolicybinding \
mesopod-outpost-rolebinding-cage-binding mesopod-outpost-namespace-cage-binding \
mesopod-outpost-workload-cage-binding mesopod-outpost-volume-cage-binding \
mesopod-outpost-pv-create-cage-binding mesopod-outpost-pv-roots-cage-binding
kubectl --context orbstack delete validatingadmissionpolicy \
mesopod-outpost-rolebinding-cage mesopod-outpost-namespace-cage \
mesopod-outpost-workload-cage mesopod-outpost-volume-cage \
mesopod-outpost-pv-create-cage mesopod-outpost-pv-roots-cage
kubectl --context orbstack delete crd outpostnodedirectoryroots.mesopod.io
# OrbStack only — Traefik's ClusterRole/ClusterRoleBinding, applied at
# install time because the caged outpost has no verb to author them:
kubectl --context orbstack delete clusterrolebinding mesopod-traefik
kubectl --context orbstack delete clusterrole mesopod-traefik
rm -f "$HOME/.mesopod/outpost-token" "$HOME/.mesopod/cluster-slug" \
"$HOME/.mesopod/install-mode"
If you force-removed the cluster while its node was offline, mesopod-system
and your app namespaces are still there; delete mesopod-system first — a new
Mesopod cluster refuses to provision over another cluster's ownership marker
(ADR-025). A force never ran decommission_cluster, so nothing was deleted for
you: run the whole operator list in the block above as well, plus the two
objects decommission_cluster would otherwise have deleted itself — the
cluster-scoped GatewayClass named mesopod-traefik, and, on k3s only, the
HelmChartConfig named traefik in kube-system (k3s's own Traefik chart
owns that object; OrbStack's Traefik profile has no HelmChartConfig at all —
it's a plain Service/Deployment/ServiceAccount inside mesopod-system, so
deleting that namespace above already took it):
kubectl --context orbstack delete namespace mesopod-system
kubectl --context orbstack delete gatewayclass mesopod-traefik
# k3s only — OrbStack's Traefik profile has no HelmChartConfig to delete:
kubectl --context orbstack -n kube-system delete helmchartconfig traefik
App namespaces and the data under them are left alone. Remove them yourself if you want the cluster clean.
Next¶
- first-deploy.md — put an app on it.
- troubleshooting.md — when a step above doesn't end the way this page says it should.
- snapshots.md — back your data up before you depend on it.
Appendix: Installing on a Linux server¶
Everything above is Mac-first, because that's the alpha audience. Mesopod still installs the same way onto a spare Linux machine or VM (amd64 or arm64, with systemd) — this is the original path, and it's unchanged except that the console's command shown to you will already be for the mode you picked when adding the cluster.
What you need¶
The table at the top of this page gives the support status of each distribution, board and hypervisor. Beyond that, the Linux path needs:
- A Linux machine or VM — amd64 or arm64, with systemd. 2 CPU / 4 GB RAM is a comfortable floor for a handful of apps. On a distribution the table doesn't name, k3s's requirements list what some need first.
- The ability to run
sudoon that machine (the install brings up k3s and writes/etc/mesopod/, both of which need root). If you are logged in as root,sudois simply a no-op. curlinstalled.- Outbound HTTPS. The machine must reach
console.mesopod.cloud:443andbridge.mesopod.cloud:443— plusget.k3s.ioduring the install itself, since it is installing k3s. No inbound ports, no port forwarding, no public IP. - Kubernetes 1.30 or newer — only if you are installing onto a cluster you already run. Otherwise the script installs k3s for you and the version is already right.
The machine should be one you leave on. Your apps are only up while it is.
Run the command on the server¶
The Mac one-liner needs no sudo; the Linux k3s path still needs root, and the
script knows it. Run the command the console gives you:
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | MESOPOD_CLAIM=mpc_… sh
If you aren't root, the script stops immediately and prints the exact sudo-prefixed line to re-run — it holds your claim already, so it can echo the full command back to you:
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | sudo MESOPOD_CLAIM=mpc_… sh
sudo prompts for your password on the terminal, not on the pipe, so the
prompt appears normally.
What the script does on Linux¶
- Reads the machine's architecture — amd64 or arm64.
- Installs k3s from
get.k3s.ioif it isn't already there, pinned to the v1.33 channel, and waits for the node to be Ready. This is the only thing the install fetches from outside Mesopod. SetMESOPOD_K3S_CHANNELto pin a different line. - Checks the Kubernetes version — below 1.30 it stops (see the Mac section above for why), and it checks before spending your one-time claim.
- Exchanges the claim for this cluster's token and writes it to
/etc/mesopod/outpost-token, mode 0600, before touching anything else. - Downloads this cluster's manifests, authenticated with that token.
- Downloads the outpost image as a tarball and loads it straight into the
machine's container runtime via
k3s ctr images import. - Applies the manifests and waits up to three minutes for the outpost to roll out.
Windows: one line¶
On a Windows 11 PC (22H2 or newer, x64 or arm64) the console's Windows
tab shows a PowerShell line instead of the curl one:
$env:MESOPOD_CLAIM='mpc_…'; irm https://console.mesopod.cloud/api/v1/install.ps1 | iex
Paste it in an ordinary PowerShell window — not an administrator one, and not inside a Linux shell. Windows asks for permission once; the rest runs in the window that opens and ends with the same "return to the console" line as the Mac path. What it does, in order, each step skipped when already done:
- WSL. Installed if the PC has none — the script then says to restart Windows and paste the same line again; nothing has been spent and the command is still valid. Updated if older than 2.4.4.
%UserProfile%\.wslconfiggainsnetworkingMode=mirrored(other devices can reach the distro at the PC's address) andhostAddressLoopback=true(so can the PC itself). Every other line in the file is kept.- A distro of its own, named
mesopod(Ubuntu 24.04), set up as a server: systemd on, root as its user, no first-run wizard. An Ubuntu you already have is not touched. - The Hyper-V firewall opens 80 and 443 to the WSL VM — one VM for every distro on the PC, so to any other distro's listener too — and allows host loopback.
- The Linux install runs inside the distro: the same script as above, with its WSL2 checks, the port hold, k3s, the claim exchange and the outpost. It prints the address the console should show as the cluster's DNS target — under mirrored networking, the PC's own.
- A startup task, "Mesopod WSL", keeps the distro running across reboots without storing your password. If Task Scheduler cannot start WSL that way on your PC, the script asks for the password and stores it there instead.
Re-running the same line repairs a half-finished install; with no claim it resumes from the token inside the distro. If the console shows the cluster offline after your first restart, run the saved copy with the password option:
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -StartupTaskPassword
If you would rather set the distro up by hand, every step above is one the
Linux path's WSL2 checks will name as it meets it — and the script inside
the distro needs sudo, a WSL distro with systemd and mirrored networking
on, and something holding ports 80/443, which it provides itself.
Storage (WSL's own disk versus Windows drives), VPN clients and remote access are their own guide: Install Mesopod on Windows (WSL2).
Re-running on Linux¶
curl -fsSL https://console.mesopod.cloud/api/v1/install.sh | sudo sh
With no claim, the script resumes from the saved token at
/etc/mesopod/outpost-token.
Registering a host directory on Linux¶
On a server the script set up, the console shows the host directory
registration command with sudo, for the same reason the install needed it:
k3s keeps its kubeconfig readable by root only. Copy it from the console,
which fills in the cluster and every registered directory, and run it on the
server:
curl -fsSL https://console.mesopod.cloud/api/v1/register-roots.sh | sudo bash -s -- --cluster-id … --root …
Run without sudo, the script stops and prints the line to re-run. If you
installed onto a cluster you already run, with your own kubeconfig, leave
sudo out.
Removing Mesopod from a Linux server¶
On a machine where the script installed k3s, uninstalling k3s removes everything:
sudo /usr/local/bin/k3s-uninstall.sh
sudo rm -f /etc/mesopod/outpost-token
On Windows, after removing the cluster in the console, the saved installer
undoes everything it set up — the startup task, the firewall rule, the
mesopod distro and its own state — and leaves .wslconfig alone, since
mirrored networking is a machine-wide setting you may want to keep:
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -Uninstall