From 42d06e04078bd472875f10877c94dbd5942ef0c8 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 13 Jun 2024 13:24:28 +0200 Subject: [PATCH 01/32] Set up scripts for enabling/disabling WiFi network interface --- debian-pkg/etc/sudoers.d/tinypilot | 3 ++ .../tinypilot-privileged/scripts/disable-wifi | 17 +++++++++++ .../tinypilot-privileged/scripts/enable-wifi | 30 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100755 debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi create mode 100755 debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi diff --git a/debian-pkg/etc/sudoers.d/tinypilot b/debian-pkg/etc/sudoers.d/tinypilot index 1afd13d3c..1d0c4ace8 100644 --- a/debian-pkg/etc/sudoers.d/tinypilot +++ b/debian-pkg/etc/sudoers.d/tinypilot @@ -1,6 +1,9 @@ tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/change-hostname tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/collect-debug-logs tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/configure-janus +tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/disable-wifi +tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/enable-wifi +tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/read-update-log tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/read-update-log tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/update tinypilot ALL=(ALL) NOPASSWD: /sbin/shutdown diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi new file mode 100755 index 000000000..5ce9869ad --- /dev/null +++ b/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi @@ -0,0 +1,17 @@ +#!/bin/bash + +# Exit on first error. +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +readonly SCRIPT_DIR +readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" + +# shellcheck source=lib/markers.sh +. "${SCRIPT_DIR}/lib/markers.sh" + +# Remove any existing automated configuration. +"${SCRIPT_DIR}/strip-marker-sections" "${CONFIG_FILE}" + +# Block (deactivate) WLAN, which will take effect instantly. +rfkill block wlan diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi new file mode 100755 index 000000000..7a2c1e694 --- /dev/null +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -0,0 +1,30 @@ +#!/bin/bash + +# Exit on first error. +set -e + +# TODO parse these parameters from CLI args. +readonly WIFI_COUNTRY='DE' +readonly WIFI_SSID='my-home-network' +readonly WIFI_PSK='p4ssw0rd' + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +readonly SCRIPT_DIR +readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" +# shellcheck source=lib/markers.sh +. "${SCRIPT_DIR}/lib/markers.sh" + +# Remove any existing automated configuration. +"${SCRIPT_DIR}/strip-marker-sections" "${CONFIG_FILE}" + +# Write out the new configuration. +{ + echo "${MARKER_START}" + echo "country=${WIFI_COUNTRY}" + echo "$(wpa_passphrase "${WIFI_SSID}" "${WIFI_PSK}" | sed '/^\t#psk=.*/d')" + echo "${MARKER_END}" +} | sudo tee --append "${CONFIG_FILE}" > /dev/null + +# Effectuate changes. +rfkill unblock wifi +wpa_cli -i wlan0 reconfigure From d02832d45ce9268817ca5486458457ea87ea19c7 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 13 Jun 2024 13:26:37 +0200 Subject: [PATCH 02/32] Fix bash style --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 7a2c1e694..8f45e8f5d 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -21,7 +21,7 @@ readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" { echo "${MARKER_START}" echo "country=${WIFI_COUNTRY}" - echo "$(wpa_passphrase "${WIFI_SSID}" "${WIFI_PSK}" | sed '/^\t#psk=.*/d')" + wpa_passphrase "${WIFI_SSID}" "${WIFI_PSK}" | sed '/^\t#psk=.*/d' echo "${MARKER_END}" } | sudo tee --append "${CONFIG_FILE}" > /dev/null From 9bbc056c6e4dcec1804b99b3cdcbd0bcdf1ca0dc Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 13 Jun 2024 13:28:14 +0200 Subject: [PATCH 03/32] Remove redundant entry --- debian-pkg/etc/sudoers.d/tinypilot | 1 - 1 file changed, 1 deletion(-) diff --git a/debian-pkg/etc/sudoers.d/tinypilot b/debian-pkg/etc/sudoers.d/tinypilot index 1d0c4ace8..88ecea9d3 100644 --- a/debian-pkg/etc/sudoers.d/tinypilot +++ b/debian-pkg/etc/sudoers.d/tinypilot @@ -4,7 +4,6 @@ tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/configure-janus tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/disable-wifi tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/enable-wifi tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/read-update-log -tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/read-update-log tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/update tinypilot ALL=(ALL) NOPASSWD: /sbin/shutdown tinypilot ALL=(ALL) NOPASSWD: /usr/sbin/service janus restart From e24545924adb1b82c2d24a7ba7071402eb6288f9 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 13 Jun 2024 13:47:43 +0200 Subject: [PATCH 04/32] Add commentary --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 8f45e8f5d..b34866185 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -21,6 +21,10 @@ readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" { echo "${MARKER_START}" echo "country=${WIFI_COUNTRY}" + # Generate the "network" block of the config. The `wpa_passphrase` command + # hashes the WiFi password so that it is not persisted in clear text. + # However, the command still outputs the original password as a comment line, + # so we strip off that line (which starts with `#psk=`). wpa_passphrase "${WIFI_SSID}" "${WIFI_PSK}" | sed '/^\t#psk=.*/d' echo "${MARKER_END}" } | sudo tee --append "${CONFIG_FILE}" > /dev/null From 76371333af5083e0d52f78de82a2dcbb04fa856d Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 17 Jun 2024 18:34:53 +0200 Subject: [PATCH 05/32] Read WiFi settings from CLI args --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index b34866185..3cec741ce 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -4,9 +4,9 @@ set -e # TODO parse these parameters from CLI args. -readonly WIFI_COUNTRY='DE' -readonly WIFI_SSID='my-home-network' -readonly WIFI_PSK='p4ssw0rd' +readonly WIFI_COUNTRY="$1" +readonly WIFI_SSID="$2" +readonly WIFI_PSK="$3" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" readonly SCRIPT_DIR From 517af0edcc9c2ae528e1ad918c72026a6432a137 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 17 Jun 2024 18:36:32 +0200 Subject: [PATCH 06/32] Add mock scripts --- dev-scripts/mock-scripts/disable-wifi | 1 + dev-scripts/mock-scripts/enable-wifi | 1 + 2 files changed, 2 insertions(+) create mode 100755 dev-scripts/mock-scripts/disable-wifi create mode 100755 dev-scripts/mock-scripts/enable-wifi diff --git a/dev-scripts/mock-scripts/disable-wifi b/dev-scripts/mock-scripts/disable-wifi new file mode 100755 index 000000000..a9bf588e2 --- /dev/null +++ b/dev-scripts/mock-scripts/disable-wifi @@ -0,0 +1 @@ +#!/bin/bash diff --git a/dev-scripts/mock-scripts/enable-wifi b/dev-scripts/mock-scripts/enable-wifi new file mode 100755 index 000000000..a9bf588e2 --- /dev/null +++ b/dev-scripts/mock-scripts/enable-wifi @@ -0,0 +1 @@ +#!/bin/bash From 3cf6e385af12f4f3480fc9fc39e834c06467314b Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 20 Jun 2024 14:12:18 +0200 Subject: [PATCH 07/32] Parse CLI arguments --- .../tinypilot-privileged/scripts/disable-wifi | 38 +++++++- .../tinypilot-privileged/scripts/enable-wifi | 93 +++++++++++++++++-- 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi index 5ce9869ad..c77ec7e2e 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi @@ -1,17 +1,45 @@ #!/bin/bash +# +# Disable the WiFi network connection. -# Exit on first error. +# Exit on first failure. set -e +print_help() { + cat < 0 )); do + case "$1" in + --help) + print_help + exit + ;; + *) + >&2 echo "Unknown flag/argument: $1" + >&2 echo "Use the '--help' flag for more information" + exit 1 + ;; + esac +done + +# Echo commands before executing them, by default to stderr. +set -x + +# Exit on unset variable. +set -u + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" readonly SCRIPT_DIR readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" -# shellcheck source=lib/markers.sh -. "${SCRIPT_DIR}/lib/markers.sh" - # Remove any existing automated configuration. "${SCRIPT_DIR}/strip-marker-sections" "${CONFIG_FILE}" -# Block (deactivate) WLAN, which will take effect instantly. +# Block (deactivate) WLAN. This will take effect instantly. rfkill block wlan diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 3cec741ce..0cf39a3d2 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -1,23 +1,100 @@ #!/bin/bash +# +# Enables a WiFi network connection. -# Exit on first error. +# Exit on first failure. set -e -# TODO parse these parameters from CLI args. -readonly WIFI_COUNTRY="$1" -readonly WIFI_SSID="$2" -readonly WIFI_PSK="$3" +print_help() { + cat < 0 )); do + case "$1" in + --help) + print_help + exit + ;; + --country) + if (( "$#" < 2 )); then + shift + break + fi + WIFI_COUNTRY="$2" + shift # For flag name. + shift # For flag value. + ;; + --ssid) + if (( "$#" < 2 )); then + shift + break + fi + WIFI_SSID="$2" + shift # For flag name. + shift # For flag value. + ;; + --psk) + if (( "$#" < 2 )); then + shift + break + fi + WIFI_PSK="$2" + shift # For flag name. + shift # For flag value. + ;; + *) + >&2 echo "Unknown flag/argument: $1" + >&2 echo "Use the '--help' flag for more information" + exit 1 + ;; + esac +done +readonly WIFI_COUNTRY +readonly WIFI_SSID +readonly WIFI_PSK="${WIFI_PSK:-''}" + +if [[ -z "${WIFI_COUNTRY}" ]]; then + >&2 echo 'Missing argument: COUNTRY' + >&2 echo "Use the '--help' flag for more information" + exit 1 +fi + +if [[ "$(echo -n "${WIFI_COUNTRY}" | wc --bytes)" != 2 ]]; then + >&2 echo 'Invalid argument: COUNTRY' + >&2 echo "Use the '--help' flag for more information" + exit 1 +fi + +if [[ -z "${WIFI_SSID}" ]]; then + >&2 echo 'Missing argument: SSID' + >&2 echo "Use the '--help' flag for more information" + exit 1 +fi + +# Echo commands before executing them, by default to stderr. +set -x + +# Exit on unset variable. +set -u SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" readonly SCRIPT_DIR -readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" -# shellcheck source=lib/markers.sh -. "${SCRIPT_DIR}/lib/markers.sh" # Remove any existing automated configuration. +readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" "${SCRIPT_DIR}/strip-marker-sections" "${CONFIG_FILE}" # Write out the new configuration. +# shellcheck source=lib/markers.sh +. "${SCRIPT_DIR}/lib/markers.sh" { echo "${MARKER_START}" echo "country=${WIFI_COUNTRY}" From e0602d4527b339063c1c4562261c58020726ced9 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 26 Jun 2024 19:02:22 +0200 Subject: [PATCH 08/32] Allow to join open networks (no PSK) --- .../tinypilot-privileged/scripts/disable-wifi | 6 +-- .../tinypilot-privileged/scripts/enable-wifi | 47 +++++++++++++------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi index c77ec7e2e..2d3d71470 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/disable-wifi @@ -9,7 +9,7 @@ print_help() { cat < /dev/null && pwd )" readonly SCRIPT_DIR -readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" +readonly CONFIG_FILE='/etc/wpa_supplicant/wpa_supplicant.conf' # Remove any existing automated configuration. "${SCRIPT_DIR}/strip-marker-sections" "${CONFIG_FILE}" -# Block (deactivate) WLAN. This will take effect instantly. +# Effectuate changes. This will disable the WiFi connection instantly. rfkill block wlan diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 0cf39a3d2..f9495ac36 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -12,7 +12,8 @@ Enables a WiFi network connection. --help Optional. Display this help and exit. --country COUNTRY A two-digit country code, as of ISO 3166-1 alpha-2. --ssid SSID The name (SSID) of the WiFi network. - --psk PSK Optional. The password for authenticating. + --psk PSK Optional. The password for authenticating. If specified, + it must be 8-63 characters in length. EOF } @@ -59,7 +60,7 @@ while (( "$#" > 0 )); do done readonly WIFI_COUNTRY readonly WIFI_SSID -readonly WIFI_PSK="${WIFI_PSK:-''}" +readonly WIFI_PSK="${WIFI_PSK:-}" if [[ -z "${WIFI_COUNTRY}" ]]; then >&2 echo 'Missing argument: COUNTRY' @@ -67,18 +68,23 @@ if [[ -z "${WIFI_COUNTRY}" ]]; then exit 1 fi -if [[ "$(echo -n "${WIFI_COUNTRY}" | wc --bytes)" != 2 ]]; then - >&2 echo 'Invalid argument: COUNTRY' - >&2 echo "Use the '--help' flag for more information" - exit 1 -fi - if [[ -z "${WIFI_SSID}" ]]; then >&2 echo 'Missing argument: SSID' >&2 echo "Use the '--help' flag for more information" exit 1 fi +# If a password is specified, it has to be 8-63 characters in length. +if [[ -n "${WIFI_PSK}" ]]; then + PSK_LENGTH="$(echo -n "${WIFI_PSK}" | wc --bytes)" + readonly PSK_LENGTH + if (( "${PSK_LENGTH}" < 8 || "${PSK_LENGTH}" > 63 )); then + >&2 echo 'Invalid argument: PSK' + >&2 echo "Use the '--help' flag for more information" + exit 1 + fi +fi + # Echo commands before executing them, by default to stderr. set -x @@ -89,7 +95,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" readonly SCRIPT_DIR # Remove any existing automated configuration. -readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" +readonly CONFIG_FILE='/etc/wpa_supplicant/wpa_supplicant.conf' "${SCRIPT_DIR}/strip-marker-sections" "${CONFIG_FILE}" # Write out the new configuration. @@ -98,11 +104,24 @@ readonly CONFIG_FILE="/etc/wpa_supplicant/wpa_supplicant.conf" { echo "${MARKER_START}" echo "country=${WIFI_COUNTRY}" - # Generate the "network" block of the config. The `wpa_passphrase` command - # hashes the WiFi password so that it is not persisted in clear text. - # However, the command still outputs the original password as a comment line, - # so we strip off that line (which starts with `#psk=`). - wpa_passphrase "${WIFI_SSID}" "${WIFI_PSK}" | sed '/^\t#psk=.*/d' + + # Generate the "network" block of the config. + # - If a password is specified, we use the `wpa_passphrase` command. This + # outputs a complete "network" block, and hashes the password instead of + # storing it in clear text. Note that it still includes the original + # password as comment line in the output, so we have to strip off that line + # (which starts with `#psk=`) + # - If no password is specified, we assemble the "network" block manually. In + # this case, we also have to set `key_mgmt=NONE` to denote an open network. + if [[ -n "${WIFI_PSK}" ]]; then + wpa_passphrase "${WIFI_SSID}" "${WIFI_PSK}" | sed '/^\t#psk=.*/d' + else + echo 'network={' + echo -e "\tssid=\"${WIFI_SSID}\"" + echo -e '\tkey_mgmt=NONE' + echo '}' + fi + echo "${MARKER_END}" } | sudo tee --append "${CONFIG_FILE}" > /dev/null From 7995bd4a0caaffa96969625a8371f185fef42a94 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 27 Jun 2024 15:02:01 +0200 Subject: [PATCH 09/32] Fix declaration of optional flags in help output --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index f9495ac36..8781e617e 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -7,7 +7,7 @@ set -e print_help() { cat < Date: Thu, 27 Jun 2024 15:05:07 +0200 Subject: [PATCH 10/32] Clarify purpose of script --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 8781e617e..73fd8f5e3 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -14,6 +14,12 @@ Enables a WiFi network connection. --ssid SSID The name (SSID) of the WiFi network. --psk PSK Optional. The password for authenticating. If specified, it must be 8-63 characters in length. + +Note: this script only stores the WiFi configuration and triggers its +activation, but it doesn't (and cannot) verify whether the device can actually +connect to the wireless network successfully. In that sense, enabling WiFi +means that the device will *attempt* to connect to the wireless network, and +otherwise fall back to the wired Ethernet connection. EOF } From dece804ee89336a26bb2e10c3cadfbc8dcf806bb Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 27 Jun 2024 15:05:23 +0200 Subject: [PATCH 11/32] =?UTF-8?q?Validate=20length=20of=20=E2=80=94country?= =?UTF-8?q?=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 73fd8f5e3..8054a2cdb 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -74,6 +74,15 @@ if [[ -z "${WIFI_COUNTRY}" ]]; then exit 1 fi +# According to ISO 3166-1 alpha-2, the country code has to contain 2 letters. +COUNTRY_LENGTH="$(echo -n "${WIFI_COUNTRY}" | wc --bytes)" +readonly COUNTRY_LENGTH +if (( "${COUNTRY_LENGTH}" != 2 )); then + >&2 echo 'Invalid argument: COUNTRY' + >&2 echo "Use the '--help' flag for more information" + exit 1 +fi + if [[ -z "${WIFI_SSID}" ]]; then >&2 echo 'Missing argument: SSID' >&2 echo "Use the '--help' flag for more information" From 49c18cd68403c09fc5e045bc0cb4df365e21540e Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 27 Jun 2024 15:05:42 +0200 Subject: [PATCH 12/32] Simplify procedure for writing to file --- debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi index 8054a2cdb..4b615560c 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi +++ b/debian-pkg/opt/tinypilot-privileged/scripts/enable-wifi @@ -138,7 +138,7 @@ readonly CONFIG_FILE='/etc/wpa_supplicant/wpa_supplicant.conf' fi echo "${MARKER_END}" -} | sudo tee --append "${CONFIG_FILE}" > /dev/null +} >> "${CONFIG_FILE}" # Effectuate changes. rfkill unblock wifi From 0933a3a4756e2fc6c722a4761911678c684159e9 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Thu, 27 Jun 2024 15:05:56 +0200 Subject: [PATCH 13/32] Reference real scripts in comment --- dev-scripts/mock-scripts/disable-wifi | 2 ++ dev-scripts/mock-scripts/enable-wifi | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev-scripts/mock-scripts/disable-wifi b/dev-scripts/mock-scripts/disable-wifi index a9bf588e2..636149d21 100755 --- a/dev-scripts/mock-scripts/disable-wifi +++ b/dev-scripts/mock-scripts/disable-wifi @@ -1 +1,3 @@ #!/bin/bash + +# Mock version of /opt/tinypilot-privileged/scripts/disable-wifi diff --git a/dev-scripts/mock-scripts/enable-wifi b/dev-scripts/mock-scripts/enable-wifi index a9bf588e2..f616e1f55 100755 --- a/dev-scripts/mock-scripts/enable-wifi +++ b/dev-scripts/mock-scripts/enable-wifi @@ -1 +1,3 @@ #!/bin/bash + +# Mock version of /opt/tinypilot-privileged/scripts/enable-wifi From c4feb1f5e21d8e2619ca3e3088fc0d4e26467da7 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 1 Jul 2024 22:46:41 +0200 Subject: [PATCH 14/32] Add script for printing marker section content --- .../scripts/print-marker-section | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100755 debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section new file mode 100755 index 000000000..77e5e2519 --- /dev/null +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section @@ -0,0 +1,89 @@ +#!/bin/bash +# +# Prints the content of a marker sections from a file. +# +# If the target file doesn’t contain marker sections, the script doesn’t output +# anything. +# If the target file contains unmatched/orphaned markers, this script fails. + +# We don’t use `set -x`, because it would output every single iteration of the +# while loop when iterating through the lines of the target file, and hence +# generate a lot of noise. + +# Exit on first failure. +set -e + +# Exit on unset variable. +set -u + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +readonly SCRIPT_DIR +# shellcheck source=lib/markers.sh +. "${SCRIPT_DIR}/lib/markers.sh" + +print_help() { + cat << EOF +Usage: ${0##*/} [--help] TARGET_FILE +Prints the content of a marker sections from a file. + TARGET_FILE Path to file with marker sections. + --help Display this help and exit. +EOF +} + +# Parse command-line arguments. +TARGET_FILE='' +while (( "$#" > 0 )); do + case "$1" in + --help) + print_help + exit + ;; + -*) + echo "Illegal option: $1" >&2 + exit 1 + ;; + *) + TARGET_FILE="$1" + shift + ;; + esac +done +readonly TARGET_FILE + +# Ensure target file is specified. +if [[ -z "${TARGET_FILE}" ]]; then + echo 'Input parameter missing: TARGET_FILE' >&2 + exit 1 +fi + +# Ensure target file exists and is a file. +if [[ ! -f "${TARGET_FILE}" ]]; then + echo "Not a file: ${TARGET_FILE}" >&2 + exit 1 +fi + +# Read the original file line by line, and print all lines that reside between +# the markers to stdout. +is_in_marker_section='false' +while IFS='' read -r line; do + if [[ "${line}" == "${MARKER_END}" ]]; then + if ! "${is_in_marker_section}"; then + echo 'Unmatched end marker' >&2 + exit 1 + fi + is_in_marker_section='false' + continue + fi + if [[ "${line}" == "${MARKER_START}" ]]; then + is_in_marker_section='true' + continue + fi + if "${is_in_marker_section}"; then + echo "${line}" + fi +done < "${TARGET_FILE}" + +if "${is_in_marker_section}"; then + echo 'Unmatched start marker' >&2 + exit 1 +fi From c80a86ce0d2cf76c99c723c2eef7ee7610a255c1 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 1 Jul 2024 22:47:56 +0200 Subject: [PATCH 15/32] Add mock script --- dev-scripts/mock-scripts/print-marker-section | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 dev-scripts/mock-scripts/print-marker-section diff --git a/dev-scripts/mock-scripts/print-marker-section b/dev-scripts/mock-scripts/print-marker-section new file mode 100755 index 000000000..4929d5d22 --- /dev/null +++ b/dev-scripts/mock-scripts/print-marker-section @@ -0,0 +1,3 @@ +#!/bin/bash + +# Mock version of /opt/tinypilot-privileged/scripts/print-marker-section From 393730533a7edee9ff0e53ea1353adb7f1774421 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 1 Jul 2024 23:04:08 +0200 Subject: [PATCH 16/32] Add tests --- ...t-marker-section => print-marker-sections} | 10 +- .../scripts/print-marker-sections.bats | 142 ++++++++++++++++++ ...t-marker-section => print-marker-sections} | 2 +- 3 files changed, 150 insertions(+), 4 deletions(-) rename debian-pkg/opt/tinypilot-privileged/scripts/{print-marker-section => print-marker-sections} (87%) create mode 100644 debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats rename dev-scripts/mock-scripts/{print-marker-section => print-marker-sections} (88%) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections similarity index 87% rename from debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section rename to debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections index 77e5e2519..ae6290d02 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-section +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections @@ -62,9 +62,10 @@ if [[ ! -f "${TARGET_FILE}" ]]; then exit 1 fi -# Read the original file line by line, and print all lines that reside between -# the markers to stdout. +# Read the original file line by line, and preserve all lines that reside +# between the start and end markers (i.e., the section contents). is_in_marker_section='false' +section_contents=() while IFS='' read -r line; do if [[ "${line}" == "${MARKER_END}" ]]; then if ! "${is_in_marker_section}"; then @@ -79,7 +80,7 @@ while IFS='' read -r line; do continue fi if "${is_in_marker_section}"; then - echo "${line}" + section_contents+=("${line}") fi done < "${TARGET_FILE}" @@ -87,3 +88,6 @@ if "${is_in_marker_section}"; then echo 'Unmatched start marker' >&2 exit 1 fi + +# Print all lines of the section contents. +printf "%s\n" "${section_contents[@]}" diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats new file mode 100644 index 000000000..4c66523b8 --- /dev/null +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats @@ -0,0 +1,142 @@ +#!/bin/bash + +{ + # Silence shellcheck for global bats variables. + # https://github.com/tiny-pilot/tinypilot/issues/1718 + # shellcheck disable=SC2154 + echo "${output}" "${status}" "${lines}" >/dev/null +} + +# Wrapper for invoking the script under test as command. +print-marker-sections() { + bash "${BATS_TEST_DIRNAME}/print-marker-sections" "$@" +} + +prints-help() { #@test + run print-marker-sections --help + expected_output="$(cat << EOF +Usage: print-marker-sections [--help] TARGET_FILE +Prints the content of a marker sections from a file. + TARGET_FILE Path to file with marker sections. + --help Display this help and exit. +EOF + )" + + [[ "${status}" == 0 ]] + [[ "${output}" == "${expected_output}" ]] +} + +rejects-missing-input-arg() { #@test + run print-marker-sections + + [[ "${status}" == 1 ]] + [[ "${output}" == 'Input parameter missing: TARGET_FILE' ]] +} + +rejects-illegal-flag() { #@test + run print-marker-sections --foo + + [[ "${status}" == 1 ]] + [[ "${output}" == 'Illegal option: --foo' ]] +} + +rejects-non-existing-file() { #@test + run print-marker-sections foo-file.txt + + [[ "${status}" == 1 ]] + [[ "${output}" == 'Not a file: foo-file.txt' ]] +} + +rejects-non-file() { #@test + tmp_dir="$(mktemp --directory)" + run print-marker-sections "${tmp_dir}" + + [[ "${status}" == 1 ]] + [[ "${output}" == "Not a file: ${tmp_dir}" ]] +} + +empty-output-if-file-has-no-markers() { #@test + target_file="$(mktemp)" + cat << EOF > "${target_file}" +line 1 +line 2 +line 3 +EOF + run print-marker-sections "${target_file}" + + [[ "${status}" == 0 ]] + [[ "${output}" == "" ]] +} + +prints-marker-section() { #@test + target_file="$(mktemp)" + cat << EOF > "${target_file}" +some line +some other line +# --- AUTOGENERATED BY TINYPILOT - START --- +to be +printed +# --- AUTOGENERATED BY TINYPILOT - END --- +final line +EOF + run print-marker-sections "${target_file}" + expected_output="$(cat << EOF +to be +printed +EOF + )" + + [[ "${status}" == 0 ]] + [[ "${output}" == "${expected_output}" ]] +} + +prints-multiple-marker-sections() { #@test + target_file="$(mktemp)" + cat << EOF > "${target_file}" +some line +some other line +# --- AUTOGENERATED BY TINYPILOT - START --- +to be +# --- AUTOGENERATED BY TINYPILOT - END --- +intermediate line +# --- AUTOGENERATED BY TINYPILOT - START --- +printed +# --- AUTOGENERATED BY TINYPILOT - END --- +final line +EOF + run print-marker-sections "${target_file}" + expected_output="$(cat << EOF +to be +printed +EOF + )" + + [[ "${status}" == 0 ]] + [[ "${output}" == "${expected_output}" ]] +} + +fails-for-unmatched-start-marker() { #@test + target_file="$(mktemp)" + cat << EOF > "${target_file}" +some line +# --- AUTOGENERATED BY TINYPILOT - START --- +to be printed +EOF + run print-marker-sections "${target_file}" + + [[ "${status}" == 1 ]] + [[ "${output}" == "Unmatched start marker" ]] +} + +fails-for-unmatched-end-marker() { #@test + target_file="$(mktemp)" + cat << EOF > "${target_file}" +some line +# --- AUTOGENERATED BY TINYPILOT - END --- +final line +EOF + run print-marker-sections "${target_file}" + + [[ "${status}" == 1 ]] + [[ "${output}" == 'Unmatched end marker' ]] +} diff --git a/dev-scripts/mock-scripts/print-marker-section b/dev-scripts/mock-scripts/print-marker-sections similarity index 88% rename from dev-scripts/mock-scripts/print-marker-section rename to dev-scripts/mock-scripts/print-marker-sections index 4929d5d22..947e0f489 100755 --- a/dev-scripts/mock-scripts/print-marker-section +++ b/dev-scripts/mock-scripts/print-marker-sections @@ -1,3 +1,3 @@ #!/bin/bash -# Mock version of /opt/tinypilot-privileged/scripts/print-marker-section +# Mock version of /opt/tinypilot-privileged/scripts/print-marker-sections From 565a7c946dc41d0ae24d6fa15d2dfe225aa96b2c Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 1 Jul 2024 22:48:52 +0200 Subject: [PATCH 17/32] Add backend endpoints and logic --- app/api.py | 88 ++++++++++ app/network.py | 123 +++++++++++++ app/request_parsers/errors.py | 4 + app/request_parsers/network.py | 48 ++++++ app/request_parsers/network_test.py | 161 ++++++++++++++++++ debian-pkg/etc/sudoers.d/tinypilot | 1 + .../mock-scripts/print-marker-sections | 10 ++ 7 files changed, 435 insertions(+) create mode 100644 app/network.py create mode 100644 app/request_parsers/network.py create mode 100644 app/request_parsers/network_test.py diff --git a/app/api.py b/app/api.py index 503ec8279..b4405a60e 100644 --- a/app/api.py +++ b/app/api.py @@ -6,8 +6,10 @@ import hostname import json_response import local_system +import network import request_parsers.errors import request_parsers.hostname +import request_parsers.network import request_parsers.paste import request_parsers.video_settings import update.launcher @@ -199,6 +201,92 @@ def hostname_set(): return json_response.error(e), 500 +@api_blueprint.route('/network', methods=['GET']) +def network_status(): + """Returns the current network status (i.e., which interfaces are active). + + Returns: + On success, a JSON data structure with the following properties: + ethernet: bool. + wifi: bool + + Example: + { + "ethernet": true, + "wifi": false + } + """ + status = network.status() + return json_response.success({ + 'ethernet': status.ethernet, + 'wifi': status.wifi, + }) + + +@api_blueprint.route('/network/wifi', methods=['GET']) +def network_wifi_get(): + """Returns the current WiFi settings, if present. + + Returns: + On success, a JSON data structure with the following properties: + countryCode: string. + ssid: string. + + Example: + { + "countryCode": "US", + "ssid": "my-network" + } + + Returns an error object on failure. + """ + wifi_settings = network.determine_wifi_settings() + return json_response.success({ + 'countryCode': wifi_settings.country_code, + 'ssid': wifi_settings.ssid, + }) + + +@api_blueprint.route('/network/wifi', methods=['PUT']) +def network_wifi_enable(): + """Enables a wireless network connection. + + Expects a JSON data structure in the request body that contains a country + code, an SSID, and optionally a password; all as strings. Example: + { + "countryCode": "US", + "ssid": "my-network", + "psk": "sup3r-s3cr3t!" + } + + Returns: + Empty response on success, error object otherwise. + """ + try: + wifi_settings = request_parsers.network.parse_wifi_settings( + flask.request) + network.enable_wifi(wifi_settings) + return json_response.success() + except request_parsers.errors.Error as e: + return json_response.error(e), 400 + except network.Error as e: + return json_response.error(e), 500 + + +@api_blueprint.route('/network/wifi', methods=['DELETE']) +def network_wifi_disable(): + """Disables the WiFi network connection. + + Returns: + Empty response on success, error object otherwise. + """ + try: + network.disable_wifi() + return json_response.success() + except network.Error as e: + return json_response.error(e), 500 + + @api_blueprint.route('/status', methods=['GET']) def status_get(): """Checks the status of TinyPilot. diff --git a/app/network.py b/app/network.py new file mode 100644 index 000000000..d034c9cf1 --- /dev/null +++ b/app/network.py @@ -0,0 +1,123 @@ +import dataclasses +import re +import subprocess + +_CONFIG_FILE = '/etc/wpa_supplicant/wpa_supplicant.conf' +_WIFI_COUNTRY_PATTERN = re.compile(r'^\s*country=(.+)$') +_WIFI_SSID_PATTERN = re.compile(r'^\s*ssid="(.+)"$') + + +class Error(Exception): + pass + + +class NetworkError(Error): + pass + + +@dataclasses.dataclass +class NetworkStatus: + ethernet: bool + wifi: bool + + +@dataclasses.dataclass +class WiFiSettings: + country_code: str + ssid: str + psk: str # Optional. + + +def status(): + """Checks the connectivity of the network interfaces. + + Returns: + NetworkStatus + """ + network_status = NetworkStatus(False, False) + try: + with open('/sys/class/net/eth0/operstate', encoding='utf-8') as file: + eth0 = file.read().strip() + network_status.ethernet = eth0 == 'up' + except OSError: + pass # We treat this as if the interface was down altogether. + try: + with open('/sys/class/net/wlan0/operstate', encoding='utf-8') as file: + wlan0 = file.read().strip() + network_status.wifi = wlan0 == 'up' + except OSError: + pass # We treat this as if the interface was down altogether. + return network_status + + +def determine_wifi_settings(): + """Determines the current WiFi settings (if set). + + Returns: + WiFiSettings: if the `ssid` and `country_code` attributes are `None`, + there is no WiFi configuration present. The `psk` property is + always `None` for security reasons. + """ + try: + config_lines = subprocess.check_output([ + 'sudo', '/opt/tinypilot-privileged/scripts/print-marker-sections', + '/etc/wpa_supplicant/wpa_supplicant.conf' + ], + stderr=subprocess.STDOUT, + universal_newlines=True) + except subprocess.CalledProcessError as e: + raise NetworkError(str(e.output).strip()) from e + + wifi = WiFiSettings(None, None, None) + for line in config_lines.split('\n'): + match_country = _WIFI_COUNTRY_PATTERN.search(line.strip()) + if match_country: + wifi.country_code = match_country.group(1) + continue + match_ssid = _WIFI_SSID_PATTERN.search(line.strip()) + if match_ssid: + wifi.ssid = match_ssid.group(1) + continue + return wifi + + +def enable_wifi(wifi_settings): + """Enables a wireless network connection. + + Note: The function is executed in a "fire and forget" manner, to prevent + the HTTP request from failing erratically due to a network interruption. + + Args: + wifi_settings: The new, desired settings. + + Raises: + NetworkError + """ + args = [ + 'sudo', '/opt/tinypilot-privileged/scripts/enable-wifi', '--country', + wifi_settings.country_code, '--ssid', wifi_settings.ssid + ] + if wifi_settings.psk: + args.extend(['--psk', wifi_settings.psk]) + try: + return subprocess.Popen(args) + except subprocess.CalledProcessError as e: + raise NetworkError(str(e.output).strip()) from e + + +def disable_wifi(): + """Removes the WiFi settings and disables the wireless connection. + + Note: The function is executed in a "fire and forget" manner, to prevent + the HTTP request from failing erratically due to a network interruption. + + Raises: + NetworkError + """ + try: + return subprocess.Popen([ + 'sudo', + '/opt/tinypilot-privileged/scripts/disable-wifi', + ]) + except subprocess.CalledProcessError as e: + raise NetworkError(str(e.output).strip()) from e diff --git a/app/request_parsers/errors.py b/app/request_parsers/errors.py index 30c7080a7..05c7d3315 100644 --- a/app/request_parsers/errors.py +++ b/app/request_parsers/errors.py @@ -14,6 +14,10 @@ class InvalidHostnameError(Error): code = 'INVALID_HOSTNAME' +class InvalidWifiSettings(Error): + code = 'INVALID_WIFI_SETTINGS' + + class InvalidVideoSettingError(Error): pass diff --git a/app/request_parsers/network.py b/app/request_parsers/network.py new file mode 100644 index 000000000..6816c8073 --- /dev/null +++ b/app/request_parsers/network.py @@ -0,0 +1,48 @@ +import network +from request_parsers import errors +from request_parsers import json + + +def parse_wifi_settings(request): + """Parses WiFi settings from the request. + + Returns: + WiFiSettings + + Raises: + InvalidWifiSettings + """ + # pylint: disable=unbalanced-tuple-unpacking + ( + country_code, + ssid, + psk, + ) = json.parse_json_body(request, + required_fields=['countryCode', 'ssid', 'psk']) + + if not isinstance(country_code, str): + raise errors.InvalidWifiSettings('The country code is not a string.') + if len(country_code) != 2: + # The ISO 3166-1 alpha-2 standard theoretically allows any 2-digit + # combination, so we don’t have to eagerly restrict this. + raise errors.InvalidWifiSettings( + 'The country code must consist of 2 characters.') + if not country_code.isalpha(): + raise errors.InvalidWifiSettings( + 'The country code must only contain letters.') + + if not isinstance(ssid, str): + raise errors.InvalidWifiSettings('The SSID is not a string.') + if len(ssid) == 0: + raise errors.InvalidWifiSettings('The SSID cannot be empty.') + + if psk is not None: + if not isinstance(psk, str): + raise errors.InvalidWifiSettings('The password is not a string.') + if len(psk) < 8 or len(psk) > 63: + # Note: this constraint is imposed by the WPA2 standard. We need + # to enforce this to prevent underlying commands from failing. + raise errors.InvalidWifiSettings( + 'The password must consist of 8-63 characters.') + + return network.WiFiSettings(country_code.upper(), ssid, psk) diff --git a/app/request_parsers/network_test.py b/app/request_parsers/network_test.py new file mode 100644 index 000000000..6996a221a --- /dev/null +++ b/app/request_parsers/network_test.py @@ -0,0 +1,161 @@ +import unittest +from unittest import mock + +from request_parsers import errors +from request_parsers import network + + +def make_mock_request(json_data): + mock_request = mock.Mock() + mock_request.get_json.return_value = json_data + return mock_request + + +class NetworkValidationTest(unittest.TestCase): + + def test_accepts_valid_wifi_credentials_with_psk(self): + wifi = network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + self.assertEqual('US', wifi.country_code) + self.assertEqual('my-network', wifi.ssid) + self.assertEqual('s3cr3t!!!', wifi.psk) + + def test_accepts_valid_wifi_credentials_without_psk(self): + wifi = network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'DE', + 'ssid': 'SomeWiFiHotspot_123', + 'psk': None + })) + self.assertEqual('DE', wifi.country_code) + self.assertEqual('SomeWiFiHotspot_123', wifi.ssid) + self.assertEqual(None, wifi.psk) + + def test_normalizes_country_code_to_uppercase(self): + wifi = network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'us', + 'ssid': 'SomeWiFiHotspot_123', + 'psk': None + })) + self.assertEqual('US', wifi.country_code) + + def test_rejects_absent_required_fields(self): + with self.assertRaises(errors.MissingFieldError): + network.parse_wifi_settings( + make_mock_request({ + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'psk': 's3cr3t!!!' + })) + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': 'my-network' + })) + + def test_rejects_country_code_with_incorrect_type(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 12, + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': None, + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + + def test_rejects_country_code_with_incorrect_length(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'A', + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'ABC', + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + + def test_rejects_country_code_non_alpha(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': '12', + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'A*', + 'ssid': 'my-network', + 'psk': 's3cr3t!!!' + })) + + def test_rejects_ssid_with_incorrect_type(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': 123, + 'psk': 's3cr3t!!!' + })) + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': None, + 'psk': 's3cr3t!!!' + })) + + def test_rejects_psk_with_incorrect_type(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': 'my-network', + 'psk': 123, + })) + + def test_rejects_ssid_with_incorrect_length(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': '', + 'psk': 's3cr3t!!!' + })) + + def test_rejects_psk_with_incorrect_length(self): + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': 'Hotspot123', + 'psk': 'x' * 7 + })) + with self.assertRaises(errors.InvalidWifiSettings): + network.parse_wifi_settings( + make_mock_request({ + 'countryCode': 'US', + 'ssid': 'Hotspot123', + 'psk': 'x' * 64 + })) diff --git a/debian-pkg/etc/sudoers.d/tinypilot b/debian-pkg/etc/sudoers.d/tinypilot index 88ecea9d3..8ef302ed8 100644 --- a/debian-pkg/etc/sudoers.d/tinypilot +++ b/debian-pkg/etc/sudoers.d/tinypilot @@ -3,6 +3,7 @@ tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/collect-debug-lo tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/configure-janus tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/disable-wifi tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/enable-wifi +tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/print-marker-sections /etc/wpa_supplicant/wpa_supplicant.conf tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/read-update-log tinypilot ALL=(ALL) NOPASSWD: /opt/tinypilot-privileged/scripts/update tinypilot ALL=(ALL) NOPASSWD: /sbin/shutdown diff --git a/dev-scripts/mock-scripts/print-marker-sections b/dev-scripts/mock-scripts/print-marker-sections index 947e0f489..e3e073952 100755 --- a/dev-scripts/mock-scripts/print-marker-sections +++ b/dev-scripts/mock-scripts/print-marker-sections @@ -1,3 +1,13 @@ #!/bin/bash # Mock version of /opt/tinypilot-privileged/scripts/print-marker-sections + +if [[ "$1" == '/etc/wpa_supplicant/wpa_supplicant.conf' ]]; then + cat << EOF +country=US +network={ + ssid="TinyPilot Office" + psk=4efa961963891a4adb1f96ed645e96cd096cfb9bf5af086cd46c21c85b5bb98b +} +EOF +fi From e9f1f01f24463e3f8f26ade9ee7012ad6a35217a Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 1 Jul 2024 23:14:48 +0200 Subject: [PATCH 18/32] Fix docstring --- .../opt/tinypilot-privileged/scripts/print-marker-sections | 4 ++-- .../tinypilot-privileged/scripts/print-marker-sections.bats | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections index ae6290d02..71a142966 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections @@ -1,6 +1,6 @@ #!/bin/bash # -# Prints the content of a marker sections from a file. +# Prints the contents of marker sections from a file. # # If the target file doesn’t contain marker sections, the script doesn’t output # anything. @@ -24,7 +24,7 @@ readonly SCRIPT_DIR print_help() { cat << EOF Usage: ${0##*/} [--help] TARGET_FILE -Prints the content of a marker sections from a file. +Prints the contents of marker sections from a file. TARGET_FILE Path to file with marker sections. --help Display this help and exit. EOF diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats index 4c66523b8..a1eaeb05d 100644 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats @@ -16,7 +16,7 @@ prints-help() { #@test run print-marker-sections --help expected_output="$(cat << EOF Usage: print-marker-sections [--help] TARGET_FILE -Prints the content of a marker sections from a file. +Prints the contents of marker sections from a file. TARGET_FILE Path to file with marker sections. --help Display this help and exit. EOF From 766b4eb710aa0755271319a241df1337634bea9d Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Mon, 1 Jul 2024 23:17:35 +0200 Subject: [PATCH 19/32] Add comment --- app/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/network.py b/app/network.py index d034c9cf1..e97347127 100644 --- a/app/network.py +++ b/app/network.py @@ -59,6 +59,8 @@ def determine_wifi_settings(): always `None` for security reasons. """ try: + # We cannot read the wpa_supplicant.conf file directly, because it is + # owned by the root user. config_lines = subprocess.check_output([ 'sudo', '/opt/tinypilot-privileged/scripts/print-marker-sections', '/etc/wpa_supplicant/wpa_supplicant.conf' From f9352bcc2a76a4da148f545448d74d526a4edf35 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Tue, 2 Jul 2024 20:26:26 +0200 Subject: [PATCH 20/32] Phrasing of error message --- .../opt/tinypilot-privileged/scripts/print-marker-sections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections index 71a142966..bafbedb34 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections @@ -39,7 +39,7 @@ while (( "$#" > 0 )); do exit ;; -*) - echo "Illegal option: $1" >&2 + >&2 echo "Unknown flag: $1" exit 1 ;; *) From e03c77700230466e25ce3e4bf6bd5fa6f8d14485 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Tue, 2 Jul 2024 20:26:42 +0200 Subject: [PATCH 21/32] =?UTF-8?q?Direct=20to=20=E2=80=94help=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opt/tinypilot-privileged/scripts/print-marker-sections | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections index bafbedb34..88bfecfba 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections @@ -40,6 +40,7 @@ while (( "$#" > 0 )); do ;; -*) >&2 echo "Unknown flag: $1" + >&2 echo "Use the '--help' flag for more information" exit 1 ;; *) @@ -53,12 +54,14 @@ readonly TARGET_FILE # Ensure target file is specified. if [[ -z "${TARGET_FILE}" ]]; then echo 'Input parameter missing: TARGET_FILE' >&2 + >&2 echo "Use the '--help' flag for more information" exit 1 fi # Ensure target file exists and is a file. if [[ ! -f "${TARGET_FILE}" ]]; then echo "Not a file: ${TARGET_FILE}" >&2 + >&2 echo "Use the '--help' flag for more information" exit 1 fi From 25591db19675ffaffead02a21ae4446c137636d6 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Tue, 2 Jul 2024 20:27:08 +0200 Subject: [PATCH 22/32] Prepend >&2 --- .../tinypilot-privileged/scripts/print-marker-sections | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections index 88bfecfba..4b14e0b25 100755 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections @@ -53,14 +53,14 @@ readonly TARGET_FILE # Ensure target file is specified. if [[ -z "${TARGET_FILE}" ]]; then - echo 'Input parameter missing: TARGET_FILE' >&2 + >&2 echo 'Input parameter missing: TARGET_FILE' >&2 echo "Use the '--help' flag for more information" exit 1 fi # Ensure target file exists and is a file. if [[ ! -f "${TARGET_FILE}" ]]; then - echo "Not a file: ${TARGET_FILE}" >&2 + >&2 echo "Not a file: ${TARGET_FILE}" >&2 echo "Use the '--help' flag for more information" exit 1 fi @@ -72,7 +72,7 @@ section_contents=() while IFS='' read -r line; do if [[ "${line}" == "${MARKER_END}" ]]; then if ! "${is_in_marker_section}"; then - echo 'Unmatched end marker' >&2 + >&2 echo 'Unmatched end marker' exit 1 fi is_in_marker_section='false' @@ -88,7 +88,7 @@ while IFS='' read -r line; do done < "${TARGET_FILE}" if "${is_in_marker_section}"; then - echo 'Unmatched start marker' >&2 + >&2 echo 'Unmatched start marker' exit 1 fi From b6316319410a789832e2b5db472bcc662546a09f Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Tue, 2 Jul 2024 20:36:45 +0200 Subject: [PATCH 23/32] Fix tests --- .../scripts/print-marker-sections.bats | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats index a1eaeb05d..a7df658d3 100644 --- a/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats +++ b/debian-pkg/opt/tinypilot-privileged/scripts/print-marker-sections.bats @@ -28,31 +28,51 @@ EOF rejects-missing-input-arg() { #@test run print-marker-sections + expected_output="$(cat << EOF +Input parameter missing: TARGET_FILE +Use the '--help' flag for more information +EOF + )" [[ "${status}" == 1 ]] - [[ "${output}" == 'Input parameter missing: TARGET_FILE' ]] + [[ "${output}" == "${expected_output}" ]] } rejects-illegal-flag() { #@test run print-marker-sections --foo + expected_output="$(cat << EOF +Unknown flag: --foo +Use the '--help' flag for more information +EOF + )" [[ "${status}" == 1 ]] - [[ "${output}" == 'Illegal option: --foo' ]] + [[ "${output}" == "${expected_output}" ]] } rejects-non-existing-file() { #@test run print-marker-sections foo-file.txt + expected_output="$(cat << EOF +Not a file: foo-file.txt +Use the '--help' flag for more information +EOF + )" [[ "${status}" == 1 ]] - [[ "${output}" == 'Not a file: foo-file.txt' ]] + [[ "${output}" == "${expected_output}" ]] } rejects-non-file() { #@test tmp_dir="$(mktemp --directory)" run print-marker-sections "${tmp_dir}" + expected_output="$(cat << EOF +Not a file: ${tmp_dir} +Use the '--help' flag for more information +EOF + )" [[ "${status}" == 1 ]] - [[ "${output}" == "Not a file: ${tmp_dir}" ]] + [[ "${output}" == "${expected_output}" ]] } empty-output-if-file-has-no-markers() { #@test From 232e4462addc111cd31c9704392bd9238e584594 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:38:44 +0200 Subject: [PATCH 24/32] Add missing `assertRaises` --- app/request_parsers/network_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/request_parsers/network_test.py b/app/request_parsers/network_test.py index 6996a221a..caec53d7e 100644 --- a/app/request_parsers/network_test.py +++ b/app/request_parsers/network_test.py @@ -51,11 +51,13 @@ def test_rejects_absent_required_fields(self): 'ssid': 'my-network', 'psk': 's3cr3t!!!' })) + with self.assertRaises(errors.MissingFieldError): network.parse_wifi_settings( make_mock_request({ 'countryCode': 'US', 'psk': 's3cr3t!!!' })) + with self.assertRaises(errors.MissingFieldError): network.parse_wifi_settings( make_mock_request({ 'countryCode': 'US', From 7ee0a820ddce71db43b45e29b2e09a9287d4503c Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:40:10 +0200 Subject: [PATCH 25/32] Rephrase error message --- app/request_parsers/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/request_parsers/network.py b/app/request_parsers/network.py index 6816c8073..848a525e4 100644 --- a/app/request_parsers/network.py +++ b/app/request_parsers/network.py @@ -21,7 +21,7 @@ def parse_wifi_settings(request): required_fields=['countryCode', 'ssid', 'psk']) if not isinstance(country_code, str): - raise errors.InvalidWifiSettings('The country code is not a string.') + raise errors.InvalidWifiSettings('The country code must be a string.') if len(country_code) != 2: # The ISO 3166-1 alpha-2 standard theoretically allows any 2-digit # combination, so we don’t have to eagerly restrict this. @@ -32,13 +32,13 @@ def parse_wifi_settings(request): 'The country code must only contain letters.') if not isinstance(ssid, str): - raise errors.InvalidWifiSettings('The SSID is not a string.') + raise errors.InvalidWifiSettings('The SSID must be a string.') if len(ssid) == 0: raise errors.InvalidWifiSettings('The SSID cannot be empty.') if psk is not None: if not isinstance(psk, str): - raise errors.InvalidWifiSettings('The password is not a string.') + raise errors.InvalidWifiSettings('The password must be a string.') if len(psk) < 8 or len(psk) > 63: # Note: this constraint is imposed by the WPA2 standard. We need # to enforce this to prevent underlying commands from failing. From e72f8883779b834c05a399e3bd6b280536a01bcf Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:40:35 +0200 Subject: [PATCH 26/32] Remove unused variabel --- app/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/network.py b/app/network.py index e97347127..a2dd17769 100644 --- a/app/network.py +++ b/app/network.py @@ -2,7 +2,6 @@ import re import subprocess -_CONFIG_FILE = '/etc/wpa_supplicant/wpa_supplicant.conf' _WIFI_COUNTRY_PATTERN = re.compile(r'^\s*country=(.+)$') _WIFI_SSID_PATTERN = re.compile(r'^\s*ssid="(.+)"$') From 1e3f47426f42ea16b8753badc9742581a6ff0a3c Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:45:04 +0200 Subject: [PATCH 27/32] Omit unused return --- app/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/network.py b/app/network.py index a2dd17769..7e784479b 100644 --- a/app/network.py +++ b/app/network.py @@ -101,7 +101,7 @@ def enable_wifi(wifi_settings): if wifi_settings.psk: args.extend(['--psk', wifi_settings.psk]) try: - return subprocess.Popen(args) + subprocess.Popen(args) except subprocess.CalledProcessError as e: raise NetworkError(str(e.output).strip()) from e @@ -116,7 +116,7 @@ def disable_wifi(): NetworkError """ try: - return subprocess.Popen([ + subprocess.Popen([ 'sudo', '/opt/tinypilot-privileged/scripts/disable-wifi', ]) From c81330541e8ad9901ec3868fd46421afa84667f2 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:45:17 +0200 Subject: [PATCH 28/32] Declare arg type in docstring --- app/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/network.py b/app/network.py index 7e784479b..1118f7102 100644 --- a/app/network.py +++ b/app/network.py @@ -89,7 +89,7 @@ def enable_wifi(wifi_settings): the HTTP request from failing erratically due to a network interruption. Args: - wifi_settings: The new, desired settings. + wifi_settings: The new, desired settings (of type WiFiSettings) Raises: NetworkError From b093d97411af700168fd50fc6ce78149d36952d6 Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:46:03 +0200 Subject: [PATCH 29/32] =?UTF-8?q?Use=20`splitlines`=20instead=20of=20`spli?= =?UTF-8?q?t(=E2=80=98\n=E2=80=99)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/network.py b/app/network.py index 1118f7102..f6dc38625 100644 --- a/app/network.py +++ b/app/network.py @@ -70,7 +70,7 @@ def determine_wifi_settings(): raise NetworkError(str(e.output).strip()) from e wifi = WiFiSettings(None, None, None) - for line in config_lines.split('\n'): + for line in config_lines.splitlines(): match_country = _WIFI_COUNTRY_PATTERN.search(line.strip()) if match_country: wifi.country_code = match_country.group(1) From 0470c5f79005dea09e7cdeec5ad9be0e8bb6ff8d Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:47:43 +0200 Subject: [PATCH 30/32] Simplify camelcase --- app/network.py | 8 ++++---- app/request_parsers/network.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/network.py b/app/network.py index f6dc38625..ea159474b 100644 --- a/app/network.py +++ b/app/network.py @@ -21,7 +21,7 @@ class NetworkStatus: @dataclasses.dataclass -class WiFiSettings: +class WifiSettings: country_code: str ssid: str psk: str # Optional. @@ -53,7 +53,7 @@ def determine_wifi_settings(): """Determines the current WiFi settings (if set). Returns: - WiFiSettings: if the `ssid` and `country_code` attributes are `None`, + WifiSettings: if the `ssid` and `country_code` attributes are `None`, there is no WiFi configuration present. The `psk` property is always `None` for security reasons. """ @@ -69,7 +69,7 @@ def determine_wifi_settings(): except subprocess.CalledProcessError as e: raise NetworkError(str(e.output).strip()) from e - wifi = WiFiSettings(None, None, None) + wifi = WifiSettings(None, None, None) for line in config_lines.splitlines(): match_country = _WIFI_COUNTRY_PATTERN.search(line.strip()) if match_country: @@ -89,7 +89,7 @@ def enable_wifi(wifi_settings): the HTTP request from failing erratically due to a network interruption. Args: - wifi_settings: The new, desired settings (of type WiFiSettings) + wifi_settings: The new, desired settings (of type WifiSettings) Raises: NetworkError diff --git a/app/request_parsers/network.py b/app/request_parsers/network.py index 848a525e4..bf22baa94 100644 --- a/app/request_parsers/network.py +++ b/app/request_parsers/network.py @@ -7,7 +7,7 @@ def parse_wifi_settings(request): """Parses WiFi settings from the request. Returns: - WiFiSettings + WifiSettings Raises: InvalidWifiSettings @@ -45,4 +45,4 @@ def parse_wifi_settings(request): raise errors.InvalidWifiSettings( 'The password must consist of 8-63 characters.') - return network.WiFiSettings(country_code.upper(), ssid, psk) + return network.WifiSettings(country_code.upper(), ssid, psk) From 49bbaeb7fcd8098f171501e5bf0ae51c49ce21eb Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:52:35 +0200 Subject: [PATCH 31/32] Use more explicit HTTP paths --- app/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/api.py b/app/api.py index b4405a60e..70b5de9a8 100644 --- a/app/api.py +++ b/app/api.py @@ -201,7 +201,7 @@ def hostname_set(): return json_response.error(e), 500 -@api_blueprint.route('/network', methods=['GET']) +@api_blueprint.route('/network/status', methods=['GET']) def network_status(): """Returns the current network status (i.e., which interfaces are active). @@ -223,7 +223,7 @@ def network_status(): }) -@api_blueprint.route('/network/wifi', methods=['GET']) +@api_blueprint.route('/network/settings/wifi', methods=['GET']) def network_wifi_get(): """Returns the current WiFi settings, if present. @@ -247,7 +247,7 @@ def network_wifi_get(): }) -@api_blueprint.route('/network/wifi', methods=['PUT']) +@api_blueprint.route('/network/settings/wifi', methods=['PUT']) def network_wifi_enable(): """Enables a wireless network connection. @@ -273,7 +273,7 @@ def network_wifi_enable(): return json_response.error(e), 500 -@api_blueprint.route('/network/wifi', methods=['DELETE']) +@api_blueprint.route('/network/settings/wifi', methods=['DELETE']) def network_wifi_disable(): """Disables the WiFi network connection. From ac397d603b71016cf23ca01e6810093181fa07cb Mon Sep 17 00:00:00 2001 From: Jan Heuermann Date: Wed, 3 Jul 2024 14:57:13 +0200 Subject: [PATCH 32/32] Ignore pylint warning --- app/network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/network.py b/app/network.py index ea159474b..75f404e32 100644 --- a/app/network.py +++ b/app/network.py @@ -101,6 +101,8 @@ def enable_wifi(wifi_settings): if wifi_settings.psk: args.extend(['--psk', wifi_settings.psk]) try: + # Ignore pylint since we're not managing the child process. + # pylint: disable=consider-using-with subprocess.Popen(args) except subprocess.CalledProcessError as e: raise NetworkError(str(e.output).strip()) from e @@ -116,6 +118,8 @@ def disable_wifi(): NetworkError """ try: + # Ignore pylint since we're not managing the child process. + # pylint: disable=consider-using-with subprocess.Popen([ 'sudo', '/opt/tinypilot-privileged/scripts/disable-wifi',