add audit

This commit is contained in:
David Young
2026-10-28 15:57:54 -06:00
parent 9b9f1d5fdd
commit 842225fd40
6 changed files with 614 additions and 10 deletions
+8
View File
@@ -67,3 +67,11 @@ To make your own repository work as a sibling project, see [skills/sibling-proje
## Firewall
firewalld blocks incoming connections except for the services and ports listed in [firewall.conf](firewall.conf) and in each sibling project's own `firewall.conf`. Each `update` opens anything newly listed, and closes ports MiniServer opened that nothing lists any more, for example after `sibling remove`. Rules you add yourself with `firewall-cmd` are left alone.
To see what's actually open:
```
audit
```
It lists every port something on the server is listening on, whatever installed it, along with who can reach it (this machine only, the LAN, Tailscale or containers) and the service behind it. Each port the LAN can reach is marked with the `firewall.conf` that asks for it, or `[in no firewall.conf]` if it was opened some other way. Ports Docker containers publish are shown as reachable whatever firewalld says, because Docker opens them itself. You can stop or start the services from the list; one you stop stays off, through `update` and `refresh` too, until you start it again in `audit`. `audit --list` only shows the ports.
Executable
+571
View File
@@ -0,0 +1,571 @@
#!/bin/bash
#
# Purpose : Show the network ports open on this machine: who can reach each one
# through the firewall, which service opened it and why, and let you
# stop or start those services.
#
# A service you stop here is also kept from starting at boot, and update leaves
# it off (see enable_services in utils.sh) until you start it here again.
# Wrapped in { ...; exit; } so bash reads the whole script up front; update
# replaces this file while it runs.
{
MINISERVER="$HOME/.local/share/MiniServer"
# Provides firewall_entries and SERVICES_OFF
source "$MINISERVER/utils.sh"
# Why a service or app opens ports, by systemd unit or process name. Services
# with ports here are also listed while they're stopped, so they can be started.
# Format: name|ports|why
KNOWN=(
"sshd.service|22/tcp|SSH: log in to this machine from other machines"
"systemd-resolved.service||Looks up domain names (DNS); 5355 is LLMNR, which answers name lookups from the LAN"
"avahi-daemon.service|5353/udp|mDNS: lets machines on your LAN reach this one as $(uname -n).local"
"cockpit.socket|9090/tcp|Cockpit: manage this server from a web browser"
"cockpit.service|9090/tcp|Cockpit: manage this server from a web browser"
"tailscaled.service|41641/udp|Tailscale: your Core's private network"
"docker.service||Docker"
"pm2-$USER.service||pm2: keeps your Node apps running; each app opens its own ports"
"hermes-gateway.service||The sandbox's proxy for Hermes Agent's gateway (srt); only this machine can reach it"
)
# Stopping these would break the server's network, its firewall or the
# graphical login, so audit doesn't offer to
PROTECTED="systemd-resolved.service NetworkManager.service systemd-networkd.service firewalld.service sddm.service dbus.service dbus-broker.service"
usage() {
cat << EOF
Usage: audit [--list]
audit Show the open ports and choose a service to stop or start
audit --list Only show the open ports
Run it with sudo rights: without them, the services run by root can't be named.
EOF
}
known_field() { # <name> <field: 2=ports 3=why>
local entry
for entry in "${KNOWN[@]}"; do
[ "${entry%%|*}" = "$1" ] && { cut -d'|' -f"$2" <<< "$entry"; return 0; }
done
return 1
}
# The running known service that uses <proto> <port>, for a socket whose
# process only root can see
known_port_owner() {
local entry unit
for entry in "${KNOWN[@]}"; do
unit="${entry%%|*}"
[[ ",$(cut -d'|' -f2 <<< "$entry")," == *",$2/$1,"* ]] || continue
systemctl is-active --quiet "$unit" 2> /dev/null && { echo "system $unit"; return 0; }
done
return 1
}
# firewall-cmd, which only answers while firewalld runs
fw() {
if [ -n "$NO_SUDO" ]; then firewall-cmd "$@"; else sudo firewall-cmd "$@"; fi
}
# The ports a firewalld service opens, as "proto port" lines in
# SERVICE_PORTS[service], with those of the services it includes
declare -A SERVICE_PORTS
cache_service() { # <service>
[ -n "${SERVICE_PORTS[$1]+set}" ] && return
# Set first, so services that include each other don't loop
SERVICE_PORTS[$1]=""
local info entry inc ports=""
info="$(fw --info-service="$1" 2> /dev/null)"
for entry in $(sed -n 's/^ *ports: //p' <<< "$info"); do
ports+="${entry#*/} ${entry%/*}"$'\n'
done
for inc in $(sed -n 's/^ *includes: //p' <<< "$info"); do
cache_service "$inc"
ports+="${SERVICE_PORTS[$inc]}"
done
SERVICE_PORTS[$1]="$ports"
}
# Add "<level> <proto> <port>" lines to ZONE_RULES[zone] for a service, where
# level is full, or some when only some source addresses are let in
add_service_rules() { # <zone> <level> <service>
local proto port
cache_service "$3"
while read -r proto port; do
[ -n "$proto" ] && ZONE_RULES[$1]+="$2 $proto $port"$'\n'
done <<< "${SERVICE_PORTS[$3]}"
}
# What firewalld lets in, from every zone: ZONE_RULES[zone] holds
# "<level> <proto> <port>" lines ("any" for any protocol or port; a port can be
# a range), ZONE_OF_IFACE the zone each interface was put in (others are in
# DEFAULT_ZONE), and SOURCE_ZONES the zones that apply to some source
# addresses on every interface
load_firewall() {
FIREWALL_ON=0 FIREWALL_UNREAD=0 DEFAULT_ZONE=""
declare -gA ZONE_RULES ZONE_OF_IFACE
ZONE_RULES=() ZONE_OF_IFACE=() SOURCE_ZONES=()
systemctl is-active --quiet firewalld && FIREWALL_ON=1
[ "$FIREWALL_ON" = 1 ] || return 0
local all line zone key value level item
if ! all="$(fw --list-all-zones 2> /dev/null)"; then
FIREWALL_UNREAD=1
return 0
fi
while IFS= read -r line; do
# A zone starts unindented, e.g. "public (default, active)"
if [[ "$line" =~ ^([^[:space:]]+)( \((.*)\))?$ ]]; then
zone="${BASH_REMATCH[1]}"
[[ "${BASH_REMATCH[3]}" == *default* ]] && DEFAULT_ZONE="$zone"
continue
fi
[ -n "$zone" ] || continue
# Rich rules, one per line after "rich rules:". Only those that accept
# connections count; one limited to a source lets in only some addresses.
if [[ "$line" =~ ^[[:space:]]+rule[[:space:]] ]]; then
[[ "$line" =~ [[:space:]]accept([[:space:]]|$) ]] || continue
level=full
[[ "$line" == *"source address="* || "$line" == *"source ipset="* || "$line" == *"source mac="* ]] && level=some
if [[ "$line" =~ [[:space:]]port\ port=\"([0-9-]+)\"\ protocol=\"([a-z]+)\" ]]; then
ZONE_RULES[$zone]+="$level ${BASH_REMATCH[2]} ${BASH_REMATCH[1]}"$'\n'
elif [[ "$line" =~ service\ name=\"([^\"]+)\" ]]; then
add_service_rules "$zone" "$level" "${BASH_REMATCH[1]}"
else
ZONE_RULES[$zone]+="$level any any"$'\n'
fi
continue
fi
[[ "$line" =~ ^[[:space:]]+([a-z -]+):[[:space:]]?(.*)$ ]] || continue
key="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}"
case "$key" in
target)
[ "$value" = ACCEPT ] && ZONE_RULES[$zone]+="full any any"$'\n' ;;
interfaces)
for item in $value; do ZONE_OF_IFACE[$item]="$zone"; done ;;
sources)
[ -n "$value" ] && SOURCE_ZONES+=("$zone") ;;
services)
for item in $value; do add_service_rules "$zone" full "$item"; done ;;
ports)
for item in $value; do ZONE_RULES[$zone]+="full ${item#*/} ${item%/*}"$'\n'; done ;;
esac
done <<< "$all"
}
# How a zone lets <proto> <port> in: full, some (from some addresses) or ""
zone_allows() { # <zone> <proto> <port>
local level proto port best=""
while read -r level proto port; do
[ -n "$level" ] || continue
[[ "$proto" == any || "$proto" == "$2" ]] || continue
[[ "$port" == any ]] || (( $3 >= ${port%-*} && $3 <= ${port#*-} )) || continue
[ "$level" = full ] && { echo full; return; }
best=some
done <<< "${ZONE_RULES[$1]}"
echo "$best"
}
# How the firewall lets <proto> <port> in from the network of <kind> (lan,
# tailscale or containers): full, some or ""
firewall_allows() { # <proto> <port> <kind>
local iface level best="" zone
if [ "$FIREWALL_ON" = 0 ] || [ "$FIREWALL_UNREAD" = 1 ]; then
[ -n "${IFACES_OF_KIND[$3]}" ] && echo full
return
fi
for iface in ${IFACES_OF_KIND[$3]}; do
level="$(zone_allows "${ZONE_OF_IFACE[$iface]:-$DEFAULT_ZONE}" "$1" "$2")"
[ "$level" = full ] && { echo full; return; }
[ -n "$level" ] && best=some
done
# A zone bound to source addresses lets them in on any interface
if [ -n "${IFACES_OF_KIND[$3]}" ]; then
for zone in "${SOURCE_ZONES[@]}"; do
[ -n "$(zone_allows "$zone" "$1" "$2")" ] && best=some
done
fi
echo "$best"
}
# The ports each firewall.conf asks for, as "<proto> <port> <project>" in
# CONF_PORTS, with services expanded to their ports
load_firewall_confs() {
local conf name entry proto port
CONF_PORTS=()
# Only shown alongside the firewall's rules
[ "$FIREWALL_ON" = 1 ] && [ "$FIREWALL_UNREAD" = 0 ] || return 0
for conf in "$MINISERVER/firewall.conf" "$HOME/.local/share/siblings/"*/firewall.conf; do
[ -f "$conf" ] || continue
name=MiniServer
[[ "$conf" == */siblings/* ]] && name="$(basename "$(dirname "$conf")")"
while IFS= read -r entry; do
case "$entry" in
port:*)
entry="${entry#port:}"
CONF_PORTS+=("${entry#*/} ${entry%/*} $name") ;;
service:*)
entry="${entry#service:}"
cache_service "$entry"
while read -r proto port; do
[ -n "$proto" ] && CONF_PORTS+=("$proto $port $name")
done <<< "${SERVICE_PORTS[$entry]}" ;;
esac
done < <(firewall_entries "$conf" 2> /dev/null)
done
}
# The projects whose firewall.conf asks for <proto> <port>, comma-separated
conf_owners() { # <proto> <port>
local entry proto port name names=""
[[ "$2" =~ ^[0-9]+$ ]] || return 0
for entry in "${CONF_PORTS[@]}"; do
read -r proto port name <<< "$entry"
[ "$proto" = "$1" ] || continue
(( $2 >= ${port%-*} && $2 <= ${port#*-} )) || continue
[[ ", $names, " == *", $name, "* ]] || names+="${names:+, }$name"
done
echo "$names"
}
# Which network an interface is on: local, lan, tailscale or containers
iface_kind() {
case "$1" in
lo) echo local ;;
tailscale*) echo tailscale ;;
docker*|br-*|veth*|virbr*) echo containers ;;
*) echo lan ;;
esac
}
# The systemd unit a process runs in, as "system <unit>", "user <unit>",
# "app <name>" or "none <name>"
process_owner() {
local pid="$1" comm="$2" cgroup unit
cgroup="$(cut -d: -f3- "/proc/$pid/cgroup" 2> /dev/null | head -n 1)"
unit="${cgroup##*/}"
case "$cgroup" in
*/app-flatpak-*.scope)
unit="${unit#app-flatpak-}"
echo "app ${unit%-*.scope}" ;;
*.scope) echo "app $comm" ;;
/system.slice/*.service) echo "system $unit" ;;
/user.slice/*/user@*.service/*.service) echo "user $unit" ;;
*) echo "none $comm" ;;
esac
}
# A port systemd itself listens on, for a service it starts on demand
socket_unit() { # <port>
systemctl list-sockets --all --no-legend 2> /dev/null \
| awk -v port="$1" '$1 ~ ":" port "$" { print $2; exit }'
}
# Collect the listening sockets into ROWS, one per service and port, with the
# networks each listens on; and the interfaces on each network into
# IFACES_OF_KIND
load_ports() {
local -A iface_of seen
local netid local_addr process addr port iface kind pid comm owner key
ROW_KEYS=()
declare -gA ROW_PROTO ROW_PORT ROW_OWNER ROW_KINDS IFACES_OF_KIND
ROW_PROTO=() ROW_PORT=() ROW_OWNER=() ROW_KINDS=() IFACES_OF_KIND=()
while read -r _ iface _ addr _; do
iface_of["${addr%/*}"]="$iface"
kind="$(iface_kind "$iface")"
[[ " ${IFACES_OF_KIND[$kind]} " == *" $iface "* ]] || IFACES_OF_KIND[$kind]+=" $iface"
done < <(ip -o addr show 2> /dev/null)
while read -r netid _ _ _ local_addr _ process; do
port="${local_addr##*:}"
addr="${local_addr%:*}"
addr="${addr#[}" addr="${addr%]}"
iface=""
[[ "$addr" == *%* ]] && iface="${addr#*%}" addr="${addr%%%*}"
case "$addr" in
0.0.0.0|::|\*) kind=all ;;
127.*|::1) kind=local ;;
*) kind="$(iface_kind "${iface:-${iface_of[$addr]:-lan}}")" ;;
esac
pid="$(grep -o 'pid=[0-9]*' <<< "$process" | head -n 1 | cut -d= -f2)"
comm="$(sed -nE 's/^users:\(\("([^"]*)".*/\1/p' <<< "$process")"
if [ -z "$pid" ]; then
owner="$(known_port_owner "$netid" "$port")" || owner="none ?"
elif [ "$comm" = systemd ] && unit="$(socket_unit "$port")" && [ -n "$unit" ]; then
owner="system $unit"
elif [ "$comm" = docker-proxy ]; then
owner="none docker-proxy"
else
owner="$(process_owner "$pid" "$comm")"
fi
# Random high ports bound to one address are a service's replies and
# discovery, not something to connect to: one row for all of them
[[ "$kind" != all && "$port" -ge 32768 ]] && port=random
key="$netid $port $owner"
if [ -z "${seen[$key]}" ]; then
seen[$key]=1
ROW_KEYS+=("$key")
ROW_PROTO[$key]="$netid" ROW_PORT[$key]="$port" ROW_OWNER[$key]="$owner"
fi
[[ " ${ROW_KINDS[$key]} " == *" $kind "* ]] || ROW_KINDS[$key]+=" $kind"
done < <($SS -tulpnH 2> /dev/null)
}
# Who can connect to a row's port, given where it listens and the firewall
reach_label() { # <proto> <port> <kinds> <owner>
local proto="$1" port="$2" kinds="$3" lan=0 ts=0 ct=0 blocked=0 parts=()
[ "$port" = random ] && port=0
for kind in $kinds; do
case "$kind" in
all) lan=1 ts=1 ;;
lan) lan=1 ;;
tailscale) ts=1 ;;
containers) ct=1 ;;
esac
done
if [ "$lan" = 1 ]; then
# Docker opens the ports containers publish itself, ahead of firewalld
if [ "$4" = "none docker-proxy" ]; then
parts+=("\e[31mLAN\e[0m (past the firewall)")
else
case "$(firewall_allows "$proto" "$port" lan)" in
full) parts+=("\e[31mLAN\e[0m") ;;
some) parts+=("\e[31mLAN\e[0m (some addresses)") ;;
*) blocked=1 ;;
esac
fi
fi
if [ "$ts" = 1 ] && [ -n "$(firewall_allows "$proto" "$port" tailscale)" ]; then
parts+=("\e[33mTailscale\e[0m")
fi
if [ "$ct" = 1 ] && [ -n "$(firewall_allows "$proto" "$port" containers)" ]; then
parts+=("\e[33mcontainers\e[0m")
fi
[ ${#parts[@]} -eq 0 ] && parts=("\e[32mthis machine\e[0m")
local label="${parts[0]}" part
for part in "${parts[@]:1}"; do label+=", $part"; done
[ "$blocked" = 1 ] && label+=" (firewall blocks LAN)"
echo -n "$label"
}
owner_label() {
local scope="${1%% *}" name="${1#* }"
case "$scope" in
system) echo "${name%.service}" ;;
user) echo "${name%.service} (yours)" ;;
app) echo "$name (app)" ;;
*) [ "$name" = docker-proxy ] && echo "Docker container" || echo "$name" ;;
esac
}
why() {
local scope="${1%% *}" name="${1#* }" text
text="$(known_field "$name" 3)" && { echo "$text"; return; }
case "$scope" in
system) systemctl show -p Description --value "$name" 2> /dev/null ;;
user) systemctl --user show -p Description --value "$name" 2> /dev/null ;;
none)
case "$name" in
"?") echo "Run audit with sudo rights to see which service this is" ;;
docker-proxy) echo "A port a Docker container publishes; see: docker ps" ;;
esac ;;
esac
}
# For a port other machines on the LAN can reach: which firewall.conf asks for
# it, or a warning that none does. Containers' ports needn't be listed.
conf_note() { # <proto> <port> <owner>
local owners
[ "$3" = "none docker-proxy" ] && return
owners="$(conf_owners "$1" "$2")"
if [ -n "$owners" ]; then
echo -n " [firewall.conf: $owners]"
else
echo -n " \e[31m[in no firewall.conf]\e[0m"
fi
}
systemctl_for() { # <scope> <arguments>
if [ "$1" = user ]; then systemctl --user "${@:2}"; else sudo systemctl "${@:2}"; fi
}
is_active() { # <scope> <unit>
if [ "$1" = user ]; then systemctl --user is-active --quiet "$2"; else systemctl is-active --quiet "$2"; fi
}
# Services that can be stopped or started here, as "scope unit": those with
# open ports, then the known ones that are installed
load_services() {
local key owner entry unit
SERVICES_LIST=()
for key in "${ROW_KEYS[@]}"; do
owner="${ROW_OWNER[$key]}"
[[ "$owner" == system\ * || "$owner" == user\ * ]] || continue
[[ " $PROTECTED " == *" ${owner#* } "* ]] && continue
[[ " ${SERVICES_LIST[*]} " == *" $owner "* ]] || SERVICES_LIST+=("$owner")
done
for entry in "${KNOWN[@]}"; do
unit="${entry%%|*}"
[[ "$unit" == *.service || "$unit" == *.socket ]] || continue
[ -n "$(known_field "$unit" 2)" ] || continue
[[ " ${SERVICES_LIST[*]} " == *" system $unit "* ]] && continue
# Listed even while running without its port open yet, as right after
# starting, so it doesn't seem to vanish
systemctl cat "$unit" &> /dev/null || continue
# cockpit.socket and cockpit.service are one service to the user
[[ "$unit" == *.socket && " ${SERVICES_LIST[*]} " == *" system ${unit%.socket}.service "* ]] && continue
[[ "$unit" == *.service && " ${SERVICES_LIST[*]} " == *" system ${unit%.service}.socket "* ]] && continue
SERVICES_LIST+=("system $unit")
done
}
show_ports() {
local key reach note text
echo
if [ "$FIREWALL_ON" = 0 ]; then
echo -e "\e[31mFirewall (firewalld) off: every port listening beyond this machine can be reached.\e[0m"
elif [ "$FIREWALL_UNREAD" = 1 ]; then
echo -e "\e[33mFirewall (firewalld) on, but its rules couldn't be read: ports are shown as if it let everything in.\e[0m"
else
echo "Firewall (firewalld) on: it blocks connections from other machines except to the ports its zones allow."
echo "Ports the LAN can reach show which firewall.conf asks for them (MiniServer's or a sibling project's)."
fi
[ -n "$NO_SUDO" ] && echo "Without sudo rights, the services run by root show as \"?\"."
echo
printf "\e[1m%-7s %-5s %-34s %-26s %s\e[0m\n" PORT PROTO "OPEN TO" SERVICE WHY
local sorted visible
# By port, with the random ports last
mapfile -t sorted < <(for key in "${ROW_KEYS[@]}"; do
printf '%s\t%s\n' "${ROW_PORT[$key]/random/99999}" "$key"; done | sort -n -k1,1 | cut -f2-)
for key in "${sorted[@]}"; do
reach="$(reach_label "${ROW_PROTO[$key]}" "${ROW_PORT[$key]}" "${ROW_KINDS[$key]}" "${ROW_OWNER[$key]}")"
# printf counts the color codes as width, so pad by the visible length
visible="$(sed 's/\\e\[[0-9;]*m//g' <<< "$reach")"
note=""
if [[ "$visible" == *LAN* && "$visible" != *"blocks LAN"* && "$FIREWALL_ON" = 1 && "$FIREWALL_UNREAD" = 0 ]]; then
note="$(conf_note "${ROW_PROTO[$key]}" "${ROW_PORT[$key]}" "${ROW_OWNER[$key]}")"
fi
text="$(why "${ROW_OWNER[$key]}")"
[ -z "$text" ] && note="${note# }"
printf "%-7s %-5s %b%*s %-26s %s%b\n" "${ROW_PORT[$key]}" "${ROW_PROTO[$key]}" \
"$reach" $((34 - ${#visible})) "" "$(owner_label "${ROW_OWNER[$key]}")" "$text" "$note"
done
}
show_services() {
local i scope unit state
echo
echo "Services you can stop or start:"
echo
for i in "${!SERVICES_LIST[@]}"; do
scope="${SERVICES_LIST[$i]%% *}" unit="${SERVICES_LIST[$i]#* }"
if is_active "$scope" "$unit"; then state="\e[32mrunning\e[0m"; else state="\e[33mstopped\e[0m"; fi
printf " %2d) %-30b %b\n" $((i + 1)) "$(owner_label "${SERVICES_LIST[$i]}")" "$state"
done
}
toggle() { # <scope> <unit>
local scope="$1" unit="$2" units=("$2") pair
if [[ " $PROTECTED " == *" $unit "* ]]; then
echo "$unit keeps the server or its network working, so audit won't stop it."
return 1
fi
if [ "$unit" = sshd.service ] && [ -n "$SSH_CONNECTION" ] && is_active "$scope" "$unit"; then
echo "You're logged in over SSH: stopping sshd would lock you out."
return 1
fi
# A service started on demand has a socket unit too; both go together
case "$unit" in
*.socket) pair="${unit%.socket}.service" ;;
*.service) pair="${unit%.service}.socket" ;;
esac
systemctl_for "$scope" cat "$pair" &> /dev/null && units+=("$pair")
mkdir -p "$(dirname "$SERVICES_OFF")"
if is_active "$scope" "$unit"; then
confirm "Stop ${units[*]} and keep it from starting at boot?" || return 0
systemctl_for "$scope" disable --now "${units[@]}" || return 1
for unit in "${units[@]}"; do
grep -qsx "$scope $unit" "$SERVICES_OFF" || echo "$scope $unit" >> "$SERVICES_OFF"
done
else
confirm "Start ${units[*]} and start it at boot?" || return 0
systemctl_for "$scope" enable --now "${units[@]}" || return 1
for unit in "${units[@]}"; do
sed -i "\|^$scope $unit\$|d" "$SERVICES_OFF" 2> /dev/null
done
wait_for_ports "$2"
fi
}
# A service takes a moment to open its ports once started: wait up to 10
# seconds for its known ones, so the table shows them
wait_for_ports() { # <unit>
local ports port proto
ports="$(known_field "$1" 2)" || return 0
for port in ${ports//,/ }; do
proto="${port#*/}" port="${port%/*}"
echo "Waiting for $1 to open port $port..."
for _ in $(seq 40); do
[ -n "$(ss -lnH "--$proto" "sport = :$port" 2> /dev/null)" ] && break
sleep 0.25
done
done
}
confirm() {
local answer
read -rp "$1 [y/N] " answer
[[ "$answer" == [yY]* ]]
}
case "$1" in
""|-l|--list) ;;
-h|--help) usage; exit 0 ;;
*) usage; exit 2 ;;
esac
# Root's sockets name their process only to root
SS="sudo ss"
NO_SUDO=""
if ! sudo -v; then
SS="ss"
NO_SUDO=1
fi
while true; do
load_firewall
load_firewall_confs
load_ports
show_ports
[ -n "$1" ] && exit 0
load_services
show_services
echo
echo " q) Quit"
echo
while true; do
read -rp "Choose a service to stop or start: " choice || exit 0
case "$choice" in
q|Q|"") exit 0 ;;
*[!0-9]*) ;;
*)
if (( choice >= 1 && choice <= ${#SERVICES_LIST[@]} )); then
entry="${SERVICES_LIST[$((choice - 1))]}"
toggle "${entry%% *}" "${entry#* }"
break
fi ;;
esac
echo "Choose a number from the list, or q."
done
done
}
+5 -2
View File
@@ -4,11 +4,14 @@
set -e
# Provides enable_services
source "$HOME/.local/share/MiniServer/utils.sh"
echo "Installing Avahi and nss-mdns..."
sudo pacman -S --noconfirm avahi nss-mdns
echo "Enabling Avahi daemon..."
sudo systemctl enable avahi-daemon.service
# Left off if you turned it off with audit
enable_services avahi-daemon.service
echo "Updating /etc/nsswitch.conf..."
sudo sed -i 's/^hosts:.*/hosts: mymachines mdns_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname dns/' /etc/nsswitch.conf
+3 -2
View File
@@ -76,13 +76,14 @@ cd ~/.local/share/MiniServer
# Source utility functions (provides install_packages, install_script and other helpers)
source utils.sh
# Install the refresh, update, sibling, version and configure scripts to the user's bin
# Install the refresh, update, sibling, version, configure and audit scripts to the user's bin
mkdir -p "$HOME/.local/bin"
install_script "$HOME/.local/share/MiniServer/refresh" "$HOME/.local/bin/refresh"
install_script "$HOME/.local/share/MiniServer/update" "$HOME/.local/bin/update"
install_script "$HOME/.local/share/MiniServer/sibling" "$HOME/.local/bin/sibling"
install_script "$HOME/.local/share/MiniServer/version" "$HOME/.local/bin/version"
install_script "$HOME/.local/share/MiniServer/configure" "$HOME/.local/bin/configure"
install_script "$HOME/.local/share/MiniServer/audit" "$HOME/.local/bin/audit"
# Replaced by refresh and update
rm -f "$HOME/.local/bin/miniserverrefresh" "$HOME/.local/bin/miniserverupdate"
@@ -142,7 +143,7 @@ cd ~/.local/share/MiniServer
# Set up Cockpit (installed with the utilities above)
setup_cockpit
echo "Cockpit is at https://$(uname -n).local:9090"
service_turned_off cockpit.socket || echo "Cockpit is at https://$(uname -n).local:9090"
# Set up firewalld (installed with the utilities above)
setup_firewall || echo "Some firewall rules could not be applied, see above. Run 'update' to retry."
+1
View File
@@ -51,6 +51,7 @@ install_script "$MINISERVER/refresh" ~/.local/bin/refresh
install_script "$MINISERVER/sibling" ~/.local/bin/sibling
install_script "$MINISERVER/version" ~/.local/bin/version
install_script "$MINISERVER/configure" ~/.local/bin/configure
install_script "$MINISERVER/audit" ~/.local/bin/audit
if [[ -z "$UPDATE_REEXECED" ]]; then
export UPDATE_REEXECED=1
+26 -6
View File
@@ -44,12 +44,29 @@ remove_packages() {
fi
}
# Function to enable systemd services if not already enabled
# Services you turned off with audit, as "<system|user> <unit>" lines
SERVICES_OFF="$HOME/.config/miniserver/services-off"
# True if you turned off the system service <unit> with audit
service_turned_off() {
grep -qsx "system $1" "$SERVICES_OFF"
}
# Function to enable systemd services if not already enabled. With --now, also
# start them. Services you turned off with audit are left off.
enable_services() {
local now=()
if [ "$1" = "--now" ]; then
now=(--now)
shift
sudo systemctl daemon-reload
fi
for service in "$@"; do
if ! systemctl is-enabled "$service" &> /dev/null; then
if service_turned_off "$service"; then
echo "$service is turned off (see audit)."
elif ! systemctl is-enabled "$service" &> /dev/null; then
echo "Enabling $service..."
sudo systemctl enable "$service"
sudo systemctl enable "${now[@]}" "$service"
else
echo "$service is already enabled."
fi
@@ -97,10 +114,13 @@ install_script() {
tmp="$(mktemp "$2.XXXXXX")" && cp "$1" "$tmp" && chmod 755 "$tmp" && mv -f "$tmp" "$2"
}
# Start Cockpit. Safe to re-run: does nothing once set up. The cockpit package
# itself comes from packages.conf; its firewall port is in firewall.conf.
# Start Cockpit, unless you turned it off with audit. Safe to re-run: does
# nothing once set up. The cockpit package itself comes from packages.conf; its
# firewall port is in firewall.conf.
setup_cockpit() {
if ! systemctl is-enabled --quiet cockpit.socket || ! systemctl is-active --quiet cockpit.socket; then
if service_turned_off cockpit.socket; then
echo "Cockpit is turned off (see audit)."
elif ! systemctl is-enabled --quiet cockpit.socket || ! systemctl is-active --quiet cockpit.socket; then
echo "Enabling Cockpit..."
sudo systemctl enable --now cockpit.socket
fi