diff --git a/README.en.md b/README.en.md index b9a6994..deb8717 100644 --- a/README.en.md +++ b/README.en.md @@ -9,7 +9,7 @@ A lightweight Incus container hosting panel for small VPS instances and low-memo ## Feature Details - Admin panel: users, quotas, domains, IPv6 pool, and audit logs -- User panel: power control, self-service Debian 13/AlmaLinux 9 reinstall, and domain configuration +- User panel: power control, self-service Debian 13/AlmaLinux 9/openSUSE Leap 16 reinstall, and domain configuration - CPU, memory, and disk oversubscription; quota changes apply live without a container restart - Strict container network isolation; prefix mode assigns each container a dedicated `/112` IPv6 block that it can subdivide - Traffic is counted in both directions; over-quota containers are rate-limited to 1 Mbps both ways @@ -76,10 +76,11 @@ Users can define an init script in the panel. It runs as root inside the contain ## Additional Images -Debian 13 is the default image. To offer AlmaLinux 9 during reinstall, build it once: +Debian 13 is the default image. To offer AlmaLinux 9 or openSUSE Leap 16 during reinstall, build it once: ```sh sudo bash scripts/60-rhel-image.sh # AlmaLinux 9 +sudo bash scripts/70-opensuse-image.sh # openSUSE Leap 16 ``` The image builder is not run by `install.sh`, keeping the default installation small. diff --git a/README.md b/README.md index 719079e..9982bb3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## 功能细节 - 管理员面板:用户、配额、域名、IPv6 地址池和审计日志 -- 用户面板:电源控制、自助重装 Debian 13/AlmaLinux 9、域名配置 +- 用户面板:电源控制、自助重装 Debian 13/AlmaLinux 9/openSUSE Leap 16、域名配置 - CPU、内存、磁盘支持超售;配额修改实时生效,无需重启容器 - 容器网络严格隔离;IPv6 前缀模式下每个容器获得独立 `/112`,可继续划分子网 - 流量按上下行合计,超额后双向限速至 1 Mbps @@ -76,10 +76,11 @@ vps config list|set|help ## 额外镜像 -默认镜像是 Debian 13。需要 AlmaLinux 9 时,可手动构建一次: +默认镜像是 Debian 13。需要 AlmaLinux 9 或 openSUSE Leap 16 时,可手动构建一次: ```sh sudo bash scripts/60-rhel-image.sh # AlmaLinux 9 +sudo bash scripts/70-opensuse-image.sh # openSUSE Leap 16 ``` 该脚本不会由 `install.sh` 自动执行,以减少小型主机的安装负担。 diff --git a/docs/architecture.md b/docs/architecture.md index 9b2d0c2..4217750 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -233,6 +233,15 @@ validates that a picked non-default image still exists. Containers run from either base with the same light provisioning (random hostname, root password, sshd enabled — the service is `sshd` on RHEL and `ssh` on Debian). +## Optional openSUSE image (`vpsmgr/opensuse-sshd`) + +`70-opensuse-image.sh` (also NOT part of `install.sh`) builds an openSUSE Leap +16 image — `images:opensuse/16.0`, published as `vpsmgr/opensuse-sshd` — with +the same hygiene: sshd + universal tooling via `zypper`, caches/logs cleaned, +base image deleted after publishing. Leap 16 ships systemd-networkd by default, +so its IPv6 is configured at runtime by the panel's standard networkd branch +(static /128 + `fe80::1` gateway), unlike the RHEL-family nmcli path. + ## Per-container provisioning `mgr.Provision` runs inside every new or reinstalled container and: diff --git a/scripts/70-opensuse-image.sh b/scripts/70-opensuse-image.sh new file mode 100644 index 0000000..1fe7b6f --- /dev/null +++ b/scripts/70-opensuse-image.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# 70-opensuse-image.sh — OPTIONAL: build an openSUSE Leap 16 "sshd + universal +# tooling" image so users can reinstall their container with something other +# than the default Debian 13. Run as root AFTER install.sh, only when you +# actually want the extra image (a toy panel should not build it on every box). +# +# sudo bash scripts/70-opensuse-image.sh +# +# Same hygiene as 50-image.sh: sshd + common tools baked in, zypper caches and +# logs cleaned before publishing, base image deleted afterwards. +set -uo pipefail + + +REMOTE_ALIAS=images:opensuse/16.0 +IMAGE=vpsmgr/opensuse-sshd +BASE_ALIAS=vpsmgr-opensuse-16 + +log(){ echo "[70] $*"; } + +incus info >/dev/null 2>&1 || { echo "[70] error: Incus not ready" >&2; exit 1; } + +if incus image show "$IMAGE" >/dev/null 2>&1; then + log "image $IMAGE already present" + exit 0 +fi + +if ! incus image list "$BASE_ALIAS" --format=csv 2>/dev/null | grep -q .; then + log "pulling $REMOTE_ALIAS (fallback base, deleted after build)..." + if ! incus image copy "$REMOTE_ALIAS" local: --alias "$BASE_ALIAS"; then + log " warn: image pull failed — nothing built" + exit 0 + fi +else + log "base image $BASE_ALIAS already present" +fi + +log "building $IMAGE (this takes a few minutes)..." +NAME=tmp-opensuse-builder +incus delete --force "$NAME" >/dev/null 2>&1 || true +if incus launch "$BASE_ALIAS" "$NAME"; then + # wait until usable + for i in $(seq 1 60); do + if incus exec "$NAME" -- /bin/true >/dev/null 2>&1; then break; fi + sleep 2 + done + # openSUSE brings eth0 up via systemd-networkd, which lags the Incus agent by + # a few seconds: running zypper before DHCP has written resolv.conf makes it + # die with "Couldn't resolve". Wait until DNS answers, or the builder install + # fails and a broken image gets published. + DNS_OK= + for i in $(seq 1 30); do + if incus exec "$NAME" -- getent hosts download.opensuse.org >/dev/null 2>&1; then + DNS_OK=1; break + fi + sleep 2 + done + if [ -z "$DNS_OK" ]; then + log " warn: builder never got working DNS; nothing built" + incus delete --force "$NAME" >/dev/null 2>&1 || true + incus image delete "$BASE_ALIAS" >/dev/null 2>&1 || true + elif incus exec "$NAME" -- sh -c ' +set -e +# universal user tooling (mirrors the Debian image): sshd, curl/wget need +# ca-certificates or HTTPS fails; bind-utils is the openSUSE dig/nslookup +# package. zypper is retried: the mirror can flap on a slow uplink right after +# boot. +for attempt in 1 2 3; do + zypper --non-interactive install --no-recommends \ + openssh-server ca-certificates curl wget less bind-utils openssh-clients unzip nano \ + && break + sleep 5 +done +# hard gate: never publish an image without sshd baked in +rpm -q openssh-server >/dev/null +mkdir -p /etc/ssh/sshd_config.d +printf "PermitRootLogin yes\nPasswordAuthentication yes\n" > /etc/ssh/sshd_config.d/99-vpsmgr.conf +systemctl enable sshd +# slim the published image: drop zypper caches and logs +zypper clean --all 2>/dev/null || true +rm -rf /var/cache/zypp /var/log/* /tmp/* /var/tmp/* 2>/dev/null || true +# Drop the baked-in machine-id so every container boots its own: a shared +# machine-id means a shared DHCPv6 DUID, which breaks dnsmasq lease renewals +# and drops the container global IPv6 at the 1h lease mark. +rm -f /etc/machine-id /var/lib/dbus/machine-id 2>/dev/null || true +# IPv6 for openSUSE containers is configured at runtime by the panel: Leap 16 +# ships systemd-networkd by default, so the panel networkd branch applies +# (static /128 + fe80::1 gateway, no SLAAC/DHCPv6) — nothing to bake here. +'; then + incus stop "$NAME" --timeout=30 || true + if incus publish "$NAME" --alias "$IMAGE"; then + incus delete --force "$NAME" || true + # keep only the modified image — the base was a build intermediate + if incus image delete "$BASE_ALIAS" >/dev/null 2>&1; then + log "removed base image $BASE_ALIAS (only $IMAGE kept)" + else + log " warn: could not remove base image $BASE_ALIAS" + fi + log "image published: $IMAGE" + else + log " warn: publish FAILED — $IMAGE NOT built (base image kept; re-run to retry)" + incus delete --force "$NAME" >/dev/null 2>&1 || true + exit 1 + fi + else + log " warn: install in builder failed; nothing built" + incus delete --force "$NAME" >/dev/null 2>&1 || true + incus image delete "$BASE_ALIAS" >/dev/null 2>&1 || true + fi +else + log " warn: could not launch builder; nothing built" +fi + +echo "[70] done" diff --git a/src/internal/mgr/image.go b/src/internal/mgr/image.go index 108b06d..caff85a 100644 --- a/src/internal/mgr/image.go +++ b/src/internal/mgr/image.go @@ -13,9 +13,10 @@ type ManagedImage struct { // imageLabels maps known managed image aliases to friendly display names. var imageLabels = map[string]string{ - "vpsmgr/debian-sshd": "Debian 13", - "vpsmgr/alma-sshd": "Alma 9", - "vpsmgr/rocky-sshd": "Rocky 9", + "vpsmgr/debian-sshd": "Debian 13", + "vpsmgr/alma-sshd": "Alma 9", + "vpsmgr/rocky-sshd": "Rocky 9", + "vpsmgr/opensuse-sshd": "openSUSE Leap 16", } // collectManagedImages picks the reinstallable images out of a raw alias list: diff --git a/src/internal/mgr/image_test.go b/src/internal/mgr/image_test.go index 8783fb3..ce0529f 100644 --- a/src/internal/mgr/image_test.go +++ b/src/internal/mgr/image_test.go @@ -34,6 +34,15 @@ func TestCollectManagedImages(t *testing.T) { {"vpsmgr/rocky-sshd", "Rocky 9"}, }, }, + { + name: "default plus opensuse, sorted after alma", + defaultAlias: "vpsmgr/debian-sshd", + aliases: []string{"vpsmgr/opensuse-sshd", "vpsmgr/debian-sshd"}, + want: []ManagedImage{ + {"vpsmgr/debian-sshd", "Debian 13"}, + {"vpsmgr/opensuse-sshd", "openSUSE Leap 16"}, + }, + }, { name: "default missing locally still offered, unknown alias humanized", defaultAlias: "vpsmgr/debian-sshd",