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.

488 lines
14 KiB

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