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.

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