You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
16 KiB
534 lines
16 KiB
#!/bin/bash
|
|
|
|
MIAOU_BASEDIR=$(readlink -f "$(dirname "$0")/..")
|
|
readonly MIAOU_BASEDIR
|
|
|
|
# shellcheck source=/dev/null
|
|
. "$MIAOU_BASEDIR/lib/functions.sh"
|
|
miaou_init
|
|
|
|
EXPANDED_CONF="$MIAOU_CONFIGDIR/miaou.expanded.yaml"
|
|
NEW_GROUP=lxd
|
|
readonly NEW_GROUP EXPANDED_CONF
|
|
|
|
on_exit() {
|
|
if [ -n "${1:-}" ]; then
|
|
echowarn "Aborted by $1"
|
|
elif [ "${status:-}" -ne 0 ]; then
|
|
echowarn "Failure (status $status)"
|
|
fi
|
|
}
|
|
|
|
function prepare_lxd {
|
|
local PREFIX="lxd:prepare"
|
|
|
|
# test group lxd assign to current user
|
|
if ! groups | grep -q lxd; then
|
|
echo "define lxd and assign to user <$USER>"
|
|
sudo groupadd --force "$NEW_GROUP"
|
|
sudo usermod --append --groups "$NEW_GROUP" "$(whoami)"
|
|
|
|
realcommand="$MIAOU_BASEDIR/lib/install.sh"
|
|
sg "$NEW_GROUP" -c "EMAIL=$valid_email $realcommand SESSION_RELOAD_REQUIRED $TARGET"
|
|
|
|
set +e
|
|
disable_all_signals
|
|
sudo su - "$(whoami)"
|
|
kill -9 "$PPID"
|
|
# no further processing because exec has been called!
|
|
|
|
else
|
|
echo "user <$USER> already belongs to group <lxd>!"
|
|
fi
|
|
|
|
sudo /opt/miaou-bash/tools/idem_apt_install lxd btrfs-progs
|
|
|
|
# sudo tee /etc/systemd/system/lxd-containers-restart-on-failure.service &>/dev/null <<EOF
|
|
# [Unit]
|
|
# Description=restart lxd containers when no dhclient
|
|
# After=lxd.service lxd-containers.service
|
|
# Requires=lxd.socket
|
|
# StartLimitInterval=60
|
|
# StartLimitBurst=5
|
|
|
|
# [Service]
|
|
# Type=exec
|
|
|
|
# ExecCondition=sh -c '[ \$(lxc list status=running -c4 -fcsv | wc -l) -gt 0 ]] && lxc list status=running -c4 -fcsv | grep -vq eth0'
|
|
# ExecStart=systemctl restart lxd-containers.service
|
|
# ExecStartPost=sh -c 'sleep 2 ; [ \$(lxc list status=running -c4 -fcsv | wc -l) -eq 0 ]] || lxc list status=running -c4 -fcsv | grep -q eth0'
|
|
|
|
# Restart=on-failure
|
|
# RestartSec=10
|
|
|
|
# [Install]
|
|
# WantedBy=multi-user.target
|
|
# EOF
|
|
# sudo systemctl daemon-reload
|
|
}
|
|
|
|
function configure_lxd {
|
|
local PREFIX="lxd:configure"
|
|
|
|
# test lxdbr0 FIXME: preseed too much repetitions!!!!
|
|
if ! lxc network info lxdbr0 &>/dev/null; then
|
|
|
|
echo "bridge <lxdbr0> down, so initialization will use default preseed..."
|
|
|
|
if [[ $(printenv container) == 'lxc' ]]; then
|
|
echo "--------------------------------"
|
|
echo "nested configuration applying..."
|
|
echo "--------------------------------"
|
|
cat <<EOF | sg $NEW_GROUP -c 'lxd init --preseed'
|
|
config: {}
|
|
networks:
|
|
- name: lxdbr0
|
|
type: bridge
|
|
config:
|
|
ipv4.address: auto
|
|
ipv6.address: none
|
|
storage_pools:
|
|
- config: {}
|
|
description: ""
|
|
name: default
|
|
driver: dir
|
|
profiles:
|
|
- config:
|
|
security.privileged: "true"
|
|
description: ""
|
|
devices:
|
|
eth0:
|
|
name: eth0
|
|
nictype: bridged
|
|
parent: lxdbr0
|
|
type: nic
|
|
root:
|
|
path: /
|
|
pool: default
|
|
type: disk
|
|
name: default
|
|
projects: []
|
|
cluster: null
|
|
EOF
|
|
else
|
|
empty_block_partition=$(lsblk -o NAME,FSTYPE,TYPE -ti -P | grep 'FSTYPE="" TYPE="part"' | head -n1 | cut -d'"' -f2)
|
|
if [[ -n "$empty_block_partition" ]]; then
|
|
echo "--------------------------------"
|
|
echo "use empty block partition /dev/$empty_block_partition for speed and optimization"
|
|
echo "--------------------------------"
|
|
cat <<EOF | sudo lxd init --preseed
|
|
config: {}
|
|
networks:
|
|
- config:
|
|
ipv4.address: auto
|
|
ipv6.address: none
|
|
description: ""
|
|
name: lxdbr0
|
|
type: ""
|
|
project: default
|
|
storage_pools:
|
|
- config:
|
|
source: /dev/$empty_block_partition
|
|
description: ""
|
|
name: default
|
|
driver: btrfs
|
|
profiles:
|
|
- config: {}
|
|
description: ""
|
|
devices:
|
|
eth0:
|
|
name: eth0
|
|
network: lxdbr0
|
|
type: nic
|
|
root:
|
|
path: /
|
|
pool: default
|
|
type: disk
|
|
name: default
|
|
projects: []
|
|
cluster: null
|
|
EOF
|
|
else
|
|
echo "--------------------------------"
|
|
echo "use dir partition for development purpose"
|
|
echo "--------------------------------"
|
|
cat <<EOF | lxd init --preseed
|
|
config: {}
|
|
networks:
|
|
- name: lxdbr0
|
|
type: bridge
|
|
config:
|
|
ipv4.address: auto
|
|
ipv6.address: none
|
|
storage_pools:
|
|
- config: {}
|
|
description: ""
|
|
name: default
|
|
driver: dir
|
|
profiles:
|
|
- config: {}
|
|
description: ""
|
|
devices:
|
|
eth0:
|
|
name: eth0
|
|
network: lxdbr0
|
|
type: nic
|
|
root:
|
|
path: /
|
|
pool: default
|
|
type: disk
|
|
name: default
|
|
projects: []
|
|
cluster: null
|
|
EOF
|
|
fi
|
|
fi
|
|
else
|
|
echo "bridge <lxdbr0> found implies it has been already initialized!"
|
|
fi
|
|
|
|
set_alias 'sameuser' "exec @ARG1@ -- su --whitelist-environment container,container_hostname - $(whoami)"
|
|
set_alias 'login' 'exec @ARGS@ --mode interactive -- /bin/bash -c $@${user:-root} - exec su --whitelist-environment container,container_hostname - '
|
|
set_alias 'll' 'list -c ns4mDNc'
|
|
|
|
# test environment container hostname
|
|
local env_container_hostname
|
|
env_container_hostname=$(sg $NEW_GROUP -c 'lxc profile get default environment.container_hostname')
|
|
if [[ -z "$env_container_hostname" ]]; then
|
|
env_container_hostname=$(hostname -s)
|
|
if env | grep -q container_hostname; then
|
|
local previous_container_hostname
|
|
previous_container_hostname=$(env | grep container_hostname | cut -d '=' -f2)
|
|
env_container_hostname="$previous_container_hostname $env_container_hostname"
|
|
fi
|
|
|
|
echo -n "set environment container_hostname to <$env_container_hostname> ... "
|
|
sg $NEW_GROUP -c "lxc profile set default environment.container_hostname \"$env_container_hostname\""
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo "environment container_hostname <$env_container_hostname> already defined!"
|
|
fi
|
|
|
|
if ! grep -q "root:$(id -u):1" /etc/subuid; then
|
|
echo -n "saving subuid, subgid permissions for <$(whoami)> ..."
|
|
printf "root:$(id -u):1\n" | sudo tee -a /etc/subuid /etc/subgid &>/dev/null
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo "subuid, subgid allowing <$(whoami)> already done!"
|
|
fi
|
|
|
|
if [[ ! -d "$HOME/LXD/SHARED" ]]; then
|
|
echo -n "$HOME/LXD/SHARED creating ... "
|
|
mkdir "$HOME/LXD/SHARED" -p
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo "folder <$HOME/LXD/SHARED> already created!"
|
|
fi
|
|
|
|
if [[ ! -d "$HOME/LXD/BACKUP" ]]; then
|
|
echo -n "$HOME/LXD/BACKUP creating ... "
|
|
mkdir "$HOME/LXD/BACKUP" -p
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo "folder <$HOME/LXD/BACKUP> already created!"
|
|
fi
|
|
|
|
}
|
|
|
|
function set_alias {
|
|
local name="$1"
|
|
local command="$2"
|
|
if ! lxc alias list -f csv | grep -q "^$name,"; then
|
|
echo -n "defining new lxc alias <$name> ..."
|
|
lxc alias add "$name" "$command"
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo "lxc alias <$name> already defined!"
|
|
fi
|
|
|
|
}
|
|
|
|
function miaou_evalfrombashrc() {
|
|
local PREFIX="miaou:bashrc"
|
|
output=$(
|
|
/opt/miaou-bash/tools/append_or_replace \
|
|
"^eval \"\\$\($MIAOU_BASEDIR/lib/install.sh shellenv\)\"$" \
|
|
"eval \"\$($MIAOU_BASEDIR/lib/install.sh shellenv)\"" \
|
|
"$HOME/.bashrc"
|
|
)
|
|
|
|
if [[ "$output" == "appended" ]]; then
|
|
echo "new path <$MIAOU_BASEDIR> created!"
|
|
SESSION_RELOAD_REQUIRED=true
|
|
else
|
|
echo "path <$MIAOU_BASEDIR> already loaded!"
|
|
fi
|
|
}
|
|
|
|
function ask_target() {
|
|
PS3='Choose miaou target purpose: '
|
|
foods=("Dev" "Beta" "Prod")
|
|
select ans in "${foods[@]}"; do
|
|
builtin echo "${ans^^}"
|
|
break
|
|
done
|
|
}
|
|
|
|
function check_credential {
|
|
local PREFIX="check:credential"
|
|
|
|
check_yaml_defined_value /etc/miaou/defaults.yaml 'credential.username' &&
|
|
check_yaml_defined_value /etc/miaou/defaults.yaml 'credential.shadow' &&
|
|
check_yaml_defined_value /etc/miaou/defaults.yaml 'credential.email'
|
|
}
|
|
|
|
function check_target() {
|
|
case "${TARGET^^}" in
|
|
DEV) ;;
|
|
BETA) ;;
|
|
PROD) ;;
|
|
*)
|
|
if [[ -f /etc/miaou/defaults.yaml ]]; then
|
|
# load already defined target in expanded conf
|
|
TARGET=$(grep -Es "^target:" /etc/miaou/defaults.yaml | cut -d ' ' -f2)
|
|
else
|
|
TARGET=$(ask_target)
|
|
fi
|
|
;;
|
|
esac
|
|
TARGET=${TARGET,,} # downcase
|
|
return 0
|
|
}
|
|
|
|
function miaou_configfiles() {
|
|
local PREFIX="miaou:config"
|
|
|
|
if [[ ! -d /etc/miaou ]]; then
|
|
echo -n "configuration initializing ..."
|
|
sudo mkdir -p /etc/miaou
|
|
sudo chown "$USER" /etc/miaou
|
|
PREFIX="" echoinfo DONE
|
|
fi
|
|
|
|
if [[ ! -f /etc/miaou/defaults.yaml ]]; then
|
|
echo -n "building /etc/miaou/defaults.yaml for the first time..."
|
|
shadow_passwd=$(sudo grep "$CURRENT_USER" /etc/shadow | cut -d ':' -f2)
|
|
env current_user="$CURRENT_USER" shadow_passwd="$shadow_passwd" valid_email="$valid_email" tera -e --env-key env --env-only -t "$MIAOU_BASEDIR/templates/etc/defaults.yaml.j2" -o /etc/miaou/defaults.yaml >/dev/null
|
|
yq ".target=\"$TARGET\"" /etc/miaou/defaults.yaml -i
|
|
PREFIX="" echoinfo DONE
|
|
fi
|
|
|
|
if [[ ! -f /etc/miaou/miaou.yaml ]]; then
|
|
echo -n "building /etc/miaou/miaou.yaml for the first time..."
|
|
cp "$MIAOU_BASEDIR/templates/etc/miaou.yaml.j2" /etc/miaou/miaou.yaml
|
|
PREFIX="" echoinfo DONE
|
|
fi
|
|
|
|
PREVIOUS_TARGET=""
|
|
echo "expanded configuration stored in <$MIAOU_CONFIGDIR>!"
|
|
[[ -f "$EXPANDED_CONF" ]] && PREVIOUS_TARGET=$(grep -Es "^target:" "$EXPANDED_CONF" | cut -d ' ' -f2)
|
|
|
|
if [[ "$PREVIOUS_TARGET" != "$TARGET" ]]; then
|
|
if [[ -z "$PREVIOUS_TARGET" ]]; then
|
|
echo "new target defined <$TARGET>"
|
|
else
|
|
echowarnn "TARGET has changed from <$PREVIOUS_TARGET> to <$TARGET>, do you agree?"
|
|
if askConfirmation N; then
|
|
echowarn "removing previous settings, please restart <miaou> to apply changes"
|
|
rm "$MIAOU_CONFIGDIR" -rf
|
|
else
|
|
echoerr "TARGET not accepted, exit"
|
|
exit 102
|
|
fi
|
|
fi
|
|
yq ".target=\"$TARGET\"" /etc/miaou/defaults.yaml -i
|
|
else
|
|
echo "target <$TARGET> already defined!"
|
|
fi
|
|
}
|
|
|
|
function opt_link() {
|
|
if [[ $MIAOU_BASEDIR != '/opt/miaou-server' ]]; then
|
|
if [[ -L '/opt/miaou-server' && -d '/opt/miaou-server' && $(readlink /opt/miaou-server) == "$MIAOU_BASEDIR" ]]; then
|
|
echo "symbolic link /opt/miaou-server already set up!"
|
|
else
|
|
sudo rm -f /opt/miaou-server
|
|
sudo ln -s "$MIAOU_BASEDIR" /opt/miaou-server
|
|
echo "symbolic link /opt/miaou-server successfully defined!"
|
|
fi
|
|
else
|
|
echo "real path /opt/miaou-server already set up!"
|
|
fi
|
|
}
|
|
|
|
function miaou_resolver() {
|
|
local PREFIX="miaou:resolver"
|
|
bridge=$(ip addr show lxdbr0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)
|
|
gateway=$(ip route | grep default | cut -d' ' -f3)
|
|
|
|
echo -n "test gateway <$gateway> as DNS resolver..."
|
|
if dig +short "@$gateway" "$FDN_DOMAINNAME" >/dev/null; then
|
|
resolver="$gateway"
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
PREFIX="" echoinfo "finally replaced by FDN_RESOLVER=<$FDN_RESOLVER>"
|
|
resolver="$FDN_RESOLVER"
|
|
fi
|
|
|
|
if command -v nmcli &>/dev/null; then
|
|
if [[ ! -f /etc/NetworkManager/dispatcher.d/50-miaou-resolver ]]; then
|
|
echo -n "use NetworkManager dispatcher to deal with LXD bridge automatically..."
|
|
sudo cp "$MIAOU_BASEDIR/templates/network-manager/50-miaou-resolver" /etc/NetworkManager/dispatcher.d/
|
|
sudo chmod +x /etc/NetworkManager/dispatcher.d/50-miaou-resolver
|
|
ACTIVE_CONNECTION=$(nmcli -g NAME connection show --active | head -n1)
|
|
sudo nmcli connection up "$ACTIVE_CONNECTION" &>/dev/null
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo "miaou-resolver in NetworkManager dispatcher already initialized!"
|
|
fi
|
|
fi
|
|
|
|
if sudo systemctl is-enabled systemd-resolved.service --quiet &>/dev/null; then
|
|
echo -n "disabling systemd-resolved..."
|
|
sudo systemctl stop systemd-resolved.service --quiet
|
|
sudo systemctl disable systemd-resolved.service --quiet
|
|
PREFIX="" echoinfo DONE
|
|
fi
|
|
|
|
if ! grep -q "nameserver $bridge" /etc/resolv.conf; then
|
|
echo -n "customize resolv.conf from scratch with bridge=<$bridge>, resolver=<$resolver> ..."
|
|
sudo chattr -i /etc/resolv.conf
|
|
sudo tee /etc/resolv.conf &>/dev/null <<EOF
|
|
# generated by miaou
|
|
nameserver $bridge # LXD bridge
|
|
nameserver $resolver # resolver
|
|
EOF
|
|
PREFIX="" echoinfo DONE
|
|
sudo chattr +i /etc/resolv.conf
|
|
else
|
|
echo "customize resolv.conf already defined!"
|
|
fi
|
|
}
|
|
|
|
function extra_dev_desktop {
|
|
# FIXME: detect if DEV && DESKTOP_USAGE to install spicy-client and other desktop tools
|
|
:
|
|
}
|
|
|
|
function override_lxd_service {
|
|
local PREFIX="lxd:override"
|
|
|
|
if [[ ! -d /etc/systemd/system/lxd.service.d ]]; then
|
|
echo -n "override lxd service..."
|
|
sudo mkdir -p /etc/systemd/system/lxd.service.d
|
|
sudo tee /etc/systemd/system/lxd.service.d/override.conf &>/dev/null <<EOF
|
|
[Service]
|
|
ExecStartPost=systemctl reload nftables.service
|
|
Environment=LANGUAGE=en:en_US
|
|
EOF
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl restart lxd.service
|
|
PREFIX="" echoinfo "DONE"
|
|
else
|
|
echo "lxd service already overridden!"
|
|
fi
|
|
}
|
|
|
|
function ask_for_email {
|
|
local PREFIX="install:ask_for_email"
|
|
valid_email=$(auto_detect_email)
|
|
|
|
while ! is_email_valid "$valid_email"; do
|
|
echo -n "mandatory email: "
|
|
read -rei "$valid_email" valid_email
|
|
done
|
|
}
|
|
|
|
function preload_bookworm_image {
|
|
local PREFIX="preload:bookworm"
|
|
|
|
if [[ $(lxc image list debian/12/cloud -f csv | wc -l) -lt 1 ]]; then
|
|
echo -n "downloading images from public remote, please hold on..."
|
|
sg $NEW_GROUP -c 'lxc image copy images:debian/12/cloud local: --copy-aliases --quiet'
|
|
PREFIX="" echoinfo DONE
|
|
else
|
|
echo -n "refreshing images from public remote..."
|
|
sg $NEW_GROUP -c 'lxc image refresh debian/12/cloud --quiet'
|
|
PREFIX="" echoinfo DONE
|
|
fi
|
|
}
|
|
|
|
function is_email_valid {
|
|
[[ "$1" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$ ]]
|
|
}
|
|
|
|
function auto_detect_email {
|
|
found_email="${EMAIL:-}"
|
|
[[ -z "$found_email" ]] && [[ -f /etc/miaou/defaults.yaml ]] && found_email=$(yq '.credential.email' /etc/miaou/defaults.yaml)
|
|
[[ "$found_email" == 'null' ]] && found_email=''
|
|
if ! is_email_valid "$found_email"; then
|
|
if [[ -f $HOME/.ssh/authorized_keys ]]; then
|
|
while IFS= read -r line; do
|
|
found_email="$line"
|
|
is_email_valid "$found_email" && break
|
|
done < <(cut -d ' ' -f3 <"$HOME/.ssh/authorized_keys")
|
|
fi
|
|
fi
|
|
|
|
builtin echo "$found_email"
|
|
}
|
|
|
|
### MAIN
|
|
|
|
if [[ "${1:-}" == "SESSION_RELOAD_REQUIRED" ]]; then
|
|
SESSION_RELOAD_REQUIRED=true
|
|
shift
|
|
else
|
|
SESSION_RELOAD_REQUIRED=false
|
|
fi
|
|
|
|
if [[ "${1:-}" == "shellenv" ]]; then
|
|
unset PREFIX
|
|
echo "export MIAOU_BASEDIR=$MIAOU_BASEDIR"
|
|
echo "export PATH=\"\$MIAOU_BASEDIR/scripts\":\$PATH"
|
|
else
|
|
|
|
. "$MIAOU_BASEDIR/lib/init.sh"
|
|
|
|
trap 'status=$?; on_exit; exit $status' EXIT
|
|
trap 'trap - HUP; on_exit SIGHUP; kill -HUP $$' HUP
|
|
trap 'trap - INT; on_exit SIGINT; kill -INT $$' INT
|
|
trap 'trap - TERM; on_exit SIGTERM; kill -TERM $$' TERM
|
|
|
|
PREFIX="miaou"
|
|
: $PREFIX
|
|
TARGET=${1:-}
|
|
CURRENT_USER=$(id -un)
|
|
|
|
check_normal_user
|
|
check_target
|
|
ask_for_email
|
|
|
|
sudo_required
|
|
install_miaou_bash
|
|
install_mandatory_commands
|
|
prepare_toolbox
|
|
add_toolbox_sudoers
|
|
prepare_nftables
|
|
prepare_lxd
|
|
override_lxd_service
|
|
configure_lxd
|
|
preload_bookworm_image
|
|
miaou_resolver
|
|
miaou_evalfrombashrc
|
|
miaou_configfiles
|
|
opt_link
|
|
extra_dev_desktop
|
|
|
|
if [[ "$SESSION_RELOAD_REQUIRED" == true ]]; then
|
|
echoinfo "successful \`session\` installation"
|
|
echowarn "current session has been reloaded, due to new group <$NEW_GROUP>! Next time you type \`exit\`, this session will emit a kill signal!"
|
|
else
|
|
echoinfo "successful installation"
|
|
fi
|
|
fi
|