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.

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