provisioning tool for building opinionated architecture
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.

455 lines
14 KiB

9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
  1. #!/bin/bash
  2. MIAOU_BASEDIR=$(readlink -f "$(dirname "$0")/..")
  3. # shellcheck source=/dev/null
  4. . "$MIAOU_BASEDIR/lib/functions.sh"
  5. readonly MIAOU_BASEDIR
  6. miaou_init
  7. EXPANDED_CONF="$MIAOU_CONFIGDIR/miaou.expanded.yaml"
  8. NEW_GROUP=lxd
  9. readonly NEW_GROUP EXPANDED_CONF
  10. on_exit() {
  11. if [ -n "${1:-}" ]; then
  12. echowarn "Aborted by $1"
  13. elif [ "${status:-}" -ne 0 ]; then
  14. echowarn "Failure (status $status)"
  15. fi
  16. }
  17. function prepare_lxd {
  18. local PREFIX="lxd:prepare"
  19. # test group lxd assign to current user
  20. if ! groups | grep -q lxd; then
  21. echo "define lxd and assign to user <$USER>"
  22. sudo groupadd --force "$NEW_GROUP"
  23. sudo usermod --append --groups "$NEW_GROUP" "$(whoami)"
  24. sg "$NEW_GROUP" -c "$0 SESSION_RELOAD_REQUIRED $TARGET $@"
  25. sg "$NEW_GROUP" -c bash
  26. kill -9 "$PPID"
  27. # no further processing because exec has been called!
  28. else
  29. echo "user <$USER> already belongs to group <lxd>!"
  30. fi
  31. sudo /opt/miaou-bash/tools/idem_apt_install lxd btrfs-progs
  32. override_lxd_service
  33. # test lxdbr0
  34. if ! lxc network info lxdbr0 &>/dev/null; then
  35. echo "bridge <lxdbr0> down, so initialization will use default preseed..."
  36. if [[ $(printenv container) == 'lxc' ]]; then
  37. echo "nested configuration applying..."
  38. cat <<EOF | sg $NEW_GROUP -c 'lxd init --preseed'
  39. config: {}
  40. networks:
  41. - name: lxdbr0
  42. type: bridge
  43. config:
  44. ipv4.address: auto
  45. ipv6.address: none
  46. storage_pools:
  47. - config: {}
  48. description: ""
  49. name: default
  50. driver: dir
  51. profiles:
  52. - config:
  53. security.privileged: "true"
  54. description: ""
  55. devices:
  56. eth0:
  57. name: eth0
  58. nictype: bridged
  59. parent: lxdbr0
  60. type: nic
  61. root:
  62. path: /
  63. pool: default
  64. type: disk
  65. name: default
  66. projects: []
  67. cluster: null
  68. EOF
  69. else
  70. empty_block_partition=$(lsblk -o NAME,FSTYPE,GROUP --noheadings -p | grep -E ^.─ | grep disk | awk '{if($3=="") print $1}' | cut -d'/' -f3)
  71. if [[ -n "$empty_block_partition" ]]; then
  72. echo "use empty block partition /dev/$empty_block_partition for speed and optimization"
  73. cat <<EOF | sudo lxd init --preseed
  74. config: {}
  75. networks:
  76. - config:
  77. ipv4.address: auto
  78. ipv6.address: none
  79. description: ""
  80. name: lxdbr0
  81. type: ""
  82. project: default
  83. storage_pools:
  84. - config:
  85. source: /dev/$empty_block_partition
  86. description: ""
  87. name: default
  88. driver: btrfs
  89. profiles:
  90. - config: {}
  91. description: ""
  92. devices:
  93. eth0:
  94. name: eth0
  95. network: lxdbr0
  96. type: nic
  97. root:
  98. path: /
  99. pool: default
  100. type: disk
  101. name: default
  102. projects: []
  103. cluster: null
  104. EOF
  105. echo OK
  106. else
  107. echo "use dir partition for development purpose"
  108. cat <<EOF | lxd init --preseed
  109. config: {}
  110. networks:
  111. - name: lxdbr0
  112. type: bridge
  113. config:
  114. ipv4.address: auto
  115. ipv6.address: none
  116. EOF
  117. fi
  118. fi
  119. else
  120. echo "bridge <lxdbr0> found implies it has been already initialized!"
  121. fi
  122. set_alias 'sameuser' "exec @ARG1@ -- su --whitelist-environment container,container_hostname - $(whoami)"
  123. set_alias 'login' 'exec @ARGS@ --mode interactive -- /bin/bash -c $@${user:-root} - exec su --whitelist-environment container,container_hostname - '
  124. set_alias 'll' 'list -c ns4mDN'
  125. # test environment container hostname
  126. local env_container_hostname
  127. env_container_hostname=$(sg $NEW_GROUP -c 'lxc profile get default environment.container_hostname')
  128. if [[ -z "$env_container_hostname" ]]; then
  129. env_container_hostname=$(hostname -s)
  130. if env | grep -q container_hostname; then
  131. local previous_container_hostname
  132. previous_container_hostname=$(env | grep container_hostname | cut -d '=' -f2)
  133. env_container_hostname="$previous_container_hostname $env_container_hostname"
  134. fi
  135. echo -n "set environment container_hostname to <$env_container_hostname> ... "
  136. sg $NEW_GROUP -c "lxc profile set default environment.container_hostname \"$env_container_hostname\""
  137. PREFIX="" echoinfo OK
  138. else
  139. echo "environment container_hostname <$env_container_hostname> already defined!"
  140. fi
  141. if ! grep -q "root:$(id -u):1" /etc/subuid; then
  142. echo -n "subuid, subgid allowing <$(whoami)> ..."
  143. printf "root:$(id -u):1\n" | sudo tee -a /etc/subuid /etc/subgid
  144. PREFIX="" echoinfo DONE
  145. else
  146. echo "subuid, subgid allowing <$(whoami)> already done!"
  147. fi
  148. if [[ ! -d "$HOME/LXD/SHARED" ]]; then
  149. echo -n "$HOME/LXD/SHARED creating ... "
  150. mkdir "$HOME/LXD/SHARED" -p
  151. PREFIX="" echoinfo DONE
  152. else
  153. echo "folder <$HOME/LXD/SHARED> already created!"
  154. fi
  155. if [[ ! -d "$HOME/LXD/BACKUP" ]]; then
  156. echo -n "$HOME/LXD/BACKUP creating ... "
  157. mkdir "$HOME/LXD/BACKUP" -p
  158. PREFIX="" echoinfo DONE
  159. else
  160. echo "folder <$HOME/LXD/BACKUP> already created!"
  161. fi
  162. }
  163. function set_alias {
  164. local name="$1"
  165. local command="$2"
  166. if ! lxc alias list -f csv | grep -q "^$name,"; then
  167. echo -n "defining new lxc alias <$name> ..."
  168. lxc alias add "$name" "$command"
  169. PREFIX="" echoinfo OK
  170. else
  171. echo "lxc alias <$name> already defined!"
  172. fi
  173. }
  174. function miaou_evalfrombashrc() {
  175. local PREFIX="miaou:bashrc"
  176. output=$(
  177. /opt/miaou-bash/tools/append_or_replace \
  178. "^eval \"\\$\($MIAOU_BASEDIR/lib/install.sh shellenv\)\"$" \
  179. "eval \"\$($MIAOU_BASEDIR/lib/install.sh shellenv)\"" \
  180. "$HOME/.bashrc"
  181. )
  182. if [[ "$output" == "appended" ]]; then
  183. echo "new path <$MIAOU_BASEDIR> created!"
  184. SESSION_RELOAD_REQUIRED=true
  185. else
  186. echo "path <$MIAOU_BASEDIR> already loaded!"
  187. fi
  188. }
  189. function ask_target() {
  190. PS3='Choose miaou target purpose: '
  191. foods=("Dev" "Beta" "Prod")
  192. select ans in "${foods[@]}"; do
  193. builtin echo "${ans^^}"
  194. break
  195. done
  196. }
  197. function check_credential {
  198. local PREFIX="check:credential"
  199. check_yaml_defined_value /etc/miaou/defaults.yaml 'credential.username' &&
  200. check_yaml_defined_value /etc/miaou/defaults.yaml 'credential.shadow' &&
  201. check_yaml_defined_value /etc/miaou/defaults.yaml 'credential.email'
  202. }
  203. function check_target() {
  204. case "${TARGET^^}" in
  205. DEV) ;;
  206. BETA) ;;
  207. PROD) ;;
  208. *)
  209. if [[ -f /etc/miaou/defaults.yaml ]]; then
  210. # load already defined target in expanded conf
  211. TARGET=$(grep -Es "^target:" /etc/miaou/defaults.yaml | cut -d ' ' -f2)
  212. else
  213. TARGET=$(ask_target)
  214. fi
  215. ;;
  216. esac
  217. TARGET=${TARGET,,} # downcase
  218. return 0
  219. }
  220. function miaou_configfiles() {
  221. local PREFIX="miaou:config"
  222. if [[ ! -d /etc/miaou ]]; then
  223. echo -n "configuration initializing ..."
  224. sudo mkdir -p /etc/miaou
  225. sudo chown "$USER" /etc/miaou
  226. PREFIX="" echoinfo OK
  227. fi
  228. if [[ ! -f /etc/miaou/defaults.yaml ]]; then
  229. echo -n "building /etc/miaou/defaults.yaml for the first time..."
  230. shadow_passwd=$(sudo grep "$CURRENT_USER" /etc/shadow | cut -d ':' -f2)
  231. 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
  232. yq ".target=\"$TARGET\"" /etc/miaou/defaults.yaml -i
  233. PREFIX="" echoinfo OK
  234. fi
  235. if [[ ! -f /etc/miaou/miaou.yaml ]]; then
  236. echo -n "building /etc/miaou/miaou.yaml for the first time..."
  237. cp "$MIAOU_BASEDIR/templates/etc/miaou.yaml.j2" /etc/miaou/miaou.yaml
  238. PREFIX="" echoinfo OK
  239. fi
  240. PREVIOUS_TARGET=""
  241. echo "expanded configuration stored in <$MIAOU_CONFIGDIR>!"
  242. [[ -f "$EXPANDED_CONF" ]] && PREVIOUS_TARGET=$(grep -Es "^target:" "$EXPANDED_CONF" | cut -d ' ' -f2)
  243. if [[ "$PREVIOUS_TARGET" != "$TARGET" ]]; then
  244. if [[ -z "$PREVIOUS_TARGET" ]]; then
  245. echo "new target defined <$TARGET>"
  246. else
  247. echowarnn "TARGET has changed from <$PREVIOUS_TARGET> to <$TARGET>, do you agree?"
  248. if askConfirmation N; then
  249. echowarn "removing previous settings, please restart <miaou> to apply changes"
  250. rm "$MIAOU_CONFIGDIR" -rf
  251. else
  252. echoerr "TARGET not accepted, exit"
  253. exit 102
  254. fi
  255. fi
  256. yq ".target=\"$TARGET\"" /etc/miaou/defaults.yaml -i
  257. else
  258. echo "target <$TARGET> already defined!"
  259. fi
  260. }
  261. function opt_link() {
  262. if [[ $MIAOU_BASEDIR != '/opt/miaou' ]]; then
  263. if [[ -L '/opt/miaou' && -d '/opt/miaou' && $(readlink /opt/miaou) == "$MIAOU_BASEDIR" ]]; then
  264. echo "symbolic link /opt/miaou already set up!"
  265. else
  266. sudo rm -f /opt/miaou
  267. sudo ln -s "$MIAOU_BASEDIR" /opt/miaou
  268. echo "symbolic link /opt/miaou successfully defined!"
  269. fi
  270. else
  271. echo "real path /opt/miaou already set up!"
  272. fi
  273. }
  274. function miaou_resolver() {
  275. local PREFIX="miaou:resolver"
  276. bridge=$(ip addr show lxdbr0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)
  277. gateway=$(ip route | grep default | cut -d' ' -f3)
  278. if command -v nmcli &>/dev/null; then
  279. if [[ ! -f /etc/NetworkManager/dispatcher.d/50-miaou-resolver ]]; then
  280. echo -n "use NetworkManager dispatcher to deal with LXD bridge automatically..."
  281. sudo cp "$MIAOU_BASEDIR/templates/network-manager/50-miaou-resolver" /etc/NetworkManager/dispatcher.d/
  282. sudo chmod +x /etc/NetworkManager/dispatcher.d/50-miaou-resolver
  283. ACTIVE_CONNECTION=$(nmcli -g NAME connection show --active | head -n1)
  284. sudo nmcli connection up "$ACTIVE_CONNECTION" &>/dev/null
  285. PREFIX="" echoinfo OK
  286. else
  287. echo "miaou-resolver in NetworkManager dispatcher already initialized!"
  288. fi
  289. else
  290. if ! grep -q "nameserver $bridge" /etc/resolv.conf; then
  291. echo "customize resolv.conf from scratch (SERVER)..."
  292. sudo tee /etc/resolv.conf &>/dev/null <<EOF
  293. nameserver $bridge
  294. nameserver $gateway
  295. EOF
  296. PREFIX="" echoinfo OK
  297. else
  298. echo "customize resolv.conf already already defined!"
  299. fi
  300. fi
  301. }
  302. function extra_dev_desktop {
  303. # FIXME: detect if DEV && DESKTOP_USAGE to install spicy-client and other desktop tools
  304. :
  305. }
  306. function override_lxd_service {
  307. local PREFIX="lxd:override"
  308. if [[ ! -d /etc/systemd/system/lxd.service.d ]]; then
  309. echo -n "override lxd service..."
  310. sudo mkdir -p /etc/systemd/system/lxd.service.d
  311. cat <<EOF | sudo tee /etc/systemd/system/lxd.service.d/override.conf
  312. [Service]
  313. ExecStartPost=systemctl reload nftables.service
  314. Environment=LANGUAGE=en:en_US
  315. EOF
  316. sudo systemctl daemon-reload
  317. sudo systemctl restart lxd.service
  318. PREFIX="" echo "OK"
  319. else
  320. echo "lxd service already overridden!"
  321. fi
  322. }
  323. function ask_for_email {
  324. local PREFIX="install:ask_for_email"
  325. valid_email=$(auto_detect_email)
  326. while ! is_email_valid "$valid_email"; do
  327. echo -n "mandatory email: "
  328. read -rei "$valid_email" valid_email
  329. done
  330. }
  331. function preload_bookworm_image {
  332. local PREFIX="preload:bookworm"
  333. if [[ $(lxc image list debian/12/cloud -f csv | wc -l) -lt 1 ]]; then
  334. echo -n "downloading images from public remote, please hold on..."
  335. sg $NEW_GROUP -c 'lxc image copy images:debian/12/cloud local: --copy-aliases --quiet'
  336. PREFIX="" echoinfo OK
  337. else
  338. echo -n "refreshing images from public remote..."
  339. sg $NEW_GROUP -c 'lxc image refresh debian/12/cloud --quiet'
  340. PREFIX="" echoinfo DONE
  341. fi
  342. }
  343. function is_email_valid {
  344. [[ "$1" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$ ]]
  345. }
  346. function auto_detect_email {
  347. found_email=""
  348. if [[ -f $HOME/.ssh/authorized_keys ]]; then
  349. while IFS= read -r line; do
  350. found_email="$line"
  351. is_email_valid "$found_email" && break
  352. done < <(cut -d ' ' -f3 <"$HOME/.ssh/authorized_keys")
  353. fi
  354. builtin echo "$found_email"
  355. }
  356. ### MAIN
  357. if [[ "${1:-}" == "SESSION_RELOAD_REQUIRED" ]]; then
  358. SESSION_RELOAD_REQUIRED=true
  359. shift
  360. else
  361. SESSION_RELOAD_REQUIRED=false
  362. fi
  363. if [[ "${1:-}" == "shellenv" ]]; then
  364. unset PREFIX
  365. echo "export MIAOU_BASEDIR=$MIAOU_BASEDIR"
  366. echo "export PATH=\"\$MIAOU_BASEDIR/scripts\":\$PATH"
  367. else
  368. . "$MIAOU_BASEDIR/lib/init.sh"
  369. trap 'status=$?; on_exit; exit $status' EXIT
  370. trap 'trap - HUP; on_exit SIGHUP; kill -HUP $$' HUP
  371. trap 'trap - INT; on_exit SIGINT; kill -INT $$' INT
  372. trap 'trap - TERM; on_exit SIGTERM; kill -TERM $$' TERM
  373. PREFIX="miaou"
  374. : $PREFIX
  375. TARGET=${1:-}
  376. CURRENT_USER=$(id -un)
  377. check_normal_user
  378. check_target
  379. ask_for_email
  380. sudo_required
  381. install_miaou_bash
  382. install_mandatory_commands
  383. prepare_toolbox
  384. add_toolbox_sudoers
  385. prepare_nftables
  386. prepare_lxd "$@"
  387. preload_bookworm_image
  388. miaou_resolver
  389. miaou_evalfrombashrc
  390. miaou_configfiles
  391. opt_link
  392. extra_dev_desktop
  393. if [[ "$SESSION_RELOAD_REQUIRED" == true ]]; then
  394. echoinfo "successful session installation"
  395. echowarn "be aware: the current session has been reloaded, due to new group <$NEW_GROUP>."
  396. echowarn "-> exit means kill!"
  397. else
  398. echoinfo "successful installation"
  399. fi
  400. fi