second commit

This commit is contained in:
pvincent
2024-02-21 23:32:34 +04:00
parent 9a4551ca3a
commit 7cdc45397d
82 changed files with 7172 additions and 6 deletions
+221
View File
@@ -0,0 +1,221 @@
#!/bin/bash
DEFAULT_BACKUP_FOLDER="$HOME/RECOVERY_MARIADB"
confirm() {
read -rp "$1 ([y]es or [N]o): "
case $(echo "$REPLY" | tr '[:upper:]' '[:lower:]') in
y | yes) echo "yes" ;;
*) echo "no" ;;
esac
}
synopsis() {
echo "usage: "
printf "\t list | console | connections\n"
printf "\t ---------------------------\n"
printf "\t use <DB_NAME>\n"
printf "\t create <DB_NAME> [PASSWORD]\n"
printf "\t ---------------------------\n"
printf "\t backup <DB_NAME> [FOLDER]\n"
printf "\t restore <DB_NAME> <FILE>\n"
printf "\t ---------------------------\n"
printf "\t rename <DB_NAME> <NEW_NAME>\n"
}
list() {
lxc exec ct1 -- sh -c "echo \"SELECT schema_name FROM information_schema.schemata where schema_name not in ('information_schema','mariadb','mysql','performance_schema')\" | mariadb -u root --skip-column-names -r "
}
console() {
if [[ -z $1 ]]; then
lxc exec ct1 -- mariadb -u root
else
lxc exec ct1 -- sh -c "echo \"$1\" | mariadb -u root"
fi
}
connections() {
lxc exec ct1 -- sh -c "echo \"select id, user, host, db, command, time, state, info, progress from information_schema.processlist\" | mariadb -u root "
}
use() {
lxc exec ct1 -- mariadb -u root "$DB_NAME"
}
create() {
# shellcheck disable=SC1091
source /opt/debian-bash/lib/functions.sh
# shellcheck disable=SC2034
mapfile -t DBs < <(list)
local NEW_DB="${1:-$DB_NAME}"
local NEW_PASSWORD="${2:-$NEW_DB}"
if ! containsElement DBs "$NEW_DB"; then
lxc exec ct1 -- sh -c "echo \"\
CREATE DATABASE \\\`$NEW_DB\\\`; \
GRANT ALL ON \\\`$NEW_DB\\\`.* TO \\\`$NEW_DB\\\`@'%' IDENTIFIED BY '$NEW_PASSWORD'; \
FLUSH PRIVILEGES; \
\" | mariadb -u root"
else
echo "$NEW_DB already exists!"
fi
}
backup() {
if [[ ! -d "$FOLDER" ]]; then
echo "error: Folder required!"
file "$FOLDER"
exit 2
fi
mkdir -p "$FOLDER"
DATE=$(date '+%F')
ARCHIVE="$FOLDER"/$DB_NAME-$DATE.mariadb.gz
if [[ -f $ARCHIVE ]]; then
VERSION_CONTROL=numbered mv -b "$ARCHIVE" "$FOLDER"/"$DB_NAME"-"$DATE"-daily.mariadb.gz
fi
echo "backup $DB_NAME into $FOLDER"
mariadb-dump -h ct1.lxd -u "$DB_NAME" -p"$DB_NAME" "$DB_NAME" | gzip >"$ARCHIVE"
echo "archive file created: $ARCHIVE"
}
restore() {
echo "restore $DB_NAME $FILE"
if [[ ! -f "$FILE" ]]; then
echo "error: Backup file (*.mariadb.gz) required!"
file "$FILE"
exit 2
fi
PROCESSES=$(lxc exec ct1 -- sh -c "echo \"select id, user, host, db, command, time, state, info, progress from information_schema.processlist\" | mariadb -u root")
set +e
PROCESS_COUNT=$(echo "$PROCESSES" | grep -c "$DB_NAME")
if [[ $PROCESS_COUNT -gt 0 ]]; then
echo "FAILURE: There are some connections to database, please consider stopping bound services"
echo
echo "$PROCESSES"
exit 2
fi
set -e
if [[ "yes" == $(confirm "RESTORATION will drop DATABASE, please acknowledge with care!!!") ]]; then
if list | grep -q "$DB_NAME"; then
echo "backup <$DB_NAME> for safety reason"
backup
echo "drop database <$DB_NAME>"
lxc exec ct1 -- sh -c "echo \"DROP DATABASE \\\`$DB_NAME\\\`\" | mariadb -u root"
fi
echo "create <$DB_NAME>"
create
# lxc exec ct1 -- sh -c "CREATE DATABASE \\\`$DB_NAME\\\`\" | mariadb -u root"
gunzip -c "$FILE" | grep -av "^CREATE DATABASE" | grep -av "^USE" | mariadb -h ct1.lxd -u "$DB_NAME" -p"$DB_NAME" "$DB_NAME"
echo RESTORATION completed successfully
else
exit 1
fi
}
rename() {
echo "rename $DB_NAME to $NEW_NAME"
local DB_NAME_FOUND=false
for database in $(list); do
if [[ $database == "$DB_NAME" ]]; then
DB_NAME_FOUND=true
fi
if [[ $database == "$NEW_NAME" ]]; then
echoerr "$NEW_NAME already exists! please provide another name instead of <$NEW_NAME> or run list command"
exit 20
fi
done
if [[ ! $DB_NAME_FOUND ]]; then
echoerr "source <$DB_NAME> does not exist!"
exit 20
fi
if [[ "$DB_NAME" == "$NEW_NAME" ]]; then
echowarn "no need to rename; no change required <$DB_NAME>"
exit 0
fi
echo "create new database <$NEW_NAME>"
create "$NEW_NAME"
for table in $(console "use '$DB_NAME'; show tables"); do
if [[ $table != "Tables_in_$DB_NAME" ]]; then
echo "renaming table \`$DB_NAME\`.$table to \`$NEW_NAME\`.$table"
console "use '$DB_NAME'; rename table \\\`$DB_NAME\\\`.$table to \\\`$NEW_NAME\\\`.$table;"
fi
done
echo "every table has been renamed, so remove old database <$DB_NAME>"
console "drop user \\\`$DB_NAME\\\`"
console "drop database \\\`$DB_NAME\\\`"
}
# MAIN
set -Eeuo pipefail
# shellcheck source=/dev/null
. "$MIAOU_BASEDIR/lib/functions.sh"
[[ $# -lt 1 ]] && synopsis && exit 1
ACTION=$1
case $ACTION in
console)
shift
TAIL=$*
console "$TAIL"
;;
list)
list
;;
connections)
connections
;;
use)
[[ $# -lt 2 ]] && synopsis && exit 1
DB_NAME=$2
use
;;
create)
[[ $# -lt 2 ]] && synopsis && exit 1
DB_NAME=$2
DB_PASSWORD=${3:-$DB_NAME}
create
;;
backup)
[[ $# -lt 2 ]] && synopsis && exit 1
DB_NAME=$2
FOLDER=${3:-$DEFAULT_BACKUP_FOLDER}
backup
;;
restore)
[[ $# -lt 3 ]] && synopsis && exit 1
DB_NAME=$2
FILE=$3
FOLDER=${4:-$DEFAULT_BACKUP_FOLDER}
DB_PASSWORD="$DB_NAME"
restore
;;
rename)
[[ $# -lt 3 ]] && synopsis && exit 1
DB_NAME=$2
NEW_NAME=$3
rename
;;
*)
synopsis
exit 1
;;
esac
+200
View File
@@ -0,0 +1,200 @@
#!/bin/bash
confirm() {
read -p "$1 ([y]es or [N]o): "
case $(echo $REPLY | tr '[A-Z]' '[a-z]') in
y | yes) echo "yes" ;;
*) echo "no" ;;
esac
}
synopsis() {
echo "usage: "
printf "\t list | console | connections\n"
printf "\t ---------------------------\n"
printf "\t use <DB_NAME>\n"
printf "\t lookup <DB_NAME> <TERM>\n"
printf "\t create <DB_NAME> [PASSWORD]\n"
printf "\t ---------------------------\n"
printf "\t backup <DB_NAME> [FOLDER]\n"
printf "\t restore <DB_NAME> <FILE> [--yes]\n"
printf "\t ---------------------------\n"
printf "\t rename <DB_NAME> <NEW_NAME>\n"
}
list() {
lxc exec ct1 -- su - postgres -c "psql -Atc \"SELECT datname FROM pg_database WHERE datistemplate=false AND datname<>'postgres';\""
}
console() {
if [[ -z $1 ]]; then
lxc exec ct1 -- su - postgres
else
lxc exec ct1 -- su - postgres -c "$1"
fi
}
connections() {
PROCESSES=$(console "psql -c \"select pid as process_id, usename as username, datname as database_name, client_addr as client_address, application_name, backend_start, state, state_change from pg_stat_activity WHERE datname<>'postgres' ORDER BY datname, usename;\"")
printf "$PROCESSES\n"
}
use() {
echo >&2 "about to connect to <${DB_NAME}> ..."
if [[ -z $1 ]]; then
lxc exec ct1 -- su - postgres -c "psql $DB_NAME"
else
local sql="psql -A -t $DB_NAME -c \\\"$1;\\\""
local command="su - postgres -c \"$sql\""
lxc exec ct1 -- sh -c "$command"
fi
}
create() {
echo >&2 "about to create to <${DB_NAME}> ..."
source /opt/debian-bash/lib/functions.sh
local DBs=($(list))
if ! $(containsElement DBs $DB_NAME); then
local SQL="CREATE USER \\\\\\\"$DB_NAME\\\\\\\" WITH PASSWORD '$DB_PASSWORD'"
local command="su - postgres sh -c \"psql -c \\\"$SQL\\\"\" && su - postgres sh -c \"createdb -O $DB_NAME $DB_NAME\" && echo CREATE DB"
# echo $command
lxc exec ct1 -- sh -c "$command"
else
echo $DB_NAME already exists!
fi
}
lookup() {
if [[ ${#TERM} -ge 4 ]]; then
echo >&2 "about to lookup term <${TERM}> over all tables of database <$DB_NAME> ..."
local command="pg_dump --data-only --inserts $DB_NAME 2>/dev/null | grep --color \"$TERM\""
lxc exec ct1 -- su - postgres -c "$command"
else
echo "term <$TERM> should contain 4 chars minimum!" && exit 2
fi
}
backup() {
if [[ ! -d "$FOLDER" ]]; then
echo "error: Folder required!"
file $FOLDER
exit 2
fi
DATE=$(date '+%F')
ARCHIVE="$FOLDER"/$DB_NAME-$DATE.postgres.gz
if [[ -f $ARCHIVE ]]; then
VERSION_CONTROL=numbered mv -b $ARCHIVE "$FOLDER"/$DB_NAME-$DATE-daily.postgres.gz
fi
echo "backup $DB_NAME $FOLDER"
PGPASSWORD=$DB_NAME pg_dump -U $DB_NAME $DB_NAME -h ct1.lxd | gzip >"$ARCHIVE"
echo "archive file created: $ARCHIVE"
}
restore() {
echo "restore $DB_NAME $FILE"
if [[ ! -f "$FILE" ]]; then
echo "error: Backup file (*.postgres.gz) required!"
file $FILE
exit 2
fi
PROCESSES=$(console "psql -c \"select pid as process_id, usename as username, datname as database_name, client_addr as client_address, application_name, backend_start, state, state_change from pg_stat_activity WHERE datname='$DB_NAME';\"")
PROCESS_COUNT=$(echo "$PROCESSES" | wc -l)
if [[ $PROCESS_COUNT -gt 3 ]]; then
echo "FAILURE: There are some connections to database, please consider stopping bound services"
echo
printf "$PROCESSES\n"
exit 2
fi
if [[ $YES == "true" || "yes" == $(confirm "RESTORATION will drop DATABASE, please acknowledge with care!!!") ]]; then
FOLDER="$HOME/RECOVERY_POSTGRES"
mkdir -p "$FOLDER"
backup
echo "backup successful, now drop and restore"
lxc exec ct1 -- su - postgres -c "dropdb $DB_NAME && createdb -O $DB_NAME $DB_NAME"
gunzip -c "$FILE" | grep -v "^CREATE DATABASE" | PGPASSWORD=$DB_NAME PGOPTIONS='--client-min-messages=warning' psql -X -q -1 -v ON_ERROR_STOP=1 --pset pager=off -U $DB_NAME -h ct1.lxd $DB_NAME 2>&1 >/dev/null
else
exit 1
fi
}
rename() {
echo "rename <$DB_NAME> to <$DB_NEW_NAME>"
mapfile -t LIST <<<"$(list)"
found=false
for db in "${LIST[@]}"; do
[[ "$db" == "$DB_NEW_NAME" ]] && echoerr "destination database <$DB_NEW_NAME> already exists! Please provide another name." && exit 11
[[ "$db" == "$DB_NAME" ]] && found=true
done
$found || (echoerr "source database <$DB_NAME> not found!" && exit 12)
console "psql -c \"ALTER DATABASE \\\"$DB_NAME\\\" RENAME TO \\\"$DB_NEW_NAME\\\" \""
console "psql -c \"ALTER USER \\\"$DB_NAME\\\" RENAME TO \\\"$DB_NEW_NAME\\\" \""
console "psql -c \"ALTER USER \\\"$DB_NEW_NAME\\\" PASSWORD '$DB_NEW_NAME' \""
}
# MAIN
. "$MIAOU_BASEDIR/lib/init.sh"
[[ $# -lt 1 ]] && synopsis && exit 1
ACTION=$1
case $ACTION in
console)
shift
TAIL="$@"
console "$TAIL"
;;
list)
list
;;
connections)
connections
;;
use)
[[ $# -lt 2 ]] && synopsis && exit 1
DB_NAME=$2
shift 2
TAIL="$@"
use "$TAIL"
;;
create)
[[ $# -lt 2 ]] && synopsis && exit 1
DB_NAME=$2
DB_PASSWORD=${3:-$DB_NAME}
create
;;
lookup)
[[ $# -lt 3 ]] && synopsis && exit 1
DB_NAME=$2
TERM=$3
lookup
;;
backup)
[[ $# -lt 2 ]] && synopsis && exit 1
DB_NAME=$2
FOLDER=${3:-.}
backup
;;
restore)
[[ $# -lt 3 ]] && synopsis && exit 1
DB_NAME=$2
FILE=$3
YES=true
restore
;;
rename)
[[ $# -lt 3 ]] && synopsis && exit 1
DB_NAME=$2
DB_NEW_NAME=$3
rename
;;
*)
synopsis
exit 1
;;
esac
+180
View File
@@ -0,0 +1,180 @@
#!/bin/bash
function check_container_missing() {
if container_exists "$CONTAINER"; then
echoerr "$CONTAINER already created!"
exit 1
fi
}
function usage() {
echo 'USAGE with options:'
echo -e "\t\tlxc-miaou-create <CONTAINER_NAME> -o sameuser[,nesting,ssh]"
}
function check() {
check_container_missing || return 1
return 0
}
function set_options() {
declare -a options=("$@")
length=${#options[@]}
if [[ "$length" -ne 0 ]]; then
if [[ "$length" -ne 2 ]]; then
echoerr "unrecognized options: $@" && usage && exit 30
else
prefix="${options[0]}"
option="${options[1]}"
if [[ "$prefix" == '-o' ]]; then
IFS=',' read -r -a options <<<"$option"
for i in ${options[@]}; do
case "$i" in
sameuser) OPTION_SAMEUSER=true ;;
nesting) OPTION_NESTING=true ;;
ssh) OPTION_SSH=true ;;
*) echoerr "unrecognized options: $@" && usage && exit 32 ;;
esac
done
# echo "OPTION_SAMEUSER=$OPTION_SAMEUSER, OPTION_NESTING=$OPTION_NESTING, OPTION_SSH=$OPTION_SSH"
else
echoerr "unrecognized options prefix: $prefix" && usage && exit 31
fi
fi
shift
fi
}
function create() {
local PREFIX="miaou:create"
if [[ "$OPTION_SAMEUSER" == true ]]; then
miaou_user=$(whoami)
fi
echo -n "creating new container <$CONTAINER> based on image <$CONTAINER_RELEASE>... "
bridge_gw=$(lxc network get lxdbr0 ipv4.address | cut -d'/' -f1)
user_data="$(
cat <<EOF
#cloud-config
timezone: 'Indian/Reunion'
apt:
preserve_sources_list: false
conf: |
Acquire::Retries "60";
DPkg::Lock::Timeout "60";
primary:
- arches: [default]
uri: http://debian.mithril.re/debian
security:
- arches: [default]
uri: http://debian.mithril.re/debian-security
sources_list: |
# generated by miaou-cloud
deb \$PRIMARY \$RELEASE main
deb \$PRIMARY \$RELEASE-updates main
deb \$SECURITY \$RELEASE-security main
package_update: true
package_upgrade: true
package_reboot_if_required: true
packages:
- git
- file
- bc
- bash-completion
write_files:
- path: /etc/sudoers.d/10-add_TOOLBOX_to_secure_path
content: >
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/TOOLBOX"
runcmd:
- [ systemctl, mask, systemd-hostnamed.service ]
- [ systemctl, disable, e2scrub_reap.service ]
- [ systemctl, disable, systemd-resolved.service, --now ]
- [ systemctl, reset-failed ]
- [ rm, /etc/resolv.conf]
- [ rm, /etc/sudoers.d/90-cloud-init-users]
- "echo nameserver $bridge_gw > /etc/resolv.conf"
final_message: "Container from datasource \$datasource is finally up, after \$UPTIME seconds"
EOF
)"
lxc init images:debian/$CONTAINER_RELEASE/cloud "$CONTAINER" --config user.user-data="$user_data" -q
# allow directory `SHARED` to be read-write mounted
lxc config set "$CONTAINER" raw.idmap "both $(id -u) 0" -q
mkdir -p "$HOME/LXD/SHARED/$CONTAINER"
lxc config device add "$CONTAINER" SHARED disk source="$HOME/LXD/SHARED/$CONTAINER" path=/mnt/SHARED -q
lxc config device add "$CONTAINER" TOOLBOX disk source=/TOOLBOX path=/TOOLBOX -q
lxc config device add "$CONTAINER" DEBIAN_BASH disk source=$(realpath /opt/debian-bash) path=/opt/debian-bash -q
lxc config set "$CONTAINER" environment.PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/debian-bash/tools:/TOOLBOX -q
if [[ "$OPTION_NESTING" == true ]]; then
lxc config set $CONTAINER security.nesting true -q
lxc config device add "$CONTAINER" miaou disk source=/opt/miaou path=/opt/miaou -q
fi
lxc start "$CONTAINER" -q
# initializing debian-bash
lxc exec "$CONTAINER" -- /opt/debian-bash/init.sh
# default configuration files (btm,)
lxc exec "$CONTAINER" -- mkdir -p /root/.config/bottom
lxc file push "$MIAOU_BASEDIR/templates/bottom/bottom.toml" "$CONTAINER/root/.config/bottom/bottom.toml" -q
# purge cloud-init after success
lxc exec "$CONTAINER" -- systemd-run -q -p After=cloud-final.service -p Type=oneshot --no-block bash -c '\
cloud-init status --wait &&\
cp /var/lib/cloud/data/status.json /root/cloud-status.json &&\
systemctl stop cloud-{config,final,init-local,init}.service &&\
systemctl disable cloud-{config,final,init-local,init}.service &&\
systemctl stop cloud-config.target cloud-init.target &&\
apt-get purge -y cloud-init &&\
rm -rf /var/lib/cloud && \
userdel -rf debian \
'
if [[ "$OPTION_SAMEUSER" == true ]]; then
if ! lxc exec "$CONTAINER" -- grep "$miaou_user" /etc/passwd; then
lxc exec "$CONTAINER" -- useradd -ms /bin/bash -G sudo "$miaou_user"
fi
if ! lxc exec "$CONTAINER" -- passwd -S "$miaou_user" | cut -d ' ' -f2 | grep -q ^P; then
shadow_passwd=$(load_yaml_from_expanded credential.shadow)
shadow_remainder=$(lxc exec "$CONTAINER" -- bash -c "grep $miaou_user /etc/shadow | cut -d':' -f3-")
lxc exec "$CONTAINER" -- /opt/debian-bash/tools/append_or_replace "^$miaou_user:.*:" "$miaou_user:$shadow_passwd:$shadow_remainder" /etc/shadow >/dev/null
fi
fi
if [[ "$OPTION_SSH" == true ]]; then
lxc exec "$CONTAINER" -- /opt/debian-bash/tools/idem_apt_install openssh-server
fi
if [[ "$OPTION_SSH" == true && "$OPTION_SAMEUSER" == true ]]; then
lxc-miaou-enable-ssh "$CONTAINER"
fi
PREFIX="" echoinfo OK
echo "hint: \`lxc login $CONTAINER [--env user=<USER>]\`"
[[ "$OPTION_SAMEUSER" == true ]] && echo "hint: \`lxc sameuser $CONTAINER\`"
true
}
## MAIN
. "$MIAOU_BASEDIR/lib/init.sh"
OPTION_SAMEUSER=false
OPTION_NESTING=false
OPTION_SSH=false
PREFIX="miaou"
arg1_required "$@" || (usage && exit 1)
readonly CONTAINER=$1
readonly CONTAINER_RELEASE="bookworm"
shift
set_options "$@"
readonly FULL_OPTIONS="$@"
check
create
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
function check_container_exists() {
if ! container_exists "$CONTAINER"; then
echoerr "container <$CONTAINER> does not exist!"
exit 1
fi
}
function check() {
check_container_exists || return 1
return 0
}
function enable_ssh() {
echo "lxc: enable ssh in container <$CONTAINER> for user <$SSH_USER>"
if ! container_running "$CONTAINER"; then
echowarn "container <$CONTAINER> seems to be asleep, starting ..."
lxc start "$CONTAINER"
echowarn DONE
fi
lxc exec "$CONTAINER" -- bash <<EOF
set -Eeuo pipefail
if ! id "$SSH_USER" &>/dev/null; then
echo "adding new user <$SSH_USER>"
useradd -ms /bin/bash -G sudo "$SSH_USER"
else
echo "bash: $SSH_USER exists already!"
fi
EOF
miaou_user=$(whoami)
shadow_passwd=$(load_yaml_from_expanded credential.shadow)
shadow_remainder=$(lxc exec "$CONTAINER" -- bash -c "grep $SSH_USER /etc/shadow | cut -d':' -f3-")
lxc exec "$CONTAINER" -- /opt/debian-bash/tools/append_or_replace "^$SSH_USER:.*:" "$SSH_USER:$shadow_passwd:$shadow_remainder" /etc/shadow >/dev/null
lxc exec "$CONTAINER" -- /opt/debian-bash/tools/idem_apt_install openssh-server
previous_users=($(
lxc exec "$CONTAINER" -- bash <<EOF
set -Eeuo pipefail
if [[ -f /etc/ssh/sshd_config ]] && grep -q AllowUsers /etc/ssh/sshd_config ; then
cat /etc/ssh/sshd_config | grep AllowUsers | cut -d' ' -f 2-
fi
EOF
))
if containsElement previous_users "$SSH_USER"; then
echo "sshd_config: AllowUsers $SSH_USER already done!"
else
echo "previous_users ${previous_users[*]}"
previous_users+=("$SSH_USER")
echo -n "building template for sshd_config..."
USERS=${previous_users[*]} tera -e --env-key env -t "$MIAOU_BASEDIR/templates/dev-container-ssh/sshd_config.j2" -o "/tmp/sshd_config" "$MIAOU_CONFIGDIR/miaou.expanded.yaml" >/dev/null
echo 'OK'
echo -n "copying sshd_config over container <$CONTAINER> ... "
lxc file push --uid 0 --gid 0 "/tmp/sshd_config" "$CONTAINER/etc/ssh/sshd_config" &>/dev/null
echo 'OK'
lxc exec "$CONTAINER" -- systemctl reload sshd.service
fi
lxc exec "$CONTAINER" -- mkdir -p "/home/$SSH_USER/.ssh"
lxc exec "$CONTAINER" -- chown "$SSH_USER:$SSH_USER" "/home/$SSH_USER/.ssh"
lxc exec "$CONTAINER" -- chmod 760 "/home/$SSH_USER/.ssh"
lxc file push --uid 0 --gid 0 "/home/$miaou_user/.ssh/id_rsa.pub" "$CONTAINER/home/$SSH_USER/.ssh/authorized_keys" &>/dev/null
lxc exec "$CONTAINER" -- chown "$SSH_USER:$SSH_USER" "/home/$SSH_USER/.ssh/authorized_keys"
lxc exec "$CONTAINER" -- chmod 600 "/home/$SSH_USER/.ssh/authorized_keys"
echo "create symbolic link for curl from TOOLBOX as required for Codium remote-ssh"
lxc exec "$CONTAINER" -- ln -sf /TOOLBOX/curl /usr/bin/
echo "SUCCESS: container $CONTAINER listening on port 22"
}
## MAIN
. "$MIAOU_BASEDIR/lib/init.sh"
arg1_required "$@"
readonly CONTAINER=$1
if [[ -z "${2:-}" ]]; then
readonly SSH_USER=$(id -un)
else
readonly SSH_USER="$2"
fi
check
enable_ssh
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
lxc list -c nDm -f compact status=running | tail -n+2 | sort -k2 -h -r
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
lxc list -c nmD -f compact status=running | tail -n+2 | sort -k2 -h -r
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
function restart_dnsmasq() {
echo -n "lxd: restart dnsmasq... "
lxc network get lxdbr0 raw.dnsmasq >/tmp/dnsmaq.conf
lxc network set lxdbr0 raw.dnsmasq - </tmp/dnsmaq.conf
echo "OK"
}
## MAIN
. "$MIAOU_BASEDIR/lib/init.sh"
restart_dnsmasq
Executable
+479
View File
@@ -0,0 +1,479 @@
#!/bin/bash
usage() {
PREFIX="miaou:usage" echo '<init>'
exit 0
}
yqm() {
#read only
yq "$1" "$EXPANDED_CONF"
}
yqmi() {
# for update
yq "$1" "$EXPANDED_CONF" -i
}
yqmt() {
# tabular
yq "$1" "$EXPANDED_CONF" -o t
}
compute_fqdn_middlepart() {
case "$1" in
prod)
local fqdn_middlepart="."
;;
beta)
local fqdn_middlepart=".beta."
;;
dev)
local fqdn_middlepart=".dev."
;;
*)
echowarn "unknown target <${target}>, please fix with correct value from {prod, beta, dev} and try again..."
exit 1
;;
esac
builtin echo "$fqdn_middlepart"
}
# archive_conf(FILE)
# save patch in archived folder of current file
function archive_conf() {
PREFIX="miaou:conf:archive"
file="$1"
filename=$(basename "$file")
mkdir -p "$MIAOU_CONFIGDIR/archived/$filename"
previous="$MIAOU_CONFIGDIR/archived/$filename/previous"
# shellcheck disable=SC2012
latest_patch=$(ls -1tr "$MIAOU_CONFIGDIR/archived/$filename/" | tail -n1)
if [[ -z "$latest_patch" ]]; then
echo -n "archiving first file <$file> ..."
cp "$file" "$previous"
PREFIX="" echoinfo OK
elif [[ "$file" -nt "$latest_patch" ]]; then
patchname="$MIAOU_CONFIGDIR/archived/$filename/$(date +%F_%T)"
if ! diff "$previous" "$file" >"$patchname"; then
echo -n "archiving patch <$patchname> ..."
cp "$file" "$previous"
PREFIX="" echoinfo OK
else
rm "$patchname"
fi
fi
}
function archive_allconf() {
mkdir -p "$MIAOU_CONFIGDIR"
archive_conf "$CONF"
archive_conf "$DEFAULTS"
}
function check_expand_conf() {
PREFIX="miaou:conf:check"
if ! "$FORCE" && [ -f "$EXPANDED_CONF" ] && [ "$EXPANDED_CONF" -nt "$CONF" ] && [ "$EXPANDED_CONF" -nt "$DEFAULTS" ]; then
echo "already expanded!"
return 1
fi
}
function expand_conf() {
PREFIX="miaou:conf"
if [[ -f "$EXPANDED_CONF" ]]; then
current_target=$(grep -Es "^target:" /etc/miaou/defaults.yaml | cut -d ' ' -f2)
previous_target=$(grep -Es "^target:" "$EXPANDED_CONF" | cut -d ' ' -f2)
[[ "$current_target" != "$previous_target" ]] && echoerr "TARGET <$previous_target> mismatched <$current_target>" && exit 101
fi
# initialize expanded conf by merging default
# shellcheck disable=SC2016
yq eval-all '. as $item ireduce ({}; . * $item )' "$CONF" "$DEFAULTS" >"$EXPANDED_CONF"
# append unique container unless overridden
mapfile -t services_app_only < <(yqmt '.services.[].[] | has("container") | select ( . == false) | [(parent|key)+" " +key]')
for i in "${services_app_only[@]}"; do
read -r -a item <<<"$i"
domain=${item[0]}
subdomain=${item[1]}
app=$(yqm ".services.\"$domain\".\"$subdomain\".app")
container=$(get_container_for_domain_subdomain_app "$domain" "$subdomain" "$app")
yqmi ".services.\"$domain\".\"$subdomain\".container=\"$container\""
done
# append enabled=true unless overridden
mapfile -t services_app_only < <(yqmt '.services.[].[] | has("enabled") | select ( . == false) | [(parent|key)+" " +key] | unique ')
# echo "found <${#services_app_only[@]}> enabled services"
for i in "${services_app_only[@]}"; do
read -r -a item <<<"$i"
domain=${item[0]}
subdomain=${item[1]}
yqmi ".services.\"$domain\".\"$subdomain\".enabled=true"
done
# compute fqdn
target=$(yqm '.target')
fqdn_middlepart=$(compute_fqdn_middlepart "$target")
# write fqdn_middlepart
yqmi ".expanded.fqdn_middlepart = \"$fqdn_middlepart\""
# add monitored.containers section
yqmi '.expanded.monitored.containers = ([ .services[] | to_entries | .[] | .value | select (.enabled == true ) | .container ] | unique)'
# add monitored.hosts section
yqmi '.expanded.monitored.hosts = [( .services[][] | select (.enabled == true ) | {"domain": ( parent | key ), "subdomain": key, "fqdn": key + (parent | parent | parent | .expanded.fqdn_middlepart) + ( parent | key ), "container":.container, "port":.port, "app":.app })]'
# add services section
if [[ ${#services_app_only[@]} -gt 0 ]]; then
yqmi '.expanded.services = [( .services[][] | select (.enabled == true ) | {"domain": ( parent | key ), "subdomain": key, "fqdn": key + (parent | parent | parent | .expanded.fqdn_middlepart) + ( parent | key ), "container":.container, "port":.port, "app":.app, "name": .name // ""})]'
else
yqmi '.expanded.services = []'
fi
# add firewall section, bridge_subnet + mail_passthrough if any
bridge_subnet=$(lxc network get lxdbr0 ipv4.address)
yqmi ".firewall.bridge_subnet = \"$bridge_subnet\""
container_mail_passthrough=$(yqm ".firewall.container_mail_passthrough")
}
function build_routes() {
PREFIX="miaou:routes"
mapfile -t fqdns < <(yqm '.expanded.services[].fqdn')
echo "found <${#fqdns[@]}> fqdn"
raw_dnsmasq=''
for i in "${fqdns[@]}"; do
raw_dnsmasq+="address=/$i/$DMZ_IP\\n"
# append domains to conf
echo "re-routing any connection from <$i> to internal container <$DMZ_CONTAINER.lxd>"
done
builtin echo -e "$raw_dnsmasq" | lxc network set $BRIDGE raw.dnsmasq -
}
function build_dmz_reverseproxy() {
PREFIX="miaou:build:dmz"
echo -n "building configuration for nginx ... "
mkdir -p "$MIAOU_CONFIGDIR/nginx"
tera -t "$MIAOU_BASEDIR/templates/nginx/_default.j2" "$EXPANDED_CONF" -o "$MIAOU_CONFIGDIR/nginx/_default" &>/dev/null
tera -t "$MIAOU_BASEDIR/templates/nginx/hosts.j2" "$EXPANDED_CONF" -o "$MIAOU_CONFIGDIR/nginx/hosts" &>/dev/null
PREFIX="" echo OK
echo -n "pushing configuration to <$DMZ_CONTAINER> ... "
for f in "$MIAOU_CONFIGDIR"/nginx/*; do
lxc file push --uid=0 --gid=0 "$f" "$DMZ_CONTAINER/etc/nginx/sites-available/" &>/dev/null
done
PREFIX="" echo OK
cat <<EOF | PREFIX="miaou:build:dmz" lxc_exec "$DMZ_CONTAINER"
cd /etc/nginx/sites-enabled/
for i in ../sites-available/*; do
# echo dmz: enabling... \$i
ln -sf \$i
done
nginx -tq
systemctl restart nginx
EOF
echo "nginx reloaded successfully!"
}
function monit_show() {
PREFIX="monit:show"
: $PREFIX
readarray -t hosts < <(yqmt '.expanded.monitored.hosts[] | [ .container, .port, .fqdn, .app ]')
echo "================="
echo "${#hosts[@]} available hosts"
echo "================="
for host in "${hosts[@]}"; do
read -r -a item <<<"$host"
container=${item[0]}
port=${item[1]}
fqdn=${item[2]}
app=${item[3]}
[[ -n ${PREFIX:-} ]] && printf "${DARK}%25.25s${NC} " "${PREFIX}"
if curl -m $MAX_WAIT -I -4so /dev/null "http://$container:$port"; then
builtin echo -ne "${GREEN}✔${NC}"
else
builtin echo -ne "${RED}✘${NC}"
fi
printf "\t%10.10s\thttps://%-40s\thttp://%s\n" "$app" "$fqdn" "$container:$port"
done
}
function build_monit() {
# test whether monitored items actually run safely
PREFIX="monit:build"
echo -n "testing monitored hosts ..."
readarray -t hosts < <(yqmt '.expanded.monitored.hosts[] | [ .container, .port, .fqdn ]')
for host in "${hosts[@]}"; do
read -r -a item <<<"$host"
container=${item[0]}
port=${item[1]}
fqdn=${item[2]}
if ! (lxc exec "$container" -- ss -tln | grep -q "\(0.0.0.0\|*\):$port"); then
echoerr
echoerr "no HTTP server responds on <$container.lxd:$port>"
echoerr "please review configuration <miaou.yaml> for fqdn: $fqdn"
exit 2
fi
if ! curl_check_unsecure "https://$fqdn"; then
echoerr
echoerr "DMZ does not seem to dispatch <https://$fqdn> please review DMZ Nginx proxy"
exit 3
elif [[ "$target" != 'dev' ]] && ! curl_check "https://$fqdn"; then
PREFIX="" echo
echowarn "T=$target missing valid certificate for fqdn <https://$fqdn> please review DMZ certbot"
fi
done
PREFIX="" echo OK
# templates for monit
echo -n "copying templates for monit ..."
mkdir -p "$MIAOU_CONFIGDIR/monit"
tera -t "$MIAOU_BASEDIR/templates/monit/containers.j2" "$EXPANDED_CONF" -o "$MIAOU_CONFIGDIR/monit/containers" >/dev/null
tera -t "$MIAOU_BASEDIR/templates/monit/hosts.j2" "$EXPANDED_CONF" -o "$MIAOU_CONFIGDIR/monit/hosts" >/dev/null
PREFIX="" echo OK
}
# count_service_for_container(container: string)
# returns how many services run inside container according to expanded conf
function count_service_for_container() {
container_mail_passthrough="$1"
count=$(yqm ".expanded.services.[] | select(.container == \"$container_mail_passthrough\") | .fqdn" | wc -l)
builtin echo "$count"
}
function build_nftables() {
PREFIX="miaou:nftables:build"
mkdir -p "$MIAOU_CONFIGDIR/nftables.rules.d"
container_mail_passthrough=$(yqm '.firewall.container_mail_passthrough')
if [[ "$container_mail_passthrough" != null ]]; then
ip_mail_passthrough=$(lxc list "$container_mail_passthrough" -c4 -f csv | grep eth0 | cut -d ' ' -f1)
[[ -z "$ip_mail_passthrough" ]] && echoerr "container <$container_mail_passthrough> passthrough unknown ip!" && exit 55
echo "passthrough=$container_mail_passthrough/$ip_mail_passthrough"
count=$(count_service_for_container "$container_mail_passthrough")
[[ $count == 0 ]] && echowarn "no service detected => no passthrough, no change!"
[[ $count -gt 1 ]] && echoerr "count <$count> services detected on container <$container_mail_passthrough>, please disable some and leave only one service for safety!!!" && exit 56
ip_mail_passthrough=$ip_mail_passthrough tera -e --env-key env -t "$MIAOU_BASEDIR/templates/nftables/lxd.table.j2" "$EXPANDED_CONF" -o "$MIAOU_CONFIGDIR/nftables.rules.d/lxd.table" &>/dev/null
else
echo "no container passthrough"
tera -t "$MIAOU_BASEDIR/templates/nftables/lxd.table.j2" "$EXPANDED_CONF" -o "$MIAOU_CONFIGDIR/nftables.rules.d/lxd.table" &>/dev/null
fi
if ! diff -q "$MIAOU_CONFIGDIR/nftables.rules.d/lxd.table" /etc/nftables.rules.d/lxd.table; then
sudo_required "reloading nftables"
echo -n "reloading nftables..."
sudo cp "$MIAOU_CONFIGDIR/nftables.rules.d/lxd.table" /etc/nftables.rules.d/lxd.table
sudo systemctl reload nftables
PREFIX="" echo OK
fi
}
# check whether http server responds 200 OK, required <url>, ie: http://example.com:8001, https://example.com
function curl_check() {
arg1_required "$@"
# echo "curl $1"
curl -m $MAX_WAIT -sLI4 "$1" | grep -q "^HTTP.* 200"
}
# check whether https server responds 200 OK, even unsecured certificate (auto-signed in mode DEV)
function curl_check_unsecure() {
arg1_required "$@"
curl -m $MAX_WAIT -skLI4 "$1" | grep -q "^HTTP.* 200"
}
function get_dmz_ip() {
if ! container_running "$DMZ_CONTAINER"; then
echowarn "Container running dmz <$DMZ_CONTAINER> seems down"
echoerr "please \`lxc start $DMZ_CONTAINER\` or initialize first!"
exit 1
fi
dmz_ip=$(host "$DMZ_CONTAINER.lxd" | cut -d ' ' -f4)
if ! valid_ipv4 "$dmz_ip"; then
echowarn "dmz seems up but no valid ip <$dmz_ip> found!"
echoerr "please fix this networking issue, then retry..."
exit 1
else
builtin echo "$dmz_ip"
fi
}
function fetch_container_of_type() {
local type="$1"
readarray -t dmzs < <(yqm ".containers.[].[] | select(.==\"$type\") | parent | key")
case ${#dmzs[@]} in
0) : ;;
1) builtin echo "${dmzs[0]}" ;;
*) for d in "${dmzs[@]}"; do
builtin echo "$d"
done ;;
esac
}
function get_container_for_domain_subdomain_app() {
local domain="$1"
local subdomain="$2"
local app="$3"
readarray -t containers < <(fetch_container_of_type "$app")
case ${#containers[@]} in
0) echoerr "no container of type <$app> found amongst containers for $subdomain.$domain\nHINT : Please, either :\n1. define at least one container for recipe <$app>\n2. remove all services related to recipe <$app>" && exit 1 ;;
1) builtin echo "${containers[0]}" ;;
*)
for d in "${containers[@]}"; do
echowarn "container of type $app found in <$d>"
done
echoerr "multiple containers (${#containers[@]}) provided same app <$app>, therefore container is mandatory alongside $subdomain.$domain" && exit 2
;;
esac
}
function get_unique_container_dmz() {
readarray -t containers < <(fetch_container_of_type "dmz")
case ${#containers[@]} in
0) echoerr "no container of type <dmz> found amongst containers" && exit 1 ;;
1) builtin echo "${containers[0]}" ;;
*)
for d in "${containers[@]}"; do
echowarn "container of type dmz found in <$d>"
done
echoerr "multiple dmz (${#containers[@]}) are not allowed, please select only one " && exit 2
;;
esac
}
function prepare_dmz_container() {
"$MIAOU_BASEDIR"/recipes/dmz/install.sh "$DMZ_CONTAINER"
}
function check_resolv_conf() {
local bridge_gw resolver
bridge_gw=$(lxc network get lxdbr0 ipv4.address | cut -d'/' -f1)
resolver=$(grep nameserver /etc/resolv.conf | head -n1 | cut -d ' ' -f2)
PREFIX="resolver:check" echo "container resolver is <$resolver>"
PREFIX="resolver:check" echo "container bridge is <$bridge_gw>"
[[ "$bridge_gw" != "$resolver" ]] && return 21
return 0
}
function prepare_containers() {
PREFIX="miaou:prepare"
readarray -t containers < <(yqmt ".containers.[] | [ key, .[] ] ")
for i in "${containers[@]}"; do
read -r -a item <<<"$i"
container=${item[0]}
for ((j = 1; j < ${#item[@]}; j++)); do
service="${item[$j]}"
recipe_install="$MIAOU_BASEDIR/recipes/$service/install.sh"
if [[ -f "$recipe_install" ]]; then
echo "install [$service] onto container <$container>"
"$recipe_install" "$container"
else
echoerr "FAILURE, for container <$container>, install recipe [$service] not found!"
echoerr "please review configuration, mismatch recipe name maybe?"
exit 50
fi
done
done
}
function build_services() {
PREFIX="miaou:build:services"
echo "building services..."
readarray -t services < <(yqmt '.expanded.services[] | [ .[] ]')
for i in "${services[@]}"; do
read -r -a item <<<"$i"
fqdn=${item[2]}
container=${item[3]}
port=${item[4]}
app=${item[5]}
name=${item[6]:-}
recipe="$MIAOU_BASEDIR/recipes/$app/crud.sh"
if [[ -f "$recipe" ]]; then
echo "read [$app:$name] onto container <$container>"
if ! "$recipe" -r --port "$port" --container "$container" --name "$name" --fqdn "$fqdn"; then
echoinfo "CREATE RECIPE"
"$recipe" -c --port "$port" --container "$container" --name "$name" --fqdn "$fqdn"
echoinfo "CREATE RECIPE: OK"
fi
else
echowarn "for container <$container>, crud recipe [$app] not found!"
fi
done
}
### MAIN
. "$MIAOU_BASEDIR/lib/init.sh"
readonly CONF="/etc/miaou/miaou.yaml"
readonly DEFAULTS="/etc/miaou/defaults.yaml"
readonly EXPANDED_CONF="$MIAOU_CONFIGDIR/miaou.expanded.yaml"
readonly BRIDGE="lxdbr0"
readonly MAX_WAIT=3 # timeout in seconds
# shellcheck disable=SC2034
declare -a options=("$@")
FORCE=false
if containsElement options "-f" || containsElement options "--force"; then
FORCE=true
fi
if containsElement options "history"; then
echo "TODO: HISTORY"
exit 0
fi
if containsElement options "config"; then
editor /etc/miaou/miaou.yaml
if diff -q /etc/miaou/miaou.yaml $HOME/.config/miaou/archived/miaou.yaml/previous; then
exit 0
fi
fi
if check_expand_conf; then
archive_allconf
expand_conf
check_resolv_conf
build_nftables
prepare_containers
DMZ_CONTAINER=$(get_unique_container_dmz)
readonly DMZ_CONTAINER
build_services
DMZ_IP=$(get_dmz_ip)
readonly DMZ_IP
build_dmz_reverseproxy
build_routes
build_monit
fi
monit_show
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
readonly DOMAIN=$1
readonly PROTOCOL=${2:-https}
readonly TIMEOUT=10 # max seconds to wait
result=0
function usage {
echo 'usage: <DOMAIN> [ https | 443 | smtps | 587 | pop3 | 993 | imap | 995 | ALL ]'
exit -1
}
function check_ssl {
local protocol=$1
case $protocol in
SMTPS )
local extra="-starttls smtp -showcerts"
;;
esac
echo -n "$protocol "
certificate_info=$(echo | timeout $TIMEOUT openssl s_client $extra -connect $DOMAIN:$2 2>/dev/null)
issuer=$(echo "$certificate_info" | openssl x509 -noout -text 2>/dev/null | grep Issuer: | cut -d: -f2)
date=$( echo "$certificate_info" | openssl x509 -noout -enddate 2>/dev/null | cut -d'=' -f2)
date_s=$(date -d "${date}" +%s)
now_s=$(date -d now +%s)
date_diff=$(( (date_s - now_s) / 86400 ))
if [[ -z $date ]]; then
echo -n "does not respond "
echo -ne "\033[31;1m"
echo FAILURE
(( result += 1 ))
elif [[ $date_diff -gt 20 ]]; then
echo -n "issuer:$issuer "
echo -n "will expire in $date_diff days "
echo -ne "\033[32;1m"
echo ok
elif [[ $date_diff -gt 0 ]];then
echo -n "issuer:$issuer "
echo -n "will expire in $date_diff days "
echo -ne "\033[31;1m"
echo WARNING
(( result += 1 ))
else
echo -n "issuer:$issuer "
echo -n "has already expired $date_diff ago "
echo -ne "\033[31;1m"
echo FAILURE
(( result += 1 ))
fi
echo -ne "\033[0m"
}
#MAIN
[[ -z "$DOMAIN" ]] && usage
case $PROTOCOL in
https | 443 )
check_ssl HTTPS 443;;
smtps | 587 )
check_ssl SMTPS 587;;
pop3 | 995 )
check_ssl POP3 995;;
imap | 993 )
check_ssl IMAP 993;;
all | ALL )
check_ssl HTTPS 443
check_ssl SMTPS 587
check_ssl POP3 995
check_ssl IMAP 993
;;
*)
usage
;;
esac
exit "$result"