1
0
mirror of https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git synced 2025-12-14 19:02:45 +03:00

split alpine-config out of cloud-config

This commit is contained in:
Jake Buchholz Göktürk 2024-11-04 17:05:50 +00:00
parent e75ec5b4f6
commit b8218bd1f0
4 changed files with 316 additions and 315 deletions

View File

@ -11,13 +11,13 @@ works with multiple cloud providers. Currently, the following are supported:
* [AWS](https://aws.amazon.com) - Amazon Web Services
* [Azure](https://azure.microsoft.com) - Microsoft Azure
* [GCP](https://cloud.google.com) - Google Cloud Platform
* [Hetzner](https://www.hetzner.com)
* [Incus](https://linuxcontainers.org/incus)
* [Hetzner](https://www.hetzner.com) - Hetzner Cloud
* [Incus](https://linuxcontainers.org/incus) - Incus Containers and Virtual Machines
* [NoCloud](
https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html
) - cloud-init's NoCloud AWS-compatible user provided data source
* [OCI](https://cloud.oracle.com) - Oracle Cloud Infrastructure
[Scaleway](https://www.scaleway.com)
[Scaleway](https://www.scaleway.com) - Scaleway Cloud
Tiny Cloud is also used for Alpine Linux's experimental "auto-install" feature.
@ -52,9 +52,6 @@ As Tiny Cloud is meant to be tiny, it has few dependencies:
* `sfdisk`
* [`yx`](https://gitlab.com/tomalok/yx) (for extracting data from YAML files)
Optional dependencies:
* `nvme-cli` (for AWS nitro NVMe symlinks)
_Tiny Cloud has been developed specifically for use with the
[Alpine Cloud Images](
https://gitlab.alpinelinux.org/alpine/cloud/alpine-cloud-images)

View File

@ -2,10 +2,6 @@
## SOON-ish
* Move the bulk of `#alpine-config` handler that is compatible with
`#cloud-config` to that handler (which is currently just a stub), and only
`#alpine-config` extensions remain there.
* Support cloud auto-detection, where it's possible to do so.
## FUTURE

View File

@ -1,176 +1,14 @@
# Script UserData Functions
# #alpine-config UserData Functions
# vim:set filetype=sh:
# shellcheck shell=sh
INIT_ACTIONS_MAIN="$(insert_before create_default_user userdata_user $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_MAIN="$(insert_after set_hostname \
"userdata_bootcmd userdata_groups userdata_users userdata_write_files userdata_ntp userdata_apk_cache userdata_apk_repositories userdata_package_update userdata_package_upgrade userdata_packages" \
$INIT_ACTIONS_MAIN)"
INIT_ACTIONS_MAIN="$(insert_after set_ssh_keys ssh_authorized_keys $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_FINAL="$INIT_ACTIONS_FINAL userdata_runcmd userdata_autoinstall"
# NOTE: alpine-config extends cloud-config
get_userdata() {
IFS="/"
yx -f "$TINY_CLOUD_VAR/user-data" $1 2>/dev/null
unset IFS
}
: "${LIBDIR:=$PREFIX/lib}"
. "${LIBDIR}/tiny-cloud/user-data/cloud-config"
init__userdata_user() {
local name="$(get_userdata user/name)"
if [ -z "$name" ]; then
name="$(get_userdata user)"
if [ -n "$(get_userdata user/$name)" ]; then
log -s err "user/name is required"
return
fi
fi
if get_userdata | grep -q -x users; then
local default_user="$(get_userdata users/1)"
if [ "$default_user" != "default" ]; then
CLOUD_USER="$(get_userdata users/1/name)"
return 0
fi
fi
CLOUD_USER="${name:-$CLOUD_USER}"
}
set_ssh_authorized_keys_for() {
local user="$1"
local userdata_path="$2"
local sshkeys="$(get_userdata $userdata_path)"
if [ -z "$sshkeys" ]; then
return
fi
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"
for i in $sshkeys; do
local key="$(get_userdata $userdata_path/$i)"
if [ -n "$key" ]; then
echo "$key" >> "$keys_file"
fi
done
}
init__ssh_authorized_keys() {
if [ -z "$CLOUD_USER" ]; then
return
fi
set_ssh_authorized_keys_for "$CLOUD_USER" ssh_authorized_keys
}
init__userdata_bootcmd() {
# run bootcmd
local bootcmds="$(get_userdata bootcmd)"
for i in $bootcmds; do
local cmd="$(get_userdata bootcmd/"$i")"
sh -c "$cmd"
done
}
# write_file <path> <mode> <owner> <encoding> <append>
write_file() {
# Defaults used are the same as for full cloud-init "spec":
# https://cloudinit.readthedocs.io/en/latest/reference/modules.html#write-files
local path="$1"
local mode="${2:-0644}"
local owner="${3:-root:root}"
local encoding="${4:-text/plain}"
local append="${5:-false}"
if [ "$append" != "true" ] && [ "$append" != "false" ]; then
log err "append must be true or false"
return
fi
local tmpfile="$(mktemp $TINY_CLOUD_VAR/user-data.write_files.XXXXXX)"
case "$encoding" in
gzip|gz|gz+base64|gzip+base64|gz+b64|gzip+b64)
base64 -d | gzip -d > "$tmpfile"
;;
base64|b64)
base64 -d > "$tmpfile"
;;
text/plain)
cat > "$tmpfile"
;;
esac
if [ "$append" = "true" ]; then
cat "$tmpfile" >> "$path"
else
cat "$tmpfile" > "$path"
fi
rm -f "$tmpfile"
chmod "$mode" "$path"
# mocked as we do not know which users we could use in testing
# this way we can check the proper invocation at least
$MOCK chown "$owner" "$path"
}
init__userdata_write_files() {
local files="$(get_userdata write_files)"
for i in $files; do
local path="$(get_userdata write_files/$i/path)"
if [ -z "$path" ]; then
continue
fi
mkdir -p "$(dirname "$ROOT/$path")"
get_userdata write_files/$i/content | write_file "$ROOT/$path" \
"$(get_userdata write_files/$i/permissions)" \
"$(get_userdata write_files/$i/owner)" \
"$(get_userdata write_files/$i/encoding)" \
"$(get_userdata write_files/$i/append)"
done
}
init__userdata_ntp() {
local ntp_enabled="$(get_userdata ntp/enabled)"
if [ "$ntp_enabled" != "yes" ] && [ "$ntp_enabled" != "true" ]; then
return
fi
local ntp_client="$(get_userdata ntp/ntp_client)"
local svc= pkg=
case "$ntp_client" in
busybox)
svc=ntpd
;;
chrony|"")
pkg=chrony
svc=chronyd
;;
openntpd)
pkg=openntpd
svc=openntpd
;;
esac
if [ -n "$pkg" ]; then
$MOCK apk add "$pkg"
fi
if [ -n "$svc" ]; then
$MOCK rc-update add "$svc" default
$MOCK rc-service "$svc" start
fi
}
INIT_ACTIONS_MAIN="$(insert_after userdata_ntp "userdata_apk_cache userdata_apk_repositories" $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_FINAL="$INIT_ACTIONS_FINAL userdata_autoinstall"
init__userdata_apk_cache() {
local cache="$(get_userdata apk/cache)"
@ -215,142 +53,8 @@ init__userdata_apk_repositories() {
done
}
init__userdata_package_update() {
local update="$(get_userdata package_update)"
if [ "$update" = "true" ]; then
$MOCK apk update
fi
}
init__userdata_package_upgrade() {
local upgrade="$(get_userdata package_upgrade)"
if [ "$upgrade" = "true" ]; then
$MOCK apk upgrade
fi
}
init__userdata_packages() {
local packages="$(get_userdata packages)"
local pkgs=
for i in $packages; do
pkgs="$pkgs $(get_userdata packages/$i)"
done
if [ -n "$pkgs" ]; then
$MOCK apk add $pkgs
fi
}
init__userdata_runcmd() {
local runcmds="$(get_userdata runcmd)"
for i in $runcmds; do
local cmd="$(get_userdata runcmd/$i)"
sh -c "$cmd"
done
}
init__userdata_groups() {
local groups="$(get_userdata groups)"
for i in $groups; do
local group="$(get_userdata groups/$i)"
$MOCK addgroup $group
done
}
in_list() {
local i needle="$1"
shift
for i in "$@"; do
if [ "$i" = "$needle" ]; then
return 0
fi
done
return 1
}
init__userdata_users() {
local users="$(get_userdata users)"
for i in $users; do
local name gecos homedir shell primary_group groups
local system=false no_create_home=false lock_passwd=true
local keys="$(get_userdata users/$i)"
if [ "$i" = 1 ] && [ "$keys" = "default" ]; then
continue
fi
if in_list name $keys; then
name="$(get_userdata users/$i/name)"
else
continue
fi
if in_list gecos $keys; then
gecos="$(get_userdata users/$i/gecos)"
fi
if in_list homedir $keys; then
homedir="$(get_userdata users/$i/homedir)"
fi
if in_list shell $keys; then
shell="$(get_userdata users/$i/shell)"
fi
if in_list primary_group $keys; then
primary_group="$(get_userdata users/$i/primary_group)"
fi
if in_list system $keys; then
system="$(get_userdata users/$i/system)"
fi
if in_list no_create_home $keys; then
no_create_home="$(get_userdata users/$i/no_create_home)"
fi
if getent passwd "$user" >/dev/null; then
log -i -t "$phase" info "$ACTION: user $user already exists"
else
if [ "$system" != "true" ]; then
unset system
fi
if [ "$no_create_home" != "true" ]; then
unset no_create_home
fi
$MOCK adduser -D ${gecos:+-g "$gecos"} ${homedir:+-h "$homedir"} ${shell:+-s "$shell"} ${primary_group:+-G "$primary_group"} ${system:+-S} ${no_create_home:+-H} "$name"
fi
if in_list lock_passwd $keys; then
lock_passwd="$(get_userdata users/$i/lock_passwd)"
fi
if [ "$lock_passwd" != "false" ]; then
echo "$name:*" | $MOCK chpasswd -e
fi
if in_list ssh_authorized_keys $keys; then
set_ssh_authorized_keys_for "$name" users/$i/ssh_authorized_keys
fi
if in_list groups $keys; then
groups="$(get_userdata users/$i/groups | tr ',' ' ')"
local group
for group in $groups; do
$MOCK addgroup "$name" "$group"
done
fi
if in_list doas $keys; then
if [ -d "$ROOT/etc/doas.d" ]; then
touch "$ROOT/etc/doas.d/$name.conf"
chmod 660 "$ROOT/etc/doas.d/$name.conf"
fi
local j
for j in $(get_userdata users/$i/doas); do
local line="$(get_userdata users/$i/doas/$j)"
if [ -d "$ROOT/etc/doas.d" ]; then
echo "$line" >> "$ROOT/etc/doas.d/$name.conf"
elif [ -f "$ROOT/etc/doas.conf" ]; then
add_once "$ROOT/etc/doas.conf" "$line"
fi
done
fi
done
}
find_biggest_empty_disk() {
local d
local d p
for d in "$ROOT"/sys/class/block/*/device; do
p=${d%/device}
if [ -e "$p"/size ] && [ -z "$(blkid /dev/${p##*/})" ]; then

View File

@ -1,5 +1,309 @@
# CloudConfig UserData Functions
# #cloud-config UserData Functions
# vim:set filetype=sh:
# shellcheck shell=sh
# TODO
# NOTE: This is only a subset of what cloud-init supports!
INIT_ACTIONS_MAIN="$(insert_before create_default_user userdata_user $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_MAIN="$(insert_after set_hostname \
"userdata_bootcmd userdata_groups userdata_users userdata_write_files userdata_ntp userdata_package_update userdata_package_upgrade userdata_packages" \
$INIT_ACTIONS_MAIN)"
INIT_ACTIONS_MAIN="$(insert_after set_ssh_keys ssh_authorized_keys $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_FINAL="$INIT_ACTIONS_FINAL userdata_runcmd"
get_userdata() {
IFS="/"
yx -f "$TINY_CLOUD_VAR/user-data" $1 2>/dev/null
unset IFS
}
init__userdata_user() {
local name="$(get_userdata user/name)"
if [ -z "$name" ]; then
name="$(get_userdata user)"
if [ -n "$(get_userdata user/$name)" ]; then
log -s err "user/name is required"
return
fi
fi
if get_userdata | grep -q -x users; then
local default_user="$(get_userdata users/1)"
if [ "$default_user" != "default" ]; then
CLOUD_USER="$(get_userdata users/1/name)"
return 0
fi
fi
CLOUD_USER="${name:-$CLOUD_USER}"
}
set_ssh_authorized_keys_for() {
local user="$1"
local userdata_path="$2"
local sshkeys="$(get_userdata $userdata_path)"
if [ -z "$sshkeys" ]; then
return
fi
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"
for i in $sshkeys; do
local key="$(get_userdata $userdata_path/$i)"
if [ -n "$key" ]; then
echo "$key" >> "$keys_file"
fi
done
}
init__ssh_authorized_keys() {
if [ -z "$CLOUD_USER" ]; then
return
fi
set_ssh_authorized_keys_for "$CLOUD_USER" ssh_authorized_keys
}
init__userdata_bootcmd() {
# run bootcmd
local bootcmds="$(get_userdata bootcmd)"
for i in $bootcmds; do
local cmd="$(get_userdata bootcmd/"$i")"
sh -c "$cmd"
done
}
# write_file <path> <mode> <owner> <encoding> <append>
write_file() {
# Defaults used are the same as for full cloud-init "spec":
# https://cloudinit.readthedocs.io/en/latest/reference/modules.html#write-files
local path="$1"
local mode="${2:-0644}"
local owner="${3:-root:root}"
local encoding="${4:-text/plain}"
local append="${5:-false}"
if [ "$append" != "true" ] && [ "$append" != "false" ]; then
log err "append must be true or false"
return
fi
local tmpfile="$(mktemp $TINY_CLOUD_VAR/user-data.write_files.XXXXXX)"
case "$encoding" in
gzip|gz|gz+base64|gzip+base64|gz+b64|gzip+b64)
base64 -d | gzip -d > "$tmpfile"
;;
base64|b64)
base64 -d > "$tmpfile"
;;
text/plain)
cat > "$tmpfile"
;;
esac
if [ "$append" = "true" ]; then
cat "$tmpfile" >> "$path"
else
cat "$tmpfile" > "$path"
fi
rm -f "$tmpfile"
chmod "$mode" "$path"
# mocked as we do not know which users we could use in testing
# this way we can check the proper invocation at least
$MOCK chown "$owner" "$path"
}
init__userdata_write_files() {
local files="$(get_userdata write_files)"
for i in $files; do
local path="$(get_userdata write_files/$i/path)"
if [ -z "$path" ]; then
continue
fi
mkdir -p "$(dirname "$ROOT/$path")"
get_userdata write_files/$i/content | write_file "$ROOT/$path" \
"$(get_userdata write_files/$i/permissions)" \
"$(get_userdata write_files/$i/owner)" \
"$(get_userdata write_files/$i/encoding)" \
"$(get_userdata write_files/$i/append)"
done
}
init__userdata_ntp() {
local ntp_enabled="$(get_userdata ntp/enabled)"
if [ "$ntp_enabled" != "yes" ] && [ "$ntp_enabled" != "true" ]; then
return
fi
local ntp_client="$(get_userdata ntp/ntp_client)"
local svc= pkg=
case "$ntp_client" in
busybox)
svc=ntpd
;;
chrony|"")
pkg=chrony
svc=chronyd
;;
openntpd)
pkg=openntpd
svc=openntpd
;;
esac
if [ -n "$pkg" ]; then
$MOCK apk add "$pkg"
fi
if [ -n "$svc" ]; then
$MOCK rc-update add "$svc" default
$MOCK rc-service "$svc" start
fi
}
init__userdata_package_update() {
local update="$(get_userdata package_update)"
if [ "$update" = "true" ]; then
$MOCK apk update
fi
}
init__userdata_package_upgrade() {
local upgrade="$(get_userdata package_upgrade)"
if [ "$upgrade" = "true" ]; then
$MOCK apk upgrade
fi
}
init__userdata_packages() {
local packages="$(get_userdata packages)"
local pkgs=
for i in $packages; do
pkgs="$pkgs $(get_userdata packages/$i)"
done
if [ -n "$pkgs" ]; then
$MOCK apk add $pkgs
fi
}
init__userdata_runcmd() {
local runcmds="$(get_userdata runcmd)"
for i in $runcmds; do
local cmd="$(get_userdata runcmd/$i)"
sh -c "$cmd"
done
}
init__userdata_groups() {
local groups="$(get_userdata groups)"
for i in $groups; do
local group="$(get_userdata groups/$i)"
$MOCK addgroup $group
done
}
in_list() {
local i needle="$1"
shift
for i in "$@"; do
if [ "$i" = "$needle" ]; then
return 0
fi
done
return 1
}
init__userdata_users() {
local users="$(get_userdata users)"
for i in $users; do
local name gecos homedir shell primary_group groups
local system=false no_create_home=false lock_passwd=true
local keys="$(get_userdata users/$i)"
if [ "$i" = 1 ] && [ "$keys" = "default" ]; then
continue
fi
if in_list name $keys; then
name="$(get_userdata users/$i/name)"
else
continue
fi
if in_list gecos $keys; then
gecos="$(get_userdata users/$i/gecos)"
fi
if in_list homedir $keys; then
homedir="$(get_userdata users/$i/homedir)"
fi
if in_list shell $keys; then
shell="$(get_userdata users/$i/shell)"
fi
if in_list primary_group $keys; then
primary_group="$(get_userdata users/$i/primary_group)"
fi
if in_list system $keys; then
system="$(get_userdata users/$i/system)"
fi
if in_list no_create_home $keys; then
no_create_home="$(get_userdata users/$i/no_create_home)"
fi
if getent passwd "$user" >/dev/null; then
log -i -t "$phase" info "$ACTION: user $user already exists"
else
if [ "$system" != "true" ]; then
unset system
fi
if [ "$no_create_home" != "true" ]; then
unset no_create_home
fi
$MOCK adduser -D ${gecos:+-g "$gecos"} ${homedir:+-h "$homedir"} ${shell:+-s "$shell"} ${primary_group:+-G "$primary_group"} ${system:+-S} ${no_create_home:+-H} "$name"
fi
if in_list lock_passwd $keys; then
lock_passwd="$(get_userdata users/$i/lock_passwd)"
fi
if [ "$lock_passwd" != "false" ]; then
echo "$name:*" | $MOCK chpasswd -e
fi
if in_list ssh_authorized_keys $keys; then
set_ssh_authorized_keys_for "$name" users/$i/ssh_authorized_keys
fi
if in_list groups $keys; then
groups="$(get_userdata users/$i/groups | tr ',' ' ')"
local group
for group in $groups; do
$MOCK addgroup "$name" "$group"
done
fi
if in_list doas $keys; then
if [ -d "$ROOT/etc/doas.d" ]; then
touch "$ROOT/etc/doas.d/$name.conf"
chmod 660 "$ROOT/etc/doas.d/$name.conf"
fi
local j
for j in $(get_userdata users/$i/doas); do
local line="$(get_userdata users/$i/doas/$j)"
if [ -d "$ROOT/etc/doas.d" ]; then
echo "$line" >> "$ROOT/etc/doas.d/$name.conf"
elif [ -f "$ROOT/etc/doas.conf" ]; then
add_once "$ROOT/etc/doas.conf" "$line"
fi
done
fi
done
}