#!/bin/bash RED='\e[0;41m\e[1;37m' GREEN='\033[0;32m' YELLOW='\033[0;33m' PURPLE='\033[0;35m' DARK='\e[100m' NC='\033[0m' # No Color # BOLD='\033[1m' # DIM='\e[2m\e[0;90m' TO_BE_DEFINED="TO BE DEFINED" FDN_DOMAINNAME=fdn.fr FDN_NAMESERVER="ns0.$FDN_DOMAINNAME" FDN_RESOLVER=80.67.169.12 : $FDN_DOMAINNAME $FDN_NAMESERVER $FDN_RESOLVER function echo() { [[ -n ${PREFIX:-} ]] && printf "${DARK}%25.25s${NC} " "${PREFIX}" builtin echo "$@" } function check_normal_user() { [[ $(id -u) -lt 1000 ]] && echoerr "normal user (>1000) expected, please connect as a normal user then call again!" && exit 100 return 0 } function sudo_required() { check_normal_user command -v sudo &>/dev/null && id -G | grep -q sudo && echoerr "command not found, please install as so: \`apt install -y sudo\`" && exit 1 if ! sudo -n true &>/dev/null; then if [[ -n "${1:-}" ]]; then echowarnn "[sudo] requiring authorized access for: [ $1 ]" else echowarnn "[sudo] requiring authorized access for further processing" fi fi sudo -vp ' : ' } # idempotent cargo install function idem_cargo_install() { for i in "$@"; do if [ ! -f ~/.cargo/bin/"$i" ]; then cargo install "$i" fi done } # display error in red function echoerr() { echo -e "${RED}$*${NC}" >&2 } function echoerrn() { echo -en "${RED}$*${NC}" >&2 } # display warn in yellow function echowarn() { echo -e "${YELLOW}$*${NC}" >&2 } function echowarnn() { echo -en "${YELLOW}$*${NC}" >&2 } # display error in green function echoinfo() { echo -e "${GREEN}$*${NC}" >&2 } function echoinfon() { echo -en "${GREEN}$*${NC}" >&2 } # test whether is a valid ipv4 address? function valid_ipv4() { local ip="$1" if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then IFS='.' read -ra ADDR <<<"$ip" [[ ${ADDR[0]} -le 255 && ${ADDR[1]} -le 255 && ${ADDR[2]} -le 255 && ${ADDR[3]} -le 255 ]] return $? fi return 1 } function enable_trace() { trap 'trap_error $? ${LINENO:-0} ${BASH_LINENO:-0} ${BASH_COMMAND:-empty} $(printf "::%s" ${FUNCNAME[@]})' ERR } function disable_trace() { trap - ERR } function disable_all_signals { trap - ERR trap - HUP trap - INT trap - TERM } function prepare_nftables() { local PREFIX="miaou:nftables" if ! [[ -f /etc/nftables.rules.d/firewall.table ]]; then echo "installing nftables ..." sudo apt install -y nftables sudo cp -f "$MIAOU_BASEDIR/templates/hardened/nftables.conf" /etc/ sudo mkdir -p /etc/nftables.rules.d sudo systemctl enable nftables else echo "nftables already installed!" fi current_target="${TARGET:-not_defined_yet}" desktop=false samba=false transmission_daemon=false if [[ $current_target == not_defined_yet ]]; then echo -n "generating new firewall table first time... " else if [[ $current_target == 'dev' ]]; then if exist_command 'xprop'; then desktop=true fi if exist_command 'smbstatus'; then samba=true fi if exist_command 'transmission-daemon'; then transmission_daemon=true fi echo -n "generating new firewall table according to target=<${current_target}>, desktop=<$desktop>, samba=<$samba>, transmission_daemon=<$transmission_daemon> ..." else echo -n "generating new firewall table according to target=<${current_target}>..." fi fi sudo env target="$current_target" desktop="$desktop" samba="$samba" transmission_daemon="$transmission_daemon" tera -e --env-only --env-key env -t "$MIAOU_BASEDIR/templates/nftables/firewall.table.j2" -o /etc/nftables.rules.d/firewall.table &>/dev/null sudo systemctl reload nftables PREFIX="" echoinfo "DONE" } function miaou_init() { # shellcheck source=/dev/null [[ -f /opt/miaou-bash/lib/functions.sh ]] && source /opt/miaou-bash/lib/functions.sh # shellcheck source=/dev/null . "$MIAOU_BASEDIR/lib/functions.sh" export MIAOU_CONFIGDIR="$HOME/.config/miaou" set -Eeuo pipefail enable_trace trap 'ctrl_c $? ${LINENO:-0} ${BASH_LINENO:-0} ${BASH_COMMAND:-empty} $(printf "::%s" ${FUNCNAME[@]})' INT } function ctrl_c() { PREFIX="miaou:trap" echoerr "Ctrl + C happened, exiting!!! $*" exit 125 } # extract source code error triggered on trap error function trap_error() { ERRORS_COUNT=0 if [[ -f "$MIAOU_CONFIGDIR"/error_count ]]; then ERRORS_COUNT=$(cat "$MIAOU_CONFIGDIR"/error_count) else mkdir -p "$MIAOU_CONFIGDIR" printf 0 >"$MIAOU_CONFIGDIR"/error_count fi ERRORS_COUNT=$((ERRORS_COUNT + 1)) printf '%s' $ERRORS_COUNT >"$MIAOU_CONFIGDIR"/error_count local PREFIX="" # local file="${0:-}" local err=$1 # error status local line=$2 # LINENO # local linecallfunc=${3:-} local command="${4:-}" local funcstack="${5:-}" local caller caller=$(caller | cut -d' ' -f2) # echo >&2 # if [ "$funcstack" != "::" ]; then # echo -e "${RED}ERROR <$err>, due to command <$command> at line $line from <$caller>, stack=${funcstack}${NC}" >&2 # else # echo >&2 "ERROR DETECTED" # fi # echo # echo -e "${PURPLE}$caller:$line ${NC}EXIT ${RED}<$err>${NC}" >&2 # echo -e "${PURPLE}------------------------------------------ ${NC}" >&2 if [[ $ERRORS_COUNT == 1 ]]; then echo echo -e "${RED}ERROR <$err>, due to command <$command $funcstack>${NC}" >&2 fi echo -e "${PURPLE}$ERRORS_COUNT: $caller:$line ${RED}$command $funcstack${NC}" >&2 # echo -e "${PURPLE}----------------------------- ${PURPLE}EXIT CODE ${PURPLE}--------------${PURPLE} $err ${NC}" >&2 # if [[ $line -gt 2 ]]; then # sed "$((line - 2))q;d" "$caller" >&2 # sed "$((line - 1))q;d" "$caller" >&2 # fi # echo -ne "${BOLD}" >&2 # sed "${line}q;d" "$caller" >&2 # echo -e "${PURPLE}------------------------------------------ ${NC}" >&2 } # exist_command(cmd1, ...) # test all commands exist, else fail function exist_command() { for i in "$@"; do command -v "$i" >/dev/null || return 50 done } # test whether container is up and running? function container_running() { arg1_required "$@" container_exists "$1" && lxc list "$1" -c ns -f csv | head -n1 | grep -q "$1,RUNNING" lxc exec "$1" -- bash </dev/null fi EOF } # test arg1 required function arg1_required() { [[ -z "${1:-}" ]] && echoerr "ERROR: arg#1 expected!" && return 125 return 0 } # test arg2 required function arg2_required() { [[ -z "${2:-}" ]] && echoerr "ERROR: arg#2 expected!" && return 125 return 0 } # test arg3 required function arg3_required() { [[ -z "${3:-}" ]] && echoerr "ERROR: arg#3 expected!" && return 125 || return 0 } # test arg4 required function arg4_required() { [[ -z "${4:-}" ]] && echoerr "ERROR: arg#4 expected!" && return 125 || return 0 } # test whether container exists yet? function container_exists() { arg1_required "$@" lxc list "$1" -c n -f csv | grep -q "^$1\$" } # 3 args expected: function wait_for_command { arg3_required "$@" command=$1 delay=$2 max_attempt=$3 attempt=0 while ! eval "$command"; do attempt=$((attempt + 1)) if [[ $attempt -gt $max_attempt ]]; then echoerr "command <$command> failed after a delay of $(bc <<<"$max_attempt * $delay")s and $max_attempt attempts" return 1 else sleep "$delay" fi done echo SUCCESS } function wait_for_container_full_initialization { arg1_required "$@" wait_for_command "lxc exec $1 -- test -f /root/cloud-status.json" 0.2 40 } # build debian image with prebuild miaou-bash and various useful settings # ARG1=release [bullseye, buster] function build_miaou_image() { local RELEASE="$1" local IMAGE_LABEL="$RELEASE-miaou" local PREFIX="miaou:image" local DEB_REPOSITORY DEB_REPOSITORY=$(grep ^deb /etc/apt/sources.list | head -n1 | cut -d ' ' -f2 | cut -d '/' -f3) if ! lxc image -cl list -f csv | grep -q "$IMAGE_LABEL"; then echo "building lxc image <$IMAGE_LABEL> ... " echo "image will reuse same local repository <$DEB_REPOSITORY>" creation_date=$(date +%s) sudo /opt/miaou-bash/tools/idem_apt_install debootstrap cat </etc/apt/sources.list deb http://$DEB_REPOSITORY/debian $RELEASE main contrib deb http://$DEB_REPOSITORY/debian $RELEASE-updates main contrib deb http://$DEB_REPOSITORY/debian-security/ $RELEASE/updates main contrib EOF3 else cat </etc/apt/sources.list deb http://$DEB_REPOSITORY/debian $RELEASE main contrib deb http://$DEB_REPOSITORY/debian $RELEASE-updates main contrib deb http://$DEB_REPOSITORY/debian-security/ $RELEASE-security main contrib EOF3 fi echo APT UPDATE apt update && apt dist-upgrade -y apt install -y curl wget file git sudo bash-completion curl https://git.artcode.re/miaou/miaou-bash/raw/branch/main/install.sh | sudo bash -s -- --host # TODO: remove line below # ln -sf /usr/share/zoneinfo/Indian/Reunion /etc/localtime cat </etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). # The loopback network interface auto lo iface lo inet loopback auto eth0 iface eth0 inet dhcp source /etc/network/interfaces.d/* EOF3 echo "deboostrap ready!" EOF2 cd /tmp/$IMAGE_LABEL-image tar -czf rootfs.tar.gz -C /tmp/$IMAGE_LABEL . cat <metadata.yaml architecture: "x86_64" creation_date: $creation_date properties: architecture: "x86_64" description: "Debian $RELEASE for miaou instances" os: "debian" release: "$RELEASE" EOF2 tar -czf metadata.tar.gz metadata.yaml EOF1 lxc image import "/tmp/$IMAGE_LABEL-image/metadata.tar.gz" "/tmp/$IMAGE_LABEL-image/rootfs.tar.gz" --alias "$IMAGE_LABEL" echo "image <$IMAGE_LABEL> successfully built!" echo DONE else echo "image <$IMAGE_LABEL> already built!" fi } # convert array to string according to IFS arg1 # example: join "," "${MY_ARRAY[@]}" => one,two,three function join() { local IFS="$1" shift builtin echo "$*" } # execute remote scripting onto one LXC container [COMMANDS, ...] # may use one command like: `lxc_exec ct1 uname -a` # or pipe like so: ` # cat < about to be created ..." local extra_release="${2:-}" if [[ -n "$extra_release" ]] && ! lxc image info "${extra_release}-miaou" >/dev/null; then echoerrn "unknown extra_release <${extra_release}-miaou>!\nHINT : please add it into /etc/miaou/defaults.yaml, then re-install miaou!" exit 128 fi if [[ -n "$extra_release" ]]; then echoerrn "FIXME: lxc-miaou-create -o release=bookworm should be implemented ...." lxc-miaou-create "$ct" "$extra_release" else lxc-miaou-create "$ct" fi echo "DONE" fi if ! container_running "$ct"; then echowarn "container <$ct> seems to be asleep, starting ..." lxc start "$ct" echowarn DONE fi } function load_yaml_from_expanded { arg1_required "$@" yaml_key="$1" yaml_file="$MIAOU_CONFIGDIR/miaou.expanded.yaml" yaml_value=$(yq ".$yaml_key" "$yaml_file") if [[ -n "$yaml_value" ]] && [[ "$yaml_value" != "null" ]] && [[ "$yaml_value" != "$TO_BE_DEFINED" ]]; then PREFIX="" echo "$yaml_value" else echoerr "undefined value for key: <$yaml_key> from file: <$yaml_file>" return 98 fi } function check_yaml_defined_value { yaml_file="$1" yaml_key="$2" yaml_value=$(yq ".$yaml_key" "$yaml_file") if [[ -n "$yaml_value" ]] && [[ "$yaml_value" != "null" ]] && [[ "$yaml_value" != "$TO_BE_DEFINED" ]]; then return 0 else echoerr "undefined value for key: <$yaml_key> from file: <$yaml_file>" return 99 fi } # halt unless current user is root function root_required() { [[ $(id -u) == 0 ]] || (echoerr "root required" && return 1) } # arg#1: environment variable # read from environment or ask entry before exporting new variable function env_or_ask { if [[ -n ${1+x} ]]; then if printenv "$1" >/dev/null; then echo "value defined as $(printenv "$1")" else printf "Please define %20s: " "$1" read -r export "$1=\"$REPLY\"" >/dev/null fi else echoerr "env_or_ask requires one argument: " && exit 5 fi } # grab and install related project function install_miaou_bash() { local PREFIX="miaou-bash:install" if [[ ! -d /opt/miaou-bash ]]; then echo "installing curl wget commands ..." sudo apt install -y curl wget echo "installing miaou-bash..." curl https://git.artcode.re/miaou/miaou-bash/raw/branch/main/install.sh | sudo bash -s -- --host export PATH=$PATH:/opt/miaou-bash/tools/ echo "OK" else echo "addon already installed!" fi # shellcheck source=/dev/null source /etc/bash.bashrc sudo /opt/miaou-bash/tools/idem_apt_install bash-completion } function add_toolbox_sudoers { local PREFIX="toolbox:sudoers" echo -n "creating sudoers file to allow sudo as command from /TOOLBOX... " sudo mkdir -p /etc/sudoers.d if [[ ! -f /etc/sudoers.d/add_TOOLBOX_to_PATH ]]; then sudo tee /etc/sudoers.d/add_TOOLBOX_to_PATH &>/dev/null </dev/null; then echo -n "installing ... " curl -sSf https://sh.rustup.rs | sh -s -- -y # shellcheck source=/dev/null source "$HOME/.cargo/env" /opt/miaou-bash/tools/append_or_replace "^PATH=\$PATH:\$HOME/\\.cargo/bin" "PATH=\$PATH:\$HOME/.cargo/bin" ~/.bashrc PREFIX="" echo "OK" else echo "command already installed!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/fd" ]; then idem_cargo_install fd-find sudo cp "$HOME"/.cargo/bin/fd /TOOLBOX/fd PREFIX="" echo "successfully installed!" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/viu" ]; then idem_cargo_install viu sudo cp "$HOME"/.cargo/bin/viu /TOOLBOX/ PREFIX="" echo "successfully installed!" else PREFIX="" echo "already done!" fi echo -n "installing alias ... " if [ ! -f "/TOOLBOX/rg" ]; then sudo /opt/miaou-bash/tools/idem_apt_install ripgrep sudo ln /usr/bin/rg /TOOLBOX/ PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing alias ... " if [ ! -f "/TOOLBOX/ag" ]; then sudo /opt/miaou-bash/tools/idem_apt_install silversearcher-ag sudo ln /usr/bin/ag /TOOLBOX/ PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/bandwhich" ]; then idem_cargo_install bandwhich sudo cp "$HOME"/.cargo/bin/bandwhich /TOOLBOX/bandwhich PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing alias ... " if [ ! -f "/TOOLBOX/btm" ]; then VERSION=$(wget_semver github ClementTsang/bottom) cd /tmp wget "https://github.com/ClementTsang/bottom/releases/download/$VERSION/bottom_x86_64-unknown-linux-musl.tar.gz" tar -xzvf bottom_x86_64-unknown-linux-musl.tar.gz sudo cp btm /usr/local/bin/ sudo ln /usr/local/bin/btm /TOOLBOX/ PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/micro" ]; then cd /tmp || (echoerr "/tmp wrong permission" && exit 101) curl -q https://getmic.ro | GETMICRO_REGISTER=n sh sudo mv micro /TOOLBOX/micro sudo chown root:root /TOOLBOX/micro PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/ncdu" ]; then sudo /opt/miaou-bash/tools/idem_apt_install ncdu sudo cp /usr/bin/ncdu /TOOLBOX/ncdu PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/unzip" ]; then sudo /opt/miaou-bash/tools/idem_apt_install unzip sudo cp /usr/bin/unzip /TOOLBOX/unzip PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/tree" ]; then sudo /opt/miaou-bash/tools/idem_apt_install tree sudo cp /bin/tree /TOOLBOX/tree PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/duf" ]; then VERSION=$(/opt/miaou-bash/tools/wget_semver github muesli/duf) VERSION_WITHOUT_V=${VERSION#v} wget -O /tmp/duf.deb "https://github.com/muesli/duf/releases/download/${VERSION}/duf_${VERSION_WITHOUT_V}_linux_amd64.deb" sudo dpkg -i /tmp/duf.deb sudo cp /bin/duf /TOOLBOX/duf PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/curl" ]; then sudo wget -O /TOOLBOX/curl "https://github.com/moparisthebest/static-curl/releases/latest/download/curl-amd64" sudo chmod +x /TOOLBOX/curl PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi echo -n "installing ... " if [ ! -f "/TOOLBOX/wget" ]; then sudo ln -f /usr/bin/wget /TOOLBOX/wget PREFIX="" echo "successfully installed" else PREFIX="" echo "already done!" fi } # install_mandatory_commands function install_mandatory_commands() { local PREFIX="mandatory:commands" echo "installing various mandatory commands" sudo /opt/miaou-bash/tools/idem_apt_install dnsutils build-essential curl mariadb-client postgresql-client if ! exist_command tera; then echo "installing ..." local version=v0.2.4 wget -q "https://github.com/chevdor/tera-cli/releases/download/${version}/tera-cli_linux_amd64.deb" -O /tmp/tera-cli_linux_amd64.deb sudo dpkg -i /tmp/tera-cli_linux_amd64.deb else echo "command already installed!" fi if ! exist_command yq; then local version binary version='v4.35.2' binary='yq_linux_amd64' sudo sh -c "wget https://github.com/mikefarah/yq/releases/download/${version}/${binary}.tar.gz -O - |\ tar -xz ./${binary} && sudo mv ${binary} /usr/bin/yq" else echo "command already installed!" fi } # flatten array, aka remove duplicated elements in array # return: `mapfile -t OUTPUT_ARRAY < <(sort_array "${INPUT_ARRAY[@]}")` function flatten_array { declare -a array=("$@") IFS=" " read -r -a array <<<"$(tr ' ' '\n' <<<"${array[@]}" | sort -u | tr '\n' ' ')" printf '%s\n' "${array[@]}" }