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.

489 lines
15 KiB

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