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

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
2 months ago
10 months ago
10 months ago
10 months ago
2 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
8 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
8 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 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