mirror of
https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git
synced 2026-02-04 04:22:43 +03:00
Introducing Tiny Cloud!
This commit is contained in:
parent
0bfdd16977
commit
8ffdca9786
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*~
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
.vscode/
|
||||||
@ -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
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
61
Makefile
61
Makefile
@ -1,5 +1,60 @@
|
|||||||
PREFIX?=/
|
PREFIX?=/
|
||||||
|
|
||||||
.PHONY: install
|
SUBPACKAGES = core network openrc aws azure gcp oci
|
||||||
install:
|
|
||||||
install -Dm 755 tiny-ec2-bootstrap $(PREFIX)/etc/init.d/tiny-ec2-bootstrap
|
.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
|
||||||
198
README.md
198
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
|
The Tiny Cloud bootstrapper performs critical initialization tasks for cloud
|
||||||
instance based on the local settings assigned at boot time as well as the
|
instances during their first boot. Unlike the more popular and feature-rich
|
||||||
user's configured settings. This is, in-concept, similar to
|
[cloud-init](https://cloudinit.readthedocs.io/en/latest), Tiny Cloud seeks to
|
||||||
[cloud-init](https://cloudinit.readthedocs.io/en/latest/) but trades features
|
do just what is necessary with a small footprint and minimal dependencies.
|
||||||
and cloud platform support for small size and limited external 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
|
## Requirements
|
||||||
|
|
||||||
The most important feature of this bootstrapper is the very limited set of
|
As Tiny Cloud is meant to be tiny, it has very few dependencies:
|
||||||
dependencies. In-fact, this works with just BusyBox (provided ash and wget
|
* Busybox (`ash`, `wget`, etc.)
|
||||||
are built in) and a couple utilities for expanding the root filesystem.
|
* `ifupdown-ng` (optional, for network management)
|
||||||
The full list of required dependencies are:
|
* `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)
|
Tiny Cloud has been developed specifically for use with the
|
||||||
- wget
|
[Alpine Cloud Images](https://gitlab.alpinelinux.org/alpine/cloud/alpine-cloud-images)
|
||||||
- sfdisk
|
project, and as such, it is currently tailored for use with [Alpine Linux](
|
||||||
- partx
|
https://alpinelinux.org), the [OpenRC](https://github.com/OpenRC/openrc) init
|
||||||
- resize2fs
|
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
|
Typically, Tiny Cloud is installed and configured when building a cloud image,
|
||||||
supports EC2; [EC2 metadata
|
and is available on Alpine Linux as the [`tiny-cloud`](
|
||||||
service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html)
|
https://pkgs.alpinelinux.org/packages?name=tiny-cloud) APK...
|
||||||
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...
|
|
||||||
```
|
```
|
||||||
EC2_USER="otheruser"
|
apk install tiny-cloud
|
||||||
```
|
```
|
||||||
The EC2 user *must* already exist in the AMI -- `tiny-ec2-bootstrap` will
|
This will install the necessary init scripts, libraries, etc. plus any missing
|
||||||
**NOT** add the user automatically.
|
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
|
Next, enable the three primary init scripts...
|
||||||
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
|
rc-update add tiny-cloud-early sysinit
|
||||||
data to `/var/lib/cloud/user-data.sh`, make the file executable, and execute
|
rc-update add tiny-cloud default
|
||||||
the file piping any output to `/var/log/cloud-bootstrap.log`.
|
rc-update add tiny-cloud-final default
|
||||||
|
```
|
||||||
|
|
||||||
The user data script can do anything it pleases with the instance. It will be
|
## Configuration
|
||||||
run as root and networking will be up. No other guarantees about system state
|
|
||||||
are made at the point the script runs.
|
|
||||||
|
|
||||||
## 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
|
*Because Tiny Cloud does not currently do auto-detection, you **MUST** set a
|
||||||
tested.
|
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.
|
||||||
|
|||||||
147
bin/imds
Executable file
147
bin/imds
Executable file
@ -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 <<EOT
|
||||||
|
Usage: imds [-h] { -e | +e | +n | +s | +t | @<alias> | <imds-path> } ...
|
||||||
|
-h : help
|
||||||
|
-e / +e : ignore / catch errors
|
||||||
|
+n / +s / +t : insert newline / space / tab
|
||||||
|
<alias> :-
|
||||||
|
hostname : instance hostname
|
||||||
|
ssh-keys : instance SSH keys
|
||||||
|
userdata : instance user data
|
||||||
|
nics : instance NICs
|
||||||
|
nic:<iface>[,<nic-key> ...] : specific NIC interface
|
||||||
|
<iface> : network interface (i.e. eth1)
|
||||||
|
<nic-key> :- { -e | +e | +n | +s | +t | @<nic-alias> | <nic-path> }
|
||||||
|
<nic-alias> :-
|
||||||
|
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 "$@"
|
||||||
32
etc/conf.d/tiny-cloud.example
Normal file
32
etc/conf.d/tiny-cloud.example
Normal file
@ -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=
|
||||||
23
etc/init.d/tiny-cloud
Executable file
23
etc/init.d/tiny-cloud
Executable file
@ -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 $?
|
||||||
|
}
|
||||||
24
etc/init.d/tiny-cloud-early
Executable file
24
etc/init.d/tiny-cloud-early
Executable file
@ -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
|
||||||
|
}
|
||||||
29
etc/init.d/tiny-cloud-final
Executable file
29
etc/init.d/tiny-cloud-final
Executable file
@ -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 $?
|
||||||
|
}
|
||||||
4
etc/network/interfaces.d/DEFAULT
Normal file
4
etc/network/interfaces.d/DEFAULT
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
auto %%
|
||||||
|
iface %%
|
||||||
|
use dhcp
|
||||||
|
use imds
|
||||||
4
etc/network/interfaces.d/lo
Normal file
4
etc/network/interfaces.d/lo
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
auto lo
|
||||||
|
iface lo
|
||||||
|
use link
|
||||||
|
use loopback
|
||||||
45
lib/mdev/nvme-ebs-links
Executable file
45
lib/mdev/nvme-ebs-links
Executable file
@ -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
|
||||||
86
lib/mdev/vnic-eth-hotplug
Executable file
86
lib/mdev/vnic-eth-hotplug
Executable file
@ -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"
|
||||||
40
lib/tiny-cloud/aws/imds
Normal file
40
lib/tiny-cloud/aws/imds
Normal file
@ -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"; }
|
||||||
11
lib/tiny-cloud/aws/mdev
Normal file
11
lib/tiny-cloud/aws/mdev
Normal file
@ -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'
|
||||||
|
}
|
||||||
30
lib/tiny-cloud/azure/imds
Normal file
30
lib/tiny-cloud/azure/imds
Normal file
@ -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
|
||||||
|
}
|
||||||
24
lib/tiny-cloud/common
Normal file
24
lib/tiny-cloud/common
Normal file
@ -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
|
||||||
|
}
|
||||||
26
lib/tiny-cloud/gcp/imds
Normal file
26
lib/tiny-cloud/gcp/imds
Normal file
@ -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 '<login>:'
|
||||||
|
imds -e "$ssh_keys" | cut -d: -f2-
|
||||||
|
done | sort -u
|
||||||
|
}
|
||||||
29
lib/tiny-cloud/init-common
Normal file
29
lib/tiny-cloud/init-common
Normal file
@ -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"
|
||||||
|
}
|
||||||
45
lib/tiny-cloud/init-early
Normal file
45
lib/tiny-cloud/init-early
Normal file
@ -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"
|
||||||
27
lib/tiny-cloud/init-final
Normal file
27
lib/tiny-cloud/init-final
Normal file
@ -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")
|
||||||
|
}
|
||||||
39
lib/tiny-cloud/init-main
Normal file
39
lib/tiny-cloud/init-main
Normal file
@ -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"
|
||||||
|
}
|
||||||
37
lib/tiny-cloud/mdev
Normal file
37
lib/tiny-cloud/mdev
Normal file
@ -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
|
||||||
22
lib/tiny-cloud/oci/imds
Normal file
22
lib/tiny-cloud/oci/imds
Normal file
@ -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
|
||||||
|
}
|
||||||
44
sbin/assemble-interfaces
Executable file
44
sbin/assemble-interfaces
Executable file
@ -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" <<EOT
|
||||||
|
# NOTE: $0 rewrites this file. Edit files in
|
||||||
|
# /etc/network/interfaces.d/ to persist any customizations.
|
||||||
|
|
||||||
|
EOT
|
||||||
|
|
||||||
|
# existing loopback and eths
|
||||||
|
for i in /sys/class/net/*; do
|
||||||
|
IFACE="$(basename "$i")"
|
||||||
|
case $IFACE in
|
||||||
|
lo|eth*)
|
||||||
|
[ ! -f "$IFACE" ] && sed -e "s/%%/$IFACE/g" DEFAULT > "$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"
|
||||||
144
sbin/imds-net-sync
Executable file
144
sbin/imds-net-sync
Executable file
@ -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] <object> <command> [<parameters>]
|
||||||
|
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>
|
||||||
|
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} "<imds-ips>" "<iface-ips>"
|
||||||
|
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
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
14
usr/libexec/ifupdown-ng/imds
Executable file
14
usr/libexec/ifupdown-ng/imds
Executable file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user