1
0
mirror of https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git synced 2026-06-21 00:07:16 +03:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Justin
b96c9adeac Merge branch 'systemd' into 'main'
Add Systemd init system support

Closes #77

See merge request alpine/cloud/tiny-cloud!148
2026-06-14 07:29:55 +00:00
Jake Buchholz Göktürk
19688765e9 fix regression introduced in 3.3.1 2026-06-08 12:29:31 -07:00
Natanael Copa
263803475e Fix regression introduced with autodetect 2026-06-08 19:27:53 +00:00
Jake Buchholz Göktürk
dcadf2ad0a release 3.3.1 2026-06-07 10:50:00 -07:00
Natanael Copa
f6046351ca Tiny cloud autodetect 2026-06-07 16:01:36 +00:00
Justin Zobel
9e2e6d63af Add Systemd init system support
Fixes #77
2026-04-16 15:58:57 +09:30
11 changed files with 200 additions and 67 deletions

View File

@ -1,5 +1,14 @@
# CHANGELOG
## 2026-06-08 - Tiny Cloud v3.3.2
* Fixes an autodetect regression introduced in v3.3.1
## 2026-06-07 - Tiny Cloud v3.3.1
* Adds `tiny-cloud autodetect` subcommand, which outputs what Tiny Cloud's cloud
autodetection currently finds, thanks @ncopa!
## 2026-05-23 - Tiny Cloud v3.3.0
#### GOOGLE CLOUD BEHAVIOR CHANGE

View File

@ -5,7 +5,7 @@ MAN1PAGES = docs/imds.1
MAN5PAGES = docs/tiny-cloud.conf.5 docs/cloud-config.5 docs/alpine-config.5
MAN8PAGES = docs/tiny-cloud.8
SUBPACKAGES = core openrc $(CLOUDS)
SUBPACKAGES = core init $(CLOUDS)
.PHONY: check install $(SUBPACKAGES)
@ -31,9 +31,13 @@ core: $(MAN1PAGES) $(MAN5PAGES) $(MAN8PAGES)
install -Dm755 -t "$(PREFIX)"/usr/sbin \
sbin/tiny-cloud
openrc:
install -Dm755 -t "$(PREFIX)"/etc/init.d \
dist/openrc/*
init:
[ $(command -v rc-status) ] && \
install -Dm755 -t "$(PREFIX)"/etc/init.d \
dist/openrc/*
[ $(command -v systemctl) ] && \
install -Dm755 -t "$(PREFIX)"/etc/systemd/system \
dist/systemd/*
$(CLOUDS):
install -Dm644 -t "$(PREFIX)"/usr/lib/tiny-cloud/cloud/$@ \

10
dist/systemd/tiny-cloud-boot.service vendored Normal file
View File

@ -0,0 +1,10 @@
[Unit]
Description=Tiny Cloud Bootstrap - boot phase
[Service]
Type=oneshot
ExecStart=tiny-cloud boot
Restart=no
[Install]
WantedBy=multi-user.target

11
dist/systemd/tiny-cloud-early.service vendored Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=Tiny Cloud Bootstrap - early phase
After=network.target
[Service]
Type=oneshot
ExecStart=tiny-cloud early
Restart=no
[Install]
WantedBy=multi-user.target

11
dist/systemd/tiny-cloud-final.service vendored Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=Tiny Cloud Bootstrap - final phase
After=tiny-cloud-boot.service tiny-cloud-main.service tiny-cloud-early.service
[Service]
Type=oneshot
ExecStart=tiny-cloud final
Restart=no
[Install]
WantedBy=multi-user.target

12
dist/systemd/tiny-cloud-main.service vendored Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=Tiny Cloud Bootstrap - main phase
After=network.target sshd.service
[Service]
Type=oneshot
ExecStart=tiny-cloud main
Restart=no
[Install]
WantedBy=multi-user.target

View File

@ -6,7 +6,7 @@ tiny-cloud - perform first-boot initialization for cloud instances
# SYNOPSIS
*tiny-cloud* [*-h*|*--help*] { *boot* | *early* | *main* | *final* | *-b*|*--bootstrap* { *complete* | *incomplete* | *status* } | *-E*|*--enable* | *-D*|*--disable* }
*tiny-cloud* [*-h*|*--help*] { *boot* | *early* | *main* | *final* | *autodetect* | *-b*|*--bootstrap* { *complete* | *incomplete* | *status* } | *-E*|*--enable* | *-D*|*--disable* }
# DESCRIPTION
@ -61,6 +61,11 @@ been marked complete, later phase invocations exit without doing further work.
Run finalization actions. By default this marks bootstrap complete after any
additional configured final actions have succeeded.
*autodetect*
Run provider autodetection probes and print the detected provider name.
This does not use the configured provider, kernel command line hints, or the
cached autodetection result.
# OPERATION
When invoked with a phase argument, *tiny-cloud* first checks whether

View File

@ -47,31 +47,43 @@ line_kval() {
cat "${2:--}" 2>/dev/null | xargs -n1 | grep "^$1=" | cut -d= -f2- | paste -sd' ' | tr "$3" ' '
}
if [ "$CLOUD" = "auto" ]; then
cloud_alias() {
case "$1" in
ec2) echo aws;;
gce) echo gcp;;
nocloud-net) echo nocloud;;
oracle) echo oci;;
*) echo "$1";;
esac
}
cloud_hint() {
local F cloud
# try kernel cmdline & DMI product serial
for F in "$PROC/cmdline" "$SYS/class/dmi/id/product_serial"; do
cloud=$(line_kval tinycloud "$F" : | line_kval cloud)
[ -z "$cloud" ] && cloud=$(line_kval ds "$F" ';' | cut -d' ' -f1 | lower)
[ -n "$cloud" ] && {
cloud_alias "$cloud"
return
}
done
return 0
}
cloud_autodetect() {
for i in "$LIBDIR"/tiny-cloud/cloud/*/autodetect; do
if [ -x "$i" ]; then
"$i" || :
fi
done | sort -n | cut -d' ' -f2 | head -n 1
}
if [ "$CLOUD" = "auto" ] && [ -z "$TINY_CLOUD_NO_RESOLVE" ]; then
# previously detected?
CLOUD=$(cat "$TINY_CLOUD_VAR"/.autodetect 2>/dev/null) || {
# try kernel cmdline & DMI product serial
for F in "$PROC/cmdline" "$SYS/class/dmi/id/product_serial"; do
CLOUD=$(line_kval tinycloud "$F" : | line_kval cloud)
[ -z "$CLOUD" ] && CLOUD=$(line_kval ds "$F" ';' | cut -d' ' -f1 | lower)
[ -n "$CLOUD" ] && break
done
if [ -n "$CLOUD" ]; then
# convert cloud-init cloud names
case "$CLOUD" in
ec2) CLOUD=aws;;
gce) CLOUD=gcp;;
nocloud-net) CLOUD=nocloud;;
oracle) CLOUD=oci;;
esac
else
# try all the autodetects, sorted by confidence...
CLOUD=$(
for i in "$LIBDIR"/tiny-cloud/cloud/*/autodetect; do
[ -x "$i" ] && "$i"
done | sort -n | cut -d' ' -f2 | head -n 1
)
fi
CLOUD=$(cloud_hint)
[ -z "$CLOUD" ] && CLOUD=$(cloud_autodetect)
if [ -z "$CLOUD" ] || [ ! -d "$LIBDIR/tiny-cloud/cloud/$CLOUD" ]; then
log -t autodetect err "unable to determine cloud"
CLOUD=unknown

View File

@ -7,11 +7,12 @@ set -e
: "${PREFIX:=/usr}"
: "${LIBDIR:=$PREFIX/lib}"
[ "$1" = "autodetect" ] && TINY_CLOUD_NO_RESOLVE=1
. "$LIBDIR/tiny-cloud/common"
usage() {
cat <<-EOF
Usage: ${0##*/} [-h | --help] { boot | early | main | final | --bootstrap {complete|incomplete|status} | --enable | --disable }
Usage: ${0##*/} [-h | --help] { boot | early | main | final | autodetect | --bootstrap {complete|incomplete|status} | --enable | --disable }
EOF
}
@ -72,6 +73,14 @@ shift
case "$phase" in
boot|early|main|final) ;;
autodetect)
CLOUD=$(cloud_autodetect)
if [ -z "$CLOUD" ] || [ ! -d "$LIBDIR/tiny-cloud/cloud/$CLOUD" ]; then
echo unknown
exit 1
fi
echo "$CLOUD"
exit 0;;
*) usage >&2; exit 1;;
esac

View File

@ -26,6 +26,8 @@ init_tests \
set_ssh_keys_gcp \
userdata_type \
run_userdata \
autodetect_config_overrides_cmdline \
autodetect_cache_overrides_cmdline \
autodetect_aws_cmdline \
autodetect_aws_nitro \
autodetect_aws_xen \
@ -38,6 +40,7 @@ init_tests \
autodetect_nocloud_dmi \
autodetect_nocloud_volume \
autodetect_oci \
autodetect_scaleway_cmdline \
autodetect_scaleway \
autodetect_unknown
@ -49,7 +52,7 @@ expand_root_body() {
for provider in $PROVIDERS; do
CLOUD="$provider" atf_check \
-o match:"resize2fs /dev/xvda" \
sh -c ". $lib; init__expand_root"
sh -e -c ". $lib; init__expand_root"
done
}
@ -67,7 +70,7 @@ expand_root_partition_body() {
-o match:"sfdisk .*/dev/nvme0n1" \
-o match:"partx .*/dev/nvme0n1" \
-o match:"resize2fs /dev/nvme0n1p2" \
sh -c ". $lib; init__expand_root"
sh -e -c ". $lib; init__expand_root"
done
}
@ -103,7 +106,7 @@ expand_root_lvm_partition_body() {
-o match:"pvresize /dev/vda2" \
-o match:"lvextend -l \\+85%VG /dev/mapper/vg0-root" \
-o match:"resize2fs /dev/mapper/vg0-root" \
sh -c ". $lib; init__expand_root"
sh -e -c ". $lib; init__expand_root"
done
}
@ -136,7 +139,7 @@ expand_root_lvm_whole_disk_body() {
-o match:"resize2fs /dev/mapper/vg1-root" \
-o not-match:"sfdisk" \
-o not-match:"partx" \
sh -c ". $lib; init__expand_root"
sh -e -c ". $lib; init__expand_root"
done
}
@ -149,7 +152,7 @@ ethernets_body() {
-o match:"eth0 eth2 eth11" \
-o not-match:"br0" \
-o not-match:"lo" \
sh -c ". $lib; ethernets | tr '\n' ' '"
sh -e -c ". $lib; ethernets | tr '\n' ' '"
}
find_first_interface_up_body() {
@ -158,7 +161,7 @@ find_first_interface_up_body() {
atf_check \
-o match:"eth1" \
sh -c ". $lib; find_first_interface_up eth0 eth1"
sh -e -c ". $lib; find_first_interface_up eth0 eth1"
}
auto_detect_ethernet_interface_body() {
@ -167,13 +170,13 @@ auto_detect_ethernet_interface_body() {
atf_check \
-o match:"^eth1$" \
sh -c ". $lib; auto_detect_ethernet_interface"
sh -e -c ". $lib; auto_detect_ethernet_interface"
# test that we pick first if all are down
echo down > sys/class/net/eth1/operstate
atf_check \
-o match:"^eth0$" \
sh -c ". $lib; TINY_CLOUD_LINK_WAIT_MAX=1; auto_detect_ethernet_interface"
sh -e -c ". $lib; TINY_CLOUD_LINK_WAIT_MAX=1; auto_detect_ethernet_interface"
}
set_default_interfaces_body() {
@ -181,7 +184,7 @@ set_default_interfaces_body() {
echo up > sys/class/net/eth1/operstate
atf_check \
sh -c ". $lib; init__set_default_interfaces"
sh -e -c ". $lib; init__set_default_interfaces"
atf_check \
-o match:"auto lo" \
-o match:"iface eth1" \
@ -194,7 +197,7 @@ enable_sshd_body() {
CLOUD="$provider" atf_check \
-o match:"rc-update.* add sshd default" \
-o match:"rc-update.* --update" \
sh -c ". $lib; init__enable_sshd"
sh -e -c ". $lib; init__enable_sshd"
done
}
@ -204,7 +207,7 @@ create_default_user_body() {
-o match:"adduser.*alpine" \
-o match:"addgroup alpine wheel" \
-o match:"chpasswd -e" \
sh -c ". $lib; init__create_default_user"
sh -e -c ". $lib; init__create_default_user"
}
save_userdata_plain_body() {
@ -212,7 +215,7 @@ save_userdata_plain_body() {
#userdata
EOF
CLOUD="nocloud" atf_check -e match:"NoCloud 'meta-data' is empty" \
sh -c ". \"$lib\"; init__save_userdata"
sh -e -c ". \"$lib\"; init__save_userdata"
atf_check -o match:"^#userdata" cat var/lib/cloud/user-data
}
@ -224,7 +227,7 @@ save_userdata_compressed_body() {
CLOUD="nocloud" atf_check \
-e 'ignore' \
sh -c ". \"$lib\"; init__save_userdata"
sh -e -c ". \"$lib\"; init__save_userdata"
if ! grep "^#userdata" var/lib/cloud/user-data; then
atf_fail "$comp failed"
@ -239,7 +242,7 @@ set_hostname_body() {
CLOUD="nocloud" atf_check \
-o match:"hostname.*-F $PWD/etc/hostname" \
sh -c ". \"$lib\"; init__set_hostname"
sh -e -c ". \"$lib\"; init__set_hostname"
atf_check -o match:"^myhostname$" cat etc/hostname
}
@ -255,7 +258,7 @@ set_ssh_keys_body() {
EOF
CLOUD="nocloud" atf_check \
-o match:"chown.*/\.ssh" \
sh -c ". \"$lib\"; init__set_ssh_keys"
sh -e -c ". \"$lib\"; init__set_ssh_keys"
atf_check -o match:"^ssh-ed25519 keydata" \
-o match:"^ssh-rsa foobar" \
cat home/alpine/.ssh/authorized_keys
@ -286,7 +289,7 @@ set_ssh_keys_gcp_body() {
-e ignore \
-o match:"chown -R bar:1001 .*\\.ssh" \
-o not-match:"no SSH keys found for alpine" \
sh -c ". \"$lib\"; init__set_ssh_keys"
sh -e -c ". \"$lib\"; init__set_ssh_keys"
atf_check -o match:"^ssh-ed25519 foobar1 alpine" \
-o match:"^ssh-rsa foobar5 google-ssh" \
-o not-match:"foobar4" \
@ -302,32 +305,32 @@ userdata_type_body() {
rm -f var/lib/cloud/user-data
CLOUD="$c" atf_check \
-o match:"missing" \
sh -c ". \"$lib\"; userdata_type"
sh -e -c ". \"$lib\"; userdata_type"
touch var/lib/cloud/user-data
CLOUD="$c" atf_check \
-o match:"empty" \
sh -c ". \"$lib\"; userdata_type"
sh -e -c ". \"$lib\"; userdata_type"
echo "#tiny-cloud-config" > var/lib/cloud/user-data
CLOUD="$c" atf_check \
-o match:"tiny-cloud-config" \
sh -c ". \"$lib\"; userdata_type"
sh -e -c ". \"$lib\"; userdata_type"
echo "no-content-type" > var/lib/cloud/user-data
CLOUD="$c" atf_check \
-o match:"unknown" \
sh -c ". \"$lib\"; userdata_type"
sh -e -c ". \"$lib\"; userdata_type"
echo "#alpine-config" > var/lib/cloud/user-data
CLOUD="$c" atf_check \
-o match:"alpine-config" \
sh -c ". \"$lib\"; userdata_type"
sh -e -c ". \"$lib\"; userdata_type"
echo "#!/bin/sh" > var/lib/cloud/user-data
CLOUD="$c" atf_check -s exit:0 \
-o match:"script" \
sh -c ". \"$lib\"; userdata_type"
sh -e -c ". \"$lib\"; userdata_type"
done
}
@ -337,10 +340,10 @@ run_userdata_body() {
echo "hello from user-data"
EOF
CLOUD="nocloud" atf_check -e match:"NoCloud 'meta-data' is empty" \
sh -c ". \"$lib\"; init__save_userdata"
sh -e -c ". \"$lib\"; init__save_userdata"
CLOUD="nocloud" atf_check \
-o match:"hello from user-data" \
sh -c ". \"$lib\"; init__run_userdata"
sh -e -c ". \"$lib\"; init__run_userdata"
grep "hello from user-data" var/log/user-data.log || atf_fail "user-data.log failed"
grep -w "0" var/log/user-data.exit || atf_fail "user-data.exit failed"
}
@ -348,7 +351,24 @@ run_userdata_body() {
autodetect_unknown_body() {
atf_check \
-o match:"unknown" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_config_overrides_cmdline_body() {
mkdir -p proc
echo "quiet ds=scaleway console=ttyS0" > proc/cmdline
CLOUD=aws atf_check \
-o match:"aws" \
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_cache_overrides_cmdline_body() {
mkdir -p proc var/lib/cloud
echo aws > var/lib/cloud/.autodetect
echo "quiet ds=scaleway console=ttyS0" > proc/cmdline
atf_check \
-o match:"aws" \
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_aws_cmdline_body() {
@ -356,7 +376,7 @@ autodetect_aws_cmdline_body() {
echo "quiet tinycloud=cloud=aws console=ttyS0" > proc/cmdline
atf_check \
-o match:"aws" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_aws_nitro_body() {
@ -366,7 +386,7 @@ autodetect_aws_nitro_body() {
EOT
atf_check \
-o match:"aws" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_aws_xen_body() {
@ -376,7 +396,7 @@ autodetect_aws_xen_body() {
EOT
atf_check \
-o match:"aws" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_azure_body() {
@ -386,7 +406,7 @@ autodetect_azure_body() {
EOT
atf_check \
-o match:"azure" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_digitalocean_body() {
@ -396,7 +416,7 @@ autodetect_digitalocean_body() {
EOT
atf_check \
-o match:"digitalocean" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_gcp_body() {
@ -406,7 +426,7 @@ autodetect_gcp_body() {
EOT
atf_check \
-o match:"gcp" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_hetzner_body() {
@ -416,7 +436,7 @@ autodetect_hetzner_body() {
EOT
atf_check \
-o match:"hetzner" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_incus_body() {
@ -424,7 +444,7 @@ autodetect_incus_body() {
touch dev/incus/sock
atf_check \
-o match:"incus" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_nocloud_cmdline_body() {
@ -432,7 +452,7 @@ autodetect_nocloud_cmdline_body() {
echo "quiet ds=nocloud;s=https://10.42.42.42/ console=ttyS0" > proc/cmdline
atf_check \
-o match:"nocloud" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_nocloud_dmi_body() {
@ -440,7 +460,7 @@ autodetect_nocloud_dmi_body() {
echo "ds=nocloud;s=https://10.42.42.42/" > sys/class/dmi/id/product_serial
atf_check \
-o match:"nocloud" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_nocloud_volume_body() {
@ -455,7 +475,7 @@ autodetect_nocloud_volume_body() {
atf_check \
-o match:"nocloud" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_oci_body() {
@ -463,7 +483,15 @@ autodetect_oci_body() {
echo OracleCloud.com > sys/class/dmi/id/chassis_asset_tag
atf_check \
-o match:"oci" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_scaleway_cmdline_body() {
mkdir -p proc
echo "quiet ds=scaleway console=ttyS0" > proc/cmdline
atf_check \
-o match:"scaleway" \
sh -e -c ". \"$lib\"; echo \$CLOUD"
}
autodetect_scaleway_body() {
@ -473,5 +501,5 @@ autodetect_scaleway_body() {
EOT
atf_check \
-o match:"scaleway" \
sh -c ". \"$lib\"; echo \$CLOUD"
sh -e -c ". \"$lib\"; echo \$CLOUD"
}

View File

@ -10,6 +10,8 @@ PROVIDERS="alpine aws azure digitalocean gcp incus hetzner nocloud oci scaleway"
init_tests \
tiny_cloud_help \
tiny_cloud_autodetect \
tiny_cloud_autodetect_unknown \
tiny_cloud_disabled \
no_metadata_boot \
no_userdata_early \
@ -28,6 +30,26 @@ tiny_cloud_help_body() {
done
}
tiny_cloud_autodetect_body() {
mkdir -p sys/class/dmi/id
cat > sys/class/dmi/id/modalias <<-EOT
dmi:bvnScaleway:bvrScaleway:bd10/22/2024:br1.0:svnScaleway:pnScaleway:pvr:rvnKVM:rnScaleway:rvr:cvnScaleway:ct1:cvr:sku:
EOT
atf_check -s exit:0 \
-o match:"^scaleway$" \
tiny-cloud autodetect
}
tiny_cloud_autodetect_unknown_body() {
mkdir -p etc proc var/lib/cloud
echo "CLOUD=auto" > etc/tiny-cloud.conf
echo scaleway > var/lib/cloud/.autodetect
echo "quiet ds=scaleway console=ttyS0" > proc/cmdline
atf_check -s exit:1 \
-o match:"^unknown$" \
tiny-cloud autodetect
}
tiny_cloud_disabled_body() {
mkdir -p etc
touch etc/tiny-cloud.disabled