diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..900bda8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.bak +*.swp +.vscode/ diff --git a/LICENSE.txt b/LICENSE.txt index 9304e54..817eab1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2017-2020 Michael Crute +Copyright (c) 2017-2022 Jake Buchholz Göktürk, Michael Crute Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/Makefile b/Makefile index 394004e..2fda04b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,60 @@ PREFIX?=/ -.PHONY: install -install: - install -Dm 755 tiny-ec2-bootstrap $(PREFIX)/etc/init.d/tiny-ec2-bootstrap +SUBPACKAGES = core network openrc aws azure gcp oci + +.PHONY: install $(SUBPACKAGES) + +# installs all subpackages, then replaces cloud-specific config with example +install: $(SUBPACKAGES) + mv "$(PREFIX)"/etc/conf.d/tiny-cloud.example "$(PREFIX)"/etc/conf.d/tiny-cloud + +core: + install -Dm755 -t "$(PREFIX)"/bin \ + bin/imds + install -Dm644 -t "$(PREFIX)"/etc/conf.d \ + etc/conf.d/tiny-cloud.example + install -Dm644 -t "$(PREFIX)"/lib/tiny-cloud \ + lib/tiny-cloud/common \ + lib/tiny-cloud/init-* \ + lib/tiny-cloud/mdev + +network: + install -Dm644 -t "$(PREFIX)"/etc/network/interfaces.d \ + etc/network/interfaces.d/* + install -Dm755 -t "$(PREFIX)"/lib/mdev \ + lib/mdev/vnic-eth-hotplug + install -Dm755 -t "$(PREFIX)"/sbin \ + sbin/* + install -Dm755 -t "$(PREFIX)"/usr/libexec/ifupdown-ng \ + usr/libexec/ifupdown-ng/imds + +openrc: + install -Dm755 -t "$(PREFIX)"/etc/init.d \ + etc/init.d/* + +aws: + install -Dm755 -t "$(PREFIX)"/lib/mdev \ + lib/mdev/nvme-ebs-links + install -Dm644 -t "$(PREFIX)"/lib/tiny-cloud/aws \ + lib/tiny-cloud/aws/* + sed -e 's/^#?CLOUD=.*/CLOUD=aws/' \ + -e 's/^#?HOTPLUG_MODULES=.*/HOTPLUG_MODULES="vnic_eth_hotplug nvme_ebs_links"/' \ + etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud + +azure: + install -Dm644 -t $(PREFIX)/lib/tiny-cloud/azure \ + lib/tiny-cloud/azure/* + sed -e 's/^#?CLOUD=.*/CLOUD=azure/' \ + etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud + +gcp: + install -Dm644 -t $(PREFIX)/lib/tiny-cloud/gcp \ + lib/tiny-cloud/gcp/* + sed -e 's/^#?CLOUD=.*/CLOUD=gcp/' \ + etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud + +oci: + install -Dm644 -t $(PREFIX)/lib/tiny-cloud/oci \ + lib/tiny-cloud/oci/* + sed -e 's/^#?CLOUD=.*/CLOUD=oci/' \ + etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud \ No newline at end of file diff --git a/README.md b/README.md index 7ddea38..5b8dc74 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,158 @@ -# Tiny EC2 Bootstrapper +# Tiny Cloud -This is designed to do the minimal amount of work required to bootstrap an EC2 -instance based on the local settings assigned at boot time as well as the -user's configured settings. This is, in-concept, similar to -[cloud-init](https://cloudinit.readthedocs.io/en/latest/) but trades features -and cloud platform support for small size and limited external dependencies. +The Tiny Cloud bootstrapper performs critical initialization tasks for cloud +instances during their first boot. Unlike the more popular and feature-rich +[cloud-init](https://cloudinit.readthedocs.io/en/latest), Tiny Cloud seeks to +do just what is necessary with a small footprint and minimal dependencies. + +A direct descendant of [tiny-ec2-bootstrap]( +https://gitlab.alpinelinux.org/alpine/cloud/tiny-ec2-bootstrap), Tiny Cloud +works with multiple cloud providers. Currently, the following are supported: +* AWS (Amazon Web Services) +* Azure (Microsoft Azure) +* GCP (Google Cloud Platform) +* OCI (Oracle Cloud Infrastructure) + +## Features + +The following actions will occur ***only once***, during the initial boot of an +instance: +* expand the root filesystem to use all available root device space, during the + **sysinit** runlevel +* 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 + +Optional features, which may not be universally necessary: +* manage symlinks from NVMe block devices to `/dev/xvd` and `/dev/sd` devices + (i.e. AWS Nitro instances) +* manage hotpluggable network interfaces +* sync IMDS-provided secondary IPv4 and IPv6 addresses network interfaces + +Also included is a handy `imds` client script for easy access to an instance's +IMDS data. ## Requirements -The most important feature of this bootstrapper is the very limited set of -dependencies. In-fact, this works with just BusyBox (provided ash and wget -are built in) and a couple utilities for expanding the root filesystem. -The full list of required dependencies are: +As Tiny Cloud is meant to be tiny, it has very few dependencies: +* Busybox (`ash`, `wget`, etc.) +* `ifupdown-ng` (optional, for network management) +* `iproute2-minimal` (optional, for syncing IPv4/IPv6 from IMDS) +* `nvme-cli` (optional, for AWS nitro NVMe symlinks) +* `partx` +* `resize2fs` +* `sfdisk` -- bash-like shell (e.g. bash, dash, ash) -- wget -- sfdisk -- partx -- resize2fs +Tiny Cloud has been developed specifically for use with the +[Alpine Cloud Images](https://gitlab.alpinelinux.org/alpine/cloud/alpine-cloud-images) +project, and as such, it is currently tailored for use with [Alpine Linux]( +https://alpinelinux.org), the [OpenRC](https://github.com/OpenRC/openrc) init +system, and the `ext4` root filesystem. If you would like to see Tiny Cloud +supported on additional distributions, init systems, and/or filesystems, please +open an issue with your request -- or better yet, submit a merge request! -## Supported Features and Environments +## Installation -cloud-init has support for many different cloud providers. This project only -supports EC2; [EC2 metadata -service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) -is a hard requirement of using this bootstrapper. All of the data for the -supported features below is sourced from the EC2 instance metadata service -which runs on every EC2 instance at IP 169.254.169.254. - -cloud-init also has a very rich feature set with support for adding users, -installing packages, and many other things. This bootstrap does not support -those things. Instead it supports: - -- setting system hostname -- installing the instance's SSH keys in the EC2 user's authorized_keys file -- running any script-like user data (must start with #!) -- disabling root and the EC2 user's password -- expanding root partition to available disk space - -These steps only run once. After the initial bootstrap the bootstrapper script -is a no-op. To force the script to run again at boot time remove the file -`/var/lib/cloud/.bootstrap-complete` and reboot the instance. - -The default EC2 user is `alpine`; this can be overriden with a -`/etc/conf.d/tiny-ec2-bootstrap` containing... +Typically, Tiny Cloud is installed and configured when building a cloud image, +and is available on Alpine Linux as the [`tiny-cloud`]( +https://pkgs.alpinelinux.org/packages?name=tiny-cloud) APK... ``` -EC2_USER="otheruser" +apk install tiny-cloud ``` -The EC2 user *must* already exist in the AMI -- `tiny-ec2-bootstrap` will -**NOT** add the user automatically. +This will install the necessary init scripts, libraries, etc. plus any missing +dependencies. -## User Data +Alternately, you can download a release tarball, and use `make` to install it. -User data is provided at instance boot time and can be any arbitrary string of -data. The bootstrapper will consider any user data that begins with the ASCII -characters `#!` to be a script. It will write the entire contents of the user -data to `/var/lib/cloud/user-data.sh`, make the file executable, and execute -the file piping any output to `/var/log/cloud-bootstrap.log`. +Next, enable the three primary init scripts... +``` +rc-update add tiny-cloud-early sysinit +rc-update add tiny-cloud default +rc-update add tiny-cloud-final default +``` -The user data script can do anything it pleases with the instance. It will be -run as root and networking will be up. No other guarantees about system state -are made at the point the script runs. +## Configuration -## Assumptions +By default, Tiny Cloud expects configuration at `/etc/conf.d/tiny-cloud`, +The stock [`etc/conf.d/tiny-cloud`](etc/conf.d/tiny-cloud) file contains +details of all tuneable settings. -- This was written for Alpine Linux; use on other distributions has not been -tested. +*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`.* -- The script is run by OpenRC. +## Operation + +The first time an instance boots -- either freshly instantiated from an image, +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 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 + +The main `tiny-cloud` init script *does* depend on the cloud provider's IMDS +data, and sets up instance's hostname and the cloud user's SSH keys before +`sshd` starts. + +### Final Phase + +`tiny-cloud-final` should be the very last init script to run in the +**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 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 +a `.bootstrap-complete` file into existence in `/var/lib/cloud` or another +directory specified by the `TINY_CLOUD_VAR` config setting. + +### Skipping Init Actions + +If you need to skip any individual init script actions (for example, if you +have a different means to set the instance hostname), you can set the +`SKIP_INIT_ACTIONS` config to a whitespace-separated list of actions to skip. + +### Further Reboots + +After the initial bootstrap of an instance, the init scripts are largely a +no-op. + +To force the init scripts to re-run on the next boot... +``` +rm -f /var/lib/cloud/.bootstrap-complete +``` +If you're instantiating an instance in order to create a new cloud image +(using [Packer](https://packer.io), or some other means), you will need to +remove this file before creating the image to ensure that instances using the +new image will also run Tiny Cloud init scripts during their first boot. + +## Cloud Hotplug Modules + +### `vnic_eth_hotplug` + +This hotplug module adds and removes ethernet interfaces as virtual NICs are +attached/detached from the instance. + +An `ifupdown-ng` executor also syncs the interfaces' secondary IPv4 and IPV6 +addresses associated with those VNICs, if the cloud's IMDS provides that +configuration data. + +### `nvme_ebs_links` + +EBS volumes are attached to AWS EC2 Nitro instances using the NVMe driver. +Unfortunately, the `/dev/nvme*` device names do not match the device name +assigned to the attached EBS volume. This hotplug module figures out what the +assigned device name is, and sets up `/dev/xvd*` and `/dev/sd*` symlinks to +the right NVMe devices for EBS volumes and their partitions. diff --git a/bin/imds b/bin/imds new file mode 100755 index 0000000..61b24a0 --- /dev/null +++ b/bin/imds @@ -0,0 +1,147 @@ +#!/bin/sh +# vim:set ts=4 et ft=sh: + +# Tiny Cloud - Instance MetaData Service client + +### configuration + +source /etc/conf.d/tiny-cloud + +### cloud-specific variables/functions + +unset \ + IMDS_HEADER \ + IMDS_URI \ + IMDS_QUERY \ + IMDS_HOSTNAME \ + IMDS_SSH_KEYS \ + IMDS_USERDATA \ + IMDS_NICS \ + IMDS_MAC \ + IMDS_IPV4 \ + IMDS_IPV6 \ + IMDS_IPV4_NET \ + IMDS_IPV6_NET \ + IMDS_IPV4_PREFIX \ + IMDS_IPV6_PREFIX +unset -f \ + _imds_token \ + _imds_header \ + _imds_nic_index \ + 2>/dev/null || true + +### default variables/functions + +CLOUD="${CLOUD:-unknown}" +IMDS_ENDPOINT="169.254.169.254" + +_imds_ssh_keys() { _imds "$IMDS_SSH_KEYS"; } +_imds_userdata() { _imds "$IMDS_USERDATA"; } + +### load cloud-specific variables and functions + +if [ ! -d /lib/tiny-cloud/"$CLOUD" ]; then + echo "ERROR: Unknown Cloud '$CLOUD'" >&2 + exit 1 +fi +source /lib/tiny-cloud/"$CLOUD"/imds + +### non-overrideable functions + +_imds() { + wget --quiet --timeout 1 --output-document - \ + --header "$(_imds_header)" \ + "http://$IMDS_ENDPOINT/$IMDS_URI/$1$IMDS_QUERY" +} + +imds() { + local cmd args key rv err=1 + if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + _imds_help + return + fi + while [ -n "$1" ]; do + cmd=_imds + args= + key="$1"; shift + case $key in + # error handling + -e) err=0; continue ;; # ignore + +e) err=1; continue ;; # return + # TODO: retry/deadline + # output control + +n) echo; continue ;; # insert newline + +s) echo -n " "; continue ;; # insert space + +t) echo -en "\t"; continue ;; # insert tab + # key aliasing + @hostname) args="$IMDS_HOSTNAME" ;; + @ssh-keys) cmd=_imds_ssh_keys ;; + @userdata) cmd=_imds_userdata ;; + @nics) args="$IMDS_NICS" ;; + @nic:*) + cmd=imds + args=$(_imds_nic_args $(echo "${key#@nic:}" | tr , ' ')) + ;; + # use key verbatim + *) args="$key" ;; + esac + # TODO: retry/deadline + "$cmd" $args 2>/dev/null + rv=$? + [ $err -eq 0 ] && continue + [ $rv = "0" ] || return $rv + done +} + +_imds_nic_args() { + local key nic + nic=$(_imds_nic_index "$1") || return 1 + if [ -z "$2" ]; then + echo "$IMDS_NICS/$nic" + return + fi + while [ -n "$2" ]; do + key="$2" + shift + case "$key" in + @mac) key="$IMDS_MAC" ;; + @ipv4) key="$IMDS_IPV4" ;; + @ipv6) key="$IMDS_IPV6" ;; + @ipv4-net) key="$IMDS_IPV4_NET" ;; + @ipv6-net) key="$IMDS_IPV6_NET" ;; + @ipv4-prefix) key="$IMDS_IPV4_PREFIX" ;; + @ipv6-prefix) key="$IMDS_IPV6_PREFIX" ;; + # error/output control passthrough + -e|+[enst]) printf "$key\n"; continue ;; + esac + printf "$IMDS_NICS/$nic/$key\n" + done +} + +_imds_help() { + cat < | } ... + -h : help + -e / +e : ignore / catch errors + +n / +s / +t : insert newline / space / tab + :- + hostname : instance hostname + ssh-keys : instance SSH keys + userdata : instance user data + nics : instance NICs + nic:[, ...] : specific NIC interface + : network interface (i.e. eth1) + :- { -e | +e | +n | +s | +t | @ | } + :- + mac : mac address + ipv4 : ipv4 address(es) + ipv6 : ipv6 address(es) + ipv4-net : subnet ipv4 network(s) + ipv6-net : subnet ipv6 network(s) + ipv4-prefix : delegated ipv4 CIDR(s) + ipv6-prefix : delegated ipv6 CIDR(s) +EOT +} + + +imds "$@" diff --git a/etc/conf.d/tiny-cloud.example b/etc/conf.d/tiny-cloud.example new file mode 100644 index 0000000..904e11b --- /dev/null +++ b/etc/conf.d/tiny-cloud.example @@ -0,0 +1,32 @@ +# Tiny Cloud configuration + +# REQUIRED: The instance's cloud provider (valid: aws, azure, gcp, oci) +# valid: aws, azure, gcp, oci +#CLOUD= + +# User account where instance SSH keys are installed +#CLOUD_USER=alpine + +# 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 var directory +#TINY_CLOUD_VAR=/var/lib/cloud + +# Location of log directory +#TINY_CLOUD_LOGS=/var/log + +# Hotplug Method (valid: mdev) +#HOTPLUG_TYPE=mdev + +# Cloud-related Hotplug Modules +# valid: vnic_eth_hotplug, nvme_ebs_links (aws) +#HOTPLUG_MODULES= + +# Explicitly skip these (whitespace delimited) things during init +# valid: expand_root install_hotplugs set_hostname set_ssh_keys +# save_userdata run_userdata +#SKIP_INIT_ACTIONS= diff --git a/etc/init.d/tiny-cloud b/etc/init.d/tiny-cloud new file mode 100755 index 0000000..6155f3a --- /dev/null +++ b/etc/init.d/tiny-cloud @@ -0,0 +1,23 @@ +#!/sbin/openrc-run +# vim:set ts=8 noet ft=sh: + +description="Tiny Cloud Bootstrap - main phase" + +depend() { + need net + before sshd +} + +start() { + source /lib/tiny-cloud/init-main + + is_bootstrapped && return 0 + + ebegin "Setting Instance Hostname" + set_hostname + eend $? + + 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 new file mode 100755 index 0000000..4cbe46d --- /dev/null +++ b/etc/init.d/tiny-cloud-early @@ -0,0 +1,24 @@ +#!/sbin/openrc-run +# vim:set ts=8 noet ft=sh: + +description="Tiny Cloud Bootstrap - early phase" + +depend() { + before mdev +} + +start() { + source /lib/tiny-cloud/init-early + + is_bootstrapped && return 0 + + ebegin "Expanding Root Volume/Partition" + expand_root + eend $? + + if has_cloud_hotplugs; then + ebegin "Installing Cloud Hotplugs" + install_hotplugs + eend $? + fi +} \ No newline at end of file diff --git a/etc/init.d/tiny-cloud-final b/etc/init.d/tiny-cloud-final new file mode 100755 index 0000000..24f1b43 --- /dev/null +++ b/etc/init.d/tiny-cloud-final @@ -0,0 +1,29 @@ +#!/sbin/openrc-run +# vim:set ts=8 noet ft=sh: + +description="Tiny Cloud Bootstrap - final phase" + +depend() { + after * + provide cloud-final +} + +start() { + source /lib/tiny-cloud/init-final + + is_bootstrapped && return 0 + + ebegin "Saving Instance UserData" + save_userdata + eend $? + + if is_userdata_script; then + ebegin "Executing UserData Script" + run_userdata + eend $? + fi + + ebegin "Marking Instance Bootstrap Complete" + bootstrap_complete + eend $? +} \ No newline at end of file diff --git a/etc/network/interfaces.d/DEFAULT b/etc/network/interfaces.d/DEFAULT new file mode 100644 index 0000000..efd2628 --- /dev/null +++ b/etc/network/interfaces.d/DEFAULT @@ -0,0 +1,4 @@ +auto %% +iface %% + use dhcp + use imds diff --git a/etc/network/interfaces.d/lo b/etc/network/interfaces.d/lo new file mode 100644 index 0000000..53c1f60 --- /dev/null +++ b/etc/network/interfaces.d/lo @@ -0,0 +1,4 @@ +auto lo +iface lo + use link + use loopback \ No newline at end of file diff --git a/lib/mdev/nvme-ebs-links b/lib/mdev/nvme-ebs-links new file mode 100755 index 0000000..e407bab --- /dev/null +++ b/lib/mdev/nvme-ebs-links @@ -0,0 +1,45 @@ +#!/bin/sh +# vim:set ts=2 et: + +source /lib/tiny-cloud/common + +# nvme tool not installed? +[ -x /usr/sbin/nvme ] || log crit "nvme cli not installed" + +raw_ebs_alias() { + /usr/sbin/nvme id-ctrl /dev/"$BASE" -b 2>/dev/null | + dd bs=32 skip=96 count=1 2>/dev/null +} + +case $ACTION in + add|"") + BASE=$(echo "$MDEV" | sed -re 's/^(nvme[0-9]+n[0-9]+).*/\1/') + PART=$(echo "$MDEV" | sed -re 's/nvme[0-9]+n[0-9]+p?//g') + # TODO: deadline instead of max tries + MAXTRY=30 + TRY=0 + until [ -n "$EBS" ]; do + EBS=$(raw_ebs_alias | sed -nre '/^(\/dev\/)?(s|xv)d[a-z]{1,2} /p' | tr -d ' ') + [ -n "$EBS" ] && break + TRY=$((TRY + 1)) + if [ $TRY -eq $MAXTRY ]; then + log err "Failed to get EBS volume alias for $MDEV after $MAXTRY attempts ($(raw_ebs_alias))" + exit 1 + fi + sleep 0.1 + done + # remove any leading '/dev/', 'sd', or 'xvd', and append partition + EBS=${EBS#/dev/} + EBS=${EBS#sd} + EBS=${EBS#xvd}$PART + ln -sf "$MDEV" "sd$EBS" && log notice "Added sd$EBS symlink for $MDEV" + ln -sf "$MDEV" "xvd$EBS" && log notice "Added xvd$EBS symlink for $MDEV" + ;; + remove) + for TARGET in sd* xvd* + do + [ $(readlink "$TARGET" 2>/dev/null) = "$MDEV" ] && rm -f "$TARGET" && \ + log notice "Removed $TARGET symlink for $MDEV" + done + ;; +esac diff --git a/lib/mdev/vnic-eth-hotplug b/lib/mdev/vnic-eth-hotplug new file mode 100755 index 0000000..a8adb9f --- /dev/null +++ b/lib/mdev/vnic-eth-hotplug @@ -0,0 +1,86 @@ +#!/bin/sh +# vim:set ts=4 et: + +set -e + +source /lib/tiny-cloud/common + +if [ -z "$MDEV" ] || [ -z "$ACTION" ]; then + log crit "MDEV or ACTION undefined, aborting" +fi + +IFACE_CFG=/etc/network/interfaces + +ip() { + local v=-4 lev=info + if [ "$1" = '-4' ] || [ "$1" = '-6' ]; then + v="$1" + shift + fi + local op="$2" + + [ "$op" = show ] && lev=debug + if /sbin/ip "$v" "$@" || [ -n "$FAIL_OK" ]; then + log "$lev" "OK: ip $v $*" + else + log err "FAIL: ip $v $*" + fi +} + +interface_up() { + log info "Bringing up $MDEV" + # umask so udhcpc PID file isn't non-owner writeable + (umask 0022 && ifup "$MDEV") +} + +cleanup_interface() { + local v pref rtable="${MDEV#eth}" + let rtable+=10000 + + log info "Cleaning up $MDEV" + + # kill related udhcpc, don't panic if it's not there + kill "$(cat "/run/udhcpc.$MDEV.pid")" || true + + # tidy up /run/ifstate, if it exists + [ -f /run/ifstate ] && sed -i -e "/^$MDEV=/d" /run/ifstate + rm -f /run/ifstate."$MDEV".lock + + # remove related rules + for v in 4 6; do + for pref in $(ip -"$v" rule show table "$rtable" | cut -d: -f1); do + ip -"$v" rule del pref "$pref" + done + done +} + +is_networking_started() { service networking status -q 2>/dev/null; } + +log info "STARTING: $ACTION $MDEV" + +if exec 200>>"$IFACE_CFG"; then + if flock 200; then + case $ACTION in + add|"") + assemble-interfaces + is_networking_started && interface_up + ;; + remove) + assemble-interfaces + is_networking_started && cleanup_interface + ;; + *) + log err "Unknown action '$ACTION'" + exit 1 + ;; + esac + else + log err "Unable to flock $IFACE_CFG" + exit 1 + fi +else + log err "Unable to assign fd 200 to flock $IFACE_CFG" + exit 1 +fi + +log info "FINISHED: $ACTION $MDEV" diff --git a/lib/tiny-cloud/aws/imds b/lib/tiny-cloud/aws/imds new file mode 100644 index 0000000..2501544 --- /dev/null +++ b/lib/tiny-cloud/aws/imds @@ -0,0 +1,40 @@ +# AWS Instance MetaData Service variables and functions +# vim:set ts=4 et ft=sh: + +IMDS_HEADER="X-aws-ec2-metadata-token" +IMDS_TOKEN_TTL_HEADER="X-aws-ec2-metadata-token-ttl-seconds" +IMDS_TOKEN_TTL=${IMDS_TOKEN_TTL:-5} +IMDS_URI="latest" + +IMDS_HOSTNAME="meta-data/hostname" +IMDS_SSH_KEYS="meta-data/public-keys" +IMDS_USERDATA="user-data" + +IMDS_NICS="meta-data/network/interfaces/macs" +IMDS_MAC="mac" +IMDS_IPV4="local-ipv4s" +IMDS_IPV6="ipv6s" +IMDS_IPV4_NET="subnet-ipv4-cidr-block" +IMDS_IPV6_NET="subnet-ipv6-cidr-blocks" +IMDS_IPV4_PREFIX="ipv4-prefix" +IMDS_IPV6_PREFIX="ipv6-prefix" + +_imds_token() { + echo -ne "PUT /latest/api/token" \ + "HTTP/1.0\r\n$IMDS_TOKEN_TTL_HEADER: $IMDS_TOKEN_TTL\r\n\r\n" | + nc -w 1 "$IMDS_ENDPOINT" 80 | tail -n 1 +} + +_imds_header() { + echo "$IMDS_HEADER: $(_imds_token)" +} + +# digs deeper than the default +_imds_ssh_keys() { + local key + for key in $(imds "$IMDS_SSH_KEYS"); do + imds "$IMDS_SSH_KEYS/${key%=*}/openssh-key" + done | sort -u +} + +_imds_nic_index() { cat "/sys/class/net/$1/address"; } diff --git a/lib/tiny-cloud/aws/mdev b/lib/tiny-cloud/aws/mdev new file mode 100644 index 0000000..a5cc02b --- /dev/null +++ b/lib/tiny-cloud/aws/mdev @@ -0,0 +1,11 @@ +# AWS mdev Hotplug Modules +# vim:set ts=4 et ft=sh: + +# makes symlinks for NVMe devices that correlate to AWS EBS sd/xvd devices +mod__nvme_ebs_links() { + # nvme-cli not installed? + [ -x /usr/sbin/nvme ] || return 1 + + install_before '^nvme\.\*' \ + 'nvme[0-9]+n.* root:disk 0660 */lib/mdev/nvme-ebs-links' +} \ No newline at end of file diff --git a/lib/tiny-cloud/azure/imds b/lib/tiny-cloud/azure/imds new file mode 100644 index 0000000..b04dc79 --- /dev/null +++ b/lib/tiny-cloud/azure/imds @@ -0,0 +1,30 @@ +# Azure Instance MetaData Service variables and functions +# vim:set ts=4 et ft=sh: + +IMDS_HEADER="Metadata" +IMDS_QUERY="?format=text&api-version=2021-05-01" +IMDS_URI="metadata/instance" + +IMDS_HOSTNAME="compute/name" +IMDS_SSH_KEYS="compute/publicKeys" +IMDS_USERDATA="compute/userData" + +IMDS_NICS="" + +_imds_header() { + echo "$IMDS_HEADER: true" +} + +# dig deeper than default +_imds_ssh_keys() { + local key + + for key in $(imds "$IMDS_SSH_KEYS"); do + imds "$IMDS_SSH_KEYS/${key}/keyData" + done | sort -u +} + +# decode userdata value +_imds_userdata() { + imds "$IMDS_USERDATA" | base64 -d +} \ No newline at end of file diff --git a/lib/tiny-cloud/common b/lib/tiny-cloud/common new file mode 100644 index 0000000..2590796 --- /dev/null +++ b/lib/tiny-cloud/common @@ -0,0 +1,24 @@ +# Tiny Cloud - common script functions +# vim: ts=4 et ft=sh: + +log() { + local facility=kern + local stderr + local tag=$(basename "$0") + while [ "${1:0:1}" = '-' ]; do + case "$1" in + -f) facility="$2"; shift ;; + -s) stderr=-s ;; + -t) tag="$tag/$2"; shift ;; + esac + shift + done + local level="$1" + [ -z "$DEBUG" ] && [ "$level" = debug ] && return + shift + + logger $stderr -p "$facility.$level" -t "$tag" "$@" + case "$level" in + crit|alert|emerg) exit 1 ;; + esac +} \ No newline at end of file diff --git a/lib/tiny-cloud/gcp/imds b/lib/tiny-cloud/gcp/imds new file mode 100644 index 0000000..5259d7e --- /dev/null +++ b/lib/tiny-cloud/gcp/imds @@ -0,0 +1,26 @@ +# Google Cloud Instance MetaData Service variables and functions +# vim:set ts=4 et ft=sh: + +IMDS_HEADER="Metadata-Flavor" +IMDS_URI="computeMetadata/v1" + +IMDS_HOSTNAME="instance/hostname" +IMDS_SSH_KEYS=" + project/attributes/ssh-keys + instance/attributes/ssh-keys +" +IMDS_USERDATA="instance/attributes/user-data" + +_imds_header() { + echo "$IMDS_HEADER: Google" +} + +# merge project and instance keys +_imds_ssh_keys() { + local ssh_keys + + for ssh_keys in $IMDS_SSH_KEYS; do + # ignore errors and strip leading ':' + imds -e "$ssh_keys" | cut -d: -f2- + done | sort -u +} \ No newline at end of file diff --git a/lib/tiny-cloud/init-common b/lib/tiny-cloud/init-common new file mode 100644 index 0000000..4e02612 --- /dev/null +++ b/lib/tiny-cloud/init-common @@ -0,0 +1,29 @@ +# Tiny Cloud - Common Initialization +# vim:set ts=4 et ft=sh: + +source /etc/conf.d/tiny-cloud + +# 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} +SKIP_INIT_ACTIONS=${SKIP_INIT_ACTIONS:-} + +# is initial bootstrap already done? +is_bootstrapped() { [ -f "$TINY_CLOUD_VAR"/.bootstrap-complete ]; } + +# indicate bootstrap is done +bootstrap_complete() { touch "$TINY_CLOUD_VAR"/.bootstrap-complete ]; } + +# should we skip this action? +skip_action() { + local action="$1" + + # no action? don't skip. + [ -z "$action" ] && return 1 + + # action not in the skip list? + echo "$SKIP_INIT_ACTIONS" | grep -Eq "\b$action\b" || return 1 + echo -n " SKIPPING" +} \ 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..e490e9e --- /dev/null +++ b/lib/tiny-cloud/init-early @@ -0,0 +1,45 @@ +# Tiny Cloud - Early Phase Functions +# vim:set ts=4 et ft=sh: + +source /lib/tiny-cloud/init-common + +expand_root() { + skip_action expand_root && return + + # 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/" + ) + local partition + + if [ "$mountpoint" != "$volume" ]; then + # it's a partition, resize it + partition=$(echo "$mountpoint" | sed -Ee "s/.*(\d+)$/\1/") + echo ", +" | sfdisk -q --no-reread -N "$partition" "$volume" + partx -u "$volume" + fi + # resize filesystem + mount -orw,remount / + resize2fs "$mountpoint" +} + +has_cloud_hotplugs() { [ -n "$HOTPLUG_MODULES" ]; } + +install_hotplugs() { + skip_action install_hotplugs && return + + local result + + for module in $HOTPLUG_MODULES; do + result='-' + echo -n " $module" + if type "mod__$module" | grep -q "is a function"; then + "mod__$module" && result='+' || result='!' + fi + echo -n "($result)" + done +} + +HOTPLUG_TYPE=${HOTPLUG_TYPE:-mdev} +[ -f /lib/tiny-cloud/"$HOTPLUG_TYPE" ] && source /lib/tiny-cloud/"$HOTPLUG_TYPE" diff --git a/lib/tiny-cloud/init-final b/lib/tiny-cloud/init-final new file mode 100644 index 0000000..46a104f --- /dev/null +++ b/lib/tiny-cloud/init-final @@ -0,0 +1,27 @@ +# Tiny Cloud - Final Phase Functions +# vim:set ts=4 et ft=sh: + +source /lib/tiny-cloud/init-common + +save_userdata() { + skip_action save_userdata && return + + imds -e @userdata > "$TINY_CLOUD_VAR/$CLOUD_USERDATA" +} + +is_userdata_script() { + head -n1 "$TINY_CLOUD_VAR/$CLOUD_USERDATA" | grep -q '#!/' +} + +run_userdata() { + skip_action run_userdata && return + + 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..2f2e44c --- /dev/null +++ b/lib/tiny-cloud/init-main @@ -0,0 +1,39 @@ +# Tiny Cloud - Main Phase Functions +# vim:set ts=4 et ft=sh: + +source /lib/tiny-cloud/init-common + +# ensure existence of output directories +[ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS" +[ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR" + +set_hostname() { + skip_action set_hostname && return + + 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() { + skip_action set_ssh_keys && return + + 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/mdev b/lib/tiny-cloud/mdev new file mode 100644 index 0000000..7e5ea25 --- /dev/null +++ b/lib/tiny-cloud/mdev @@ -0,0 +1,37 @@ +# Tiny Cloud - mdev hotplug functions +# vim:set ts=4 et ft=sh: + +# generic helper function to install mdev rules +install_before() { + local before="$1" + shift + local line="$*" + + # already installed + fgrep -q "$line" /etc/mdev.conf && return 0 + + if grep -q "$before" /etc/mdev.conf; then + # install before existing rule + line="-$line" + else + # no rule exists, put it before the catch-all fallback + before='^# fallback' + line="$line\n" + fi + sed -i -Ee "s|($before.*)|$line\n\1|" /etc/mdev.conf +} + +# hotpluggable VNICs (multi-cloud) +mod__vnic_eth_hotplug() { + [ -f /lib/mdev/vnic-eth-hotplug ] || return 1 + + install_before '^eth' \ + 'eth[0-9] root:root 0644 */lib/mdev/vnic-eth-hotplug' + + # NICs attached at launch don't get added with mdev -s + assemble-interfaces +} + +# load cloud-specific functions + +[ -f /lib/tiny-cloud/"$CLOUD"/mdev ] && source /lib/tiny-cloud/"$CLOUD"/mdev diff --git a/lib/tiny-cloud/oci/imds b/lib/tiny-cloud/oci/imds new file mode 100644 index 0000000..9c78e16 --- /dev/null +++ b/lib/tiny-cloud/oci/imds @@ -0,0 +1,22 @@ +# OCI Instance MetaData Service variables and functions +# vim:set ts=4 et ft=sh: + +IMDS_HEADER="Authorization" +IMDS_URI="opc/v2" + +IMDS_HOSTNAME="instance/hostname" +IMDS_SSH_KEYS="instance/metadata/ssh_authorized_keys" +IMDS_USERDATA="instance/metadata/userdata" + +_imds_header() { + echo "$IMDS_HEADER: Bearer Oracle" +} + +_imds_nic_index() { + local m n=0 + local mac=$(cat /sys/class/net/$1/mac) + while m=$(imds $IMDS_NICS/$n/mac | tr A-F a-f); do + [ "$m" = "$mac" ] && echo $n; return 0 + done + return 1 +} diff --git a/sbin/assemble-interfaces b/sbin/assemble-interfaces new file mode 100755 index 0000000..e2683f0 --- /dev/null +++ b/sbin/assemble-interfaces @@ -0,0 +1,44 @@ +#!/bin/sh +# vim:set ts=4 et: + +set -e + +IFACE_CFG=/etc/network/interfaces +IFACE_DIR="${IFACE_CFG}.d" + +cd "$IFACE_DIR" + +cat > "$IFACE_CFG.new" < "$IFACE" + printf "%s\n\n" "$(cat "$IFACE")" >> "$IFACE_CFG.new" + ;; + *) continue ;; + esac +done + +# all the rest +for i in "$IFACE_DIR"/*; do + IFACE="$(basename "$i")" + case $IFACE in + DEFAULT|lo|eth*) + continue + ;; + *) + printf "%s\n\n" "$(cat "$IFACE")" >> "$IFACE_CFG.new" + ;; + esac +done + +# install new interfaces config +[ -f "$IFACE_CFG" ] && cp -a "$IFACE_CFG" "$IFACE_CFG.bak" +mv "$IFACE_CFG.new" "$IFACE_CFG" diff --git a/sbin/imds-net-sync b/sbin/imds-net-sync new file mode 100755 index 0000000..a6db02c --- /dev/null +++ b/sbin/imds-net-sync @@ -0,0 +1,144 @@ +#!/bin/sh +# vim: ts=4 et ft=sh: + +# Sync interface's network configuration with IMDS + +[ -z "$VERBOSE" ] || set -x + +source /lib/tiny-cloud/common + +IFACE=${IFACE:-unknown} +[ "$IFACE" = unknown ] && log -s crit "IFACE not set, aborting" + +# kill interface's imds-net-sync daemon +[ "$1" = '-k' ] && PHASE=pre=down && shift + +PHASE=${PHASE:-post-up} + +# route table number +RTABLE=${IFACE#eth} +let RTABLE+=10000 + +# ip [+F] [-4|-6] [] +ip() { + local fail_ok v=-4 cmd level + [ "$1" = '+F' ] && fail_ok=1 && shift + if [ "$1" = '-4' ] || [ "$1" = '-6' ]; then + v="$1" + shift + fi + cmd="$2" + [ "$cmd" = show ] && level=debug || level=info + if /sbin/ip "$v" "$@" || [ -n "$fail_ok" ]; then + log -s "$level" "OK: ip $v $*" + else + log -s err "FAIL: ip $v $*" + fi +} + +# get secondary IPv4s currently on the interface +iface_ip4s() { + ip -4 addr show "$IFACE" secondary | + sed -E -e '/inet /!d' -e 's/.*inet ([0-9.]+).*/\1/' +} + +# get IPv6s currently on the interface +iface_ip6s() { + ip -6 addr show "$IFACE" scope global | + sed -E -e '/inet6/!d' -e 's/.*inet6 ([0-9a-f:]+).*/\1/' +} + +imds_ip4s() { + local ip4=$(imds "@nic:$IFACE,@ipv4") + local ip4s=$(echo "$ip4" | tail +2) # secondary IPv4s + local ip4p ip4_cidr ip4_gw + + # non-eth0 interfaces need custom route tables + # + if [ "$IFACE" != eth0 ] && [ -n "$ip4s" ] && + [ -z $(ip +F -4 route show table "$RTABLE" 2>/dev/null) ]; then + ip4p=$(echo "$ip4" | head -1) # primary IPv4 + ip4_cidr=$(imds "@nic:$IFACE,@ipv4-net") # TODO: get from iface instead? + # TODO: this may not hold true for non-AWS clouds + ip4_gw=$(echo "$ip4_cidr" | cut -d/ -f1 | + awk -F. '{ print $1"."$2"."$3"."$4+1 }') + ip -4 route add default via "$ip4_gw" dev "$IFACE" table "$RTABLE" + ip -4 route add "$ip4_cidr" dev "$IFACE" proto kernel scope link \ + src "$ip4p" table "$RTABLE" + fi + echo "$ip4s" +} + +imds_ip6s() { + local ip6s gw tries=20 + ip6s=$(imds "@nic:$IFACE,@ipv6") + + # non-eth0 interfaces need custom route tables + # + # NOTE: busybox iproute2 doesn't do 'route show table' properly for IPv6, + # so iproute2-minimal package is required! + # + if [ "$IFACE" != eth0 ] && [ -n "$ip6s" ] && + [ -z $(ip +F -6 route show table "$RTABLE" 2>/dev/null) ]; then + while true; do + gw=$(ip -6 route show dev "$IFACE" default | awk '{ print $3 }') + [ -n "$gw" ] && break + let tries-- + if [ "$tries" -eq 0 ]; then + log -s warn "Unable to get IPv6 gateway RA after 10s" + break + fi + sleep 0.5 + done + ip -6 route add default via "$gw" dev "$IFACE" table "$RTABLE" + fi + echo "$ip6s" +} + +in_list() { + echo "$2" | grep -q "^$1$" +} + +# ip_addr {4|6} {add|del} +ip_addr() { + local mask=32 # IPv4 always /32 + [ "$1" -eq 6 ] && mask=128 # IPv6 always /128 + ip -"$1" addr "$2" "$3/$mask" dev "$IFACE" + + # TODO: only non eth0? delegated ipv[46] prefixes? + [ "$IFACE" = eth0 ] && return + + # non-eth0 interfaces get rules associating IPs with route tables + ip -"$1" rule "$2" from "$3" lookup "$RTABLE" +} + +# sync_ips {4|6} "" "" +sync_ips() { + local i + # remove extra IPs + for i in $3; do + in_list "$i" "$2" || ip_addr "$1" del "$i" + done + # add missing IPs + for i in $2; do + in_list "$i" "$3" || ip_addr "$1" add "$i" + done +} + +imds_iface_sync() { + log -s info "SYNCING: $IFACE" + sync_ips 4 "$(imds_ip4s)" "$(iface_ip4s)" + sync_ips 6 "$(imds_ip6s)" "$(iface_ip6s)" + log -s info "FINISHED: $IFACE" +} + +case "$PHASE" in + post-up) + # TODO: daemonize this + imds_iface_sync + ;; + pre-down) + # TODO: kill daemon, maybe some cleanup + ;; + *) +esac \ No newline at end of file diff --git a/tiny-ec2-bootstrap b/tiny-ec2-bootstrap deleted file mode 100644 index 42514e6..0000000 --- a/tiny-ec2-bootstrap +++ /dev/null @@ -1,110 +0,0 @@ -#!/sbin/openrc-run -# vim:set ft=sh noet ts=4: - -description="Provides EC2 cloud bootstrap" - -# override in /etc/conf.d/tiny-ec2-bootstrap -EC2_USER=${EC2_USER:-alpine} -IMDS2_TOKEN_TTL=${IMDS2_TOKEN_TTL:-5} - -depend() { - need net - provide cloud-final -} - -_get_metadata_token() { - echo -ne "PUT /latest/api/token HTTP/1.0\r\nX-aws-ec2-metadata-token-ttl-seconds: $IMDS2_TOKEN_TTL\r\n\r\n" | - nc 169.254.169.254 80 | tail -n 1 -} - -_get_metadata() { - local uri="$1" - wget -qO - --header "X-aws-ec2-metadata-token: $(_get_metadata_token)" \ - "http://169.254.169.254/latest/$uri" 2>/dev/null -} - -_update_hostname() { - local ec2_fqdn="$(_get_metadata meta-data/hostname)" - local short_hostname="${ec2_fqdn%%\.*}" - echo "$short_hostname" > /etc/hostname - hostname -F /etc/hostname - echo -e "127.0.1.1\t$ec2_fqdn $short_hostname" >> /etc/hosts -} - -_set_ssh_keys() { - local user="$1" - local group="$(getent passwd "$user" | cut -d: -f4)" - local ssh_dir="$(getent passwd "$user" | cut -d: -f6)/.ssh" - local keys_file="$ssh_dir/authorized_keys" - - if [ ! -d "$ssh_dir" ]; then - mkdir -p "$ssh_dir" - chmod 755 "$ssh_dir" - fi - - [ -f "$keys_file" ] && rm "$keys_file" - - touch "$keys_file" - chmod 600 "$keys_file" - chown -R "$user:$group" "$ssh_dir" - - for key in $(_get_metadata meta-data/public-keys/); do - _get_metadata "meta-data/public-keys/${key%=*}/openssh-key/" >> "$keys_file" - done -} - -_run_userdata() { - local user_data="$(_get_metadata user-data)" - if printf '%s' "$user_data" | head -n1 | grep -q '^#!/'; then - printf '%s' "$user_data" >/var/lib/cloud/user-data.sh - chmod +x /var/lib/cloud/user-data.sh - - local log_file=/var/log/cloud-bootstrap.log - local ec_file=/var/log/cloud-bootstrap.exit - - { /var/lib/cloud/user-data.sh 2>&1 ; echo $? >"$ec_file"; } | tee "$log_file" - ec=$(cat "$ec_file") - - echo "User Data Script Exit Status: $ec" - return "$ec" - fi -} - -_resize_root_partition() { - local mountpoint="$(busybox mountpoint -n / | cut -d' ' -f1)" - - # mountpoint is the second partition... - if echo "$mountpoint" | cut -d' ' -f1 | grep -qE '/(nvme\d+n\d+p|xvd[a-z]+)2$'; then - local volume="$(echo "$mountpoint" | sed -Ee 's/(nvme\d+n\d+|xvd[a-z]+)p?2/\1/')" - einfo "Expanding root partition to volume size..." - echo ", +" | sfdisk -q --no-reread -N 2 "$volume" - einfo "Updating kernel with new partition table..." - partx -u "$volume" - fi - einfo "Resizing..." - resize2fs "$mountpoint" -} - -_lock_root_account() { - passwd -l root -} - -_disable_password() { - echo "$1:*" | chpasswd -e -} - -start() { - # Don't bootstrap if the host has already been bootstrapped - [ -f "/var/lib/cloud/.bootstrap-complete" ] && return 0 - - [ -d "/var/lib/cloud" ] || mkdir -p /var/lib/cloud - - ebegin "Locking root account"; _lock_root_account; eend $? - ebegin "Disabling $EC2_USER password"; _disable_password "$EC2_USER"; eend $? - ebegin "Expanding root partition"; _resize_root_partition; eend $? - ebegin "Setting ec2 hostname"; _update_hostname; eend $? - ebegin "Setting ec2 user ssh keys"; _set_ssh_keys "$EC2_USER"; eend $? - ebegin "Running ec2 user data script"; _run_userdata; eend $? - - touch "/var/lib/cloud/.bootstrap-complete" -} diff --git a/usr/libexec/ifupdown-ng/imds b/usr/libexec/ifupdown-ng/imds new file mode 100755 index 0000000..1001491 --- /dev/null +++ b/usr/libexec/ifupdown-ng/imds @@ -0,0 +1,14 @@ +#!/bin/sh +# vim: set ts=8 noet: + +# Tiny Cloud IMDS ifupdown-ng executor + +case "$PHASE" in +post-up) + /sbin/imds-net-sync + ;; +pre-down) + /sbin/imds-net-sync -k + ;; +*) ;; +esac