mirror of
https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git
synced 2025-12-14 19:02:45 +03:00
299 lines
7.1 KiB
Bash
299 lines
7.1 KiB
Bash
# Tiny Cloud - Init Functions
|
|
# vim:set filetype=sh:
|
|
# shellcheck shell=sh
|
|
|
|
# set defaults
|
|
: "${PREFIX:=/usr}"
|
|
: "${LIBDIR:=$PREFIX/lib}"
|
|
. "$LIBDIR/tiny-cloud/common"
|
|
|
|
: "${SKIP_INIT_ACTIONS:=}"
|
|
|
|
### default phase actions (without leading 'init__')
|
|
|
|
DEFAULT_ACTIONS_BOOT="
|
|
expand_root
|
|
set_ephemeral_network
|
|
set_default_interfaces
|
|
enable_sshd
|
|
"
|
|
DEFAULT_ACTIONS_EARLY="
|
|
save_userdata
|
|
"
|
|
DEFAULT_ACTIONS_MAIN="
|
|
create_default_user
|
|
set_hostname
|
|
set_ssh_keys
|
|
"
|
|
DEFAULT_ACTIONS_FINAL=""
|
|
|
|
: "${INIT_ACTIONS_BOOT=$DEFAULT_ACTIONS_BOOT}"
|
|
: "${INIT_ACTIONS_EARLY=$DEFAULT_ACTIONS_EARLY}"
|
|
: "${INIT_ACTIONS_MAIN=$DEFAULT_ACTIONS_MAIN}"
|
|
: "${INIT_ACTIONS_FINAL=$DEFAULT_ACTIONS_FINAL}"
|
|
|
|
### standard boot phase functions...
|
|
|
|
init__expand_root() {
|
|
local dev=$(awk '$2 == "/" {print $1}' "$PROC"/mounts 2>/dev/null)
|
|
local filesystem=$(awk '$2 == "/" {print $3}' "$PROC"/mounts 2>/dev/null)
|
|
local partition=$(cat "$SYS/class/block/${dev#/dev/}/partition" 2>/dev/null)
|
|
|
|
# only support ext2/ext3/ext4 for now
|
|
case "$filesystem" in
|
|
ext*) ;;
|
|
*) return;;
|
|
esac
|
|
|
|
if [ -n "$partition" ]; then
|
|
# it's a partition, resize it
|
|
local volume=$(readlink -f "$SYS/class/block/${dev#/dev/}/..")
|
|
volume="/dev/${volume##*/}"
|
|
echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume"
|
|
$MOCK partx -u "$volume"
|
|
fi
|
|
# resize filesystem
|
|
if [ -e "$dev" ] || [ -n "$MOCK" ]; then
|
|
$MOCK resize2fs "$dev"
|
|
fi
|
|
}
|
|
|
|
# collect ethernet interfaces, sorted by index
|
|
ethernets() {
|
|
for i in "$SYS/class/net/"*; do
|
|
local iface="${i##*/}"
|
|
case "$iface" in
|
|
eth*) echo "$(cat "$i/ifindex") $iface";;
|
|
esac
|
|
done | sort -n | awk '{print $2}'
|
|
}
|
|
|
|
# find the interface that is has operstate up
|
|
find_first_interface_up() {
|
|
local n=0
|
|
[ $# -eq 0 ] && return
|
|
while [ $n -le ${TINY_CLOUD_LINK_WAIT_MAX:-10} ]; do
|
|
for i in "$@"; do
|
|
if [ "$(cat "$SYS/class/net/$i/operstate")" = "up" ]; then
|
|
echo "$i"
|
|
return
|
|
fi
|
|
done
|
|
sleep 0.1
|
|
n=$((n+1))
|
|
done
|
|
}
|
|
|
|
# auto detect which network interface to auto configure
|
|
# check which is connected or fallback to first
|
|
# This will set link to down to all eth* except the found
|
|
auto_detect_ethernet_interface() {
|
|
local ifaces="$(ethernets)"
|
|
[ -z "$ifaces" ] && return
|
|
|
|
# find first connected interface
|
|
for i in $ifaces; do
|
|
$MOCK ip link set dev $i up >/dev/null
|
|
done
|
|
local iface="$(find_first_interface_up $ifaces)"
|
|
|
|
# use first if all are disconnected
|
|
if [ -z "$iface" ]; then
|
|
set -- $ifaces
|
|
iface="$1"
|
|
fi
|
|
|
|
# we will use the found interface later so lets keep it up
|
|
for i in $ifaces; do
|
|
if [ "$i" != "$iface" ]; then
|
|
$MOCK ip link set dev $i down >/dev/null
|
|
fi
|
|
done
|
|
echo "$iface"
|
|
}
|
|
|
|
# may be overridded by provider
|
|
want_ephemeral_network() {
|
|
false
|
|
}
|
|
|
|
init__set_ephemeral_network() {
|
|
if ! want_ephemeral_network; then
|
|
return
|
|
fi
|
|
local iface="$(auto_detect_ethernet_interface)"
|
|
if [ -z "$iface" ]; then
|
|
return
|
|
fi
|
|
$MOCK udhcpc -i "$iface" -f -q
|
|
}
|
|
|
|
init__set_default_interfaces() {
|
|
if [ -f "$ETC"/network/interfaces ]; then
|
|
log -i -t "$phase" info "$ACTION: already set up"
|
|
return
|
|
fi
|
|
|
|
mkdir -p "$ETC/network"
|
|
printf "%s\n%s\n\n" \
|
|
"auto lo" \
|
|
"iface lo inet loopback" \
|
|
> "$ETC/network/interfaces"
|
|
|
|
local iface="$(auto_detect_ethernet_interface)"
|
|
if [ -z "$iface" ]; then
|
|
# TODO: message/log?
|
|
return
|
|
fi
|
|
printf "%s\n%s\n\t%s\n\n" \
|
|
"auto $iface" \
|
|
"iface $iface" \
|
|
"use dhcp" >> "$ETC/network/interfaces"
|
|
}
|
|
|
|
init__create_default_user() {
|
|
local user="$CLOUD_USER"
|
|
if [ "$user" = "none" ] || [ -z "$user" ]; then
|
|
log -i -t "$phase" info "$ACTION: skip"
|
|
return
|
|
fi
|
|
# don't do anything if it already exists
|
|
if getent passwd "$user" >/dev/null; then
|
|
log -i -t "$phase" info "$ACTION: already exists"
|
|
return
|
|
fi
|
|
|
|
$MOCK addgroup "$user"
|
|
$MOCK adduser -h "/home/$user" -s /bin/sh -G "$user" -D "$user"
|
|
$MOCK addgroup "$user" wheel
|
|
echo "$user:*" | $MOCK chpasswd -e
|
|
|
|
# setup sudo and/or doas
|
|
if [ -d "$ETC/sudoers.d" ]; then
|
|
echo '%wheel ALL=(ALL) NOPASSWD: ALL' > "$ETC/sudoers.d/wheel"
|
|
fi
|
|
if [ -d "$ETC/doas.d" ]; then
|
|
echo 'permit nopass :wheel' > "$TARGET/etc/doas.d/wheel.conf"
|
|
elif [ -f "$ETC/doas.conf" ]; then
|
|
add_once "$TARGET/etc/doas.conf" "permit nopass :wheel"
|
|
fi
|
|
}
|
|
|
|
init__enable_sshd() {
|
|
$MOCK rc-update add sshd default
|
|
# in case something else has enabled/disabled dservices
|
|
$MOCK rc-update --update
|
|
}
|
|
|
|
### standard early phase functions
|
|
|
|
init__save_userdata() {
|
|
local userdata="$TINY_CLOUD_VAR/user-data"
|
|
local tmpfile=$(mktemp "$userdata.XXXXXX")
|
|
|
|
imds -e @userdata > "$tmpfile"
|
|
if printf '\037\213\010' | cmp -s -n 3 "$tmpfile"; then
|
|
gzip -dc "$tmpfile" > "$userdata"
|
|
elif printf 'BZh' | cmp -s -n 3 "$tmpfile"; then
|
|
bzip2 -dc "$tmpfile" > "$userdata"
|
|
elif printf '\375\067\172\130\132\000' | cmp -s -n 6 "$tmpfile"; then
|
|
unxz -c "$tmpfile" > "$userdata"
|
|
elif printf '\135\000\000' | cmp -s -n 3 "$tmpfile"; then
|
|
lzma -dc "$tmpfile" > "$userdata"
|
|
elif printf '\211\114\132' | cmp -s -n 3 "$tmpfile"; then
|
|
lzop -dc "$tmpfile" > "$userdata"
|
|
elif printf '\004\042\115\030' | cmp -s -n 4 "$tmpfile"; then
|
|
lz4 -dc "$tmpfile" > "$userdata"
|
|
elif printf '(\265/\375' | cmp -s -n 4 "$tmpfile"; then
|
|
zstd -dc "$tmpfile" > "$userdata"
|
|
else
|
|
cp "$tmpfile" "$userdata"
|
|
fi
|
|
rm "$tmpfile"
|
|
}
|
|
|
|
|
|
### standard main phase functions
|
|
|
|
init__set_hostname() {
|
|
local fqdn=$(imds @hostname)
|
|
if [ -z "$fqdn" ]; then
|
|
log -i -t "$phase" info "$ACTION: no hostname set"
|
|
return
|
|
fi
|
|
|
|
local host="${fqdn%%\.*}"
|
|
if [ -z "$host" ]; then
|
|
log -i -t "$phase" warning "$ACTION: invalid hostname '$fqdn'"
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$ETC"
|
|
echo "$host" > "$ETC"/hostname
|
|
$MOCK hostname -F "$ETC"/hostname
|
|
echo -e "127.0.1.1\t$fqdn $host" >> "$ETC"/hosts
|
|
}
|
|
|
|
init__set_ssh_keys() {
|
|
local sshkeys="$(imds @ssh-keys)"
|
|
if [ -z "$sshkeys" ]; then
|
|
log -i -t "$phase" info "$ACTION: no ssh key found"
|
|
return
|
|
fi
|
|
local user="$CLOUD_USER"
|
|
local pwent="$(getent passwd "$user")"
|
|
if [ -z "$pwent" ]; then
|
|
log -i -t "$phase" err "$ACTION: failed to find user $user"
|
|
return 1
|
|
fi
|
|
local group=$(echo "$pwent" | cut -d: -f4)
|
|
local ssh_dir="${ROOT}$(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"
|
|
$MOCK chown -R "$user:$group" "$ssh_dir"
|
|
echo "$sshkeys" > "$keys_file"
|
|
}
|
|
|
|
|
|
### standard final phase functions would be here, if there were any
|
|
|
|
|
|
### load cloud-specific init functions / vars (potentially overriding)
|
|
|
|
if [ "$CLOUD" = "alpine" ]; then
|
|
log -i -t "$phase" warning "CLOUD provider alpine is deprecated. Use nocloud"
|
|
CLOUD="nocloud"
|
|
fi
|
|
|
|
if [ -f "$LIBDIR/tiny-cloud/cloud/$CLOUD/init" ]; then
|
|
. "$LIBDIR/tiny-cloud/cloud/$CLOUD/init"
|
|
fi
|
|
|
|
|
|
### load user-data type-specific init functions / vars (potentially overriding)
|
|
|
|
userdata_type() {
|
|
if [ ! -f "$TINY_CLOUD_VAR/user-data" ]; then
|
|
echo missing
|
|
return
|
|
fi
|
|
header=$(head -n1 "$TINY_CLOUD_VAR/user-data" | sed -e 's/[[:space:]].*//g')
|
|
case "$header" in
|
|
'#!'*) echo script;;
|
|
'#'*) echo ${header#\#};;
|
|
*) echo unknown;;
|
|
esac
|
|
}
|
|
|
|
USERDATA_TYPE="$(userdata_type)"
|
|
if [ -f "$LIBDIR/tiny-cloud/user-data/$USERDATA_TYPE" ]; then
|
|
. "$LIBDIR/tiny-cloud/user-data/$USERDATA_TYPE"
|
|
fi
|