From b5af450a8e5e183a3f110929b38343fca0d75039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20Buchholz=20G=C3=B6kt=C3=BCrk?= Date: Sun, 2 Jan 2022 15:09:21 -0800 Subject: [PATCH] move logic from init scripts to /lib/tiny-cloud/* --- README.md | 40 ++++++++++++++-------------- etc/conf.d/tiny-cloud | 18 ++++++++++--- etc/init.d/tiny-cloud | 42 ++++++------------------------ etc/init.d/tiny-cloud-early | 26 ++++--------------- etc/init.d/tiny-cloud-final | 30 +++++++-------------- lib/tiny-cloud/aws/imds | 2 +- lib/tiny-cloud/azure/imds | 2 +- lib/tiny-cloud/gcp/imds | 2 +- lib/tiny-cloud/imds | 52 +++++++++++++++++++++---------------- lib/tiny-cloud/init-common | 15 +++++++++++ lib/tiny-cloud/init-early | 28 ++++++++++++++++++++ lib/tiny-cloud/init-final | 30 +++++++++++++++++++++ lib/tiny-cloud/init-main | 37 ++++++++++++++++++++++++++ lib/tiny-cloud/oci/imds | 2 +- 14 files changed, 201 insertions(+), 125 deletions(-) create mode 100644 lib/tiny-cloud/init-common create mode 100644 lib/tiny-cloud/init-early create mode 100644 lib/tiny-cloud/init-final create mode 100644 lib/tiny-cloud/init-main diff --git a/README.md b/README.md index e2beca0..085482a 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ instance: * set the instance's hostname from instance metadata * install SSH keys from instance metadata to the cloud user account's `authorized_keys` file (the user must already exist) -* save the instance user-data to a file and if it's a script, execute it at the - end of the **default** runlevel +* save the instance user-data to a file, and if it's a script, execute it at + the end of the **default** runlevel Optional features, which may not be universally necessary: * manage symlinks from NVMe block devices to `/dev/xvd` and `/dev/sd` devices @@ -68,26 +68,26 @@ rc-update add tiny-cloud-final default ## Configuration -Tiny Cloud looks expects configuration to be found at -[`/etc/conf.d/tiny-cloud`](etc/conf.d/tiny-cloud), which documents all -tuneable parameters (and their defaults). +By default, Tiny Cloud expects configuration at `/etc/conf.d/tiny-cloud`, +although the location can be overridden by setting the `TINY_CLOUD_CONF` +environment variable. The stock [`etc/conf.d/tiny-cloud`]( +etc/conf.d/tiny-cloud) file contains details of all tuneable settings. -However, because Tiny Cloud does not do auto-detection, you ***must*** set a -value for `CLOUD` indicating which cloud provider which will be used when -instantiating the image. Current valid values are `aws`, `azure`, `gcp`, and -`oci`. +*Because Tiny Cloud does not currently do auto-detection, you **MUST** set a +configuration value for `CLOUD` indicating which cloud provider will be used. +Current valid values are `aws`, `azure`, `gcp`, and `oci`.* ## Operation The first time an instance boots -- either freshly instantiated from an image, -or after installation on an existing instance -- Tiny Cloud sets up the +or after installation on a pre-existing instance -- Tiny Cloud sets up the instance in three phases... ### Early Phase The `tiny-cloud-early` init script does not depend on the cloud provider's -Instance MetaData Service (IMDS), and does therefore does not have a dependency -on networking. During this "early" phase, the root filesystem is expanded, and +Instance MetaData Service (IMDS), and therefore does not have a dependency on +networking. During this "early" phase, the root filesystem is expanded, and any necessary `mdev` rules for device hotplug are set up. ### Main Phase @@ -99,16 +99,18 @@ data, and sets up instance's hostname and the cloud user's SSH keys before ### Final Phase `tiny-cloud-final` should be the very last init script to run in the -**default** runlevel, and saves the instance's user data to -`/var/lib/cloud/user-data`. +**default** runlevel. By default, it saves the instance's user data to +`/var/lib/cloud/user-data`, which is overrideable via the `TINY_CLOUD_VAR` +andr `CLOUD_USERDATA` config settings. -If the user data is a script that starts with `#!` (aka "[shebang]( -https://en.wikipedia.org/wiki/Shebang_(Unix))"), it will be executed; its -output (combined STDOUT and STDERR) is saved to `/var/log/cloud/user-data.log` -and the exit code can be found in `/var/log/cloud/user-data.exit`. +If the user data is a script starting with `#!/`, it will be executed; its +output (combined STDOUT and STDERR) and exit code are saved to +`/var/log/user-data.log` and `/var/log/user-data.exit`, respectively -- unless +overriden with `TINY_CLOUD_LOGS` and `CLOUD_USERDATA` config settings. If all went well, the very last thing `tiny-cloud-final` does is touch -`/var/lib/cloud/.bootstrap-complete` into existence. +a `.bootstrap-complete` file into existence in `/var/lib/cloud` or another +directory specified by the `TINY_CLOUD_VAR` config setting. ### Further Reboots diff --git a/etc/conf.d/tiny-cloud b/etc/conf.d/tiny-cloud index 74b3f26..0739192 100644 --- a/etc/conf.d/tiny-cloud +++ b/etc/conf.d/tiny-cloud @@ -1,4 +1,4 @@ -# tiny-cloud configuration +# Tiny Cloud configuration # REQUIRED: The instance's cloud provider (valid: aws, azure, gcp, oci) CLOUD= @@ -6,5 +6,17 @@ CLOUD= # User account where instance SSH keys are installed #CLOUD_USER=alpine -# Number of seconds an AWS IMDS token is valid -#IMDS_TOKEN_TTL=5 \ No newline at end of file +# Filename of userdata file (in TINY_CLOUD_VAR directory) +#CLOUD_USERDATA=user-data + +# IMDS token validity, in seconds (AWS only) +#IMDS_TOKEN_TTL=5 + +# Location of libraries +#TINY_CLOUD_LIBS=/lib/tiny-cloud + +# Location of var directory +#TINY_CLOUD_VAR=/var/lib/cloud + +# Location of log directory +#TINY_CLOUD_LOGS=/var/log diff --git a/etc/init.d/tiny-cloud b/etc/init.d/tiny-cloud index 736aff6..acbcc3d 100644 --- a/etc/init.d/tiny-cloud +++ b/etc/init.d/tiny-cloud @@ -8,44 +8,18 @@ depend() { before sshd } -_update_hostname() { - local fqdn=$(imds_hostname) - local host="${fqdn%%\.*}" - echo "$host" > /etc/hostname - hostname -F /etc/hostname - echo -e "127.0.1.1\t$fqdn $host" >> /etc/hosts -} - -_set_ssh_keys() { - local user="$1" - local pwent=$(getent passwd "$user") - local group=$(echo "$pwent" | cut -d: -f4) - local ssh_dir="$(echo "$pwent" | cut -d: -f6)/.ssh" - local keys_file="$ssh_dir/authorized_keys" - - if [ ! -d "$ssh_dir" ]; then - mkdir -p "$ssh_dir" - chmod 700 "$ssh_dir" - fi - - touch "$keys_file" - chmod 600 "$keys_file" - chown -R "$user:$group" "$ssh_dir" - imds_ssh_keys > "$keys_file" -} - start() { - [ -f "/var/lib/cloud/.bootstrap-complete" ] && return 0 - [ -d "/var/lib/cloud" ] || mkdir -p /var/lib/cloud + source /lib/tiny-cloud/init-main - source /var/lib/tiny-cloud/imds - CLOUD_USER="${CLOUD_USER:-alpine}" + # TODO: background tiny-cloud network watcher? - ebegin "Setting Instance Hostname"; _update_hostname; eend $? + is_bootstrapped && return 0 - ebegin "Installing SSH Keys for $CLOUD_USER User" - _set_ssh_keys "$CLOUD_USER" + ebegin "Setting Instance Hostname" + set_hostname eend $? - # TODO: background tiny-cloud-network watcher? + ebegin "Installing SSH Keys for $CLOUD_USER User" + set_ssh_keys "$CLOUD_USER" + eend $? } \ No newline at end of file diff --git a/etc/init.d/tiny-cloud-early b/etc/init.d/tiny-cloud-early index 184177a..59249f2 100644 --- a/etc/init.d/tiny-cloud-early +++ b/etc/init.d/tiny-cloud-early @@ -7,30 +7,14 @@ depend() { before mdev } -_expand_root_partition() { - local mountpoint=$(busybox mountpoint -n / | cut -d' ' -f1) - local volume=$(echo "$mountpoint" | - sed -Ee "s/(nvme\d+n\d|(xv|s)d[a-z])p?\d?$/\1/" - ) - - if [ "$mountpoint" != "$volume" ]; then - partition=$(echo "$volume" | sed -Ee "s/.*(\d+)$/\2/") - einfo "Expanding root partition to volume size..." - echo ", +" | sfdisk -q --no-reread -N "$partition" "$volume" - einfo "Updating kernel with new partition table..." - partx -u "$volume" - fi - einfo "Resizing root filesystem..." - resize2fs "$mountpoint" -} - start() { - [ -f "/var/lib/cloud/.bootstrap-complete" ] && return 0 - [ -d "/var/lib/cloud" ] || mkdir -p /var/lib/cloud + source /lib/tiny-cloud/init-early - source /etc/conf.d/tiny-cloud # or use auto-sourced tiny-cloud-early? + is_bootstrapped && return 0 - ebegin "Expanding Root Partition"; _expand_root_partition; eend $? + ebegin "Expanding Root Volume/Partition" + expand_root + eend $? # TODO: _setup mdev things, if applicable } \ No newline at end of file diff --git a/etc/init.d/tiny-cloud-final b/etc/init.d/tiny-cloud-final index 9c9a64e..0a6291e 100644 --- a/etc/init.d/tiny-cloud-final +++ b/etc/init.d/tiny-cloud-final @@ -8,30 +8,18 @@ depend() { provide cloud-final } -_save_userdata() { - imds_userdata > "$USERDATA" -} - -_run_userdata() { - local log="/var/log/user-data.log" - local exit="/var/log/user-data.exit" - - chmod +x "$USERDATA" - { "$USERDATA" 2>& 1; echo $? > "$exit"; } | tee "$log" - - return $(cat "$exit") -} - start() { - [ -f "/var/lib/cloud/.bootstrap-complete" ] && return 0 - [ -d "/var/lib/cloud" ] || mkdir -p /var/lib/cloud + source /lib/tiny-cloud/init-final - source /lib/tiny-cloud/imds + is_bootstrapped && return 0 - USERDATA="/var/lib/cloud/user-data" + ebegin "Saving Instance UserData" + save_userdata + eend $? - ebegin "Saving Instance UserData"; _save_userdata; eend $? - if head -n1 "$USERDATA" | grep -q '^#!/'; then - ebegin "Executing UserData Script"; _run_userdata; eend $? + if is_userdata_script; then + ebegin "Executing UserData Script" + run_userdata + eend $? fi } \ No newline at end of file diff --git a/lib/tiny-cloud/aws/imds b/lib/tiny-cloud/aws/imds index 06b8b91..3e474e8 100644 --- a/lib/tiny-cloud/aws/imds +++ b/lib/tiny-cloud/aws/imds @@ -1,4 +1,4 @@ -# AWS Instance MetaData Service related variables and functions +# AWS Instance MetaData Service variables and functions # vim:set ft=sh noet ts=4: IMDS_HEADER="X-aws-ec2-metadata-token" diff --git a/lib/tiny-cloud/azure/imds b/lib/tiny-cloud/azure/imds index 7e63a32..08a34fb 100644 --- a/lib/tiny-cloud/azure/imds +++ b/lib/tiny-cloud/azure/imds @@ -1,4 +1,4 @@ -# Azure metadata-related variables and functions +# Azure Instance MetaData Service variables and functions # vim:set ft=sh noet ts=4: IMDS_HEADER="Metadata" diff --git a/lib/tiny-cloud/gcp/imds b/lib/tiny-cloud/gcp/imds index ea70b3f..0770337 100644 --- a/lib/tiny-cloud/gcp/imds +++ b/lib/tiny-cloud/gcp/imds @@ -1,4 +1,4 @@ -# Google Cloud metadata-related variables and functions +# Google Cloud Instance MetaData Service variables and functions # vim:set ft=sh noet ts=4: IMDS_HEADER="Metadata-Flavor" diff --git a/lib/tiny-cloud/imds b/lib/tiny-cloud/imds index f8b43b7..03708c1 100644 --- a/lib/tiny-cloud/imds +++ b/lib/tiny-cloud/imds @@ -1,32 +1,33 @@ -# tiny-cloud Instance MetaData Service related functions and variables +# Tiny Cloud - Instance MetaData Service functions and variables # vim:set ft=sh ts=4 noet: -CONF_DIR="/etc/conf.d" -LIB_DIR="/lib/tiny-cloud" +### configuration -### load configuration - -[ -f "$CONF_DIR"/tiny-cloud ] && source "$CONF_DIR"/tiny-cloud - -### configuration defaults - -CLOUD="${CLOUD:-unknown}" -CLOUD_LOGIN="${CLOUD_LOGIN:-alpine}" -if [ ! -d "$LIB_DIR/$CLOUD" ]; then - echo "ERROR: Unknown Cloud '$CLOUD'" >&2 - exit 1 +if [ -z "$TINY_CLOUD_LIBS" ]; then + TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} + [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" + TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} fi -IMDS_HEADER= -IMDS_QUERY= -IMDS_URL= +# defaults +CLOUD="${CLOUD:-unknown}" -IMDS_HOSTNAME= -IMDS_SSH_KEYS= -IMDS_USERDATA= +# these are always cloud-specific -### default/common functions +unset \ + IMDS_HEADER \ + IMDS_QUERY \ + IMDS_URL \ + IMDS_HOSTNAME \ + IMDS_SSH_KEYS \ + IMDS_USERDATA +unset -f \ + _imds_token \ + _imds_header \ + 2>/dev/null || true + +# without error reporting or ending newline enforcement _imds() { wget --quiet --header "$(_imds_header)" --output-document - \ "$IMDS_URL/$1/$IMDS_QUERY" @@ -55,6 +56,11 @@ imds_ssh_keys() { imds "$IMDS_SSH_KEYS" } -# cloud-specific things (potentially overriding the above) +### load cloud-specific things -[ -f "$LIB_DIR/$CLOUD/metadata" ] && source "$LIB_DIR/$CLOUD"/imds +if [ ! -d "$TINY_CLOUD_LIBS/$CLOUD" ]; then + echo "ERROR: Unknown Cloud '$CLOUD'" >&2 + exit 1 +fi + +source "$TINY_CLOUD_LIBS/$CLOUD/imds" diff --git a/lib/tiny-cloud/init-common b/lib/tiny-cloud/init-common new file mode 100644 index 0000000..31529ed --- /dev/null +++ b/lib/tiny-cloud/init-common @@ -0,0 +1,15 @@ +# Tiny Cloud - Common Initialization +# vim:set ft=sh ts=4 noet: + +# set defaults +CLOUD_USER=${CLOUD_USER:-alpine} +CLOUD_USERDATA=${CLOUD_USERDATA:-user-data} +TINY_CLOUD_LOGS=${TINY_CLOUD_LOGS:-/var/log} +TINY_CLOUD_VAR=${TINY_CLOUD_VAR:-/var/lib/cloud} + +# ensure existence of output directories +[ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR" +[ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS" + +# is initial bootstrap already done? +is_bootstrapped() { [ -f "$TINY_CLOUD_VAR"/.bootstrap-complete ]; } \ No newline at end of file diff --git a/lib/tiny-cloud/init-early b/lib/tiny-cloud/init-early new file mode 100644 index 0000000..68c8bd9 --- /dev/null +++ b/lib/tiny-cloud/init-early @@ -0,0 +1,28 @@ +# Tiny Cloud - Early Phase Functions +# vim:set ft=sh ts=4 noet: + +if [ -z "$TINY_CLOUD_LIBS" ]; then + TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} + [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" + TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} +fi + +source "$TINY_CLOUD_LIBS"/init-common + +expand_root() { + # explicitly use busybox, in case util-linux is also installed + local mountpoint=$(busybox mountpoint -n / | cut -d' ' -f1) + local volume=$(echo "$mountpoint" | + sed -Ee "s/(nvme\d+n\d|(xv|s)d[a-z])p?\d?$/\1/" + ) + + if [ "$mountpoint" != "$volume" ]; then + partition=$(echo "$volume" | sed -Ee "s/.*(\d+)$/\2/") + einfo "Expanding root partition to volume size..." + echo ", +" | sfdisk -q --no-reread -N "$partition" "$volume" + einfo "Updating kernel with new partition table..." + partx -u "$volume" + fi + einfo "Resizing root filesystem..." + resize2fs "$mountpoint" +} \ No newline at end of file diff --git a/lib/tiny-cloud/init-final b/lib/tiny-cloud/init-final new file mode 100644 index 0000000..107d949 --- /dev/null +++ b/lib/tiny-cloud/init-final @@ -0,0 +1,30 @@ +# Tiny Cloud - Final Phase Functions +# vim:set ft=sh ts=4 noet: + +if [ -z "$TINY_CLOUD_LIBS" ]; then + TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} + [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" + TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} +fi + +source "$TINY_CLOUD_LIBS"/init-common +source "$TINY_CLOUD_LIBS"/imds + +save_userdata() { + imds_userdata > "$USERDATA" +} + +is_userdata_script() { + head -n1 "$TINY_CLOUD_VAR/$CLOUD_USERSDATA" | grep -q '%#!/' +} + +run_userdata() { + local log="$TINY_CLOUD_LOGS/$CLOUD_USERDATA.log" + local exit="$TINY_CLOUD_LOGS/$CLOUD_USERDATA.exit" + local userdata="$TINY_CLOUD_VAR/$CLOUD_USERDATA" + + chmod +x "$userdata" + { "$userdata" 2>& 1; echo $? > "$exit"; } | tee "$log" + + return $(cat "$exit") +} \ No newline at end of file diff --git a/lib/tiny-cloud/init-main b/lib/tiny-cloud/init-main new file mode 100644 index 0000000..9ff1ebe --- /dev/null +++ b/lib/tiny-cloud/init-main @@ -0,0 +1,37 @@ +# Tiny Cloud - Main Phase Functions +# vim:set ft=sh ts=4 noet: + +if [ -z "$TINY_CLOUD_LIBS" ]; then + TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} + [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" + TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} +fi + +source "$TINY_CLOUD_LIBS"/init-common +source "$TINY_CLOUD_LIBS"/imds + +set_hostname() { + local fqdn=$(imds_hostname) + local host="${fqdn%%\.*}" + echo "$host" > /etc/hostname + hostname -F /etc/hostname + echo -e "127.0.1.1\t$fqdn $host" >> /etc/hosts +} + +set_ssh_keys() { + local user="$CLOUD_USER" + local pwent=$(getent passwd "$user") + local group=$(echo "$pwent" | cut -d: -f4) + local ssh_dir="$(echo "$pwent" | cut -d: -f6)/.ssh" + local keys_file="$ssh_dir/authorized_keys" + + if [ ! -d "$ssh_dir" ]; then + mkdir -p "$ssh_dir" + chmod 700 "$ssh_dir" + fi + + touch "$keys_file" + chmod 600 "$keys_file" + chown -R "$user:$group" "$ssh_dir" + imds_ssh_keys > "$keys_file" +} \ No newline at end of file diff --git a/lib/tiny-cloud/oci/imds b/lib/tiny-cloud/oci/imds index 264b634..1589aac 100644 --- a/lib/tiny-cloud/oci/imds +++ b/lib/tiny-cloud/oci/imds @@ -1,4 +1,4 @@ -# OCI metadata-related variables and functions +# OCI Instance MetaData Service variables and functions # vim:set ft=sh noet ts=4: IMDS_HEADER="Authorization"