diff --git a/docs/IMAGE.md b/docs/IMAGE.md index 3a81eee..f2599d1 100644 --- a/docs/IMAGE.md +++ b/docs/IMAGE.md @@ -98,9 +98,11 @@ make iso ISO_ARGS="--web-port 8089 --no-auto-update --timezone Europe/Moscow" Для массовой раскатки: ```bash -make iso ISO_ARGS="--unattended --username admin --password-hash '$(openssl passwd -6)' --hostname node01" +make iso ISO_ARGS="--unattended --username sysadmin --password-hash '$(openssl passwd -6)' --hostname node01" ``` +Системные имена вроде `admin`, `root` или `kvm` установщик Ubuntu не принимает, и установка остановится на первом шаге. Поэтому `build-iso.sh` сразу отказывается собирать образ с таким `--username`. Полный список лежит в `image/reserved-usernames`, это копия списка из установщика Ubuntu. + > ⚠️ Такой образ сам уходит в установку через 10 секунд в меню и **без подтверждения стирает самый большой диск** машины. Файл получает суффикс `-unattended`, пункты меню — пометку «автоматически, диск будет стёрт». Не оставляйте такую флешку в серверах. --- diff --git a/image/build-iso.sh b/image/build-iso.sh index c8a79db..0f7cf78 100755 --- a/image/build-iso.sh +++ b/image/build-iso.sh @@ -106,6 +106,7 @@ esac [[ "$WEB_PORT" =~ ^[0-9]+$ ]] && (( WEB_PORT >= 1 && WEB_PORT <= 65535 )) || die "invalid --web-port: $WEB_PORT" if [[ "$UNATTENDED" == "1" ]]; then [[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || die "--unattended needs a valid --username" + ! grep -qxF "$USERNAME" "${IMAGE_DIR}/reserved-usernames" || die "--username ${USERNAME} is reserved by the Ubuntu installer (like admin, root, kvm); pick another name" [[ "$PASSWORD_HASH" == \$* ]] || die "--unattended needs --password-hash (generate: openssl passwd -6)" [[ "$HOSTNAME_VALUE" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{0,62}$ ]] || die "invalid --hostname" fi diff --git a/image/reserved-usernames b/image/reserved-usernames new file mode 100644 index 0000000..9a29be1 --- /dev/null +++ b/image/reserved-usernames @@ -0,0 +1,127 @@ +# Имена, которые установщик Ubuntu (subiquity) не даёт выбрать для первого пользователя. +# Копия https://github.com/canonical/subiquity/blob/main/reserved-usernames (27.09.2026). +# build-iso.sh проверяет по ней --username, иначе автоматическая установка падает на первом шаге. + +# Static users from base-passwd/passwd.master (3.5.41). +root +daemon +bin +sys +sync +games +man +lp +mail +news +uucp +proxy +www-data +backup +list +irc +gnats +nobody + +# Other static groups from base-passwd/group.master (3.5.41). +adm +tty +disk +kmem +dialout +fax +voice +cdrom +floppy +tape +sudo +audio +dip +operator +src +shadow +utmp +video +sasl +plugdev +staff +users +nogroup + +# Reserved usernames listed in base-passwd/README (3.5.41). +netplan +ftn +mysql +tac-plus +alias +qmail +qmaild +qmails +qmailr +qmailq +qmaill +qmailp +asterisk +vpopmail +vchkpw +slurm +hacluster +haclient +grsec-tpe +grsec-sock-all +grsec-sock-clt +grsec-sock-srv +grsec-proc +ceph +opensrf +libvirt-qemu + +# Ubuntu creates the admin group and adds the first user to it in order to +# grant them sudo privileges. +admin + +# Other miscellaneous system users/groups created by common packages. While +# it's useful to add things here that people might run into, it's not +# absolutely critical; the worst that will happen is that the installation +# will fail at some later point. +Debian-exim +bind +crontab +cupsys +dcc +dhcp +dictd +dnsmasq +dovecot +fetchmail +firebird +ftp +fuse +gdm +haldaemon +hplilp +identd +input +jwhois +klog +kvm +lpadmin +maas +messagebus +mythtv +netdev +powerdev +radvd +render +saned +sbuild +scanner +sgx +slocate +ssh +sshd +ssl-cert +sslwrap +statd +syslog +telnetd +tftpd diff --git a/tests/test_core.py b/tests/test_core.py index e86905b..02db236 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -194,3 +194,13 @@ def test_stable_channel_follows_release_tags(tmp_path, monkeypatch, data_dirs): (tmp_path / "web.env").write_text("VIRTUALITY_UPDATE_CHANNEL=main\n") data = update_core.check_updates(fetch=True) assert data["channel"] == "main" and data["target"] == "main" and data["has_update"] + + +@pytest.mark.parametrize("username", ["admin", "root", "kvm", "libvirt-qemu"]) +def test_build_iso_refuses_names_the_ubuntu_installer_rejects(username): + """Установщик Ubuntu не принимает системные имена: автоматический образ с таким именем падал на первом шаге.""" + import subprocess + + result = subprocess.run(["bash", str(ROOT / "image" / "build-iso.sh"), "--unattended", "--username", username, "--password-hash", "$6$salt$hash"], capture_output=True, text=True) + assert result.returncode != 0 and "reserved by the Ubuntu installer" in result.stderr +