diff --git a/bin/reboot-ask.py b/bin/reboot-ask.py index db33536..f0a8dd6 100755 --- a/bin/reboot-ask.py +++ b/bin/reboot-ask.py @@ -4,6 +4,7 @@ import sys import getopt import signal +import subprocess from typing import NoReturn from libinithooks.dialog_wrapper import Dialog @@ -22,7 +23,7 @@ def usage(msg: str | getopt.GetoptError = "") -> NoReturn: sys.exit(1) -def main(): +def main() -> None: signal.signal(signal.SIGINT, signal.SIG_IGN) try: opts, _ = getopt.gnu_getopt(sys.argv[1:], "h", ["help"]) @@ -38,6 +39,7 @@ def main(): if not reboot: sys.exit(1) + subprocess.run(["/usr/sbin/reboot"]) if __name__ == "__main__": diff --git a/bin/simplehttpd.py b/bin/simplehttpd.py index 4049f1e..8df39a1 100755 --- a/bin/simplehttpd.py +++ b/bin/simplehttpd.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # Copyright (c) 2012-2015 Liraz Siri -# Copyright (c) 2015-2021 TurnKey GNU/Linux - https://www.turnkeylinux.org +# Copyright (c) 2015-2026 TurnKey GNU/Linux - https://www.turnkeylinux.org """ Simple HTTP server @@ -25,6 +25,7 @@ import os from os.path import exists, abspath, isdir, splitext from tempfile import NamedTemporaryFile +import socket import sys import getopt from typing import NoReturn @@ -110,6 +111,12 @@ def translate_path(self, path: str) -> str: class SimpleWebServer: class TCPServer(socketserver.ForkingTCPServer): allow_reuse_address = True + address_family = socket.AF_INET6 # enables IPv6 + + def server_bind(self): + # Disable IPV6_V6ONLY so the socket accepts IPv4 too (dual-stack) + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + super().server_bind() class HTTPRequestHandler(SecureHTTPRequestHandler): ALLOWED_EXTS = ["css", "gif", "html", "js", "png", "jpg", "txt"] @@ -117,12 +124,62 @@ class HTTPRequestHandler(SecureHTTPRequestHandler): class Address: @staticmethod def parse_address(address: str) -> tuple[str, int]: - if ":" in address: + """Parse address and return listening IP & port. + + Accepts a port number or an IP & port string. Supports both IPv4 + and IPv6. + + Args: + address (str): + Port to listen on (integer as a string) or an IP address + (interface) and port to listen on. + + IP address and port should be separated by a colon (':'). + + IPv6 address should be wrapped in square brakets ('[]'). + + E.g.: + '8080' (port only) + '[dead::beef]:8080' (IPv6 and port) + '123.123.123.123:8080' (IPv4 and port) + + Returns: + tuple[str, int]: + Tuple of IP address (interface) and port. + + If address= then the address will default to + '::' (i.e. all interfaces via IPv4 & IPv6). + + Raises: + SimpleWebServerError: + If address is malformed. + + """ + # Default to all interfaces, IPv4 & IPv6 + host = "::" + # IPv6 with brackets: [::1]:8080 + if address.startswith("["): + try: + bracket_end = address.index("]") + except ValueError: + raise SimpleWebServerError( + f"Malformed IPv6 address: '{address}'" + " - expected closing ']'", + ) + host = address[1:bracket_end] + rest = address[bracket_end + 1:] + if not rest.startswith(":"): + raise SimpleWebServerError( + f"Malformed address: '{address}'" + " - expected ':port' after ']'", + ) + _port = rest[1:] + # IPv4 or bare port (no brackets, at most one colon) + elif address.count(":") == 1: host, _port = address.split(":", 1) + # Bare port number only else: - host = "0.0.0.0" _port = address - try: port = int(_port) assert port > 0 and port < 65535 diff --git a/bin/turnkey-init-fence b/bin/turnkey-init-fence index 167fc63..c846e84 100755 --- a/bin/turnkey-init-fence +++ b/bin/turnkey-init-fence @@ -19,7 +19,10 @@ iptables_delete_redirect() { local to_port=$2 echo "Removing REDIRECT firewall rule: $dport => $to_port" while true; do - (2>&1 iptables -t nat -D PREROUTING -p tcp --dport "$dport" -j REDIRECT --to-port "$to_port") > /dev/null || break + (2>&1 iptables -t nat -D PREROUTING -p tcp --dport "$dport" -j REDIRECT --to-port "$to_port") \ + > /dev/null || break + (2>&1 ip6tables -t nat -D PREROUTING -p tcp --dport "$dport" -j REDIRECT --to-port "$to_port") \ + > /dev/null || break done } @@ -29,6 +32,7 @@ iptables_add_redirect() { echo "Adding REDIRECT firewall rule: $dport => $to_port" iptables_delete_redirect "$dport" "$to_port" iptables -t nat -A PREROUTING -p tcp --dport "$dport" -j REDIRECT --to-port "$to_port" + ip6tables -t nat -A PREROUTING -p tcp --dport "$dport" -j REDIRECT --to-port "$to_port" } iptables_unensure_accept() { @@ -36,7 +40,10 @@ iptables_unensure_accept() { local dport=$1 echo "Removing ACCEPT firewall rule for fence port: $dport" while true; do - (2>&1 iptables -t filter -D INPUT -p tcp -m tcp --dport "$dport" -j ACCEPT) > /dev/null || break + (2>&1 iptables -t filter -D INPUT -p tcp -m tcp --dport "$dport" -j ACCEPT) \ + > /dev/null || break + (2>&1 ip6tables -t filter -D INPUT -p tcp -m tcp --dport "$dport" -j ACCEPT) \ + > /dev/null || break done } @@ -46,6 +53,7 @@ iptables_ensure_accept() { echo "Adding ACCEPT firewall rule for fence port: $dport" iptables_unensure_accept "$dport" iptables -t filter -A INPUT -p tcp -m tcp --dport "$dport" -j ACCEPT + ip6tables -t filter -A INPUT -p tcp -m tcp --dport "$dport" -j ACCEPT } iptables_redirect() { diff --git a/firstboot.d/01ipconfig b/firstboot.d/01ipconfig index 8f3b3ab..e7a027b 100755 --- a/firstboot.d/01ipconfig +++ b/firstboot.d/01ipconfig @@ -3,6 +3,8 @@ # firstboot network interfaces file generation - runs non-interactively. # - config can be customized via inithooks conf file (i.e. preseed) +# TODO: update to also support ipv6 + # shellcheck source=default/inithooks source /etc/default/inithooks diff --git a/firstboot.d/05autogrow-fs b/firstboot.d/05autogrow-fs index 29d7205..79597d4 100755 --- a/firstboot.d/05autogrow-fs +++ b/firstboot.d/05autogrow-fs @@ -1,45 +1,64 @@ #!/bin/bash # auto grow filesystem for block device based appliances - (c) Aug/2014 by Peter Lieven -[ -n "$_TURNKEY_INIT" ] && exit 0 +[[ -z "$_TURNKEY_INIT" ]] || exit 0 -. /etc/default/inithooks - -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi -[ "$AUTOGROW" != "ONCE" ] && [ "$AUTOGROW" != "ALWAYS" ] && exit 0 -[ $(dirname $0) = "/usr/lib/inithooks/everyboot.d" ] && [ "$AUTOGROW" != "ALWAYS" ] && exit 0 +if [[ "$AUTOGROW" != "ONCE" ]] && [[ "$AUTOGROW" != "ALWAYS" ]]; then + exit 0 +fi +if [[ "$(dirname "$0")" == "/usr/lib/inithooks/everyboot.d" ]] \ + && [[ "$AUTOGROW" != "ALWAYS" ]]; then + exit 0 +fi -[ -z "$AUTOGROW_DEV" ] && AUTOGROW_DEV=/dev/vda -[ -z "$AUTOGROW_PART" ] && AUTOGROW_PART="${AUTOGROW_DEV}2" -[ -z "$AUTOGROW_FS" ] && AUTOGROW_FS=/dev/turnkey/root -[ -z "$AUTOGROW_FS_ALWAYS" ] && AUTOGROW_FS_ALWAYS=TRUE +[[ -n "$AUTOGROW_DEV" ]] || AUTOGROW_DEV=/dev/vda +[[ -n "$AUTOGROW_PART" ]] || AUTOGROW_PART="${AUTOGROW_DEV}2" +[[ -n "$AUTOGROW_FS" ]] || AUTOGROW_FS=/dev/turnkey/root +[[ -n "$AUTOGROW_FS_ALWAYS" ]] || AUTOGROW_FS_ALWAYS=TRUE -DEVSIZE=$(blockdev --getsize $AUTOGROW_DEV) +DEVSIZE=$(blockdev --getsize "$AUTOGROW_DEV") -[ "0$DEVSIZE" -eq 0 ] && exit 1 +if [[ "0$DEVSIZE" -eq 0 ]]; then + exit 1 +fi -PARTINFO=$(sfdisk -d -uS $AUTOGROW_DEV | grep -A1 $AUTOGROW_PART | grep -v $AUTOGROW_PART) -if [ -n "$PARTINFO" ]; then - PARTINFO=${PARTINFO/:/ } - PARTINFO=${PARTINFO//,/ } - PARTINFO=${PARTINFO//=/ } - X=$(echo $PARTINFO | grep "start 0 size 0 Id 0") - [ $? -ne 0 ] && exit 1 +PARTINFO=$(sfdisk -d -uS "$AUTOGROW_DEV" \ + | grep -A1 "$AUTOGROW_PART" \ + | grep -v "$AUTOGROW_PART") +if [[ -n "$PARTINFO" ]]; then + PARTINFO=${PARTINFO/:/ } + PARTINFO=${PARTINFO//,/ } + PARTINFO=${PARTINFO//=/ } + if grep -s "start 0 size 0 Id 0" <<<"$PARTINFO"; then + exit 1 + fi fi -PARTINFO=$(sfdisk -d -uS $AUTOGROW_DEV | grep $AUTOGROW_PART) -[ -z "$PARTINFO" ] && exit 1 +PARTINFO=$(sfdisk -d -uS "$AUTOGROW_DEV" | grep "$AUTOGROW_PART") +if [[ -z "$PARTINFO" ]]; then + exit 1 +fi PARTINFO=${PARTINFO/:/ } PARTINFO=${PARTINFO//,/ } PARTINFO=${PARTINFO//=/ } -PARTINFOX=(${PARTINFO}) +PARTINFOX=("${PARTINFO}") -[ "${PARTINFOX[0]}" != "$AUTOGROW_PART" ] && exit 1 -[ "${PARTINFOX[1]}" != "start" ] && exit 1 -[ "${PARTINFOX[3]}" != "size" ] && exit 1 -[ "${PARTINFOX[5]}" != "Id" ] && [ "${PARTINFOX[5]}" != "type" ] && exit 1 +if [[ "${PARTINFOX[0]}" != "$AUTOGROW_PART" ]] \ + || [[ "${PARTINFOX[1]}" != "start" ]] \ + || [[ "${PARTINFOX[3]}" != "size" ]]; then + exit 1 +fi +if [[ "${PARTINFOX[5]}" != "Id" ]] && [[ "${PARTINFOX[5]}" != "type" ]]; then + exit 1 +fi START=${PARTINFOX[2]} SIZE=${PARTINFOX[4]} @@ -47,24 +66,34 @@ ID=${PARTINFOX[6]} [ "$ID" != "8e" ] && exit 1 -if [ $((START + SIZE + 65536)) -lt $DEVSIZE ]; then - [ -e /var/tmp/autogrow.size ] && [ $(cat /var/tmp/autogrow.size) -eq $DEVSIZE ] && exit 1 - echo $DEVSIZE >/var/tmp/autogrow.size - sfdisk -d -uS $AUTOGROW_DEV | grep "${AUTOGROW_DEV}1" >/tmp/sfdisk.dump - echo "$AUTOGROW_PART : start= $START, size= $((DEVSIZE - START)), ${PARTINFOX[5]}= $ID">>/tmp/sfdisk.dump - cat /tmp/sfdisk.dump | sfdisk -uS --no-reread --force $AUTOGROW_DEV - exit 42 -else - if [ "$AUTOGROW_FS" != "SKIP" ]; then - [ -e /var/tmp/autogrow_fs.size ] && [ $(cat /var/tmp/autogrow_fs.size) -eq $DEVSIZE ] && exit 0 - echo $DEVSIZE >/var/tmp/autogrow_fs.size - pvresize $AUTOGROW_PART - [ $(dirname $0) = "/usr/lib/inithooks/everyboot.d" ] && [ "$AUTOGROW_FS_ALWAYS" != "TRUE" ] && exit 0 - lvextend -l+100%FREE $AUTOGROW_FS 2>/dev/null - ERR=$? - [ $ERR -eq 3 ] && exit 0 - [ $ERR -eq 0 ] && resize2fs $AUTOGROW_FS && touch /forcefsck && exit 42 - fi +if [[ $((START + SIZE + 65536)) -lt "$DEVSIZE" ]]; then + if [[ -e /var/tmp/autogrow.size ]] \ + && [[ "$( /var/tmp/autogrow.size + sfdisk -d -uS "$AUTOGROW_DEV" | grep "${AUTOGROW_DEV}1" > /tmp/sfdisk.dump + echo "$AUTOGROW_PART : start= $START, size= $((DEVSIZE - START)), ${PARTINFOX[5]}= $ID" \ + >> /tmp/sfdisk.dump + sfdisk -uS --no-reread --force "$AUTOGROW_DEV" < /tmp/sfdisk.dump + exit 42 +elif [[ "$AUTOGROW_FS" != "SKIP" ]]; then + if [[ -e /var/tmp/autogrow_fs.size ]] \ + && [[ $( /var/tmp/autogrow_fs.size + pvresize "$AUTOGROW_PART" + if [[ "$(dirname "$0")" == "/usr/lib/inithooks/everyboot.d" ]] \ + && [[ "$AUTOGROW_FS_ALWAYS" != "TRUE" ]]; then + exit 0 + fi + lvextend -l+100%FREE "$AUTOGROW_FS" 2>/dev/null || ERR=$? + if [[ "$ERR" -eq 3 ]]; then + exit 0 + fi + if [[ $ERR -eq 0 ]] && resize2fs "$AUTOGROW_FS" && touch /forcefsck; then + exit 42 + fi fi - exit 1 diff --git a/firstboot.d/09hostname b/firstboot.d/09hostname index df88ea4..d2d5acf 100755 --- a/firstboot.d/09hostname +++ b/firstboot.d/09hostname @@ -1,11 +1,14 @@ #!/bin/bash -e # set hostname -. /etc/default/inithooks +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF - -[ -z "$HOSTNAME" ] && exit 0 +[[ -n "$HOSTNAME" ]] || exit 0 old=$(hostname) @@ -24,9 +27,9 @@ for file in \ /etc/motd \ /etc/ssmtp/ssmtp.conf do - [ -f $file ] && sed -i -e "s:$old:$HOSTNAME:g" $file + if [[ -f $file ]]; then + sed -i -e "s:$old:$HOSTNAME:g" $file + fi done -hostname $HOSTNAME - -exit 0 +hostname "$HOSTNAME" diff --git a/firstboot.d/10randomize-cronapt b/firstboot.d/10randomize-cronapt index 283e865..d553651 100755 --- a/firstboot.d/10randomize-cronapt +++ b/firstboot.d/10randomize-cronapt @@ -1,11 +1,11 @@ #!/bin/bash -e # set random hour/minute for security updates (cron-apt) -[ -n "$_TURNKEY_INIT" ] && exit 0 +[[ -z "$_TURNKEY_INIT" ]] || exit 0 # random hour: 0-23, minute: 0-59 -HOUR=$[ ($RANDOM % 23) ] -MINUTE=$[ ($RANDOM % 59) ] +HOUR=$(( RANDOM % 23 )) +MINUTE=$(( RANDOM % 59 )) cat > /etc/cron.d/cron-apt << EOF # cron job for cron-apt package diff --git a/firstboot.d/10randomize-crontab b/firstboot.d/10randomize-crontab index 7e7837f..c985fa9 100755 --- a/firstboot.d/10randomize-crontab +++ b/firstboot.d/10randomize-crontab @@ -1,7 +1,7 @@ #!/bin/bash -[ ! -e /etc/crontab ] && exit 0 -[ -n "$_TURNKEY_INIT" ] && exit 0 +[[ -e /etc/crontab ]] || exit 0 +[[ -z "$_TURNKEY_INIT" ]] || exit 0 getrnd() { echo $(($(hexdump -n 2 -e '"%1u"' /dev/urandom) % $1)) @@ -34,4 +34,3 @@ sed --in-place --regexp-extended \ --expression="s/47(\s*)6(\s*)\*(\s*)\*(\s*)7(\s*)root/${WEEKLY_MINUTE}\1${WEEKLY_HOUR}\2*\3*\47\5root/g" \ --expression="s/52(\s*)6(\s*)1(\s*)\*(\s*)\*(\s*)root/${MONTHLY_MINUTE}\1${MONTHLY_HOUR}\21\3*\4*\5root/g" \ /etc/crontab - diff --git a/firstboot.d/10regen-sshkeys b/firstboot.d/10regen-sshkeys index d178814..4ed7818 100755 --- a/firstboot.d/10regen-sshkeys +++ b/firstboot.d/10regen-sshkeys @@ -2,7 +2,7 @@ # generate new SSH keys # note: ssh daemon needs to be restarted for changes to take effect -[ -n "$_TURNKEY_INIT" ] && exit 0 +[[ -z "$_TURNKEY_INIT" ]] || exit 0 echo "Stopping SSH service to regenerate new keys" systemctl stop ssh.service @@ -14,7 +14,7 @@ keys="rsa ecdsa ed25519" for key in $keys; do file=$tmp_dir/ssh_host_${key}_key echo "Generating new SSH2 ${key^^} key; this may take a while..." - ssh-keygen -q -f "${file}" -N '' -t ${key} + ssh-keygen -q -f "${file}" -N '' -t "${key}" done echo "Moving keys into place" @@ -23,5 +23,3 @@ rm -rf $tmp_dir echo "Restarting SSH service" systemctl start ssh.service - -exit 0 diff --git a/firstboot.d/15regen-sslcert b/firstboot.d/15regen-sslcert index 60a5a8f..2309baf 100755 --- a/firstboot.d/15regen-sslcert +++ b/firstboot.d/15regen-sslcert @@ -1,9 +1,7 @@ #!/bin/bash -e # Regenerate self-signed TLS/SSL cert & key -[[ -n "$_TURNKEY_INIT" ]] && exit 0 - -[[ -e $INITHOOKS_CONF ]] && . "$INITHOOKS_CONF" +[[ -z "$_TURNKEY_INIT" ]] || exit 0 _hook=$(basename "$0") @@ -21,7 +19,7 @@ info "Generating SSL/TLS cert & key." $turnkey_make_ssl_cert --default --force # Restart relevant services -SERVICES=(nginx apache2 lighttpd tomcat10 tomcat11) +SERVICES=(nginx apache2 lighttpd tomcat10 tomcat11 webmin) info "Restarting relevant services." for service in "${SERVICES[@]}"; do @@ -34,5 +32,3 @@ done # final tidy up update-ca-certificates - -exit 0 diff --git a/firstboot.d/29sudoadmin b/firstboot.d/29sudoadmin index c15b1a9..7d2ba10 100755 --- a/firstboot.d/29sudoadmin +++ b/firstboot.d/29sudoadmin @@ -1,11 +1,16 @@ -#!/bin/bash +#!/bin/bash -e -[ -n "$_TURNKEY_INIT" ] && exit 0 +[[ -z "$_TURNKEY_INIT" ]] || exit 0 -. /etc/default/inithooks -if [ "$(echo $SUDOADMIN | tr [A-Z] [a-z] )" = "true" ]; then +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi + +if [[ "${SUDOADMIN,,}" == "true" ]]; then turnkey-sudoadmin on --disable-setpass -elif [ "$(echo $SUDOADMIN | tr [A-Z] [a-z] )" = "false" ]; then +elif [[ "${SUDOADMIN,,}" == "false" ]]; then turnkey-sudoadmin off --disable-setpass fi - diff --git a/firstboot.d/30rootpass b/firstboot.d/30rootpass index 231862a..c4e3544 100755 --- a/firstboot.d/30rootpass +++ b/firstboot.d/30rootpass @@ -1,11 +1,16 @@ #!/bin/bash -e # set root password -USERNAME=root +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi -. /etc/default/inithooks -[ "$(echo $SUDOADMIN | tr [A-Z] [a-z] )" = "true" ] && USERNAME=admin +USERNAME="root" +if [[ "${SUDOADMIN,,}" == "true" ]]; then + USERNAME="admin" +fi -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF $INITHOOKS_PATH/bin/setpass.py $USERNAME --pass="$ROOT_PASS" - diff --git a/firstboot.d/30turnkey-init-fence b/firstboot.d/30turnkey-init-fence index c1370de..6f5073f 100644 --- a/firstboot.d/30turnkey-init-fence +++ b/firstboot.d/30turnkey-init-fence @@ -1,13 +1,18 @@ -#!/bin/bash +#!/bin/bash -e # Note that as of v19.0, the init-fence service is enabled by default -[ -n "$_TURNKEY_INIT" ] && exit 0 +[[ -z "$_TURNKEY_INIT" ]] || exit 0 USERNAME=root - -. /etc/default/inithooks -[ "$(echo $SUDOADMIN | tr [A-Z] [a-z] )" = "true" ] && USERNAME=admin +# shellcheck source=default/inithooks +source /etc/default/inithooks +USERNAME="root" +if [[ "${SUDOADMIN,,}" == "true" ]]; then + USERNAME="admin" +fi PROFILE_FIRSTLOGIN=$(eval printf ~$USERNAME)/.profile.d/turnkey-init-fence -[ -f $PROFILE_FIRSTLOGIN ] && chmod +x $PROFILE_FIRSTLOGIN +if [[ -f "$PROFILE_FIRSTLOGIN" ]]; then + chmod +x "$PROFILE_FIRSTLOGIN" +fi diff --git a/firstboot.d/80hub-services b/firstboot.d/80hub-services index 13c7013..1ca54cd 100755 --- a/firstboot.d/80hub-services +++ b/firstboot.d/80hub-services @@ -1,10 +1,13 @@ #!/bin/bash -e # initialize hub services (tklbam, hubdns) -. /etc/default/inithooks - -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF -[ "$HUB_APIKEY" == "SKIP" ] && exit 0 - +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi +if [[ "$HUB_APIKEY" == "SKIP" ]]; then + exit 0 +fi $INITHOOKS_PATH/bin/hubservices.py --apikey="$HUB_APIKEY" --fqdn="$FQDN" - diff --git a/firstboot.d/85secalerts b/firstboot.d/85secalerts index ade97e0..1ace248 100755 --- a/firstboot.d/85secalerts +++ b/firstboot.d/85secalerts @@ -2,12 +2,17 @@ # enable security alert emails # SEC_ALERTS: SKIP, $EMAIL (if none specified, will be interactive) -. /etc/default/inithooks +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF -[ "$SEC_ALERTS" == "SKIP" ] && exit 0 +if [[ "$SEC_ALERTS" == "SKIP" ]]; then + exit 0 +fi $INITHOOKS_PATH/bin/secalerts.py \ --email="$SEC_ALERTS" \ --email-placeholder="$($INITHOOKS_PATH/bin/inithooks_cache.py APP_EMAIL)" - diff --git a/firstboot.d/95secupdates b/firstboot.d/95secupdates index 1af4dc2..c8f1855 100755 --- a/firstboot.d/95secupdates +++ b/firstboot.d/95secupdates @@ -2,8 +2,12 @@ # install security updates # SEC_UPDATES: SKIP, FORCE (if none specified, will be interactive) -. /etc/default/inithooks -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi # exit if running live grep -qs boot=live /proc/cmdline && exit 2 @@ -15,29 +19,28 @@ install_updates() { fi LOGFILE=/var/log/cron-apt/log - # containers won't have /lib/modules; sending stderr to /dev/null avoids - # displaying error message about missing path + # containers don't have /lib/modules so suppress error message + # shellcheck disable=SC2012 OLDMD5=$(ls -la /lib/modules /boot 2>/dev/null | md5sum) for actionfile in /etc/cron-apt/action.d/*; do - while read aptcmd; do - aptcmd=$(echo $aptcmd | sed "s|-q||") - aptcmd=$(echo $aptcmd | sed "s|-o quiet=.*||") - DEBIAN_FRONTEND=noninteractive apt-get $aptcmd | tee -a $LOGFILE - done < $actionfile + while read -r aptcmd; do + aptcmd="${aptcmd%% -q}" + aptcmd="${aptcmd%% -o quiet=*}" + DEBIAN_FRONTEND=noninteractive apt-get "$aptcmd" | tee -a $LOGFILE + done < "$actionfile" done # per above note re containers + # shellcheck disable=SC2012 NEWMD5=$(ls -la /lib/modules /boot 2>/dev/null | md5sum) - if [ "$NEWMD5" != "$OLDMD5" ]; then + if [[ "$NEWMD5" != "$OLDMD5" ]]; then chmod +x $INITHOOKS_PATH/firstboot.d/99reboot fi } -[ "$SEC_UPDATES" == "SKIP" ] && exit 0 - -if [ "$SEC_UPDATES" == "FORCE" ]; then +if [[ "$SEC_UPDATES" == "SKIP" ]]; then + exit 0 +elif [[ "${SEC_UPDATES,,}" == "force" ]]; then install_updates else $INITHOOKS_PATH/bin/secupdates-ask.py && install_updates fi - -exit 0 diff --git a/firstboot.d/97turnkey-init-fence-disable b/firstboot.d/97turnkey-init-fence-disable index 3cf9ab5..055fb65 100755 --- a/firstboot.d/97turnkey-init-fence-disable +++ b/firstboot.d/97turnkey-init-fence-disable @@ -1,16 +1,16 @@ -#!/bin/bash +#!/bin/bash -e -[ -z "$_TURNKEY_INIT" ] && exit 0; # ONLY run from within turnkey-init +systemctl disable turnkey-init-fence +systemctl stop turnkey-init-fence -if systemctl is-active --quiet turnkey-init-fence; then - systemctl disable turnkey-init-fence - systemctl stop turnkey-init-fence +# ONLY run this from within turnkey-init + if [[ -n "$_TURNKEY_INIT" ]]; then + for home in /root /home/admin; do + if [[ -e $home/.profile.d/turnkey-init-fence ]]; then + chmod -x $home/.profile.d/turnkey-init-fence + fi + done + chmod -x /usr/lib/inithooks/firstboot.d/??turnkey-init-fence* fi -for home in /root /home/admin; do - [ -e $home/.profile.d/turnkey-init-fence ] && chmod -x $home/.profile.d/turnkey-init-fence; -done - -chmod -x /usr/lib/inithooks/firstboot.d/??turnkey-init-fence* - -echo 'turnkey-init-fence is down' +echo "turnkey-init-fence is down" diff --git a/firstboot.d/98finalize b/firstboot.d/98finalize index b7e4963..18c3041 100755 --- a/firstboot.d/98finalize +++ b/firstboot.d/98finalize @@ -1,13 +1,16 @@ #!/bin/bash -e -INITHOOKS_DEFAULT=/etc/default/inithooks -. $INITHOOKS_DEFAULT +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi # blank out configuration file (usually contains passwords) -[ -e $INITHOOKS_CONF ] && echo > $INITHOOKS_CONF +if [[ -e $INITHOOKS_CONF ]]; then + echo > $INITHOOKS_CONF +fi # set run_firstboot to false -sed -i '/RUN_FIRSTBOOT/ s/=.*/=false/g' $INITHOOKS_DEFAULT - -exit 0 - +sed -i "\|RUN_FIRSTBOOT| s|=.*|=false|" /etc/default/inithooks diff --git a/firstboot.d/99reboot b/firstboot.d/99reboot index beb89b1..d96a238 100644 --- a/firstboot.d/99reboot +++ b/firstboot.d/99reboot @@ -2,22 +2,23 @@ # reboot system (kernel upgrade, set chmod +x by 95secupdates) # will be skipped if running live or REBOOT set to SKIP -chmod -x $0 # self-deactivating +chmod -x "$0" # self-deactivating -. /etc/default/inithooks -[ -e $INITHOOKS_CONF ] && . $INITHOOKS_CONF - -grep -qs boot=live /proc/cmdline && exit 2 - -[ "$REBOOT" == "SKIP" ] && exit 0 +# shellcheck source=default/inithooks +source /etc/default/inithooks +if [[ -e "$INITHOOKS_CONF" ]]; then + # shellcheck disable=SC1090 + source "$INITHOOKS_CONF" +fi -reboot() { - init 6 -} +if grep -qs boot=live /proc/cmdline; then + exit 2 +elif [[ "${REBOOT,,}" == "skip" ]]; then + exit 0 +fi -if [ "$SEC_UPDATES" == "FORCE" ]; then +if [[ "${SEC_UPDATES,,}" == "force" ]]; then echo "rebooting due to kernel security upgrade..." reboot -else - $INITHOOKS_PATH/bin/reboot-ask.py && reboot fi +$INITHOOKS_PATH/bin/reboot-ask.py diff --git a/turnkey-init b/turnkey-init index b4c0cf1..ce7229b 100755 --- a/turnkey-init +++ b/turnkey-init @@ -1,7 +1,7 @@ #!/usr/bin/python3 # # Copyright (c) 2012-2015 Alon Swartz -# Copyright (c) 2016-2019 TurnKey GNU/Linux https://www.turnkeylinux.org +# Copyright (c) 2016-2026 TurnKey GNU/Linux https://www.turnkeylinux.org # # This file is part of InitHooks. # @@ -11,7 +11,7 @@ # option) any later version. # """ -Execute firstboot initialization hooks, skipping blacklist +Execute firstboot initialization hooks, skipping blacklisted scripts. firstboot.d hook execution environment: @@ -19,18 +19,19 @@ firstboot.d hook execution environment: """ import os +import subprocess import sys +from typing import NoReturn from conffile import ConfFile -import subprocess -def fatal(e): +def fatal(e: str) -> NoReturn: print("error: " + str(e), file=sys.stderr) sys.exit(1) -def usage(e=None): +def usage(e: str | None = None) -> NoReturn: if e: print("error: " + str(e), file=sys.stderr) @@ -40,35 +41,27 @@ def usage(e=None): print(" -h|--help - print this help and exit") print(" -c|--full-confconsole - after initialization open full confconsole (default is basic).") print(" If Confconsole not installed, will be ignored.") - print("\nBlacklist:\n", file=sys.stderr) - for script in BLACKLIST: - print(" %s" % script, file=sys.stderr) sys.exit(1) -# deprecated: hooks can use _TURNKEY_INIT to detect if they're running under -# turnkey-init -BLACKLIST = ['15regen-sslcert', - '92etckeeper', ] - - class Config(ConfFile): CONF_FILE = "/etc/default/inithooks" class InitHooks: - def __init__(self): + def __init__(self, force_all_hooks: bool = False) -> None: self.conf = Config() + self.force_all_hooks = force_all_hooks @staticmethod - def _exec(cmd): - subprocess.run(['env', - 'PATH=/usr/local/sbin:/usr/local/bin:' - '/usr/sbin:/usr/bin:/sbin:/bin', - cmd]) - - def execute(self, dname): - os.environ['_TURNKEY_INIT'] = '1' + def _exec(cmd: str) -> None: + env = os.environ.copy() + env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" + subprocess.run([cmd], env=env) + + def execute(self, dname) -> None: + if not self.force_all_hooks: + os.environ["_TURNKEY_INIT"] = "1" dpath = os.path.join(self.conf.inithooks_path, dname) if not os.path.exists(dpath): return @@ -77,31 +70,35 @@ class InitHooks: scripts.sort() for fname in scripts: fpath = os.path.join(dpath, fname) - if os.access(fpath, os.X_OK) and fname not in BLACKLIST: + if os.access(fpath, os.X_OK): self._exec(fpath) -def main(): +def main() -> None: if os.geteuid() != 0: fatal("turnkey-init must be run with root permissions") - confconsole = '/usr/bin/confconsole' - if os.path.exists(confconsole) and os.access(confconsole, os.X_OK): - confconsole = [confconsole, '--usage'] + confconsole = ["/usr/bin/confconsole"] + if os.path.exists(confconsole[0]) and os.access(confconsole[0], os.X_OK): + confconsole.append("--usage") else: # /bin/true ignores args and always returns true - confconsole = ['/bin/true', '--null'] + confconsole = ["/bin/true", "--null"] + + force_all_hooks = False args = sys.argv[1:] if args: for arg in args: - if arg in ('-h', '--help'): + if arg in ("-h", "--help"): usage() - if arg in ('-c', '--full-confconsole'): + if arg in ("-c", "--full-confconsole"): confconsole = confconsole[:-1] + if arg in ("-f", "--force"): + force_all_hooks = True - inithooks = InitHooks() - inithooks.execute('firstboot.d') + inithooks = InitHooks(force_all_hooks=force_all_hooks) + inithooks.execute("firstboot.d") subprocess.run(confconsole)