fix: make Guacamole fixture dual-stack and keep VM display awake

This commit is contained in:
2026-09-14 06:31:07 +00:00
parent d01bb5a9a7
commit 102a5486f4
9 changed files with 180 additions and 11 deletions
+8
View File
@@ -893,6 +893,14 @@ watchdog/tunnel is bound
only to the helper's private Docker gateway. Stop the helper before the normal only to the helper's private Docker gateway. Stop the helper before the normal
`test-host reset`. It is a recovery interface, not a product component or a `test-host reset`. It is a recovery interface, not a product component or a
replacement for Guest Control/native test automation. replacement for Guest Control/native test automation.
Because Windows can power off the virtual monitor while the VM remains
running, `test-host prepare` must also reapply the disposable display/sleep
keepalive and record `prepare-display-keepalive`. A zero-bpp VRDE framebuffer
otherwise leaves an otherwise authenticated Guacamole browser at “Waiting for
response”. If the public browser hostname has an AAAA record, `rdp-access up
--bind 0.0.0.0` must own a tracked IPv6-to-IPv4 forward and expose its
`ipv6_forward` status; this prevents a browser selecting IPv6 for the WebSocket
from silently taking a different, refused path.
For this provisioned lane, the approved host-only credential-file location is For this provisioned lane, the approved host-only credential-file location is
`/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password`. It must `/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password`. It must
+10 -3
View File
@@ -91,6 +91,9 @@ self-signed HTTPS Guacamole gateway while retaining VRDE on Helium loopback and
the reconnecting SSH watchdog/tunnel on a private Docker gateway. Follow its the reconnecting SSH watchdog/tunnel on a private Docker gateway. Follow its
full lease/prepare/up/ full lease/prepare/up/
down/reset lifecycle; it is not an alternative to the native test controller. down/reset lifecycle; it is not an alternative to the native test controller.
In public-bind mode the helper also owns a tracked IPv6-to-IPv4 `socat` forward
when the browser hostname has an AAAA record; check `ipv6_forward=active` in
`rdp-access status` before diagnosing a browser-side “Waiting for response”.
## Snapshots and reset contract ## Snapshots and reset contract
@@ -112,10 +115,14 @@ stateful run must:
1. Acquire the run lease and verify the VM name, UUID, and snapshot UUID. 1. Acquire the run lease and verify the VM name, UUID, and snapshot UUID.
2. Restore `baseline-clean-administrator` if the current state is not the baseline. 2. Restore `baseline-clean-administrator` if the current state is not the baseline.
3. Start headless and wait for `VMState=running` plus Guest Additions readiness. 3. Start headless and wait for `VMState=running` plus Guest Additions readiness.
4. Run the bounded test, collect redacted artifacts, and close every Guest 4. Apply the disposable display/sleep keepalive (`monitor-timeout`, standby,
and hibernate timers set to zero) so VirtualBox VRDE cannot expose a
zero-bpp framebuffer after Windows idle timeout. This is recorded as
`prepare-display-keepalive` and is reapplied after every snapshot restore.
5. Run the bounded test, collect redacted artifacts, and close every Guest
Control process that was opened by the run. Control process that was opened by the run.
5. Request a graceful guest shutdown and wait for `VMState=poweroff`. 6. Request a graceful guest shutdown and wait for `VMState=poweroff`.
6. Restore `baseline-clean-administrator` again and leave the VM powered off. 7. Restore `baseline-clean-administrator` again and leave the VM powered off.
Use `controlvm ... poweroff` only for a hung, disposable test; it can lose Use `controlvm ... poweroff` only for a hung, disposable test; it can lose
guest state. Never delete any clean snapshot, unregister the VM, or alter guest state. Never delete any clean snapshot, unregister the VM, or alter
+3
View File
@@ -208,6 +208,9 @@ Interactive browser access is a deliberately temporary recovery path only. See
[`test/rdp-access`](../test/rdp-access/README.md) for the Docker-only, [`test/rdp-access`](../test/rdp-access/README.md) for the Docker-only,
self-signed HTTPS Guacamole lifecycle; it must be started only after the native self-signed HTTPS Guacamole lifecycle; it must be started only after the native
fixture controller has prepared and leased the VM, and stopped before reset. fixture controller has prepared and leased the VM, and stopped before reset.
For a public hostname with an AAAA record, its `up --bind 0.0.0.0` mode also
tracks an IPv6-to-IPv4 forward; verify `ipv6_forward=active` before debugging
a browser stuck at “Waiting for response”.
The Linux production Compose asset has a separate, loopback-only smoke lane. It The Linux production Compose asset has a separate, loopback-only smoke lane. It
uses a disposable self-signed key only under the ignored `.test-runs` tree, uses a disposable self-signed key only under the ignored `.test-runs` tree,
+14
View File
@@ -316,6 +316,18 @@ wait_guest_additions() {
fail "Guest Additions did not publish version and Windows OS-release properties" fail "Guest Additions did not publish version and Windows OS-release properties"
} }
configure_display_keepalive() {
# VirtualBox VRDE reports a zero-bpp framebuffer when Windows powers off
# the virtual monitor. That leaves an authenticated Guacamole tunnel in
# its "Waiting for response" state even though RDP negotiation succeeded.
# Keep the disposable diagnostic fixture awake; this is deliberately
# applied after every snapshot restore because power-plan state belongs to
# the guest snapshot, not to the controller.
provisioner_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \
/d /s /c '(powercfg /change monitor-timeout-ac 0 && powercfg /change monitor-timeout-dc 0 && powercfg /change standby-timeout-ac 0 && powercfg /change standby-timeout-dc 0 && powercfg /change hibernate-timeout-ac 0 && powercfg /change hibernate-timeout-dc 0 && echo RVBOX_GUEST_OK)' >/dev/null || \
fail "fixture provisioner could not disable disposable display/sleep timers"
}
wait_service() { wait_service() {
wanted=$1 wanted=$1
attempt=0 attempt=0
@@ -366,6 +378,8 @@ case "$action" in
step prepare-vm-started step prepare-vm-started
wait_guest_additions wait_guest_additions
step prepare-guest-additions-ready step prepare-guest-additions-ready
configure_display_keepalive
step prepare-display-keepalive
assert_clean_guest assert_clean_guest
step prepare-clean-baseline-verified step prepare-clean-baseline-verified
;; ;;
+26 -7
View File
@@ -9,6 +9,7 @@ The helper builds this private path:
```text ```text
browser -- HTTPS/self-signed --> nginx + Guacamole containers browser -- HTTPS/self-signed --> nginx + Guacamole containers
(IPv4 and, in public mode, a tracked IPv6 forward)
| |
private Docker gateway private Docker gateway
| |
@@ -69,9 +70,23 @@ port (`54001`). They can be overridden without editing tracked files through
`RVBOX_TEST_VBOX_HOST`, `RDP_ACCESS_VRDE_HOST`, `RDP_ACCESS_VRDE_PORT`, `RVBOX_TEST_VBOX_HOST`, `RDP_ACCESS_VRDE_HOST`, `RDP_ACCESS_VRDE_PORT`,
`RDP_ACCESS_WEB_USER`, `RDP_ACCESS_RDP_USER`, `RDP_ACCESS_BIND`, `RDP_ACCESS_WEB_USER`, `RDP_ACCESS_RDP_USER`, `RDP_ACCESS_BIND`,
`RDP_ACCESS_HTTP_PORT`, `RDP_ACCESS_TUNNEL_PORT`, and `RDP_ACCESS_HTTP_PORT`, `RDP_ACCESS_TUNNEL_PORT`, and
`RDP_ACCESS_PUBLIC_HOST`. The helper deliberately limits the VRDE host to `RDP_ACCESS_PUBLIC_HOST`, `RDP_ACCESS_IPV6_BIND`, and
`RDP_ACCESS_IPV6_FORWARD`. The helper deliberately limits the VRDE host to
Helium loopback (`127.0.0.1` or `localhost`) so an override cannot accidentally Helium loopback (`127.0.0.1` or `localhost`) so an override cannot accidentally
turn the diagnostic server into a remote target. turn the diagnostic server into a remote target.
Set `RDP_ACCESS_GUACD_LOG_LEVEL=debug` temporarily when collecting detailed
guacd/RDP negotiation diagnostics; the default is `info`.
When `--bind 0.0.0.0` is used, `up` also starts a tracked `socat` listener on
`[::]:$RDP_ACCESS_HTTP_PORT` and forwards it to the IPv4 gateway listener. This
matters when the public hostname has an AAAA record: some browsers choose IPv6
for the WebSocket even if the initial page used IPv4. The listener is recorded
under `.runtime/ipv6-forward.pid` and is removed by `down` and `clean`. Its
default bind is `::` (`RDP_ACCESS_IPV6_BIND`); set
`RDP_ACCESS_IPV6_FORWARD=never` to deliberately use IPv4 only, `always` to make
IPv6 startup a hard requirement, or leave the default `auto` for a warning and
an IPv4-only fallback when `socat` is unavailable. `status` reports
`ipv6_forward=active`, `active_external`, `inactive`, or `disabled`.
If `up` is run again while the Compose services are still running, it is If `up` is run again while the Compose services are still running, it is
idempotent: an existing healthy tunnel is reused, and a missing tunnel is idempotent: an existing healthy tunnel is reused, and a missing tunnel is
@@ -123,7 +138,9 @@ test/rdp-access/rdp-access url
If the browser reaches Guacamole but stays on “Waiting for response”, run If the browser reaches Guacamole but stays on “Waiting for response”, run
`status` first. Confirm `private_tunnel=active` (or `status` first. Confirm `private_tunnel=active` (or
`active_external`), then check the VM state and the last lines of `active_external`) and, for a public dual-stack hostname,
`ipv6_forward=active` (or a known-good external forward). Then check the VM
state and the last lines of
`.runtime/tunnel.log`. A tunnel can be recreated without losing the Compose `.runtime/tunnel.log`. A tunnel can be recreated without losing the Compose
stack by running `up` again. This helper already uses `security=rdp` and stack by running `up` again. This helper already uses `security=rdp` and
disables Guacamole's GFX extension, which are required by the fixture's legacy disables Guacamole's GFX extension, which are required by the fixture's legacy
@@ -132,9 +149,11 @@ VRDE server. Do not switch the helper to native Windows RDP:
unusable, stop this helper and use Guest Control for the deterministic portion unusable, stop this helper and use Guest Control for the deterministic portion
of the work; record the blocked interactive step in the native test report. of the work; record the blocked interactive step in the native test report.
The helper requires Docker/Docker Compose, SSH access through the existing The helper requires Docker/Docker Compose, `socat` for the optional public
`helium-remote` alias, and the fixture password file documented in dual-stack forward, SSH access through the existing `helium-remote` alias, and
the fixture password file documented in
[`docs/testing-vm.md`](../../docs/testing-vm.md). It does not install host [`docs/testing-vm.md`](../../docs/testing-vm.md). It does not install host
packages, write credentials into Git, or alter VM settings. The tracked files packages, write credentials into Git, or alter VM identity/settings. The
are Docker-only configuration and the controller script; all generated content authoritative `test-host prepare` step applies only the disposable
is ignored beneath `.runtime/`. display/sleep keepalive needed by VirtualBox VRDE; all generated content is
ignored beneath `.runtime/`.
+2
View File
@@ -1,6 +1,8 @@
services: services:
guacd: guacd:
image: guacamole/guacd:1.6.0 image: guacamole/guacd:1.6.0
environment:
LOG_LEVEL: ${RDP_ACCESS_GUACD_LOG_LEVEL:-info}
guacamole: guacamole:
image: guacamole/guacamole:1.6.0 image: guacamole/guacamole:1.6.0
+3
View File
@@ -15,5 +15,8 @@ server {
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
proxy_buffering off; proxy_buffering off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
proxy_set_header X-Accel-Buffering no;
} }
} }
+113 -1
View File
@@ -16,6 +16,9 @@ project=rvbox-rdp-access
: "${RDP_ACCESS_PUBLIC_HOST:=localhost}" : "${RDP_ACCESS_PUBLIC_HOST:=localhost}"
: "${RDP_ACCESS_WEB_USER:=rvboxtest}" : "${RDP_ACCESS_WEB_USER:=rvboxtest}"
: "${RDP_ACCESS_RDP_USER:=rvboxtest}" : "${RDP_ACCESS_RDP_USER:=rvboxtest}"
: "${RDP_ACCESS_GUACD_LOG_LEVEL:=info}"
: "${RDP_ACCESS_IPV6_BIND:=::}"
: "${RDP_ACCESS_IPV6_FORWARD:=auto}"
: "${RVBOX_TEST_VBOX_HOST:=helium-remote}" : "${RVBOX_TEST_VBOX_HOST:=helium-remote}"
: "${RDP_ACCESS_VRDE_HOST:=127.0.0.1}" : "${RDP_ACCESS_VRDE_HOST:=127.0.0.1}"
: "${RDP_ACCESS_VRDE_PORT:=3389}" : "${RDP_ACCESS_VRDE_PORT:=3389}"
@@ -46,7 +49,10 @@ up options:
Environment equivalents: RDP_ACCESS_BIND, RDP_ACCESS_HTTP_PORT, Environment equivalents: RDP_ACCESS_BIND, RDP_ACCESS_HTTP_PORT,
RDP_ACCESS_TUNNEL_PORT, RDP_ACCESS_PUBLIC_HOST, RDP_ACCESS_WEB_USER, RDP_ACCESS_TUNNEL_PORT, RDP_ACCESS_PUBLIC_HOST, RDP_ACCESS_WEB_USER,
RDP_ACCESS_RDP_USER, RVBOX_TEST_VBOX_HOST, RDP_ACCESS_VRDE_HOST, and RDP_ACCESS_RDP_USER, RVBOX_TEST_VBOX_HOST, RDP_ACCESS_VRDE_HOST, and
RDP_ACCESS_VRDE_PORT. RDP_ACCESS_VRDE_PORT. In public mode, RDP_ACCESS_IPV6_BIND and
RDP_ACCESS_IPV6_FORWARD (auto, always, or never) control the reversible
IPv6-to-IPv4 listener for dual-stack hostnames. Set
RDP_ACCESS_GUACD_LOG_LEVEL=debug for temporary protocol diagnostics.
EOF EOF
} }
@@ -69,6 +75,7 @@ compose() {
RDP_ACCESS_RUNTIME_DIR=$runtime_dir \ RDP_ACCESS_RUNTIME_DIR=$runtime_dir \
RDP_ACCESS_BIND=$RDP_ACCESS_BIND \ RDP_ACCESS_BIND=$RDP_ACCESS_BIND \
RDP_ACCESS_HTTP_PORT=$RDP_ACCESS_HTTP_PORT \ RDP_ACCESS_HTTP_PORT=$RDP_ACCESS_HTTP_PORT \
RDP_ACCESS_GUACD_LOG_LEVEL=$RDP_ACCESS_GUACD_LOG_LEVEL \
RDP_ACCESS_CERT_NAME=$RDP_ACCESS_PUBLIC_HOST \ RDP_ACCESS_CERT_NAME=$RDP_ACCESS_PUBLIC_HOST \
RDP_ACCESS_CERT_SAN=$cert_san \ RDP_ACCESS_CERT_SAN=$cert_san \
RDP_ACCESS_HOST_UID=$(id -u) \ RDP_ACCESS_HOST_UID=$(id -u) \
@@ -100,6 +107,8 @@ cert_name_file=$runtime_dir/cert-name
tunnel_stop_file=$runtime_dir/tunnel.stop tunnel_stop_file=$runtime_dir/tunnel.stop
tunnel_pid_file=$runtime_dir/tunnel-watchdog.pid tunnel_pid_file=$runtime_dir/tunnel-watchdog.pid
tunnel_log_file=$runtime_dir/tunnel.log tunnel_log_file=$runtime_dir/tunnel.log
ipv6_pid_file=$runtime_dir/ipv6-forward.pid
ipv6_log_file=$runtime_dir/ipv6-forward.log
stop_tunnel() { stop_tunnel() {
mkdir -p "$runtime_dir" mkdir -p "$runtime_dir"
@@ -135,6 +144,102 @@ stop_tunnel() {
rm -f "$tunnel_pid_file" "$tunnel_stop_file" rm -f "$tunnel_pid_file" "$tunnel_stop_file"
} }
ipv6_process_matches() {
ipv6_pid=$1
ipv6_cmd=$(ps -p "$ipv6_pid" -o args= 2>/dev/null || true)
case $ipv6_cmd in
*"TCP6-LISTEN:$RDP_ACCESS_HTTP_PORT"*"TCP4:127.0.0.1:$RDP_ACCESS_HTTP_PORT"*) return 0 ;;
*) return 1 ;;
esac
}
ipv6_listener_active() {
command -v ss >/dev/null 2>&1 || return 1
ss -H -ltn6 2>/dev/null | awk -v port=":$RDP_ACCESS_HTTP_PORT" \
'{ if ($4 ~ (port "$")) found=1 } END { exit !found }'
}
stop_ipv6_forward() {
[ -f "$ipv6_pid_file" ] || return 0
ipv6_pid=$(cat "$ipv6_pid_file" 2>/dev/null || true)
case $ipv6_pid in
''|*[!0-9]*) ;;
*)
if ipv6_process_matches "$ipv6_pid"; then
kill "$ipv6_pid" >/dev/null 2>&1 || true
sleep 1
if ipv6_process_matches "$ipv6_pid"; then
kill -KILL "$ipv6_pid" >/dev/null 2>&1 || true
fi
fi
;;
esac
rm -f "$ipv6_pid_file"
}
start_ipv6_forward() {
[ "$RDP_ACCESS_BIND" = 0.0.0.0 ] || return 0
case $RDP_ACCESS_IPV6_FORWARD in
never) stop_ipv6_forward; return 0 ;;
auto|always) ;;
*) fail "RDP_ACCESS_IPV6_FORWARD must be auto, always, or never" ;;
esac
if ! command -v socat >/dev/null 2>&1; then
if [ "$RDP_ACCESS_IPV6_FORWARD" = always ]; then
fail "socat is required for RDP_ACCESS_IPV6_FORWARD=always"
fi
printf 'rdp-access: warning: socat unavailable; IPv6 forwarding disabled (IPv4 remains active)\n' >&2
return 0
fi
if ipv6_listener_active; then
# Do not kill an unrelated listener. The operator can inspect it via
# status; down only stops a PID that this helper started and verified.
printf 'rdp-access: IPv6 port %s is already occupied; retaining the existing listener\n' \
"$RDP_ACCESS_HTTP_PORT" >&2
return 0
fi
stop_ipv6_forward
umask 077
mkdir -p "$runtime_dir"
: >"$ipv6_log_file"
chmod 600 "$ipv6_log_file"
setsid nohup socat \
"TCP6-LISTEN:$RDP_ACCESS_HTTP_PORT,ipv6only=1,reuseaddr,fork,bind=$RDP_ACCESS_IPV6_BIND" \
"TCP4:127.0.0.1:$RDP_ACCESS_HTTP_PORT" \
</dev/null >>"$ipv6_log_file" 2>&1 &
ipv6_pid=$!
printf '%s\n' "$ipv6_pid" >"$ipv6_pid_file"
chmod 600 "$ipv6_pid_file"
attempts=0
while [ "$attempts" -lt 10 ]; do
if ipv6_process_matches "$ipv6_pid" && ipv6_listener_active; then
return 0
fi
attempts=$((attempts + 1))
sleep 1
done
stop_ipv6_forward
if [ "$RDP_ACCESS_IPV6_FORWARD" = always ]; then
fail "could not start IPv6 forwarding listener; inspect $ipv6_log_file"
fi
printf 'rdp-access: warning: IPv6 forwarding could not start (IPv4 remains active); inspect %s\n' \
"$ipv6_log_file" >&2
}
ipv6_forward_status() {
if [ -f "$ipv6_pid_file" ] && ipv6_process_matches "$(cat "$ipv6_pid_file" 2>/dev/null || true)" && ipv6_listener_active; then
printf 'ipv6_forward=active\n'
printf 'ipv6_forward_supervisor=helper\n'
elif ipv6_listener_active; then
printf 'ipv6_forward=active_external\n'
printf 'ipv6_forward_supervisor=external\n'
elif [ "$RDP_ACCESS_BIND" != 0.0.0.0 ] || [ "$RDP_ACCESS_IPV6_FORWARD" = never ]; then
printf 'ipv6_forward=disabled\n'
else
printf 'ipv6_forward=inactive\n'
fi
}
gateway_for_network() { gateway_for_network() {
docker network inspect --format '{{(index .IPAM.Config 0).Gateway}}' "${project}_default" docker network inspect --format '{{(index .IPAM.Config 0).Gateway}}' "${project}_default"
} }
@@ -340,6 +445,8 @@ safe_name RDP_ACCESS_RDP_USER "$RDP_ACCESS_RDP_USER"
safe_name RVBOX_TEST_VBOX_HOST "$RVBOX_TEST_VBOX_HOST" safe_name RVBOX_TEST_VBOX_HOST "$RVBOX_TEST_VBOX_HOST"
case $RDP_ACCESS_VRDE_HOST in 127.0.0.1|localhost) ;; *) fail "RDP_ACCESS_VRDE_HOST must be 127.0.0.1 or localhost" ;; esac case $RDP_ACCESS_VRDE_HOST in 127.0.0.1|localhost) ;; *) fail "RDP_ACCESS_VRDE_HOST must be 127.0.0.1 or localhost" ;; esac
safe_port RDP_ACCESS_VRDE_PORT "$RDP_ACCESS_VRDE_PORT" safe_port RDP_ACCESS_VRDE_PORT "$RDP_ACCESS_VRDE_PORT"
case $RDP_ACCESS_IPV6_BIND in ::|::1) ;; *) fail "RDP_ACCESS_IPV6_BIND must be :: or ::1" ;; esac
case $RDP_ACCESS_IPV6_FORWARD in auto|always|never) ;; *) fail "RDP_ACCESS_IPV6_FORWARD must be auto, always, or never" ;; esac
case $RDP_ACCESS_PUBLIC_HOST in case $RDP_ACCESS_PUBLIC_HOST in
*[!0-9.]* ) cert_san="DNS:$RDP_ACCESS_PUBLIC_HOST" ;; *[!0-9.]* ) cert_san="DNS:$RDP_ACCESS_PUBLIC_HOST" ;;
@@ -375,6 +482,7 @@ case $action in
fail "existing Guacamole stack was found but the private VRDE tunnel could not be restored; inspect $tunnel_log_file" fail "existing Guacamole stack was found but the private VRDE tunnel could not be restored; inspect $tunnel_log_file"
fi fi
wait_for_gateway || fail "Guacamole stack is running but its HTTPS endpoint did not become ready; inspect Compose logs" wait_for_gateway || fail "Guacamole stack is running but its HTTPS endpoint did not become ready; inspect Compose logs"
start_ipv6_forward
if [ -f "$session_file" ]; then sed -n '1p' "$session_file"; fi if [ -f "$session_file" ]; then sed -n '1p' "$session_file"; fi
exit 0 exit 0
fi fi
@@ -399,6 +507,7 @@ case $action in
if ! wait_for_gateway; then if ! wait_for_gateway; then
fail "Guacamole gateway started but its HTTPS endpoint did not become ready; inspect Compose logs" fail "Guacamole gateway started but its HTTPS endpoint did not become ready; inspect Compose logs"
fi fi
start_ipv6_forward
printf 'Guacamole is ready at https://%s:%s/guacamole/\n' "$RDP_ACCESS_PUBLIC_HOST" "$RDP_ACCESS_HTTP_PORT" printf 'Guacamole is ready at https://%s:%s/guacamole/\n' "$RDP_ACCESS_PUBLIC_HOST" "$RDP_ACCESS_HTTP_PORT"
printf 'Accept the self-signed certificate warning, then sign in as %s with the fixture password.\n' "$RDP_ACCESS_WEB_USER" printf 'Accept the self-signed certificate warning, then sign in as %s with the fixture password.\n' "$RDP_ACCESS_WEB_USER"
;; ;;
@@ -420,6 +529,7 @@ case $action in
else else
printf 'private_tunnel=inactive\n' printf 'private_tunnel=inactive\n'
fi fi
ipv6_forward_status
;; ;;
url) url)
[ "$#" -eq 0 ] || { usage >&2; fail "url accepts no options"; } [ "$#" -eq 0 ] || { usage >&2; fail "url accepts no options"; }
@@ -431,12 +541,14 @@ case $action in
;; ;;
down) down)
[ "$#" -eq 0 ] || { usage >&2; fail "down accepts no options"; } [ "$#" -eq 0 ] || { usage >&2; fail "down accepts no options"; }
stop_ipv6_forward
stop_tunnel stop_tunnel
compose down --remove-orphans || true compose down --remove-orphans || true
printf 'Temporary gateway and private tunnel stopped; generated certificate and password hash retained in %s.\n' "$runtime_dir" printf 'Temporary gateway and private tunnel stopped; generated certificate and password hash retained in %s.\n' "$runtime_dir"
;; ;;
clean) clean)
[ "$#" -eq 0 ] || { usage >&2; fail "clean accepts only --images"; } [ "$#" -eq 0 ] || { usage >&2; fail "clean accepts only --images"; }
stop_ipv6_forward
stop_tunnel stop_tunnel
compose down --remove-orphans || true compose down --remove-orphans || true
case $runtime_dir in "$helper_dir"/.runtime) rm -rf "$runtime_dir" ;; *) fail "unsafe runtime path" ;; esac case $runtime_dir in "$helper_dir"/.runtime) rm -rf "$runtime_dir" ;; *) fail "unsafe runtime path" ;; esac
+1
View File
@@ -15,6 +15,7 @@
<param name="enable-wallpaper">false</param> <param name="enable-wallpaper">false</param>
<param name="enable-theming">false</param> <param name="enable-theming">false</param>
<param name="enable-font-smoothing">false</param> <param name="enable-font-smoothing">false</param>
<param name="color-depth">24</param>
<param name="disable-gfx">true</param> <param name="disable-gfx">true</param>
</connection> </connection>
</authorize> </authorize>