- Python 72.2%
- Shell 18.9%
- Dockerfile 8.9%
| docs/superpowers | ||
| hub | ||
| hub-pam | ||
| systemd | ||
| traefik | ||
| .env.example | ||
| .gitignore | ||
| book.nginx.conf | ||
| compose.yml | ||
| jupyterhub_config.py | ||
| README.md | ||
atlasworks
Ops for atlante.unipv.it — a rootless-podman stack behind Traefik with:
- a static Jupyter Book at
/ - JupyterHub at
/hub(one container per user, who picks a simulator image).
Checked out on the server at ~/atlasworks.
Run the setup on the server
0. One-time host (sudo)
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-rootless-lowports.conf
sudo sysctl --system
sudo loginctl enable-linger zanussi # keep the rootless stack up across logout/reboot
0b. One-time host — PAM auth (sudo)
Hub login authenticates against local host accounts in the jupyterhub
group. The hub runs rootless and cannot read /etc/shadow, so a root systemd
path-unit exports only that group's members into hub-pam/{passwd,shadow,group}
(owned by zanussi, gitignored), which the hub reads via libnss-extrausers.
sudo groupadd -f jupyterhub
sudo usermod -aG jupyterhub zanussi # repeat per allowed user
sudo install -m 0644 ~/atlasworks/systemd/jupyterhub-pam-sync.service /etc/systemd/system/
sudo install -m 0644 ~/atlasworks/systemd/jupyterhub-pam-sync.path /etc/systemd/system/
# SELinux (Enforcing): systemd (init_t) may only exec bin_t files
sudo semanage fcontext -a -t bin_t '/home/zanussi/atlasworks/hub-pam/sync-pam\.sh'
sudo restorecon -v /home/zanussi/atlasworks/hub-pam/sync-pam.sh
sudo systemctl daemon-reload
sudo systemctl enable --now jupyterhub-pam-sync.path
sudo systemctl start jupyterhub-pam-sync.service # initial populate
Run 0b before bringing the stack up (step 4):
compose.ymlbind-mountshub-pam/{passwd,shadow,group}as files, so they must already exist (created by the initialsync-pam.shrun above). If the stack starts first, podman auto-creates them as empty directories and every login fails.
Password changes (passwd) and group changes (usermod -aG) resync
automatically; no hub restart needed. Only local (files//etc/shadow) accounts work — SSSD/LDAP users are not supported by this path.
1. Config — cp .env.example .env, then set in .env:
HUB_HOST=atlante.unipv.it, CONTAINER_SOCK=/run/user/$(id -u)/podman/podman.sock,
ACME_EMAIL, HUB_ADMIN.
(ACME_EMAIL must ALSO be set literally in traefik/traefik.prod.yml — Traefik doesn't expand ${VAR}.)
2. Host roots
~/datasets/— raw inputs, mounted ro at/data.~/workspace/<sim>/{config,workloads,results}— per-sim config,.wlworkloads, outputs; mounted rw at/workspace. Container writes work viakeep-id(the spawner maps the container user to you) — no chown needed.
3. Build the simulator runtime image — built from the sim repo itself:
cd ~/research/autoscaling && ./docker/build.sh # -> peg-runtime-autoscaling:<date> + :latest
Builds (context = repo root): CloudSim jar + the pinned peg-autoscaling Python 3.13 kernel +
R/IRkernel + jupyterlab-git. Tags the build with the date plus a floating :latest — the tag the
hub spawns.
4. Bring up the stack
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
podman compose --profile prod up -d
5. Log in — at https://atlante.unipv.it/hub/, log in with your host username + password (you must be in the jupyterhub group; see step 0b).
HUB_ADMIN (zanussi) is the hub admin. Then pick "Autoscaling simulator (latest)".
Updating
- Bind-mounted config (
jupyterhub_config.py,compose.yml,traefik/*,.env):podman compose --profile prod up -d --force-recreate <service>. - Simulator image: rerun
~/research/autoscaling/docker/build.sh— it retags:latest, so no config edit is needed; then--force-recreate jupyterhub. Running user servers survive; new spawns get the rebuilt:latest. (Roll back by retagging a dated image to:latest.) - Book content: deployed separately by the
journalrepo'sdeploy.pyinto~/atlasworks/book/(live, no restart).