chore: enforce shellcheck across the repo
Establish shellcheck as a mandatory pre-commit quality gate and bring all 93
shell scripts to a clean state.
- tests/shellcheck.sh: wrapper that runs koalaman/shellcheck:stable via Docker
(no native binary needed), skips vendored + upstream librenms-agent scripts.
- .shellcheckrc: documents intentional codebase-wide disables (dynamic source
paths SC1090/SC1091, client-side ssh expansion SC2029).
- AGENTS.md: new Git Policy rule mandating clean shellcheck for every shell
script before commit.
Fixes applied (real bugs + quality): missing quote in netinfra/gather-configs.sh
(caused cascading parse errors), unquoted expansions, declare-and-assign masking,
egrep -> grep -E, $FUNCNAME array indexing, unused variable removal, cd || exit.
Intentional patterns (sourced config, sysfs/ps diagnostics, ssh heredocs that
expand local config) get justified targeted disables.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# ShellCheck configuration for PFVCluster
|
||||
# (used when running `shellcheck` directly; tests/shellcheck.sh applies the
|
||||
# same disables via -e for consistent results under Docker)
|
||||
#
|
||||
# These checks are DISABLED because they flag intentional conventions of this
|
||||
# codebase, not bugs:
|
||||
#
|
||||
# SC1090 / SC1091 — cannot follow dynamically-computed `source` paths. The KNEL
|
||||
# framework (vendor/) and test harness source helpers via computed include
|
||||
# dirs, which shellcheck cannot resolve statically.
|
||||
# SC2029 — ssh orchestration (tests/remote.sh and perf/k8s/dns scripts)
|
||||
# deliberately builds and expands the remote command on the CLIENT side before
|
||||
# sending it. That is the whole point of the single-chokepoint remote pattern.
|
||||
disable=SC1090,SC1091,SC2029
|
||||
|
||||
# Treat external-sourced files as bash (matches #!/usr/bin/env bash framework).
|
||||
external-sources=true
|
||||
@@ -47,6 +47,17 @@ vendor/ Vendored KNELShellFramework
|
||||
2. **Atomic commits.** Each commit coherent on its own.
|
||||
3. **Conventional format**: `feat(scope): desc`, `fix(scope): desc`,
|
||||
`docs: desc`, `refactor(scope): desc`, `test(scope): desc`.
|
||||
4. **All shell scripts MUST pass `shellcheck` before commit.** No exceptions.
|
||||
Run it via the wrapper:
|
||||
```bash
|
||||
bash tests/shellcheck.sh # whole repo
|
||||
bash tests/shellcheck.sh ups/*.sh # specific files
|
||||
```
|
||||
This invokes `koalaman/shellcheck:stable` through Docker (no native binary
|
||||
needed). Fix every reported finding — including `info`-level — or add a
|
||||
targeted `# shellcheck disable=SCxxxx # <reason>` directive with a
|
||||
justification. A script that emits any diagnostic is a protocol violation.
|
||||
Non-bash scripts (PHP with `.sh` shebang `#!/usr/bin/php`, etc.) are exempt.
|
||||
|
||||
## Automatic Gardening Protocol
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
# shellcheck disable=SC2010,SC2012 # diagnostic script; ls|grep/ls -la on sysfs & log dirs is intentional for human-readable output
|
||||
#
|
||||
# console/discover.sh — READ-ONLY discovery of console setup on pfv-tsys4
|
||||
#
|
||||
|
||||
@@ -33,8 +33,6 @@ CONMAN_LOGDIR="${CONMAN_LOGDIR:-/var/log/conman}"
|
||||
UDEV_RULES="/etc/udev/rules.d/99-console-ports.rules"
|
||||
SER2NET_CONF="/etc/ser2net.yaml"
|
||||
CONMAN_CONF="/etc/conman.conf"
|
||||
CONMAN_CONSOLES="/etc/conman/console-consoles.conf"
|
||||
CONSOLE_DEV_DIR="/dev/console"
|
||||
|
||||
echo "============================================"
|
||||
echo " Console Config Generator"
|
||||
@@ -120,9 +118,11 @@ for entry in "${ENTRIES[@]}"; do
|
||||
# Build the full ID_PATH match. The mapping stores a substring like "usb-0:1.5.4.4"
|
||||
# The actual ID_PATH is like "pci-0000:00:1a.0-usb-0:1.5.4.4:1.0"
|
||||
# We match on the substring to be portable across PCI bus changes.
|
||||
echo "" >> "$UDEV_RULES"
|
||||
echo "# $name (TCP $tcp_port): $comment" >> "$UDEV_RULES"
|
||||
echo "SUBSYSTEM==\"tty\", ENV{ID_PATH}==\"*$id_path*\", SYMLINK+=\"console/$name\"" >> "$UDEV_RULES"
|
||||
{
|
||||
echo ""
|
||||
echo "# $name (TCP $tcp_port): $comment"
|
||||
echo "SUBSYSTEM==\"tty\", ENV{ID_PATH}==\"*$id_path*\", SYMLINK+=\"console/$name\""
|
||||
} >> "$UDEV_RULES"
|
||||
done
|
||||
|
||||
echo " Written: $UDEV_RULES"
|
||||
@@ -148,13 +148,13 @@ fi
|
||||
echo "#"
|
||||
echo "# All ports use telnet(rfc2217) accepter so conman and telnet clients"
|
||||
echo "# negotiate proper telnet binary mode — this prevents CR stripping"
|
||||
echo "# and stair-stepping on devices that send \\n\\r (LF+CR) line endings."
|
||||
printf '%s\n' "# and stair-stepping on devices that send \\n\\r (LF+CR) line endings."
|
||||
echo "# Ports bound to Tailscale IP ($TS_IP) for secure remote access."
|
||||
echo "#"
|
||||
echo "# Direct telnet: telnet $TS_IP 2001"
|
||||
echo "# Via conman: conman -f <name>"
|
||||
echo ""
|
||||
echo "define: &banner \\r\\nPFV console port \\p device \\d [\\B]\\r\\n\\r\\n"
|
||||
printf '%s\n' "define: &banner \\r\\nPFV console port \\p device \\d [\\B]\\r\\n\\r\\n"
|
||||
echo ""
|
||||
|
||||
for entry in "${ENTRIES[@]}"; do
|
||||
|
||||
+3
-6
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
# shellcheck disable=SC2010 # diagnostic; ls|grep on /dev listing is intentional
|
||||
#
|
||||
# console/setup.sh — deploy console management on pfv-tsys4
|
||||
#
|
||||
@@ -19,10 +20,6 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
UDEV_RULES="/etc/udev/rules.d/99-console-ports.rules"
|
||||
SER2NET_CONF="/etc/ser2net.yaml"
|
||||
CONMAN_CONSOLES="/etc/conman/console-consoles.conf"
|
||||
CONSOLE_DEV_DIR="/dev/consoles"
|
||||
|
||||
echo "============================================"
|
||||
echo " Console Management Setup"
|
||||
@@ -100,7 +97,7 @@ if [ ! -d /dev/consoles ] || [ -z "$(ls /dev/consoles/ 2>/dev/null)" ]; then
|
||||
line="${line%%#*}"
|
||||
line="$(echo "$line" | xargs)"
|
||||
[ -z "$line" ] && continue
|
||||
IFS='|' read -r tcp_port name id_path baud comment <<< "$line"
|
||||
IFS='|' read -r _ name id_path _ _ <<< "$line"
|
||||
# Find the ttyUSB whose ID_PATH contains the mapping's id_path substring
|
||||
for tty in /dev/ttyUSB*; do
|
||||
[ -e "$tty" ] || continue
|
||||
@@ -124,7 +121,7 @@ while IFS= read -r line; do
|
||||
line="${line%%#*}"
|
||||
line="$(echo "$line" | xargs)"
|
||||
[ -z "$line" ] && continue
|
||||
IFS='|' read -r tcp_port name id_path baud comment <<< "$line"
|
||||
IFS='|' read -r _ name id_path _ _ <<< "$line"
|
||||
if [ -e "/dev/consoles/$name" ]; then
|
||||
TARGET=$(readlink -f "/dev/consoles/$name")
|
||||
echo " [OK] /dev/consoles/$name -> $TARGET"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
# shellcheck disable=SC2012,SC2001 # diagnostic script; ls -la listings and sed line-prefixing are intentional
|
||||
#
|
||||
# console/validate-conman.sh — verify conman can actually reach devices via
|
||||
# ser2net TCP ports and is capturing log output to files.
|
||||
|
||||
@@ -35,7 +35,7 @@ mkdir -p "$ZONE_DIR"
|
||||
log "Syncing zones from $PRIMARY_HOST..."
|
||||
if rsync -az --delete --temp-dir=/tmp \
|
||||
"${PRIMARY_HOST}:$ZONE_DIR/" "$ZONE_DIR/" >> "$LOG_FILE" 2>&1; then
|
||||
zone_count=$(ls "$ZONE_DIR" | wc -l)
|
||||
zone_count=$(find "$ZONE_DIR" -maxdepth 1 -type f | wc -l)
|
||||
log "Sync complete: $zone_count zones"
|
||||
else
|
||||
log "ERROR: rsync failed (rc=$?)"
|
||||
|
||||
@@ -69,9 +69,9 @@ echo " Production zones: $prod_zones"
|
||||
echo " Primary (01) zones: $pri_zones"
|
||||
echo " Secondary (02) zones: $sec_zones"
|
||||
|
||||
[ "$prod_zones" -gt 0 ] 2>/dev/null && ok "Production has $prod_zones zones" || fail "Production zone count invalid"
|
||||
[ "$pri_zones" -gt 0 ] 2>/dev/null && ok "Primary has $pri_zones zones" || fail "Primary zone count invalid"
|
||||
[ "$sec_zones" -gt 0 ] 2>/dev/null && ok "Secondary has $sec_zones zones" || fail "Secondary zone count invalid"
|
||||
if [ "$prod_zones" -gt 0 ] 2>/dev/null; then ok "Production has $prod_zones zones"; else fail "Production zone count invalid"; fi
|
||||
if [ "$pri_zones" -gt 0 ] 2>/dev/null; then ok "Primary has $pri_zones zones"; else fail "Primary zone count invalid"; fi
|
||||
if [ "$sec_zones" -gt 0 ] 2>/dev/null; then ok "Secondary has $sec_zones zones"; else fail "Secondary zone count invalid"; fi
|
||||
|
||||
if [ "$pri_zones" = "$prod_zones" ]; then
|
||||
ok "Primary zone count matches production ($pri_zones)"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
# shellcheck disable=SC2034 # sourced config file; variables are consumed by scripts that source this
|
||||
# k8s/env.sh — shared config for all k8s scripts. Source this.
|
||||
#
|
||||
# All cluster communication goes over Tailscale IPs. No LAN IPs, ever.
|
||||
|
||||
@@ -35,6 +35,7 @@ echo "============================================"
|
||||
echo ""
|
||||
echo "--- [1/5] Installing bootstrap node: $CNODE1_NAME ($CNODE1_IP) ---"
|
||||
|
||||
# shellcheck disable=SC2087 # heredoc intentionally expands local config (node IPs, k3s version) before sending to remote
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_USER}@${CNODE1_IP}" "sudo -n bash -s" <<REMOTE_BOOT
|
||||
set -euo pipefail
|
||||
export INSTALL_K3S_VERSION="$K3S_VERSION"
|
||||
@@ -103,6 +104,7 @@ for node_ip in "$CNODE2_IP" "$CNODE3_IP"; do
|
||||
echo ""
|
||||
echo "--- [4/5] Joining server: $node_name ($node_ip) ---"
|
||||
|
||||
# shellcheck disable=SC2087 # heredoc intentionally expands local config before sending to remote
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_USER}@${node_ip}" "sudo -n bash -s" <<REMOTE_JOIN
|
||||
set -euo pipefail
|
||||
export INSTALL_K3S_VERSION="$K3S_VERSION"
|
||||
|
||||
@@ -71,6 +71,7 @@ for node_ip in "$CNODE2_IP" "$CNODE3_IP"; do
|
||||
echo ""
|
||||
echo "--- [3/4] Joining server: $node_name ($node_ip) ---"
|
||||
|
||||
# shellcheck disable=SC2087 # heredoc intentionally expands local config before sending to remote
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_USER}@${node_ip}" "sudo -n bash -s" <<REMOTE_JOIN
|
||||
set -euo pipefail
|
||||
export INSTALL_K3S_VERSION="$K3S_VERSION"
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ echo "--- Workload isolation ---"
|
||||
USER_PODS=$(kubectl get pods -A --field-selector spec.nodeName="${CNODE1_NAME}" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null | wc -w)
|
||||
# Subtract system pods
|
||||
SYSTEM_PODS=$(kubectl get pods -A --field-selector spec.nodeName="${CNODE1_NAME}" -l k8s-app --no-headers 2>/dev/null | wc -l)
|
||||
if [ "$USER_PODS" -le 10 ]; then ok "Only system pods on cnodes (expected)"; else fail "Unexpected pods on $CNODE1_NAME"; fi
|
||||
if [ "$((USER_PODS - SYSTEM_PODS))" -le 0 ]; then ok "Only system pods on cnodes (expected)"; else fail "Unexpected pods on $CNODE1_NAME"; fi
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2012 # diagnostic baseline script; ls -la listings are intentional
|
||||
# baseline.sh — quick read-only baseline of a target node.
|
||||
set -u
|
||||
hdr() { printf '\n=== %s ===\n' "$1"; }
|
||||
|
||||
@@ -37,9 +37,11 @@ for f in setupVars.conf pihole-FTL.conf adlists.list custom.list local.list rege
|
||||
echo "--- /etc/pihole/$f ---"; $DG exec pihole cat "/etc/pihole/$f" 2>&1 || true
|
||||
done
|
||||
echo "-- /etc/dnsmasq.d/* --"
|
||||
# shellcheck disable=SC2016 # $f/$t expand inside the container's sh, not locally — single quotes are intentional
|
||||
$DG exec pihole sh -c 'for f in /etc/dnsmasq.d/*; do echo "--- $f ---"; cat "$f"; done' 2>&1 || true
|
||||
echo "-- pihole version --"; $DG exec pihole pihole -v 2>&1 || true
|
||||
echo "-- gravity row counts --"
|
||||
# shellcheck disable=SC2016 # $t expands inside the container's sh, not locally
|
||||
$DG exec pihole sh -c 'for t in adlist domainlist client "group" info; do printf "%s=" "$t"; sqlite3 /etc/pihole/gravity.db "SELECT COUNT(*) FROM $t;" 2>/dev/null; done' 2>&1 || true
|
||||
echo "-- adlist addresses --"
|
||||
$DG exec pihole sqlite3 /etc/pihole/gravity.db "SELECT address,enabled,comment FROM adlist;" 2>&1 || true
|
||||
|
||||
@@ -44,7 +44,7 @@ if command -v sqlite3 >/dev/null 2>&1; then
|
||||
echo "-- adlist count --"; sqlite3 -readonly "$G" "SELECT COUNT(*) FROM adlist;" 2>&1
|
||||
echo "-- domainlist count by type --"; sqlite3 -readonly "$G" "SELECT type,COUNT(*) FROM domainlist GROUP BY type;" 2>&1
|
||||
echo "-- domainlist (allow=0/allow_exact, deny=1/deny_exact, etc.) first 80 --"; sqlite3 -readonly "$G" "SELECT type,domain,enabled,comment FROM domainlist LIMIT 80;" 2>&1
|
||||
echo -- client --"; sqlite3 -readonly "$G" "SELECT ip,comment FROM client;" 2>&1
|
||||
echo "-- client --"; sqlite3 -readonly "$G" "SELECT ip,comment FROM client;" 2>&1
|
||||
echo "-- group --"; sqlite3 -readonly "$G" "SELECT id,name,enabled,comment FROM 'group';" 2>&1
|
||||
echo "-- info --"; sqlite3 -readonly "$G" "SELECT * FROM info;" 2>&1
|
||||
else
|
||||
|
||||
@@ -34,7 +34,6 @@ set -euo pipefail
|
||||
|
||||
NETBOOT="localuser@pfv-netboot"
|
||||
SVC_ROOT="/home/localuser/services"
|
||||
PIHOLE_PW='REDACTED_PASSWORD' # replicated verbatim from netboot compose
|
||||
|
||||
log() { printf '\n\033[1;36m[%s]\033[0m %s\n' "$(date +%H:%M:%S)" "$*" >&2; }
|
||||
warn() { printf '\n\033[1;33m[WARN %s]\033[0m %s\n' "$(date +%H:%M:%S)" "$*" >&2; }
|
||||
|
||||
@@ -41,6 +41,7 @@ trap 'rm -rf "$TMP"' EXIT
|
||||
# plus all error-like counters. Falls back gracefully if a counter doesn't
|
||||
# exist (different NIC drivers expose different names).
|
||||
nic_snapshot() {
|
||||
# shellcheck disable=SC2013 # interface names contain no spaces; word-splitting is safe here
|
||||
for s in $(awk '/^Slave Interface:/{print $3}' "/proc/net/bonding/$BOND"); do
|
||||
[ -n "$s" ] || continue
|
||||
echo "[$s]"
|
||||
@@ -144,9 +145,10 @@ echo "Bond: $BOND"
|
||||
echo ""
|
||||
echo "--- bond0 hash + driver ---"
|
||||
grep -E "Bonding Mode|Transmit Hash|Number of ports|Partner Mac" /proc/net/bonding/$BOND
|
||||
# shellcheck disable=SC2013 # interface names contain no spaces; word-splitting is safe here
|
||||
for s in $(awk '/^Slave Interface:/{print $3}' /proc/net/bonding/$BOND); do
|
||||
drv=$(ethtool -i "$s" 2>/dev/null | awk -F: '/^driver:/{print $2}' | sed 's/^ *//')
|
||||
speed=$(cat /sys/class/net/$s/speed 2>/dev/null)
|
||||
speed=$(cat "/sys/class/net/$s/speed" 2>/dev/null)
|
||||
ring=$(ethtool -g "$s" 2>/dev/null | awk '/RX:/{print $2; exit}')
|
||||
printf " %-8s driver=%-20s speed=%-6s current RX ring=%s\n" "$s" "$drv" "$speed" "$ring"
|
||||
done
|
||||
|
||||
@@ -68,6 +68,7 @@ echo "[$(ts)] switch=$SWITCH device=$DEVICE baud=$BAUD host=$HOST"
|
||||
|
||||
# 1. De-conflict: any local ssh to pfv-tsys4 in flight?
|
||||
echo "[$(ts)] checking for in-flight ssh to pfv-tsys4..."
|
||||
# shellcheck disable=SC2009 # intentional: need full ps columns filtered by process args
|
||||
if ps -eo pid,etime,args | grep -E 'ssh.*pfv-tsys4|scp.*pfv-tsys4' | grep -v grep >/tmp/.swcap.ps 2>&1; then
|
||||
cat /tmp/.swcap.ps
|
||||
echo "[$(ts)] ABORT: another ssh/scp to pfv-tsys4 is running (other agent?)." >&2
|
||||
|
||||
@@ -5,6 +5,7 @@ set -u
|
||||
|
||||
# De-conflict: any ssh to pfv-tsys4 right now?
|
||||
echo "===== LOCAL ssh activity ====="
|
||||
# shellcheck disable=SC2009 # intentional: need full ps columns filtered by process args
|
||||
ps -eo pid,etime,args | grep -E 'ssh.*pfv-tsys' | grep -v grep || echo "(none to pfv-tsys4)"
|
||||
|
||||
echo
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
set -u
|
||||
|
||||
echo "===== LOCAL ssh/scp activity (other-agent de-confliction) ====="
|
||||
# shellcheck disable=SC2009 # intentional: need full ps columns (etime,args) filtered by process args
|
||||
ps -eo pid,ppid,etime,user,args | grep -E 'ssh|scp' | grep -v grep || echo "(none)"
|
||||
|
||||
echo
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# shellcheck.sh - permanent wrapper to lint every shell script in this project.
|
||||
# Lint wrapper - permanent wrapper to lint every shell script in this project.
|
||||
#
|
||||
# Uses the koalaman/shellcheck:stable docker image so nothing is installed
|
||||
# on the host. Run from anywhere; lints scripts/ and any .sh under switches/.
|
||||
@@ -45,7 +45,7 @@ echo
|
||||
# Make paths relative to ROOT so docker volume maps cleanly
|
||||
REL_TARGETS=()
|
||||
for t in "${TARGETS[@]}"; do
|
||||
rel="${t#$ROOT/}"
|
||||
rel="${t#"$ROOT"/}"
|
||||
[ "$rel" = "$t" ] && rel="$t"
|
||||
REL_TARGETS+=("$rel")
|
||||
done
|
||||
@@ -61,5 +61,5 @@ if [ "$STRICT" -eq 1 ]; then
|
||||
exit $RC
|
||||
fi
|
||||
# Non-strict: only fail on parse errors / errors, not style notes.
|
||||
# shellcheck exit code 1 means "findings"; re-run with severity to distinguish.
|
||||
# (a shellcheck exit code of 1 means "findings"; re-run with severity to distinguish.)
|
||||
exit 0
|
||||
|
||||
@@ -75,8 +75,7 @@ for HOST in "$@"; do
|
||||
for vmid in $VM_LIST; do
|
||||
name=$(ssh "${SSH_OPTS[@]}" "root@$HOST" "qm config $vmid 2>/dev/null | awk -F: '/^name:/{gsub(/^ /,\"\");print \$2}'")
|
||||
echo -n " [$vmid $name] starting... "
|
||||
start_output=$(ssh "${SSH_OPTS[@]}" "root@$HOST" "qm start $vmid" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
if start_output=$(ssh "${SSH_OPTS[@]}" "root@$HOST" "qm start $vmid" 2>&1); then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAILED: $start_output"
|
||||
@@ -115,8 +114,7 @@ for HOST in "$@"; do
|
||||
if [ "$agent" = "1" ]; then
|
||||
name=$(ssh "${SSH_OPTS[@]}" "root@$HOST" "qm config $vmid 2>/dev/null | awk -F: '/^name:/{gsub(/^ /,\"\");print \$2}'")
|
||||
echo -n " [$vmid $name] agent ping... "
|
||||
result=$(ssh "${SSH_OPTS[@]}" "root@$HOST" "timeout 10 qm agent $vmid ping 2>&1")
|
||||
if [ $? -eq 0 ]; then
|
||||
if ssh "${SSH_OPTS[@]}" "root@$HOST" "timeout 10 qm agent $vmid ping" >/dev/null 2>&1; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "no response (VM may still be booting)"
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ udevadm trigger --subsystem-match=tty 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
if [ -e "/dev/${PDU_DEV_NAME}" ]; then
|
||||
echo " Device symlink active: /dev/${PDU_DEV_NAME} -> $(readlink -f /dev/${PDU_DEV_NAME})"
|
||||
echo " Device symlink active: /dev/${PDU_DEV_NAME} -> $(readlink -f "/dev/${PDU_DEV_NAME}")"
|
||||
else
|
||||
echo " WARNING: /dev/${PDU_DEV_NAME} not found yet. Adapter may be unplugged."
|
||||
echo " Falling back to /dev/ttyUSB* discovery..."
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
#magic to detect main int
|
||||
echo "Determining management interface..."
|
||||
#export MAIN_INT=$(brctl show $(netstat -rn|grep 0.0.0.0|head -n1|awk '{print $NF}') | awk '{print $NF}'|tail -1|awk -F '.' '{print $1}')
|
||||
export MAIN_INT=$(brctl show|grep vmbr0|awk '{print $NF}'|awk -F '.' '{print $1}')
|
||||
MAIN_INT=$(brctl show|grep vmbr0|awk '{print $NF}'|awk -F '.' '{print $1}')
|
||||
export MAIN_INT
|
||||
|
||||
echo "Management interface is: $MAIN_INT"
|
||||
|
||||
#fix the issue
|
||||
echo "Fixing management interface..."
|
||||
ethtool -K $MAIN_INT tso off
|
||||
ethtool -K $MAIN_INT gro off
|
||||
ethtool -K $MAIN_INT gso off
|
||||
ethtool -K $MAIN_INT tx off
|
||||
ethtool -K $MAIN_INT rx off
|
||||
ethtool -K "$MAIN_INT" tso off
|
||||
ethtool -K "$MAIN_INT" gro off
|
||||
ethtool -K "$MAIN_INT" gso off
|
||||
ethtool -K "$MAIN_INT" tx off
|
||||
ethtool -K "$MAIN_INT" rx off
|
||||
|
||||
#https://forum.proxmox.com/threads/e1000-driver-hang.58284/
|
||||
#https://serverfault.com/questions/616485/e1000e-reset-adapter-unexpectedly-detected-hardware-unit-hang
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
# auth-cloudron-ldap.sh — placeholder module (Cloudron LDAP auth integration).
|
||||
# Intentionally empty; populated when the auth stack is deployed.
|
||||
true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
# shellcheck disable=SC2103 # legacy R&D build script; cd/cd- sequence is intentional
|
||||
|
||||
#Made from instructions at https://www.tunetheweb.com/performance/http2/
|
||||
|
||||
@@ -24,17 +25,17 @@ CURL_FILE="curl-7.60.0.tar.gz"
|
||||
#Download and install latest version of openssl
|
||||
wget $OPENSSL_URL_BASE/$OPENSSL_FILE
|
||||
tar xzf $OPENSSL_FILE
|
||||
cd openssl-1.1.0h
|
||||
cd openssl-1.1.0h || exit
|
||||
./config enable-weak-ssl-ciphers shared zlib-dynamic -DOPENSSL_TLS_SECURITY_LEVEL=0 --prefix=/usr/local/custom-ssl/openssl-1.1.0h ; make ; make install
|
||||
ln -s /usr/local/custom-ssl/openssl-1.1.0h /usr/local/openssl
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
#Download and install nghttp2 (needed for mod_http2).
|
||||
wget $NGHTTP_URL_BASE/$NGHTTP_FILE
|
||||
tar xzf $NGHTTP_FILE
|
||||
cd nghttp2-1.31.0
|
||||
cd nghttp2-1.31.0 || exit
|
||||
./configure --prefix=/usr/local/custom-ssl/nghttp ; make ; make install
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
#Updated ldconfig so curl build
|
||||
|
||||
@@ -48,34 +49,34 @@ ldconfig
|
||||
#Download and install curl
|
||||
wget $CURL_URL_BASE/$CURL_FILE
|
||||
tar xzf curl-7.60.0.tar.gz
|
||||
cd curl-7.60.0
|
||||
cd curl-7.60.0 || exit
|
||||
./configure --prefix=/usr/local/custom-ssl/curl --with-nghttp2=/usr/local/custom-ssl/nghttp/ --with-ssl=/usr/local/custom-ssl/openssl-1.1.0h/ ; make ; make install
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
|
||||
#Download and install latest apr
|
||||
wget $APR_URL_BASE/$APR_FILE
|
||||
tar xzf $APR_FILE
|
||||
cd apr-1.6.3
|
||||
cd apr-1.6.3 || exit
|
||||
./configure --prefix=/usr/local/custom-ssl/apr ; make ; make install
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
#Download and install latest apr-util
|
||||
wget $APR_UTIL_URL_BASE/$APR_UTIL_FILE
|
||||
tar xzf apr-util-1.6.1.tar.gz
|
||||
cd apr-util-1.6.1
|
||||
cd apr-util-1.6.1 || exit
|
||||
./configure --prefix=/usr/local/custom-ssl/apr-util --with-apr=/usr/local/custom-ssl/apr ; make; make install
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
#Download and install apache
|
||||
wget $APACHE_URL_BASE/$APACHE_FILE
|
||||
tar xzf httpd-2.4.33.tar.gz
|
||||
cd httpd-2.4.33
|
||||
cd httpd-2.4.33 || exit
|
||||
cp -r ../apr-1.6.3 srclib/apr
|
||||
cp -r ../apr-util-1.6.1 srclib/apr-util
|
||||
./configure --prefix=/usr/local/custom-ssl/apache --with-ssl=/usr/local/custom-ssl/openssl-1.1.0h/ --with-pcre=/usr/bin/pcre-config --enable-unique-id --enable-ssl --enable-so --with-included-apr --enable-http2 --with-nghttp2=/usr/local/custom-ssl/nghttp/
|
||||
make
|
||||
make install
|
||||
ln -s /usr/local/custom-ssl/apache /usr/local/apache
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ if [ "$SUBODEV_CHECK" = 1 ]; then
|
||||
fi
|
||||
|
||||
export DEV_WORKSTATION_CHECK
|
||||
DEV_WORKSTATION_CHECK="$(hostname | egrep -c 'subopi-dev|CharlesDevServer' || true)"
|
||||
DEV_WORKSTATION_CHECK="$(hostname | grep -Ec 'subopi-dev|CharlesDevServer' || true)"
|
||||
|
||||
if [ "$DEV_WORKSTATION_CHECK" -eq 0 ]; then
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export user_check
|
||||
user_check="$(echo "$curr_user" | grep -c root)"
|
||||
|
||||
|
||||
if [ $user_check -ne 1 ]; then
|
||||
if [ "$user_check" -ne 1 ]; then
|
||||
print_error "Must run as root."
|
||||
error_out
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# shellcheck shell=bash disable=SC2148 # sourced function file (no shebang by design)
|
||||
function pi-detect()
|
||||
{
|
||||
print_info Now running "$FUNCNAME"....
|
||||
print_info Now running "${FUNCNAME[0]}"....
|
||||
if [ -f /sys/firmware/devicetree/base/model ] ; then
|
||||
export IS_RASPI="1"
|
||||
fi
|
||||
@@ -9,5 +9,5 @@ fi
|
||||
if [ ! -f /sys/firmware/devicetree/base/model ] ; then
|
||||
export IS_RASPI="0"
|
||||
fi
|
||||
print_info Completed running "$FUNCNAME"
|
||||
print_info Completed running "${FUNCNAME[0]}"
|
||||
}
|
||||
@@ -60,18 +60,18 @@ LOCALUSER_CHECK="$(getent passwd | grep -c localuser || true)"
|
||||
#######################
|
||||
|
||||
function global-oam() {
|
||||
print_info "Now running $FUNCNAME...."
|
||||
print_info "Now running ${FUNCNAME[0]}...."
|
||||
|
||||
cat "$SCRIPTS_PATH/up2date.sh" >/usr/local/bin/up2date.sh && chmod +x /usr/local/bin/up2date.sh
|
||||
|
||||
bash "$MODULES_PATH/OAM/oam-librenms.sh"
|
||||
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
|
||||
}
|
||||
|
||||
function global-systemServiceConfigurationFiles() {
|
||||
print_info "Now running $FUNCNAME...."
|
||||
print_info "Now running ${FUNCNAME[0]}...."
|
||||
|
||||
cat "$CONFIGFILES_PATH/ZSH/tsys-zshrc" >/etc/zshrc
|
||||
cat "$CONFIGFILES_PATH/SMTP/aliases" >/etc/aliases
|
||||
@@ -79,11 +79,11 @@ function global-systemServiceConfigurationFiles() {
|
||||
|
||||
newaliases
|
||||
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function global-installPackages() {
|
||||
print_info "Now running $FUNCNAME...."
|
||||
print_info "Now running ${FUNCNAME[0]}...."
|
||||
|
||||
# Setup webmin repo, used for RBAC/2fa PAM
|
||||
|
||||
@@ -195,7 +195,7 @@ function global-installPackages() {
|
||||
VIRT_TYPE="$(virt-what)"
|
||||
|
||||
export IS_VIRT_GUEST
|
||||
IS_VIRT_GUEST="$(echo "$VIRT_TYPE" | egrep -c 'hyperv|kvm' || true)"
|
||||
IS_VIRT_GUEST="$(echo "$VIRT_TYPE" | grep -Ec 'hyperv|kvm' || true)"
|
||||
|
||||
export IS_KVM_GUEST
|
||||
IS_KVM_GUEST="$(echo "$VIRT_TYPE" | grep -c 'kvm' || true)"
|
||||
@@ -227,12 +227,12 @@ function global-installPackages() {
|
||||
|
||||
# vault cli
|
||||
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function global-postPackageConfiguration() {
|
||||
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
|
||||
systemctl --now enable auditd
|
||||
|
||||
@@ -253,7 +253,7 @@ function global-postPackageConfiguration() {
|
||||
#This is under test/dev and may fail
|
||||
echo "hi from root to root" | mail -s "hi directly to root from $(hostname)" root
|
||||
|
||||
chsh -s $(which zsh) root
|
||||
chsh -s "$(which zsh)" root
|
||||
|
||||
if [ "$LOCALUSER_CHECK" -gt 0 ]; then
|
||||
chsh -s "$(which zsh)" localuser
|
||||
@@ -312,7 +312,7 @@ function global-postPackageConfiguration() {
|
||||
fi
|
||||
|
||||
export NTP_SERVER_CHECK
|
||||
NTP_SERVER_CHECK="$(hostname | egrep -c 'pfv-netboot|pfvsvrpi|pfv-netinfra' || true)"
|
||||
NTP_SERVER_CHECK="$(hostname | grep -Ec 'pfv-netboot|pfvsvrpi|pfv-netinfra' || true)"
|
||||
|
||||
if [ "$NTP_SERVER_CHECK" -eq 0 ]; then
|
||||
|
||||
@@ -341,7 +341,7 @@ function global-postPackageConfiguration() {
|
||||
tuned-adm profile virtual-guest
|
||||
fi
|
||||
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
####################################################################################################
|
||||
@@ -355,41 +355,41 @@ function global-postPackageConfiguration() {
|
||||
# SSH
|
||||
|
||||
function secharden-ssh() {
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
|
||||
bash "$MODULES_PATH/Security/secharden-ssh.sh"
|
||||
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function secharden-wazuh() {
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
bash "$MODULES_PATH/Security/secharden-wazuh.sh"
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function secharden-2fa() {
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
bash "$MODULES_PATH/Security/secharden-2fa.sh"
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function secharden-scap-stig() {
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
bash "$MODULES_PATH/Security/secharden-scap-stig.sh"
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function secharden-agents() {
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
bash "$MODULES_PATH/Security/secharden-audit-agents.sh"
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
function secharden-auto-upgrades() {
|
||||
print_info "Now running $FUNCNAME"
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
#curl --silent ${DL_ROOT}/Modules/Security/secharden-ssh.sh|$(which bash)
|
||||
print_info "Completed running $FUNCNAME"
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
|
||||
@@ -400,16 +400,16 @@ function secharden-auto-upgrades() {
|
||||
####################################################################################################
|
||||
|
||||
function auth-cloudron-ldap() {
|
||||
print_info "Now running "$FUNCNAME""
|
||||
print_info "Now running ${FUNCNAME[0]}"
|
||||
#curl --silent ${DL_ROOT}/Modules/Auth/auth-cloudron-ldap.sh|$(which bash)
|
||||
print_info "Completed running "$FUNCNAME""
|
||||
print_info "Completed running ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
####################################################################################################
|
||||
# RUn the various functions in the correct order
|
||||
####################################################################################################
|
||||
|
||||
echo >$LOGFILENAME
|
||||
echo >"$LOGFILENAME"
|
||||
|
||||
print_info "Execution starting at $CURRENT_TIMESTAMP..."
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# shellcheck shell=bash disable=SC2148 # sourced .bashrc profile fragment
|
||||
if command -v tmux &> /dev/null && [ -n "$PS1" ] && [[ ! "$TERM" =~ screen ]] && [[ ! "$TERM" =~ tmux ]] && [ -z "$TMUX" ]; then
|
||||
tmux a -t default || exec tmux new -s default && exit;
|
||||
fi
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
# shellcheck shell=bash disable=SC2148 # sourced .bashrc profile fragment
|
||||
export HISTTIMEFORMAT="%m/%d/%Y %T "
|
||||
+2
-1
@@ -52,7 +52,8 @@ function run_test_suite() {
|
||||
|
||||
function run_single_test() {
|
||||
local test_file="$1"
|
||||
local test_name="$(basename "$test_file" .sh)"
|
||||
local test_name
|
||||
test_name="$(basename "$test_file" .sh)"
|
||||
|
||||
print_info "Running test: $test_name"
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../.."
|
||||
|
||||
function test_2fa_packages() {
|
||||
echo "🔍 Testing 2FA package installation..."
|
||||
|
||||
@@ -235,7 +233,8 @@ function test_backup_existence() {
|
||||
|
||||
if [[ -d "$backup_dir" ]]; then
|
||||
# Look for recent 2FA backups
|
||||
local recent_backups=$(find "$backup_dir" -name "2fa-*" -type d -newer /etc/ssh/sshd_config 2>/dev/null | wc -l)
|
||||
local recent_backups
|
||||
recent_backups=$(find "$backup_dir" -name "2fa-*" -type d -newer /etc/ssh/sshd_config 2>/dev/null | wc -l)
|
||||
|
||||
if [[ $recent_backups -gt 0 ]]; then
|
||||
echo "✅ Recent 2FA backup found in $backup_dir"
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/bash
|
||||
#
|
||||
# tests/shellcheck.sh — enforce shellcheck (via Docker) on all repo shell scripts
|
||||
#
|
||||
# Usage:
|
||||
# bash tests/shellcheck.sh # lint every .sh in the repo
|
||||
# bash tests/shellcheck.sh path/a.sh path/b.sh # lint specific files
|
||||
#
|
||||
# Uses the koalaman/shellcheck:stable image (no native binary required).
|
||||
# Exits non-zero if ANY script emits a diagnostic. Skips non-bash scripts
|
||||
# (e.g. PHP files with a .sh extension) and the vendored/ trees.
|
||||
#
|
||||
set -uo pipefail
|
||||
|
||||
IMAGE="koalaman/shellcheck:stable"
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# --- Gather target files -----------------------------------------------------
|
||||
mapfile -t FILES < <(
|
||||
if [ "$#" -gt 0 ]; then
|
||||
# Explicit args: resolve to repo-root-relative paths, keep only .sh
|
||||
for f in "$@"; do
|
||||
f_abs="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"
|
||||
case "$f_abs" in
|
||||
"$ROOT"/vendor/*) ;; # skip vendored
|
||||
"$ROOT"/provisioning/Agents/librenms/*) ;; # upstream librenms-agent scripts
|
||||
*) case "$f_abs" in *.sh) echo "$f_abs";; esac ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
# Default: every .sh under the repo, minus vendor/
|
||||
while IFS= read -r f_abs; do
|
||||
case "$f_abs" in
|
||||
"$ROOT"/vendor/*) ;; # skip vendored
|
||||
"$ROOT"/provisioning/Agents/librenms/*) ;; # upstream librenms-agent scripts
|
||||
*) echo "$f_abs" ;;
|
||||
esac
|
||||
done < <(find "$ROOT" -type f -name '*.sh' \
|
||||
-not -path '*/.git/*' -not -path "$ROOT/vendor/*")
|
||||
fi
|
||||
)
|
||||
|
||||
if [ "${#FILES[@]}" -eq 0 ]; then
|
||||
echo "shellcheck.sh: no .sh files to check." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Filter out non-bash scripts (e.g. PHP with a .sh extension) --------------
|
||||
BASH_FILES=()
|
||||
for f in "${FILES[@]}"; do
|
||||
shebang=$(head -c 64 "$f" 2>/dev/null | head -n1)
|
||||
case "$shebang" in
|
||||
\#!/usr/bin/php*|\#!/usr/bin/env\ php*) ;; # PHP, skip
|
||||
\#!*) BASH_FILES+=("$f") ;; # any other shebang → check
|
||||
*) BASH_FILES+=("$f") ;; # no shebang → check anyway
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${#BASH_FILES[@]}" -eq 0 ]; then
|
||||
echo "shellcheck.sh: no bash scripts among targets." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Run shellcheck in Docker (mount repo root, pass root-relative paths) -----
|
||||
# shellcheck disable=SC2012 # basename loop is intentional
|
||||
REL=()
|
||||
for f in "${BASH_FILES[@]}"; do REL+=("${f#"$ROOT"/}"); done
|
||||
|
||||
# Disable checks that are intentional conventions of this codebase (not bugs):
|
||||
# SC1090/SC1091 — cannot follow dynamically-computed `source` paths (KNEL framework)
|
||||
# SC2029 — ssh orchestration deliberately expands the command client-side
|
||||
DISABLES=(
|
||||
-e SC1090
|
||||
-e SC1091
|
||||
-e SC2029
|
||||
)
|
||||
|
||||
echo "Checking ${#BASH_FILES[@]} script(s) with $IMAGE:"
|
||||
printf ' %s\n' "${REL[@]}"
|
||||
|
||||
docker run --rm \
|
||||
-v "$ROOT:/mnt:ro" \
|
||||
-w /mnt \
|
||||
"$IMAGE" -x "${DISABLES[@]}" "${REL[@]}"
|
||||
rc=$?
|
||||
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
echo "shellcheck: PASS (${#BASH_FILES[@]} scripts clean)"
|
||||
else
|
||||
echo "shellcheck: FAIL (fix the findings above or add targeted disable directives)" >&2
|
||||
fi
|
||||
exit "$rc"
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
# The authoritative pair (pfv-netinfra-01 / pfv-netinfra-02).
|
||||
DNS_PRIMARY="192.168.3.252"
|
||||
DNS_SECONDARY="192.168.3.253"
|
||||
@@ -20,7 +18,6 @@ NTP_CONF="/etc/ntpsec/ntp.conf"
|
||||
# A name every recursive resolver must be able to resolve.
|
||||
DNS_PROBE_NAME="github.com"
|
||||
|
||||
failed=0
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# --- Configuration assertions -------------------------------------------------
|
||||
|
||||
@@ -12,7 +12,8 @@ REQUIRED_COMMANDS=("curl" "wget" "git" "systemctl" "apt-get")
|
||||
|
||||
# Test functions
|
||||
function test_memory_requirements() {
|
||||
local total_mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
|
||||
local total_mem_kb
|
||||
total_mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
|
||||
local total_mem_gb=$((total_mem_kb / 1024 / 1024))
|
||||
|
||||
if [[ $total_mem_gb -ge $MIN_RAM_GB ]]; then
|
||||
@@ -25,7 +26,8 @@ function test_memory_requirements() {
|
||||
}
|
||||
|
||||
function test_disk_space() {
|
||||
local available_gb=$(df / | tail -1 | awk '{print int($4/1024/1024)}')
|
||||
local available_gb
|
||||
available_gb=$(df / | tail -1 | awk '{print int($4/1024/1024)}')
|
||||
|
||||
if [[ $available_gb -ge $MIN_DISK_GB ]]; then
|
||||
echo "✅ Disk space requirement met: ${available_gb}GB >= ${MIN_DISK_GB}GB"
|
||||
@@ -53,8 +55,10 @@ function test_required_commands() {
|
||||
|
||||
function test_os_compatibility() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
local os_id=$(grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
|
||||
local os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
|
||||
local os_id
|
||||
local os_version
|
||||
os_id=$(grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
|
||||
os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
|
||||
|
||||
case "$os_id" in
|
||||
ubuntu|debian)
|
||||
|
||||
Reference in New Issue
Block a user