Stop world-writable browser policy directories
Chromium managed policy is mandatory for every profile. World-writable dirs let any local uid plant policy, including force-installed extensions. Write goes through the omarchy-browser-policy group at 2775 so theme colour still works without other-write.
This commit is contained in:
+13
-17
@@ -6,9 +6,12 @@
|
||||
|
||||
set -e
|
||||
|
||||
setup_policy_directory() {
|
||||
sudo mkdir -p "$1"
|
||||
sudo chmod a+rw "$1"
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
setup_chromium_policy_directory() {
|
||||
browser_policy_setup_group
|
||||
browser_policy_grant_user "${USER:-$(id -un)}"
|
||||
browser_policy_setup_dir "$1"
|
||||
}
|
||||
|
||||
announce_browser_installed() {
|
||||
@@ -23,13 +26,6 @@ copy_chromium_flags() {
|
||||
omarchy-install-chromium-ytdlp
|
||||
}
|
||||
|
||||
setup_firefox_preferences() {
|
||||
local distribution_dir="$1"
|
||||
|
||||
setup_policy_directory "$distribution_dir"
|
||||
sudo cp -f "$OMARCHY_PATH/default/firefox/policies.json" "$distribution_dir/policies.json"
|
||||
}
|
||||
|
||||
setup_firefox_wayland() {
|
||||
mkdir -p ~/.config/environment.d
|
||||
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/omarchy-firefox-wayland.conf
|
||||
@@ -40,7 +36,7 @@ chromium)
|
||||
echo "Installing Chromium..."
|
||||
omarchy-pkg-add chromium
|
||||
|
||||
setup_policy_directory /etc/chromium/policies/managed
|
||||
setup_chromium_policy_directory /etc/chromium/policies/managed
|
||||
copy_chromium_flags ~/.config/chromium-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chromium"
|
||||
@@ -49,7 +45,7 @@ chrome)
|
||||
echo "Installing Chrome..."
|
||||
omarchy-pkg-aur-add google-chrome || exit 1
|
||||
|
||||
setup_policy_directory /etc/opt/chrome/policies/managed
|
||||
setup_chromium_policy_directory /etc/opt/chrome/policies/managed
|
||||
copy_chromium_flags ~/.config/chrome-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chrome"
|
||||
@@ -58,7 +54,7 @@ edge)
|
||||
echo "Installing Edge..."
|
||||
omarchy-pkg-aur-add microsoft-edge-stable-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/opt/edge/policies/managed
|
||||
setup_chromium_policy_directory /etc/opt/edge/policies/managed
|
||||
copy_chromium_flags ~/.config/microsoft-edge-stable-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Edge"
|
||||
@@ -67,7 +63,7 @@ brave)
|
||||
echo "Installing Brave..."
|
||||
omarchy-pkg-aur-add brave-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/brave/policies/managed
|
||||
setup_chromium_policy_directory /etc/brave/policies/managed
|
||||
copy_chromium_flags ~/.config/brave-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Brave"
|
||||
@@ -76,7 +72,7 @@ brave-origin)
|
||||
echo "Installing Brave Origin..."
|
||||
omarchy-pkg-aur-add brave-origin-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/brave/policies/managed
|
||||
setup_chromium_policy_directory /etc/brave/policies/managed
|
||||
copy_chromium_flags ~/.config/brave-origin-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Brave Origin"
|
||||
@@ -85,7 +81,7 @@ firefox)
|
||||
echo "Installing Firefox..."
|
||||
omarchy-pkg-add firefox || exit 1
|
||||
|
||||
setup_firefox_preferences /usr/lib/firefox/distribution
|
||||
browser_policy_setup_firefox_distribution /usr/lib/firefox/distribution
|
||||
setup_firefox_wayland
|
||||
announce_browser_installed "Firefox"
|
||||
;;
|
||||
@@ -93,7 +89,7 @@ zen)
|
||||
echo "Installing Zen..."
|
||||
omarchy-pkg-aur-add zen-browser-bin || exit 1
|
||||
|
||||
setup_firefox_preferences /opt/zen-browser/distribution
|
||||
browser_policy_setup_firefox_distribution /opt/zen-browser/distribution
|
||||
setup_firefox_wayland
|
||||
announce_browser_installed "Zen"
|
||||
;;
|
||||
|
||||
@@ -742,6 +742,15 @@ create_user() {
|
||||
# for specific commands), and a duplicate grant is harmless.
|
||||
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
|
||||
chmod 440 /etc/sudoers.d/00-omarchy-wheel
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
OMARCHY_INSTALL_USER=$username
|
||||
OMARCHY_PROVISIONING_DIR=$PROVISIONING_DIR
|
||||
browser_policy_setup_group
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ -d $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
done
|
||||
}
|
||||
|
||||
install_authorized_keys() {
|
||||
|
||||
@@ -13,11 +13,10 @@ else
|
||||
THEME_HEX_COLOR="#1c2027"
|
||||
fi
|
||||
|
||||
set_browser_policy() {
|
||||
local policy_dir="$1"
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
[[ -d $policy_dir ]] || return
|
||||
echo "{\"BrowserThemeColor\": \"$THEME_HEX_COLOR\", \"BrowserColorScheme\": \"device\"}" | tee "$policy_dir/color.json" >/dev/null
|
||||
set_browser_policy() {
|
||||
browser_policy_write_color "$1" "$THEME_HEX_COLOR"
|
||||
}
|
||||
|
||||
refresh_running_browser() {
|
||||
@@ -30,17 +29,20 @@ refresh_running_browser() {
|
||||
fi
|
||||
}
|
||||
|
||||
set_browser_policy /etc/chromium/policies/managed
|
||||
failed=0
|
||||
set_browser_policy /etc/chromium/policies/managed || failed=1
|
||||
refresh_running_browser chromium chromium
|
||||
|
||||
set_browser_policy /etc/opt/chrome/policies/managed
|
||||
set_browser_policy /etc/opt/chrome/policies/managed || failed=1
|
||||
refresh_running_browser chrome google-chrome-stable || refresh_running_browser chrome google-chrome
|
||||
|
||||
set_browser_policy /etc/opt/edge/policies/managed
|
||||
set_browser_policy /etc/opt/edge/policies/managed || failed=1
|
||||
refresh_running_browser msedge microsoft-edge-stable
|
||||
|
||||
set_browser_policy /etc/brave/policies/managed
|
||||
set_browser_policy /etc/brave/policies/managed || failed=1
|
||||
refresh_running_browser brave brave
|
||||
# Match on the binary path: the running process is named plain "brave", and a
|
||||
# bare -f brave-origin pattern would also match the installer's own terminal.
|
||||
refresh_running_browser /opt/brave-origin-bin/ brave-origin -f
|
||||
|
||||
exit "$failed"
|
||||
|
||||
@@ -1312,7 +1312,22 @@ apply_system_transition() {
|
||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
||||
as_root gtk-update-icon-cache /usr/share/icons/Yaru >/dev/null 2>&1 || true
|
||||
|
||||
as_root install -d -m 0777 /etc/chromium/policies/managed
|
||||
local browser_policy_helper=/usr/share/omarchy/install/helpers/browser-policy.sh
|
||||
if ! as_root test -f "$browser_policy_helper"; then
|
||||
warn "$browser_policy_helper is unavailable; Chromium policy directories were not hardened."
|
||||
else
|
||||
as_root env OMARCHY_PATH=/usr/share/omarchy OMARCHY_INSTALL_USER="$target_user" \
|
||||
bash -euo pipefail -c '
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
browser_policy_setup_group
|
||||
browser_policy_setup_dir /etc/chromium/policies/managed
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ $dir == "/etc/chromium/policies/managed" ]] && continue
|
||||
[[ -d $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
done
|
||||
'
|
||||
fi
|
||||
as_root install -d -m 0755 /usr/lib/chromium
|
||||
printf '%s\n' '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
|
||||
as_root tee /usr/lib/chromium/initial_preferences >/dev/null
|
||||
@@ -2306,6 +2321,11 @@ refresh_current_theme_after_upgrade() {
|
||||
# hooks because one of them runs `hyprctl reload`. Still poke terminal
|
||||
# emulators so the active upgrade terminal picks up generated theme files.
|
||||
run_as_user_omarchy omarchy-restart-terminal >/dev/null 2>&1 || true
|
||||
|
||||
# apply_system_transition purged user-owned color.json. Headless theme-set
|
||||
# skipped omarchy-theme-set-browser, so rewrite the colour here.
|
||||
run_as_user_omarchy omarchy-theme-set-browser >/dev/null 2>&1 ||
|
||||
warn "Could not apply browser theme colour. Run 'omarchy theme set \"$theme_name\"' after reboot if Chromium's theme looks stale."
|
||||
}
|
||||
|
||||
# Everything below mutates the system, so a non-zero exit from here on leaves a
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
run_logged "$OMARCHY_INSTALL/config/theme-system.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/browser-policy.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
browser_policy_setup_group
|
||||
browser_policy_setup_dir /etc/chromium/policies/managed
|
||||
@@ -6,10 +6,6 @@ ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-next-symbolic.svg \
|
||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
||||
gtk-update-icon-cache /usr/share/icons/Yaru &>/dev/null || true
|
||||
|
||||
# Chromium policy directory for theme
|
||||
mkdir -p /etc/chromium/policies/managed
|
||||
chmod a+rw /etc/chromium/policies/managed
|
||||
|
||||
# Default Chromium to follow system appearance ("device") instead of dark
|
||||
mkdir -p /usr/lib/chromium
|
||||
echo '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' > \
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
as_root() {
|
||||
if (( EUID == 0 )); then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
# Chromium-family machine policy is mandatory for every profile. A dedicated
|
||||
# group at 2775 lets every Omarchy user write color.json and every other uid
|
||||
# read; other-write stays off. Setgid so new files inherit the group.
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/as-root.sh"
|
||||
|
||||
BROWSER_POLICY_GROUP=omarchy-browser-policy
|
||||
|
||||
BROWSER_POLICY_MANAGED_DIRS=(
|
||||
/etc/chromium/policies/managed
|
||||
/etc/opt/chrome/policies/managed
|
||||
/etc/opt/edge/policies/managed
|
||||
/etc/brave/policies/managed
|
||||
)
|
||||
|
||||
BROWSER_POLICY_FIREFOX_DIRS=(
|
||||
/usr/lib/firefox/distribution
|
||||
/opt/zen-browser/distribution
|
||||
)
|
||||
|
||||
browser_policy_setup_group() {
|
||||
local provisioning_dir="${OMARCHY_PROVISIONING_DIR:-/var/lib/omarchy/provisioning}"
|
||||
|
||||
as_root groupadd --system --force "$BROWSER_POLICY_GROUP"
|
||||
as_root mkdir -p "$provisioning_dir"
|
||||
if ! grep -qxF "$BROWSER_POLICY_GROUP" "$provisioning_dir/groups" 2>/dev/null; then
|
||||
printf '%s\n' "$BROWSER_POLICY_GROUP" | as_root tee -a "$provisioning_dir/groups" >/dev/null
|
||||
fi
|
||||
|
||||
if [[ -n ${OMARCHY_INSTALL_USER:-} ]] && getent passwd "$OMARCHY_INSTALL_USER" >/dev/null; then
|
||||
as_root usermod -aG "$BROWSER_POLICY_GROUP" "$OMARCHY_INSTALL_USER"
|
||||
fi
|
||||
}
|
||||
|
||||
browser_policy_grant_user() {
|
||||
local user=${1:-}
|
||||
|
||||
if [[ -z $user || $user == "root" ]]; then
|
||||
user=${SUDO_USER:-}
|
||||
fi
|
||||
|
||||
[[ -n $user && $user != "root" ]] || return 0
|
||||
getent passwd "$user" >/dev/null || return 0
|
||||
as_root usermod -aG "$BROWSER_POLICY_GROUP" "$user"
|
||||
}
|
||||
|
||||
browser_policy_purge_dir() {
|
||||
local dir=$1
|
||||
|
||||
as_root find "$dir" -mindepth 1 -maxdepth 1 ! -user root -exec rm -rf -- {} +
|
||||
}
|
||||
|
||||
browser_policy_dir_hardened() {
|
||||
local dir=$1
|
||||
|
||||
[[ -d $dir ]] || return 1
|
||||
[[ $(stat -c '%a' "$dir") == "2775" ]] || return 1
|
||||
[[ $(stat -c '%U' "$dir") == "root" ]] || return 1
|
||||
[[ $(stat -c '%G' "$dir") == $BROWSER_POLICY_GROUP ]] || return 1
|
||||
}
|
||||
|
||||
browser_policy_setup_dir() {
|
||||
local dir=$1
|
||||
|
||||
as_root install -d -m 2775 -o root -g "$BROWSER_POLICY_GROUP" "$dir"
|
||||
browser_policy_purge_dir "$dir"
|
||||
}
|
||||
|
||||
browser_policy_file_owner() {
|
||||
local user
|
||||
|
||||
if [[ -n ${OMARCHY_INSTALL_USER:-} && $OMARCHY_INSTALL_USER != "root" ]]; then
|
||||
printf '%s\n' "$OMARCHY_INSTALL_USER"
|
||||
return
|
||||
fi
|
||||
if [[ -n ${SUDO_USER:-} && $SUDO_USER != "root" ]]; then
|
||||
printf '%s\n' "$SUDO_USER"
|
||||
return
|
||||
fi
|
||||
if [[ -n ${PKEXEC_UID:-} ]]; then
|
||||
user=$(getent passwd "$PKEXEC_UID" | cut -d: -f1)
|
||||
if [[ -n $user && $user != "root" ]]; then
|
||||
printf '%s\n' "$user"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
user=${USER:-$(id -un)}
|
||||
if [[ $user != "root" ]]; then
|
||||
printf '%s\n' "$user"
|
||||
fi
|
||||
}
|
||||
|
||||
# sudo when this process has a controlling terminal (fd 0 is /dev/null under
|
||||
# `bash -lc cmd &`, but /dev/tty still works). pkexec when it does not.
|
||||
browser_policy_elevate() {
|
||||
if (( EUID == 0 )); then
|
||||
"$@"
|
||||
elif { exec 3</dev/tty; } 2>/dev/null; then
|
||||
exec 3<&-
|
||||
sudo "$@"
|
||||
else
|
||||
pkexec "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
browser_policy_write_color() {
|
||||
local policy_dir=$1
|
||||
local hex=$2
|
||||
local dest=$policy_dir/color.json
|
||||
local payload
|
||||
local tmp
|
||||
local owner
|
||||
|
||||
[[ -d $policy_dir ]] || return 0
|
||||
|
||||
payload=$(printf '{"BrowserThemeColor": "%s", "BrowserColorScheme": "device"}\n' "$hex")
|
||||
tmp=$(mktemp) || return 1
|
||||
printf '%s' "$payload" >"$tmp"
|
||||
|
||||
# A planted symlink or directory must not be written through or into.
|
||||
if [[ -L $dest || -d $dest ]]; then
|
||||
if ! rm -rf -- "$dest" 2>/dev/null; then
|
||||
if ! browser_policy_elevate rm -rf -- "$dest"; then
|
||||
rm -f "$tmp"
|
||||
echo "omarchy-theme-set-browser: cannot replace $dest (need group $BROWSER_POLICY_GROUP)" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if install -m 664 -T "$tmp" "$dest" 2>/dev/null; then
|
||||
rm -f "$tmp"
|
||||
return 0
|
||||
fi
|
||||
|
||||
owner=$(browser_policy_file_owner)
|
||||
[[ -n $owner ]] || owner=root
|
||||
if browser_policy_elevate install -m 664 -o "$owner" -g "$BROWSER_POLICY_GROUP" -T "$tmp" "$dest"; then
|
||||
rm -f "$tmp"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$tmp"
|
||||
echo "omarchy-theme-set-browser: cannot write $dest (need group $BROWSER_POLICY_GROUP)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
browser_policy_firefox_hardened() {
|
||||
local dir=$1
|
||||
|
||||
[[ -d $dir ]] || return 1
|
||||
[[ $(stat -c '%a' "$dir") == "755" ]] || return 1
|
||||
[[ $(stat -c '%U' "$dir") == "root" ]] || return 1
|
||||
[[ -f $dir/policies.json && ! -L $dir/policies.json ]] || return 1
|
||||
}
|
||||
|
||||
browser_policy_install_firefox_policies() {
|
||||
local distribution_dir=$1
|
||||
local policies=${2:-$OMARCHY_PATH/default/firefox/policies.json}
|
||||
|
||||
as_root install -m 644 -o root -g root -T "$policies" "$distribution_dir/policies.json"
|
||||
}
|
||||
|
||||
browser_policy_setup_firefox_distribution() {
|
||||
local distribution_dir=$1
|
||||
local policies=${2:-$OMARCHY_PATH/default/firefox/policies.json}
|
||||
|
||||
as_root install -d -m 0755 -o root -g root "$distribution_dir"
|
||||
browser_policy_purge_dir "$distribution_dir"
|
||||
browser_policy_install_firefox_policies "$distribution_dir" "$policies"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
echo "Stop world-writable Chromium and Firefox policy directories"
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
browser_policy_setup_group
|
||||
browser_policy_grant_user "${USER:-$(id -un)}"
|
||||
|
||||
repaired=0
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ -d $dir ]] || continue
|
||||
browser_policy_dir_hardened "$dir" && continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
repaired=1
|
||||
done
|
||||
|
||||
if (( repaired )); then
|
||||
omarchy-theme-set-browser
|
||||
fi
|
||||
|
||||
for dir in "${BROWSER_POLICY_FIREFOX_DIRS[@]}"; do
|
||||
[[ -d $dir ]] || continue
|
||||
browser_policy_firefox_hardened "$dir" && continue
|
||||
browser_policy_setup_firefox_distribution "$dir"
|
||||
done
|
||||
Executable
+244
@@ -0,0 +1,244 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
mock_bin=$test_tmp/bin
|
||||
mkdir -p "$mock_bin"
|
||||
elev_log=$test_tmp/elev.log
|
||||
cat >"$mock_bin/sudo" <<SH
|
||||
#!/bin/bash
|
||||
printf 'SUDO %s\\n' "\$*" >>"$elev_log"
|
||||
[[ \${OMARCHY_TEST_SUDO_FAIL:-} == 1 ]] && exit 1
|
||||
exit 0
|
||||
SH
|
||||
cat >"$mock_bin/pkexec" <<SH
|
||||
#!/bin/bash
|
||||
printf 'PKEXEC %s\\n' "\$*" >>"$elev_log"
|
||||
[[ \${OMARCHY_TEST_SUDO_FAIL:-} == 1 ]] && exit 1
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "$mock_bin/sudo" "$mock_bin/pkexec"
|
||||
export PATH="$mock_bin:$PATH"
|
||||
: >"$elev_log"
|
||||
export OMARCHY_PATH="$ROOT"
|
||||
export OMARCHY_PROVISIONING_DIR="$test_tmp/provisioning"
|
||||
|
||||
source "$ROOT/install/helpers/browser-policy.sh"
|
||||
|
||||
# Temp dirs are user-owned; drop -o/-g so install(1) can run unprivileged.
|
||||
unprivileged_as_root() {
|
||||
if [[ $1 == "install" ]]; then
|
||||
shift
|
||||
local args=()
|
||||
local skip=0
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
if (( skip )); then
|
||||
skip=0
|
||||
continue
|
||||
fi
|
||||
case $arg in
|
||||
-o|-g) skip=1 ;;
|
||||
*) args+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
command install "${args[@]}"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
write_dir=$test_tmp/writable
|
||||
mkdir -p "$write_dir"
|
||||
browser_policy_write_color "$write_dir" "#aabbcc" ||
|
||||
fail "theme colour writes into a writable policy directory"
|
||||
grep -F '"BrowserThemeColor": "#aabbcc"' "$write_dir/color.json" >/dev/null ||
|
||||
fail "theme colour writes BrowserThemeColor"
|
||||
mode=$(stat -c '%a' "$write_dir/color.json")
|
||||
[[ $mode == "664" ]] || fail "theme colour creates a group-writable policy file" "mode=$mode"
|
||||
pass "theme colour writes a group-writable color.json"
|
||||
|
||||
if (( EUID == 0 )); then
|
||||
pass "running as root; skipping the mktemp-failure check"
|
||||
else
|
||||
chmod u+w "$write_dir"
|
||||
export TMPDIR=$test_tmp/missing-tmp
|
||||
if browser_policy_write_color "$write_dir" "#dead00" 2>/dev/null; then
|
||||
fail "theme colour fails when mktemp cannot create a file"
|
||||
fi
|
||||
unset TMPDIR
|
||||
grep -F '"BrowserThemeColor": "#aabbcc"' "$write_dir/color.json" >/dev/null ||
|
||||
fail "a failed mktemp leaves an existing color.json intact"
|
||||
pass "a failed mktemp does not truncate color.json"
|
||||
fi
|
||||
|
||||
printf 'original\n' >"$test_tmp/pwn"
|
||||
rm -f "$write_dir/color.json"
|
||||
ln -s "$test_tmp/pwn" "$write_dir/color.json"
|
||||
browser_policy_write_color "$write_dir" "#aabbcc" ||
|
||||
fail "theme colour replaces a planted color.json symlink"
|
||||
[[ -f $write_dir/color.json && ! -L $write_dir/color.json ]] ||
|
||||
fail "theme colour unlinks a planted color.json symlink instead of writing through it"
|
||||
grep -Fxq 'original' "$test_tmp/pwn" || fail "theme colour leaves the symlink target unchanged"
|
||||
pass "theme colour does not follow a planted color.json symlink"
|
||||
|
||||
plant_write=$test_tmp/plant-dir
|
||||
mkdir -p "$plant_write/color.json/nested"
|
||||
printf 'inside\n' >"$plant_write/color.json/nested/x"
|
||||
browser_policy_write_color "$plant_write" "#aabbcc" ||
|
||||
fail "theme colour replaces a planted color.json directory"
|
||||
[[ -f $plant_write/color.json && ! -d $plant_write/color.json ]] ||
|
||||
fail "theme colour does not write into a planted color.json directory"
|
||||
pass "theme colour does not write into a planted color.json directory"
|
||||
|
||||
missing_dir=$test_tmp/missing
|
||||
browser_policy_write_color "$missing_dir" "#aabbcc" ||
|
||||
fail "theme colour skips a policy directory that does not exist"
|
||||
[[ ! -e $missing_dir ]] || fail "theme colour does not create a missing policy directory"
|
||||
pass "theme colour skips a missing policy directory"
|
||||
|
||||
if (( EUID == 0 )); then
|
||||
pass "running as root; skipping elevation checks"
|
||||
else
|
||||
denied_dir=$test_tmp/denied
|
||||
mkdir -p "$denied_dir"
|
||||
chmod a-w "$denied_dir"
|
||||
owner=${USER:-$(id -un)}
|
||||
: >"$elev_log"
|
||||
browser_policy_write_color "$denied_dir" "#aabbcc" ||
|
||||
fail "elevated install reports success from pkexec"
|
||||
grep -E "^PKEXEC install -m 664 -o $owner -g omarchy-browser-policy -T .+ $denied_dir/color.json$" "$elev_log" >/dev/null ||
|
||||
fail "without a controlling tty, colour write elevates through pkexec as the owner" "$(cat "$elev_log")"
|
||||
if grep -E '^SUDO ' "$elev_log" >/dev/null; then
|
||||
fail "without a controlling tty, colour write does not call sudo" "$(cat "$elev_log")"
|
||||
fi
|
||||
pass "without a controlling tty, colour write elevates through pkexec"
|
||||
|
||||
: >"$elev_log"
|
||||
export OMARCHY_TEST_SUDO_FAIL=1
|
||||
if browser_policy_write_color "$denied_dir" "#aabbcc" 2>"$test_tmp/write.err"; then
|
||||
fail "theme colour fails when the policy directory is not writable"
|
||||
fi
|
||||
unset OMARCHY_TEST_SUDO_FAIL
|
||||
grep -F 'omarchy-browser-policy' "$test_tmp/write.err" >/dev/null ||
|
||||
fail "theme colour names the group when the write is denied"
|
||||
pass "theme colour reports a denied policy write"
|
||||
|
||||
if command -v script >/dev/null; then
|
||||
: >"$elev_log"
|
||||
cat >"$test_tmp/tty-write.sh" <<EOF
|
||||
source "$ROOT/install/helpers/browser-policy.sh"
|
||||
browser_policy_write_color "$denied_dir" "#aabbcc"
|
||||
EOF
|
||||
script -q -c "PATH='$mock_bin:$PATH' OMARCHY_PATH='$ROOT' bash '$test_tmp/tty-write.sh'" /dev/null >/dev/null
|
||||
grep -E "^SUDO install -m 664 -o $owner -g omarchy-browser-policy -T .+ $denied_dir/color.json$" "$elev_log" >/dev/null ||
|
||||
fail "with a controlling tty, colour write elevates through sudo" "$(cat "$elev_log")"
|
||||
if grep -E '^PKEXEC ' "$elev_log" >/dev/null; then
|
||||
fail "with a controlling tty, colour write does not call pkexec" "$(cat "$elev_log")"
|
||||
fi
|
||||
pass "with a controlling tty, colour write elevates through sudo"
|
||||
else
|
||||
pass "script(1) unavailable; skipping the controlling-tty elevation check"
|
||||
fi
|
||||
fi
|
||||
|
||||
planted_dir=$test_tmp/planted
|
||||
mkdir -p "$planted_dir/evil"
|
||||
printf 'evil\n' >"$planted_dir/evil/f"
|
||||
printf 'old\n' >"$planted_dir/color.json"
|
||||
as_root() { unprivileged_as_root "$@"; }
|
||||
browser_policy_setup_dir "$planted_dir"
|
||||
[[ ! -e $planted_dir/evil ]] || fail "policy setup drops a non-empty non-root subdirectory"
|
||||
[[ ! -e $planted_dir/color.json ]] || fail "policy setup drops a non-root color.json"
|
||||
[[ -d $planted_dir ]] || fail "policy setup leaves the managed directory in place"
|
||||
pass "policy setup drops non-root files and non-empty subdirectories"
|
||||
|
||||
owned=$test_tmp/not-root
|
||||
mkdir -p "$owned"
|
||||
chmod 2775 "$owned"
|
||||
BROWSER_POLICY_GROUP=$(id -gn)
|
||||
if browser_policy_dir_hardened "$owned"; then
|
||||
fail "a user-owned 2775 directory is not treated as hardened"
|
||||
fi
|
||||
BROWSER_POLICY_GROUP=omarchy-browser-policy
|
||||
pass "a hardened directory must be root-owned"
|
||||
|
||||
dist=$test_tmp/distribution
|
||||
mkdir -p "$dist"
|
||||
printf 'original\n' >"$test_tmp/firefox-pwn"
|
||||
ln -s "$test_tmp/firefox-pwn" "$dist/policies.json"
|
||||
as_root() { unprivileged_as_root "$@"; }
|
||||
browser_policy_install_firefox_policies "$dist" ||
|
||||
fail "Firefox policy install replaces a planted policies.json symlink"
|
||||
[[ -f $dist/policies.json && ! -L $dist/policies.json ]] ||
|
||||
fail "Firefox policy install unlinks a planted policies.json symlink instead of writing through it"
|
||||
grep -Fxq 'original' "$test_tmp/firefox-pwn" || fail "Firefox policy install leaves the symlink target unchanged"
|
||||
grep -q '"policies"' "$dist/policies.json" || fail "Firefox policy install writes the stock policies"
|
||||
pass "Firefox policy install does not follow a planted policies.json symlink"
|
||||
|
||||
dir_dist=$test_tmp/distribution-dir
|
||||
mkdir -p "$dir_dist"
|
||||
mkdir "$dir_dist/policies.json"
|
||||
as_root() { unprivileged_as_root "$@"; }
|
||||
if browser_policy_install_firefox_policies "$dir_dist" 2>/dev/null; then
|
||||
fail "Firefox policy install refuses a planted policies.json directory"
|
||||
fi
|
||||
[[ -d $dir_dist/policies.json ]] || fail "Firefox policy install leaves a planted policies.json directory in place"
|
||||
pass "Firefox policy install does not write into a planted policies.json directory"
|
||||
|
||||
grant_log=$test_tmp/usermod.calls
|
||||
as_root() {
|
||||
if [[ $1 == "usermod" ]]; then
|
||||
printf '%s\n' "$*" >>"$grant_log"
|
||||
return 0
|
||||
fi
|
||||
unprivileged_as_root "$@"
|
||||
}
|
||||
invoker=${USER:-$(id -un)}
|
||||
: >"$grant_log"
|
||||
SUDO_USER=$invoker
|
||||
browser_policy_grant_user root
|
||||
unset SUDO_USER
|
||||
grep -qx -- "usermod -aG omarchy-browser-policy $invoker" "$grant_log" ||
|
||||
fail "granting as root uses SUDO_USER" "$(cat "$grant_log")"
|
||||
: >"$grant_log"
|
||||
OMARCHY_INSTALL_USER=""
|
||||
browser_policy_grant_user ""
|
||||
[[ ! -s $grant_log ]] || fail "an empty grant does not usermod"
|
||||
pass "sudo install browser grants the invoking user, not root"
|
||||
|
||||
grep -F 'exit "$failed"' "$ROOT/bin/omarchy-theme-set-browser" >/dev/null ||
|
||||
fail "omarchy-theme-set-browser exits non-zero when a policy write fails"
|
||||
pass "omarchy-theme-set-browser exits non-zero when a policy write fails"
|
||||
|
||||
policy_files=(
|
||||
"$ROOT/bin/omarchy-install-browser"
|
||||
"$ROOT/bin/omarchy-provision-owner"
|
||||
"$ROOT/bin/omarchy-theme-set-browser"
|
||||
"$ROOT/bin/omarchy-upgrade-to-quattro"
|
||||
"$ROOT/install/config/theme-system.sh"
|
||||
"$ROOT/install/config/browser-policy.sh"
|
||||
"$ROOT/install/helpers/browser-policy.sh"
|
||||
"$ROOT/migrations/1787515927.sh"
|
||||
)
|
||||
if grep -nE 'chmod a\+rwx\b|chmod a\+rw\b|chmod a\+w\b|chmod o\+w|chmod ugo\+w|chmod 2777\b|chmod 0777\b|chmod 777\b|install -d -m 0?[27]?777' "${policy_files[@]}" >/dev/null; then
|
||||
fail "browser policy setup is not world-writable"
|
||||
fi
|
||||
pass "browser policy setup is not world-writable"
|
||||
|
||||
mapfile -t migrations < <(rg -l 'Stop world-writable Chromium and Firefox policy directories' "$ROOT/migrations")
|
||||
(( ${#migrations[@]} == 1 )) || fail "exactly one migration locks existing policy directories" "${migrations[*]}"
|
||||
grep -F 'browser_policy_dir_hardened' "${migrations[0]}" >/dev/null ||
|
||||
fail "the policy-directory migration no-ops a machine already repaired"
|
||||
grep -F 'browser_policy_grant_user' "${migrations[0]}" >/dev/null ||
|
||||
fail "the policy-directory migration still grants the current user the group"
|
||||
grep -F 'BROWSER_POLICY_FIREFOX_DIRS' "${migrations[0]}" >/dev/null ||
|
||||
fail "the policy-directory migration covers Firefox and Zen"
|
||||
grep -F '/opt/zen-browser/distribution' "$ROOT/install/helpers/browser-policy.sh" >/dev/null ||
|
||||
fail "the shared helper names the Zen distribution directory"
|
||||
pass "a migration locks existing policy directories"
|
||||
@@ -61,11 +61,13 @@ if [[ $installer == "omarchy-install-browser" && ${OMARCHY_TEST_REAL_BROWSER_INS
|
||||
fi
|
||||
|
||||
case $installer in
|
||||
omarchy-pkg-add)
|
||||
omarchy-pkg-add|omarchy-pkg-aur-add)
|
||||
package=$1
|
||||
printf 'pkg:%s\n' "$package" >>"$OMARCHY_TEST_INSTALL_LOG"
|
||||
case $package in
|
||||
chromium) command=chromium ;;
|
||||
firefox) command=firefox ;;
|
||||
zen-browser-bin) command=zen-browser ;;
|
||||
cursor-bin) command=cursor ;;
|
||||
sublime-text-4) command=sublime_text ;;
|
||||
vim) command=vim ;;
|
||||
@@ -107,6 +109,7 @@ SH
|
||||
|
||||
for installer in \
|
||||
omarchy-pkg-add \
|
||||
omarchy-pkg-aur-add \
|
||||
omarchy-install-browser \
|
||||
omarchy-install-terminal \
|
||||
omarchy-install-editor-vscode \
|
||||
@@ -205,10 +208,14 @@ OMARCHY_TEST_REAL_BROWSER_INSTALL=true omarchy-default-browser --install chromiu
|
||||
[[ $(omarchy-default-browser) == "chromium" ]] || fail "Chromium becomes the default after its full installer succeeds"
|
||||
cmp -s "$ROOT/config/chromium-flags.conf" "$test_home/.config/chromium-flags.conf" ||
|
||||
fail "Chromium browser installer copies the default flags"
|
||||
grep -Fxq 'sudo:mkdir -p /etc/chromium/policies/managed' "$setup_log" ||
|
||||
fail "Chromium browser installer creates its policy directory"
|
||||
grep -Fxq 'sudo:chmod a+rw /etc/chromium/policies/managed' "$setup_log" ||
|
||||
fail "Chromium browser installer makes its policy directory writable"
|
||||
grep -Fxq 'sudo:groupadd --system --force omarchy-browser-policy' "$setup_log" ||
|
||||
fail "Chromium browser installer creates the browser-policy group"
|
||||
grep -Fxq 'sudo:install -d -m 2775 -o root -g omarchy-browser-policy /etc/chromium/policies/managed' "$setup_log" ||
|
||||
fail "Chromium browser installer creates a group-writable managed policy directory"
|
||||
grep -Fxq 'sudo:find /etc/chromium/policies/managed -mindepth 1 -maxdepth 1 ! -user root -exec rm -rf -- {} +' "$setup_log" ||
|
||||
fail "Chromium browser installer drops non-root files from its policy directory"
|
||||
grep -Fxq "sudo:usermod -aG omarchy-browser-policy ${USER:-$(id -un)}" "$setup_log" ||
|
||||
fail "Chromium browser installer grants the installing user the browser-policy group"
|
||||
grep -Fxq 'omarchy-install-chromium-copy-url:' "$setup_log" ||
|
||||
fail "Chromium browser installer registers the Copy URL host"
|
||||
grep -Fxq 'omarchy-install-chromium-ytdlp:' "$setup_log" ||
|
||||
@@ -217,6 +224,36 @@ grep -Fxq 'omarchy-theme-set-browser:' "$setup_log" ||
|
||||
fail "Chromium browser installer applies the current theme"
|
||||
pass "Chromium browser installer restores the complete Omarchy setup"
|
||||
|
||||
: >"$install_log"
|
||||
: >"$setup_log"
|
||||
rm -f "$installed_dir/firefox"
|
||||
OMARCHY_TEST_REAL_BROWSER_INSTALL=true omarchy-default-browser --install firefox >/dev/null
|
||||
[[ $(<"$install_log") == "pkg:firefox" ]] || fail "Firefox browser installer installs the package"
|
||||
[[ $(omarchy-default-browser) == "firefox" ]] || fail "Firefox becomes the default after its full installer succeeds"
|
||||
grep -Fxq 'sudo:install -d -m 0755 -o root -g root /usr/lib/firefox/distribution' "$setup_log" ||
|
||||
fail "Firefox browser installer creates its distribution directory"
|
||||
grep -Fxq 'sudo:find /usr/lib/firefox/distribution -mindepth 1 -maxdepth 1 ! -user root -exec rm -rf -- {} +' "$setup_log" ||
|
||||
fail "Firefox browser installer drops non-root files from its distribution directory"
|
||||
grep -Fxq "sudo:install -m 644 -o root -g root -T $ROOT/default/firefox/policies.json /usr/lib/firefox/distribution/policies.json" "$setup_log" ||
|
||||
fail "Firefox browser installer copies policies.json without following a destination symlink"
|
||||
[[ -e $installed_dir/firefox ]] || fail "Firefox browser installer marks firefox installed"
|
||||
pass "Firefox browser installer restores the complete Omarchy setup"
|
||||
|
||||
: >"$install_log"
|
||||
: >"$setup_log"
|
||||
rm -f "$installed_dir/zen-browser"
|
||||
OMARCHY_TEST_REAL_BROWSER_INSTALL=true omarchy-default-browser --install zen >/dev/null
|
||||
[[ $(<"$install_log") == "pkg:zen-browser-bin" ]] || fail "Zen browser installer installs the package"
|
||||
[[ $(omarchy-default-browser) == "zen" ]] || fail "Zen becomes the default after its full installer succeeds"
|
||||
grep -Fxq 'sudo:install -d -m 0755 -o root -g root /opt/zen-browser/distribution' "$setup_log" ||
|
||||
fail "Zen browser installer creates its distribution directory"
|
||||
grep -Fxq 'sudo:find /opt/zen-browser/distribution -mindepth 1 -maxdepth 1 ! -user root -exec rm -rf -- {} +' "$setup_log" ||
|
||||
fail "Zen browser installer drops non-root files from its distribution directory"
|
||||
grep -Fxq "sudo:install -m 644 -o root -g root -T $ROOT/default/firefox/policies.json /opt/zen-browser/distribution/policies.json" "$setup_log" ||
|
||||
fail "Zen browser installer copies policies.json without following a destination symlink"
|
||||
[[ -e $installed_dir/zen-browser ]] || fail "Zen browser installer marks zen-browser installed"
|
||||
pass "Zen browser installer restores the complete Omarchy setup"
|
||||
|
||||
omarchy-default-browser zen
|
||||
rm -f "$installed_dir/chromium"
|
||||
if OMARCHY_TEST_REAL_BROWSER_INSTALL=true OMARCHY_TEST_INSTALL_FAIL=true \
|
||||
|
||||
@@ -27,16 +27,40 @@ cat >"$TMPDIR/bin/usermod" <<STUB
|
||||
#!/bin/bash
|
||||
echo "\$@" >>"$TMPDIR/usermod.calls"
|
||||
STUB
|
||||
chmod +x "$TMPDIR/bin/getent" "$TMPDIR/bin/usermod"
|
||||
cat >"$TMPDIR/bin/groupadd" <<STUB
|
||||
#!/bin/bash
|
||||
echo "\$@" >>"$TMPDIR/groupadd.calls"
|
||||
STUB
|
||||
cat >"$TMPDIR/bin/install" <<STUB
|
||||
#!/bin/bash
|
||||
echo "\$@" >>"$TMPDIR/install.calls"
|
||||
STUB
|
||||
cat >"$TMPDIR/bin/find" <<STUB
|
||||
#!/bin/bash
|
||||
echo "\$@" >>"$TMPDIR/find.calls"
|
||||
STUB
|
||||
cat >"$TMPDIR/bin/sudo" <<STUB
|
||||
#!/bin/bash
|
||||
echo "\$@" >>"$TMPDIR/sudo.calls"
|
||||
exec "\$@"
|
||||
STUB
|
||||
chmod +x "$TMPDIR/bin"/{getent,usermod,groupadd,install,find,sudo}
|
||||
export PATH="$TMPDIR/bin:$PATH"
|
||||
export OMARCHY_PATH="$ROOT"
|
||||
|
||||
# No install user (deferred-provisioning install): input recorded, usermod not called.
|
||||
# No install user (deferred-provisioning install): groups recorded, usermod not called.
|
||||
OMARCHY_INSTALL_USER="" bash -eE "$ROOT/install/config/docker.sh"
|
||||
OMARCHY_INSTALL_USER="" bash -eE "$ROOT/install/hardware/input-group.sh"
|
||||
OMARCHY_INSTALL_USER="" bash -eE "$ROOT/install/config/browser-policy.sh"
|
||||
|
||||
[[ -f $OMARCHY_PROVISIONING_DIR/groups ]] || fail "groups file written without an install user"
|
||||
grep -qxF input "$OMARCHY_PROVISIONING_DIR/groups" || fail "input group recorded"
|
||||
grep -qxF omarchy-browser-policy "$OMARCHY_PROVISIONING_DIR/groups" || fail "browser-policy group recorded"
|
||||
[[ ! -f $TMPDIR/usermod.calls ]] || fail "usermod not called without an install user"
|
||||
grep -F -- '--system --force omarchy-browser-policy' "$TMPDIR/groupadd.calls" >/dev/null ||
|
||||
fail "browser-policy group is created as a system group"
|
||||
grep -F -- '-d -m 2775 -o root -g omarchy-browser-policy /etc/chromium/policies/managed' "$TMPDIR/install.calls" >/dev/null ||
|
||||
fail "browser-policy directory is created group-writable"
|
||||
pass "deferred provisioning records groups without calling usermod"
|
||||
|
||||
# The docker group is root-equivalent and must never be granted automatically.
|
||||
@@ -45,17 +69,24 @@ pass "docker group is not recorded at install"
|
||||
|
||||
# Missing user (defensive): no usermod either.
|
||||
OMARCHY_INSTALL_USER=ghost bash -eE "$ROOT/install/hardware/input-group.sh"
|
||||
OMARCHY_INSTALL_USER=ghost bash -eE "$ROOT/install/config/browser-policy.sh"
|
||||
[[ ! -f $TMPDIR/usermod.calls ]] || fail "usermod not called for a missing user"
|
||||
pass "missing install user defers group grants"
|
||||
|
||||
# Re-running never duplicates entries.
|
||||
OMARCHY_INSTALL_USER="" bash -eE "$ROOT/install/hardware/input-group.sh"
|
||||
[[ $(grep -cxF input "$OMARCHY_PROVISIONING_DIR/groups") == 1 ]] || fail "input group recorded once"
|
||||
OMARCHY_INSTALL_USER="" bash -eE "$ROOT/install/config/browser-policy.sh"
|
||||
[[ $(grep -cxF omarchy-browser-policy "$OMARCHY_PROVISIONING_DIR/groups") == 1 ]] ||
|
||||
fail "browser-policy group recorded once"
|
||||
pass "group recording is idempotent"
|
||||
|
||||
# Existing user: usermod applies the recorded groups, and docker is never among them.
|
||||
OMARCHY_INSTALL_USER=existing bash -eE "$ROOT/install/config/docker.sh"
|
||||
OMARCHY_INSTALL_USER=existing bash -eE "$ROOT/install/hardware/input-group.sh"
|
||||
OMARCHY_INSTALL_USER=existing bash -eE "$ROOT/install/config/browser-policy.sh"
|
||||
grep -qx -- "-aG input existing" "$TMPDIR/usermod.calls" || fail "usermod grants input to the install user"
|
||||
grep -qx -- "-aG omarchy-browser-policy existing" "$TMPDIR/usermod.calls" ||
|
||||
fail "usermod grants browser-policy to the install user"
|
||||
! grep -q -- "docker" "$TMPDIR/usermod.calls" || fail "usermod must not grant docker to the install user"
|
||||
pass "existing install user gets input but never docker"
|
||||
pass "existing install user gets input and browser-policy but never docker"
|
||||
|
||||
@@ -67,6 +67,23 @@ grep -F 'OMARCHY_INSTALL_USER="$target_user"' "$upgrade_to_quattro" >/dev/null
|
||||
grep -F '"$apply_lock"' "$upgrade_to_quattro" >/dev/null
|
||||
pass "Omarchy 4 upgrade configures lock screen authentication for the target user"
|
||||
|
||||
grep -F 'install/helpers/browser-policy.sh' "$upgrade_to_quattro" >/dev/null ||
|
||||
fail "Omarchy 4 upgrade uses the shared browser-policy helper"
|
||||
grep -F 'as_root test -f "$browser_policy_helper"' "$upgrade_to_quattro" >/dev/null ||
|
||||
fail "Omarchy 4 upgrade survives a packaged tree without the browser-policy helper"
|
||||
grep -F 'browser_policy_setup_group' "$upgrade_to_quattro" >/dev/null ||
|
||||
fail "Omarchy 4 upgrade creates the browser-policy group"
|
||||
grep -F 'browser_policy_setup_dir /etc/chromium/policies/managed' "$upgrade_to_quattro" >/dev/null ||
|
||||
fail "Omarchy 4 upgrade creates a group-writable Chromium policy directory"
|
||||
grep -F 'BROWSER_POLICY_MANAGED_DIRS' "$upgrade_to_quattro" >/dev/null ||
|
||||
fail "Omarchy 4 upgrade hardens every Chromium-family policy directory"
|
||||
grep -F 'run_as_user_omarchy omarchy-theme-set-browser' "$upgrade_to_quattro" >/dev/null ||
|
||||
fail "Omarchy 4 upgrade rewrites browser theme colour after a headless theme-set"
|
||||
if grep -E 'install -d -m 0?[27]?777 /etc/.*/policies|chmod a\+rw' "$upgrade_to_quattro" >/dev/null; then
|
||||
fail "Omarchy 4 upgrade does not create a world-writable Chromium policy directory"
|
||||
fi
|
||||
pass "Omarchy 4 upgrade locks the Chromium policy directory to the browser-policy group"
|
||||
|
||||
grep -F 'OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1' "$upgrade_to_quattro" >/dev/null
|
||||
grep -F 'systemd-networkd.service' "$upgrade_to_quattro" >/dev/null
|
||||
grep -F 'systemd-networkd.socket' "$upgrade_to_quattro" >/dev/null
|
||||
|
||||
Reference in New Issue
Block a user