From 6fb8488e5bc2c5232714eed4999b57638f70af72 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:37:12 +0000 Subject: [PATCH 1/4] Give Workspace sessions their address back after a restart Kasm routes to a session by the address its container was created with, while Docker gives a starting container the lowest free address. Record the address of each running or paused session on shutdown and pin the container back to it on start. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RmwHZxdRuWgp9chBZTZTMn --- readme-vars.yml | 1 + root/etc/s6-overlay/s6-rc.d/svc-kasm/finish | 4 ++++ root/etc/s6-overlay/s6-rc.d/svc-kasm/run | 12 ++++++++++++ 3 files changed, 17 insertions(+) diff --git a/readme-vars.yml b/readme-vars.yml index 17994e7..29705dd 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -130,6 +130,7 @@ init_diagram: | "kasm:latest" <- Base Images # changelog changelogs: + - {date: "23.09.26:", desc: "Give Workspace sessions their address back after a container restart."} - {date: "04.08.26:", desc: "Give kasm_manager a healthcheck start period so a cold start does not leave kasm_proxy created but never started."} - {date: "16.04.26:", desc: "Update for 1.18.1 release. Use rolling service images. Bump docker to v29."} - {date: "13.11.25:", desc: "Pin docker to v28 to avoid API deprecation issues."} diff --git a/root/etc/s6-overlay/s6-rc.d/svc-kasm/finish b/root/etc/s6-overlay/s6-rc.d/svc-kasm/finish index e99a399..1e852a8 100755 --- a/root/etc/s6-overlay/s6-rc.d/svc-kasm/finish +++ b/root/etc/s6-overlay/s6-rc.d/svc-kasm/finish @@ -1,4 +1,8 @@ #!/usr/bin/with-contenv bash # shellcheck shell=bash +# Kasm routes to a session by the address its container was created with +# shellcheck disable=SC2016 +docker ps -q --filter "label=kasm.kasmid" | xargs -r docker inspect --format '{{.Id}} {{.State.Status}}{{range $network, $settings := .NetworkSettings.Networks}} {{$network}}={{$settings.IPAddress}}{{end}}' > /opt/kasm_sessions.state + /opt/kasm/bin/stop diff --git a/root/etc/s6-overlay/s6-rc.d/svc-kasm/run b/root/etc/s6-overlay/s6-rc.d/svc-kasm/run index eb51c00..1176de3 100755 --- a/root/etc/s6-overlay/s6-rc.d/svc-kasm/run +++ b/root/etc/s6-overlay/s6-rc.d/svc-kasm/run @@ -8,6 +8,18 @@ done # Stop any running containers that shouldn't be docker ps -q | xargs docker stop &> /dev/null +# Docker gives a starting container the lowest free address, not its previous one +if [[ -f /opt/kasm_sessions.state ]]; then + while read -r id _ networks; do + for network in ${networks}; do + [[ -n "${network#*=}" ]] || continue + docker network disconnect --force "${network%%=*}" "${id}" + docker network connect --ip "${network#*=}" "${network%%=*}" "${id}" || docker network connect "${network%%=*}" "${id}" + done + done < /opt/kasm_sessions.state + rm -f /opt/kasm_sessions.state +fi + if [[ -f /opt/kasm/bin/start ]]; then /opt/kasm/bin/start fi From a63c55d900cc6d440fef580f8a8cc3b9f5acb9c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:37:25 +0000 Subject: [PATCH 2/4] Bring Workspace sessions back after a restart dockerd restarts session containers through their unless-stopped policy, then svc-kasm stops every running container, so Kasm keeps listing sessions as running or paused while their containers are stopped. Start the sessions recorded on shutdown again, pausing those that were paused, before the services start. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RmwHZxdRuWgp9chBZTZTMn --- readme-vars.yml | 1 + root/etc/s6-overlay/s6-rc.d/svc-kasm/run | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/readme-vars.yml b/readme-vars.yml index 29705dd..954b217 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -130,6 +130,7 @@ init_diagram: | "kasm:latest" <- Base Images # changelog changelogs: + - {date: "23.09.26:", desc: "Bring running and paused Workspace sessions back after a container restart."} - {date: "23.09.26:", desc: "Give Workspace sessions their address back after a container restart."} - {date: "04.08.26:", desc: "Give kasm_manager a healthcheck start period so a cold start does not leave kasm_proxy created but never started."} - {date: "16.04.26:", desc: "Update for 1.18.1 release. Use rolling service images. Bump docker to v29."} diff --git a/root/etc/s6-overlay/s6-rc.d/svc-kasm/run b/root/etc/s6-overlay/s6-rc.d/svc-kasm/run index 1176de3..da2d693 100755 --- a/root/etc/s6-overlay/s6-rc.d/svc-kasm/run +++ b/root/etc/s6-overlay/s6-rc.d/svc-kasm/run @@ -8,14 +8,17 @@ done # Stop any running containers that shouldn't be docker ps -q | xargs docker stop &> /dev/null -# Docker gives a starting container the lowest free address, not its previous one +# Restore sessions before the services start, as Docker gives a starting +# container the lowest free address, not its previous one if [[ -f /opt/kasm_sessions.state ]]; then - while read -r id _ networks; do + while read -r id status networks; do for network in ${networks}; do [[ -n "${network#*=}" ]] || continue docker network disconnect --force "${network%%=*}" "${id}" docker network connect --ip "${network#*=}" "${network%%=*}" "${id}" || docker network connect "${network%%=*}" "${id}" done + docker start "${id}" > /dev/null + [[ "${status}" == "paused" ]] && docker pause "${id}" > /dev/null done < /opt/kasm_sessions.state rm -f /opt/kasm_sessions.state fi From e9f9a59b36811d3709e499cac375a8d6ceeece72 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 12:36:17 +0000 Subject: [PATCH 3/4] Bring Workspace sessions back after an unclean shutdown too svc-kasm/finish does not run when the container is killed, so nothing records the sessions. dockerd's own records still show which session containers were running or paused, and at which address, until dockerd starts again. Build the state file from them in init-config-kasm when finish has not written one. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RmwHZxdRuWgp9chBZTZTMn --- readme-vars.yml | 1 + root/etc/s6-overlay/s6-rc.d/init-config-kasm/run | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/readme-vars.yml b/readme-vars.yml index 954b217..aa02143 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -130,6 +130,7 @@ init_diagram: | "kasm:latest" <- Base Images # changelog changelogs: + - {date: "23.09.26:", desc: "Also bring Workspace sessions back after an unclean shutdown."} - {date: "23.09.26:", desc: "Bring running and paused Workspace sessions back after a container restart."} - {date: "23.09.26:", desc: "Give Workspace sessions their address back after a container restart."} - {date: "04.08.26:", desc: "Give kasm_manager a healthcheck start period so a cold start does not leave kasm_proxy created but never started."} diff --git a/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run b/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run index 24015ae..08bc582 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run +++ b/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run @@ -34,3 +34,9 @@ if [[ ! -L "/var/lib/docker-plugins" ]]; then mkdir -p /var/lib/docker-plugins/rclone/config mkdir -p /var/lib/docker-plugins/rclone/cache fi + +# After an unclean shutdown svc-kasm/finish has not recorded the sessions, but +# dockerd's records, read before it starts, still hold them as they were +if [[ ! -f /opt/kasm_sessions.state && -d /opt/docker/containers ]]; then + find /opt/docker/containers -name config.v2.json -exec jq -r 'select(.Config.Labels["kasm.kasmid"] and .State.Running) | "\(.ID) \(if .State.Paused then "paused" else "running" end)\(.NetworkSettings.Networks | to_entries? // [] | map(" \(.key)=\(.value.IPAddress)") | join(""))"' {} + > /opt/kasm_sessions.state +fi From bd161ffa076633d07b719f664515ac6a48566fd2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 13:43:01 +0000 Subject: [PATCH 4/4] Do not let one unreadable record stop the sessions state extraction With -exec ... {} +, jq gets every config.v2.json in a single run and find exits non-zero when it fails. The block is the last command of init-config-kasm, so a record truncated by a power loss would fail the oneshot and every record after it would be dropped. Run jq once per record instead: a bad one only loses itself and find still exits 0. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01RmwHZxdRuWgp9chBZTZTMn --- root/etc/s6-overlay/s6-rc.d/init-config-kasm/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run b/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run index a628528..9bbbce3 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run +++ b/root/etc/s6-overlay/s6-rc.d/init-config-kasm/run @@ -55,5 +55,5 @@ fi # After an unclean shutdown svc-kasm/finish has not recorded the sessions, but # dockerd's records, read before it starts, still hold them as they were if [[ ! -f /opt/kasm_sessions.state && -d /opt/docker/containers ]]; then - find /opt/docker/containers -name config.v2.json -exec jq -r 'select(.Config.Labels["kasm.kasmid"] and .State.Running) | "\(.ID) \(if .State.Paused then "paused" else "running" end)\(.NetworkSettings.Networks | to_entries? // [] | map(" \(.key)=\(.value.IPAddress)") | join(""))"' {} + > /opt/kasm_sessions.state + find /opt/docker/containers -name config.v2.json -exec jq -r 'select(.Config.Labels["kasm.kasmid"] and .State.Running) | "\(.ID) \(if .State.Paused then "paused" else "running" end)\(.NetworkSettings.Networks | to_entries? // [] | map(" \(.key)=\(.value.IPAddress)") | join(""))"' {} \; > /opt/kasm_sessions.state fi