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.

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