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.

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