From 80aa75c07a7f9e54e3fe01cd617d3719de718f9b Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 11 Aug 2026 11:21:04 -0700 Subject: [PATCH] deskflow: self-heal a server whose DHCP lease moved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deskflow's remoteHost has to be a bare IPv4 — mDNS answers .local with an fe80 link-local address first and deskflow-core fails to resolve it — which left the whole fleet hostage to a DHCP lease. When the server's lease moved, every client sat logging 'Host is down' while the server logged 'failed to accept secure socket' and looked like a TLS fault. It went unnoticed for days. deskflow-role-watchdog now closes both halves at its existing 45s cadence: - An unhealthy client re-resolves the serverName recorded in deskflow.json and retargets itself before falling back to a core restart, since restarting against a dead address only loops. It acts only on a real change, so a genuinely offline server is not rewritten every tick. - A healthy server keeps its own address record matched to its live interface, so a trackpad claim fans out a reachable address instead of an install-time one. serverName rides deskflow-set-role's new optional 4th argument, threaded through claim-control, yield-control, retarget-client, and install.sh --server-name. Older three-argument callers keep working and simply retain the stored name. New deskflow-resolve-ipv4 reads only dscacheutil ip_address lines. It prefers the .local form for bare names — through MagicDNS a bare name can return the tailnet address of a node offline for weeks — and drops loopback, since mDNS resolves a machine's own name to 127.0.0.1. Both were found while testing this. Watchdog actions now also append to ~/Library/Logs/deskflow-role-watchdog.log: logger output is not retrievable via log show on current macOS, so a silent self-heal would leave no evidence of the very failure this exists to catch. Verified on panda: pointed at the server's old lease, the second watchdog tick re-resolved to the live address, reconnected, and logged 'server neo moved 192.168.1.14 -> 192.168.1.13 — retargeting'. Server-side, a sabotaged address record was corrected on the next healthy tick. --- slab/deskflow-handoff/README.md | 41 ++++++++++++ slab/deskflow-handoff/deploy.fish | 14 +++- slab/deskflow-handoff/deskflow-claim-control | 6 +- slab/deskflow-handoff/deskflow-resolve-ipv4 | 53 +++++++++++++++ .../deskflow-handoff/deskflow-retarget-client | 7 +- slab/deskflow-handoff/deskflow-role-watchdog | 65 ++++++++++++++++++- slab/deskflow-handoff/deskflow-set-role | 17 ++++- slab/deskflow-handoff/deskflow-yield-control | 7 +- slab/deskflow-handoff/install.sh | 16 +++-- 9 files changed, 209 insertions(+), 17 deletions(-) create mode 100755 slab/deskflow-handoff/deskflow-resolve-ipv4 diff --git a/slab/deskflow-handoff/README.md b/slab/deskflow-handoff/README.md index 7e303aff3..53d98428d 100644 --- a/slab/deskflow-handoff/README.md +++ b/slab/deskflow-handoff/README.md @@ -25,6 +25,14 @@ Installed components: - `~/.local/bin/deskflow-role-watchdog` — health check that follows the current role, verifies a client is connected to its configured server (not merely any server), and repairs incomplete fleet topology from the winning generation. + It also closes the DHCP hole described under **Addressing** below: an unhealthy + client re-resolves its recorded `serverName` and retargets itself before + resorting to a core restart, and a healthy server keeps its own `address` + record matched to its live interface. Both actions are appended to + `~/Library/Logs/deskflow-role-watchdog.log` — `logger` output alone is not + retrievable via `log show` on current macOS, so the file is the real record. +- `~/.local/bin/deskflow-resolve-ipv4` — resolves a fleet name to its current + IPv4 and nothing else. See **Addressing**. - `~/.local/bin/deskflow-reconcile-topology` — compares the two controllers' roles and generations, demotes an older split-brain server, and retries retargeting missing clients without minting a new claim. @@ -42,6 +50,39 @@ client and all three client fingerprints on each controller. A TCP socket alone is not considered a successful handoff; Deskflow must complete its mutual trust check. +## Addressing + +Deskflow's `remoteHost` **must be a bare IPv4 address.** It cannot be a name: +mDNS answers `.local` with an `fe80::` link-local address *first*, and +deskflow-core takes whatever comes back first and then gives up with +`could not resolve address '.local'`. Bonjour can also select a stalled +link-local route after wake, which is the same trap from a different direction. + +That requirement makes the whole fleet hostage to a DHCP lease, and a stale one +is a silent fleet-wide outage: every client sits logging `Host is down` while the +server logs `failed to accept secure socket` and looks like a TLS problem. Two +mechanisms keep it honest, both in `deskflow-role-watchdog` (45s): + +- A **client** that is unhealthy re-resolves the `serverName` recorded in + `~/.config/slab/deskflow.json` via `deskflow-resolve-ipv4` and retargets itself + if the answer changed, *before* falling back to restarting the core — a restart + against a dead address only loops. It acts only on a genuine change, so a + server that is really offline does not get its conf rewritten every tick. +- A **server** keeps the `address` in `~/.config/slab/deskflow-handoff.json` + matched to its live interface, so a trackpad claim fans out a reachable address + rather than the one it happened to hold at install time. + +`serverName` is threaded through `deskflow-set-role`'s optional 4th argument by +`claim-control`, `yield-control`, `retarget-client`, and `install.sh --server-name`. + +**When the KVM is dead, check addressing first:** compare +`grep remoteHost ~/Library/Deskflow/Deskflow-client-role.conf` on a client +against `ipconfig getifaddr en0` on the server. + +`deskflow-resolve-ipv4` prefers the `.local` form for bare names on purpose — via +MagicDNS a bare name can return the tailnet address of a long-offline node — and +discards loopback answers, since mDNS resolves a machine's own name to 127.0.0.1. + Deskflow transport uses each machine's stable Tailscale address. On the Fuser Wi-Fi this keeps Chicken and Panda pointer latency far steadier than the direct access-point route. Role-control SSH uses those addresses too; Bonjour `.local` diff --git a/slab/deskflow-handoff/deploy.fish b/slab/deskflow-handoff/deploy.fish index 32381eb06..0f0abcb5d 100755 --- a/slab/deskflow-handoff/deploy.fish +++ b/slab/deskflow-handoff/deploy.fish @@ -10,6 +10,14 @@ set chicken_ssh fusermacminichicken@100.98.158.126 set panda_ssh fusermacminipanda@100.88.155.94 set ssh_opts -o BatchMode=yes -o ConnectTimeout=8 -o ConnectionAttempts=1 -o ControlMaster=no -o ControlPath=none +# NOTE: the 100.x addresses below are tailnet-only, so this script will not run +# on a network where the tailnet is unavailable — some networks filter VPN +# traffic, which leaves a machine's 100.x address dark even though the box is up. +# For those, swap the *_ssh values for the `chicken`/`panda`/`blueberry` +# ssh-config aliases, which prefer the LAN when Bonjour answers, and pass LAN +# addresses for --address / --server-host. --server-name stays a name either way: +# it is what lets deskflow-role-watchdog re-resolve a server whose lease moved. + for host in $blueberry_ssh $chicken_ssh $panda_ssh ssh $ssh_opts $host "rm -rf $stage; mkdir -p $stage" or exit 1 @@ -19,11 +27,11 @@ end bash $here/install.sh --defer-start --machine neo --screen-name neo --address 100.108.5.81 --controller --clients $blueberry_ssh,$chicken_ssh,$panda_ssh --role server --server-host 100.108.5.81 --trusted-servers $blueberry_fp --trusted-clients $blueberry_fp,$chicken_fp,$panda_fp or exit 1 -ssh $ssh_opts $blueberry_ssh "bash $stage/install.sh --defer-start --machine blueberry --screen-name blueberry.local --address 100.79.75.53 --controller --clients jas@100.108.5.81,fusermacminichicken@100.98.158.126,fusermacminipanda@100.88.155.94 --role client --server-host 100.108.5.81 --trusted-servers $neo_fp --trusted-clients $neo_fp,$chicken_fp,$panda_fp" +ssh $ssh_opts $blueberry_ssh "bash $stage/install.sh --defer-start --machine blueberry --screen-name blueberry.local --address 100.79.75.53 --controller --clients jas@100.108.5.81,fusermacminichicken@100.98.158.126,fusermacminipanda@100.88.155.94 --role client --server-host 100.108.5.81 --server-name neo --trusted-servers $neo_fp --trusted-clients $neo_fp,$chicken_fp,$panda_fp" or exit 1 -ssh $ssh_opts $chicken_ssh "bash $stage/install.sh --defer-start --machine chicken --screen-name chicken.local --address 100.98.158.126 --role client --server-host 100.108.5.81 --trusted-servers $neo_fp,$blueberry_fp" +ssh $ssh_opts $chicken_ssh "bash $stage/install.sh --defer-start --machine chicken --screen-name chicken.local --address 100.98.158.126 --role client --server-host 100.108.5.81 --server-name neo --trusted-servers $neo_fp,$blueberry_fp" or exit 1 -ssh $ssh_opts $panda_ssh "bash $stage/install.sh --defer-start --machine panda --screen-name panda.local --address 100.88.155.94 --role client --server-host 100.108.5.81 --trusted-servers $neo_fp,$blueberry_fp" +ssh $ssh_opts $panda_ssh "bash $stage/install.sh --defer-start --machine panda --screen-name panda.local --address 100.88.155.94 --role client --server-host 100.108.5.81 --server-name neo --trusted-servers $neo_fp,$blueberry_fp" or exit 1 ~/.local/bin/deskflow-start diff --git a/slab/deskflow-handoff/deskflow-claim-control b/slab/deskflow-handoff/deskflow-claim-control index 2100d8d09..35c6cd7db 100755 --- a/slab/deskflow-handoff/deskflow-claim-control +++ b/slab/deskflow-handoff/deskflow-claim-control @@ -94,16 +94,16 @@ SERVER_LOG_START=$(stat -f %z "$CORE_LOG" 2>/dev/null || echo 0) LOCAL_PID=$! if [[ "$ROLE" == "server" ]]; then peer_transition "${PEERS[0]}" \ - "~/.local/bin/deskflow-set-role client '$ADDRESS' '$EPOCH'" > "$PEER_OUT" 2>&1 & + "~/.local/bin/deskflow-set-role client '$ADDRESS' '$EPOCH' '$SCREEN_NAME'" > "$PEER_OUT" 2>&1 & else peer_transition "${PEERS[0]}" \ - "~/.local/bin/deskflow-yield-control '$ADDRESS' '$EPOCH'" > "$PEER_OUT" 2>&1 & + "~/.local/bin/deskflow-yield-control '$ADDRESS' '$EPOCH' '$SCREEN_NAME'" > "$PEER_OUT" 2>&1 & fi PEER_PID=$! LOG="$HOME/Library/Logs/deskflow-handoff.log" for peer in "${PEERS[@]:1}"; do - nohup "$RETARGET" "$peer" "$ADDRESS" "$EPOCH" >> "$LOG" 2>&1 & + nohup "$RETARGET" "$peer" "$ADDRESS" "$EPOCH" "$SCREEN_NAME" >> "$LOG" 2>&1 & done LOCAL_STATUS=0 diff --git a/slab/deskflow-handoff/deskflow-resolve-ipv4 b/slab/deskflow-handoff/deskflow-resolve-ipv4 new file mode 100755 index 000000000..711a7b0b4 --- /dev/null +++ b/slab/deskflow-handoff/deskflow-resolve-ipv4 @@ -0,0 +1,53 @@ +#!/bin/bash +# Resolve a fleet machine's name to its current IPv4 address, and nothing else. +# +# Deskflow's remoteHost cannot be a name. mDNS answers `.local` with an +# `fe80::` link-local address FIRST, and deskflow-core takes whatever comes back +# first, then fails with `could not resolve address '.local'`. So the +# handoff scripts store a bare IPv4 in the client conf — which makes them +# hostage to a DHCP lease. This resolver is how they get the current one back. +# +# dscacheutil (not dig) is deliberate: dig has no mDNS resolver, so it cannot +# answer for `.local` at all. Only the `ip_address:` lines are read, which is +# what filters out the link-local answer that started this whole problem. +set -euo pipefail + +NAME=${1:-} +if [[ -z "$NAME" ]]; then + echo "usage: deskflow-resolve-ipv4 NAME" >&2 + exit 64 +fi + +# Screen names are inconsistent across the fleet — neo records `neo` while +# blueberry records `blueberry.local` — so a bare name has to be tried both ways. +# +# `.local` MUST come first. On a machine using MagicDNS as its resolver a bare +# name resolves through the tailnet search domain and will happily return the +# tailnet address of a node that has been OFFLINE for weeks — that is how this +# ordering was found. mDNS only answers for hosts actually present on the link, +# which is precisely the question being asked here. +if [[ "$NAME" == *.* ]]; then + CANDIDATES=("$NAME") +else + CANDIDATES=("${NAME}.local" "$NAME") +fi + +for candidate in "${CANDIDATES[@]}"; do + # A literal IPv4 needs no lookup; accept it so callers can pass either form. + if [[ "$candidate" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + printf '%s\n' "$candidate" + exit 0 + fi + # Loopback and 0.0.0.0 are dropped: mDNS resolves a machine's OWN name to + # 127.0.0.1, and a client that wrote that into remoteHost would sit forever + # trying to reach a Deskflow server on itself. + address=$(dscacheutil -q host -a name "$candidate" 2>/dev/null \ + | sed -n 's/^ip_address: *//p' \ + | grep -vE '^(127\.|0\.0\.0\.0$)' | head -1) + if [[ -n "$address" ]]; then + printf '%s\n' "$address" + exit 0 + fi +done + +exit 68 diff --git a/slab/deskflow-handoff/deskflow-retarget-client b/slab/deskflow-handoff/deskflow-retarget-client index 0d9719ee2..832e4e7af 100755 --- a/slab/deskflow-handoff/deskflow-retarget-client +++ b/slab/deskflow-handoff/deskflow-retarget-client @@ -4,10 +4,13 @@ set -euo pipefail PEER=${1:-} SERVER_HOST=${2:-} EPOCH=${3:-0} +# Passing the server's name alongside its address lets the remote watchdog +# re-resolve it after a DHCP change instead of retrying a dead IP forever. +SERVER_NAME=${4:-} LOG="$HOME/Library/Logs/deskflow-handoff.log" if [[ -z "$PEER" || -z "$SERVER_HOST" || ! "$EPOCH" =~ ^[0-9]+$ ]]; then - echo "usage: deskflow-retarget-client PEER SERVER_HOST EPOCH" >&2 + echo "usage: deskflow-retarget-client PEER SERVER_HOST EPOCH [SERVER_NAME]" >&2 exit 64 fi @@ -19,7 +22,7 @@ SSH_OPTS=(-n -o BatchMode=yes -o ConnectTimeout=4 status=1 for attempt in 1 2 3; do if ssh "${SSH_OPTS[@]}" "$PEER" \ - "~/.local/bin/deskflow-set-role client '$SERVER_HOST' '$EPOCH'"; then + "~/.local/bin/deskflow-set-role client '$SERVER_HOST' '$EPOCH' '$SERVER_NAME'"; then exit 0 else status=$? diff --git a/slab/deskflow-handoff/deskflow-role-watchdog b/slab/deskflow-handoff/deskflow-role-watchdog index d8466ad63..34927633b 100755 --- a/slab/deskflow-handoff/deskflow-role-watchdog +++ b/slab/deskflow-handoff/deskflow-role-watchdog @@ -7,8 +7,49 @@ STATE="$HOME/.config/slab/deskflow.json" HANDOFF="$HOME/.config/slab/deskflow-handoff.json" CLIENT_CONF="$HOME/Library/Deskflow/Deskflow-client-role.conf" MISS="$HOME/.deskflow-role-watchdog.miss" +RESOLVE="$HOME/.local/bin/deskflow-resolve-ipv4" +SET_ROLE="$HOME/.local/bin/deskflow-set-role" +LOG="$HOME/Library/Logs/deskflow-role-watchdog.log" + +# `logger` alone is not enough: on current macOS its output is not retrievable +# via `log show` at all (a probe with a unique string returns nothing), so a +# silent self-heal would leave no evidence — and the DHCP outage this watchdog +# exists to fix went unnoticed for days precisely because nothing said so. +# Keep the syslog call for anyone tailing it, but the file is the real record. +note() { + printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" >> "$LOG" + logger -t deskflow-role-watchdog "$1" +} ROLE=$(/usr/bin/python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("role", "client"))' "$STATE" 2>/dev/null || echo client) +SERVER_NAME=$(/usr/bin/python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("serverName", ""))' "$STATE" 2>/dev/null || echo "") + +# The server's own address record is what a trackpad claim fans out to every +# client, so a stale one converts one DHCP lease change into a fleet-wide +# outage at the next handoff. Keep it matched to the live interface. +refresh_local_address() { + local live current + live=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || true) + [[ -n "$live" ]] || return 0 + current=$(/usr/bin/python3 -c \ + 'import json,sys; print(json.load(open(sys.argv[1])).get("address", ""))' \ + "$HANDOFF" 2>/dev/null || echo "") + [[ "$live" != "$current" ]] || return 0 + /usr/bin/python3 - "$HANDOFF" "$live" <<'PY' || return 0 +import json, sys +path, address = sys.argv[1], sys.argv[2] +with open(path) as f: + config = json.load(f) +config["address"] = address +tmp = path + ".tmp" +with open(tmp, "w") as f: + json.dump(config, f, indent=2, sort_keys=True) + f.write("\n") +import os +os.replace(tmp, path) +PY + note "local address record ${current:-unset} -> $live" +} healthy=0 if [[ "$ROLE" == "server" ]]; then @@ -24,6 +65,9 @@ fi if [[ "$healthy" == "1" ]]; then rm -f "$MISS" + if [[ "$ROLE" == "server" ]]; then + refresh_local_address + fi if [[ "$ROLE" == "server" && -x "$HOME/.local/bin/deskflow-reconcile-topology" ]]; then expected=$(/usr/bin/python3 -c \ 'import json,sys; print(len(json.load(open(sys.argv[1])).get("clients", [])))' \ @@ -44,5 +88,24 @@ if [[ ! -f "$MISS" ]]; then fi rm -f "$MISS" + +# A client that cannot reach its server may simply be holding the server's old +# DHCP lease. Re-resolve the recorded server name before bouncing the core: +# restarting against a dead address just loops, which is exactly how the fleet +# sat down for days when Neo moved from .14 to .13. Only act when the answer +# actually differs, so a genuinely-offline server still falls through to the +# kickstart below rather than rewriting the conf every 45 seconds. +if [[ "$ROLE" == "client" && -n "$SERVER_NAME" && -x "$RESOLVE" && -x "$SET_ROLE" ]]; then + resolved=$("$RESOLVE" "$SERVER_NAME" 2>/dev/null || true) + if [[ -n "$resolved" && "$resolved" != "$remote_host" ]]; then + note \ + "server $SERVER_NAME moved ${remote_host:-unset} -> $resolved — retargeting" + # set-role rewrites the conf and restarts the one launchd job itself, so + # there is deliberately no kickstart after it. + "$SET_ROLE" client "$resolved" 0 "$SERVER_NAME" >/dev/null 2>&1 && exit 0 + note "retarget to $resolved failed — kicking instead" + fi +fi + /bin/launchctl kickstart -k "gui/${UID_}/${LABEL}" 2>/dev/null -logger -t deskflow-role-watchdog "$ROLE core unhealthy — kicked $LABEL" +note "$ROLE core unhealthy — kicked $LABEL" diff --git a/slab/deskflow-handoff/deskflow-set-role b/slab/deskflow-handoff/deskflow-set-role index 24d93d1f9..fb381ff60 100755 --- a/slab/deskflow-handoff/deskflow-set-role +++ b/slab/deskflow-handoff/deskflow-set-role @@ -4,6 +4,12 @@ set -euo pipefail ROLE=${1:-} SERVER_HOST=${2:-} EPOCH=${3:-0} +# SERVER_NAME is the resolvable name behind SERVER_HOST. remoteHost has to be a +# bare IPv4 (see deskflow-resolve-ipv4), so recording the name is the only way +# the watchdog can later notice the server's DHCP lease moved and re-resolve it. +# Optional and additive: older callers that pass three arguments still work, and +# a client simply keeps whatever serverName it already had. +SERVER_NAME=${4:-} STATE="$HOME/.config/slab/deskflow.json" CLIENT_CONF="$HOME/Library/Deskflow/Deskflow-client-role.conf" EPOCH_FILE="$HOME/.config/slab/deskflow-role-epoch" @@ -65,9 +71,9 @@ CURRENT_HOST=$(sed -n 's/^remoteHost=//p' "$CLIENT_CONF" 2>/dev/null | head -1) META=() TMP="$STATE.tmp.$$" META_FILE="$STATE.meta.$$" -/usr/bin/python3 - "$STATE" "$HOME/.config/slab/deskflow-handoff.json" "$TMP" "$META_FILE" "$ROLE" <<'PY' +/usr/bin/python3 - "$STATE" "$HOME/.config/slab/deskflow-handoff.json" "$TMP" "$META_FILE" "$ROLE" "$SERVER_NAME" <<'PY' import json, sys -state_path, handoff_path, dst, meta_path, role = sys.argv[1:] +state_path, handoff_path, dst, meta_path, role, server_name = sys.argv[1:] try: with open(state_path) as f: state = json.load(f) @@ -88,6 +94,13 @@ state.update({ "label": state.get("label", "Deskflow"), "agent": "computer.aesthetic.deskflow", }) +if role == "client": + if server_name: + state["serverName"] = server_name +elif "serverName" in state: + # A server has no upstream to re-resolve; leaving a stale name behind would + # only mislead the watchdog after the next handoff. + del state["serverName"] with open(dst, "w") as f: json.dump(state, f, indent=2, sort_keys=True) f.write("\n") diff --git a/slab/deskflow-handoff/deskflow-yield-control b/slab/deskflow-handoff/deskflow-yield-control index c9806b378..2a03fe495 100755 --- a/slab/deskflow-handoff/deskflow-yield-control +++ b/slab/deskflow-handoff/deskflow-yield-control @@ -3,8 +3,11 @@ set -euo pipefail SERVER_HOST=${1:-} EPOCH=${2:-0} +# The incoming server's name, so this machine can re-resolve it later if its +# DHCP lease moves while we are the client. +SERVER_NAME=${3:-} if [[ -z "$SERVER_HOST" ]]; then - echo "usage: deskflow-yield-control SERVER_HOST [EPOCH]" >&2 + echo "usage: deskflow-yield-control SERVER_HOST [EPOCH] [SERVER_NAME]" >&2 exit 64 fi @@ -38,6 +41,6 @@ PY UNIPOINTER_STATE=$("$HOME/.local/bin/unipointer" 2>/dev/null || true) fi fi -"$HOME/.local/bin/deskflow-set-role" client "$SERVER_HOST" "$EPOCH" +"$HOME/.local/bin/deskflow-set-role" client "$SERVER_HOST" "$EPOCH" "$SERVER_NAME" [[ -n "$ACTIVE_SCREEN" ]] && echo "active-screen $ACTIVE_SCREEN" [[ -n "$UNIPOINTER_STATE" ]] && echo "unipointer-state $ACTIVE_SCREEN $UNIPOINTER_STATE" diff --git a/slab/deskflow-handoff/install.sh b/slab/deskflow-handoff/install.sh index f3d91c58d..09c6f0c0b 100755 --- a/slab/deskflow-handoff/install.sh +++ b/slab/deskflow-handoff/install.sh @@ -6,6 +6,9 @@ SCREEN_NAME="" ADDRESS="" ROLE="client" SERVER_HOST="" +# Resolvable name for SERVER_HOST. remoteHost must be a bare IPv4, so this is +# what lets deskflow-role-watchdog re-resolve the server after a DHCP change. +SERVER_NAME="" CONTROLLER=false CLIENTS="" DEFER_START=false @@ -19,6 +22,7 @@ while [[ $# -gt 0 ]]; do --address) ADDRESS=$2; shift 2 ;; --role) ROLE=$2; shift 2 ;; --server-host) SERVER_HOST=$2; shift 2 ;; + --server-name) SERVER_NAME=$2; shift 2 ;; --controller) CONTROLLER=true; shift ;; --clients) CLIENTS=$2; shift 2 ;; --defer-start) DEFER_START=true; shift ;; @@ -29,7 +33,7 @@ while [[ $# -gt 0 ]]; do done if [[ -z "$MACHINE" || -z "$SCREEN_NAME" || -z "$ADDRESS" ]]; then - echo "usage: install.sh --machine NAME --screen-name NAME --address IP [--controller --clients a,b,c] [--role server|client --server-host IP]" >&2 + echo "usage: install.sh --machine NAME --screen-name NAME --address IP [--controller --clients a,b,c] [--role server|client --server-host IP --server-name NAME]" >&2 exit 64 fi if [[ -z "$SERVER_HOST" ]]; then SERVER_HOST="$ADDRESS"; fi @@ -52,7 +56,7 @@ write_fingerprints() { done } -for file in deskflow-role-runner deskflow-set-role deskflow-role-state deskflow-retarget-client deskflow-reconcile-topology deskflow-claim-control deskflow-role-watchdog deskflow-seat-ready deskflow-active-screen deskflow-yield-control deskflow-start; do +for file in deskflow-role-runner deskflow-set-role deskflow-role-state deskflow-retarget-client deskflow-reconcile-topology deskflow-claim-control deskflow-role-watchdog deskflow-seat-ready deskflow-active-screen deskflow-yield-control deskflow-start deskflow-resolve-ipv4; do cp "$HERE/$file" "$HOME/.local/bin/$file" chmod 755 "$HOME/.local/bin/$file" done @@ -100,14 +104,18 @@ EOF /usr/bin/python3 -c 'import json,sys; path,machine,screen,address,controller,clients=sys.argv[1:]; data={"enabled":True,"machine":machine,"screenName":screen,"address":address,"controller":controller=="true","clients":[x for x in clients.split(",") if x]}; f=open(path,"w"); json.dump(data,f,indent=2,sort_keys=True); f.write("\n"); f.close()' \ "$HOME/.config/slab/deskflow-handoff.json" "$MACHINE" "$SCREEN_NAME" "$ADDRESS" "$CONTROLLER" "$CLIENTS" -/usr/bin/python3 - "$HOME/.config/slab/deskflow.json" "$ROLE" <<'PY' +/usr/bin/python3 - "$HOME/.config/slab/deskflow.json" "$ROLE" "$SERVER_NAME" <<'PY' import json, sys -path, role = sys.argv[1:] +path, role, server_name = sys.argv[1:] try: with open(path) as f: data = json.load(f) except Exception: data = {} data.update({"enabled": True, "role": role, "label": data.get("label", "Deskflow"), "agent": "computer.aesthetic.deskflow"}) +if role == "client" and server_name: + data["serverName"] = server_name +elif role == "server": + data.pop("serverName", None) with open(path, "w") as f: json.dump(data, f, indent=2, sort_keys=True) f.write("\n") -- 2.51.2