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.

485 lines
14 KiB

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