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.

537 lines
16 KiB

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