public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script
@ 2024-11-18 12:38 Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 1/5] fix #5579: first-boot: add initial service packaging Christoph Heiss
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Christoph Heiss @ 2024-11-18 12:38 UTC (permalink / raw)
  To: pve-devel

This implements #5579 [0] as proposed by Thomas [1].

Adds a new (optional) section to the auto-installer answer file called
`first-boot`, which can be used to the configure a script/executable
file to run on the first boot after the installation.

To used the baked-in (via the `proxmox-auto-install-assistant prepare-iso
--on-first-boot`) file from the ISO:

  [first-boot]
  source = "from-iso"

Or fetching it from a URL:

  [first-boot]
  source = "from-url"
  url = "http://example.com/first-boot"
  cert_fingerprint = ".." # if needed

Optionally, a ordering can be specified, when to run the script:

  [first-boot]
  source = "from-url"
  ordering = "fully-up" # default value
  # one of "before-network", "network-online" or "fully-up"

The structure the section is mostly taken from the `network` section to
provide consistency.

[0] https://bugzilla.proxmox.com/show_bug.cgi?id=5579
[1] https://bugzilla.proxmox.com/show_bug.cgi?id=5579#c5

Inner workings
==============

This creates a new package `proxmox-first-boot`, which only has a
systemd .service file. The service is conditioned to start based on the 
existence of a flag file in /var/lib/proxmox-first-boot, which will
be created by the installer if such a script is supplied by the user.

The script will also be passed the ordering of the service as the first
argument, as set in the answer file.

The above package will only be installed to the target system when a
first-boot script is set, to unnecessarily polluting the system.

Testing
=======

Tested this with the latest (8.2-2) PVE ISO, both baking it into the ISO
and fetching it from an URL. Did not test with PBS explicitly (yet), but
I see no reason why it shouldn't work, as it is completely
product-agnostic.

History
=======

v1: https://lore.proxmox.com/pve-devel/20241113135908.1622968-1-c.heiss@proxmox.com/

Changes v1 -> v2:
  * package is only installed when enabled
  * dropped already applied patches
  * added option to configure ordering

Diffstat
========

Christoph Heiss (5):
  fix #5579: first-boot: add initial service packaging
  fix #5579: setup: introduce 'first_boot' low-level installer options
  fix #5579: auto-install-assistant: enable baking in first-boot script
  fix #5579: auto-installer: add optional first-boot hook script
  fix #5579: install: setup proxmox-first-boot service if enabled

 Makefile                                      | 13 +++-
 Proxmox/Install.pm                            | 55 ++++++++++++++++-
 Proxmox/Install/Config.pm                     | 20 +++++++
 debian/control                                |  7 +++
 debian/proxmox-first-boot.install             |  3 +
 debian/rules                                  |  5 ++
 proxmox-auto-install-assistant/Cargo.toml     |  1 +
 proxmox-auto-install-assistant/src/main.rs    | 30 +++++++++-
 proxmox-auto-installer/Cargo.toml             |  2 +-
 proxmox-auto-installer/src/answer.rs          | 59 +++++++++++++++++++
 .../src/bin/proxmox-auto-installer.rs         | 49 +++++++++++++--
 proxmox-auto-installer/src/utils.rs           | 28 ++++++++-
 .../tests/resources/parse_answer/btrfs.json   |  3 +-
 .../resources/parse_answer/disk_match.json    |  3 +-
 .../parse_answer/disk_match_all.json          |  3 +-
 .../parse_answer/disk_match_any.json          |  3 +-
 .../resources/parse_answer/first-boot.json    | 19 ++++++
 .../resources/parse_answer/first-boot.toml    | 18 ++++++
 .../parse_answer/hashed_root_password.json    |  3 +-
 .../tests/resources/parse_answer/minimal.json |  3 +-
 .../resources/parse_answer/nic_matching.json  |  3 +-
 .../resources/parse_answer/specific_nic.json  |  3 +-
 .../tests/resources/parse_answer/zfs.json     |  3 +-
 proxmox-first-boot/Makefile                   | 10 ++++
 .../etc/proxmox-first-boot-multi-user.service | 15 +++++
 .../proxmox-first-boot-network-online.service | 17 ++++++
 .../proxmox-first-boot-network-pre.service    | 17 ++++++
 proxmox-installer-common/src/lib.rs           |  6 ++
 proxmox-installer-common/src/setup.rs         | 17 ++++++
 proxmox-tui-installer/src/setup.rs            |  4 +-
 30 files changed, 399 insertions(+), 23 deletions(-)
 create mode 100644 debian/proxmox-first-boot.install
 create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/first-boot.json
 create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/first-boot.toml
 create mode 100644 proxmox-first-boot/Makefile
 create mode 100644 proxmox-first-boot/etc/proxmox-first-boot-multi-user.service
 create mode 100644 proxmox-first-boot/etc/proxmox-first-boot-network-online.service
 create mode 100644 proxmox-first-boot/etc/proxmox-first-boot-network-pre.service

-- 
2.47.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH installer v2 1/5] fix #5579: first-boot: add initial service packaging
  2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
@ 2024-11-18 12:38 ` Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 2/5] fix #5579: setup: introduce 'first_boot' low-level installer options Christoph Heiss
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Christoph Heiss @ 2024-11-18 12:38 UTC (permalink / raw)
  To: pve-devel

While there is the `systemd-first-boot.service`, it uses the
non-existence of `/etc/machine-id` as condition to run. As we already
set up that file in the installer ourselves, we cannot use that.

Instead our service depends on a custom flag file in
/var/lib/proxmox-first-boot and will only run if that is present.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * changed to three separate services, of which the requested one gets
    enabled manually, as suggested by Thomas

 Makefile                                        | 13 ++++++++++---
 debian/control                                  |  7 +++++++
 debian/proxmox-first-boot.install               |  3 +++
 debian/rules                                    |  5 +++++
 proxmox-first-boot/Makefile                     | 10 ++++++++++
 .../etc/proxmox-first-boot-multi-user.service   | 15 +++++++++++++++
 .../proxmox-first-boot-network-online.service   | 17 +++++++++++++++++
 .../etc/proxmox-first-boot-network-pre.service  | 17 +++++++++++++++++
 8 files changed, 84 insertions(+), 3 deletions(-)
 create mode 100644 debian/proxmox-first-boot.install
 create mode 100644 proxmox-first-boot/Makefile
 create mode 100644 proxmox-first-boot/etc/proxmox-first-boot-multi-user.service
 create mode 100644 proxmox-first-boot/etc/proxmox-first-boot-network-online.service
 create mode 100644 proxmox-first-boot/etc/proxmox-first-boot-network-pre.service

diff --git a/Makefile b/Makefile
index d85347f..a17f6c5 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,10 @@ BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)
 
 DEB=$(PACKAGE)_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb
 ASSISTANT_DEB=proxmox-auto-install-assistant_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb
+FIRST_BOOT_DEB=proxmox-first-boot_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb
+
+ALL_DEBS = $(DEB) $(ASSISTANT_DEB) $(FIRST_BOOT_DEB)
+
 DSC=$(PACKAGE)_$(DEB_VERSION).dsc
 
 CARGO ?= cargo
@@ -61,6 +65,7 @@ $(BUILDDIR):
 	  proxmox-tui-installer/ \
 	  proxmox-installer-common/ \
 	  proxmox-post-hook \
+	  proxmox-first-boot \
 	  test/ \
 	  $(SHELL_SCRIPTS) \
 	  $@.tmp
@@ -73,9 +78,10 @@ country.dat: country.pl
 
 deb: $(DEB)
 $(ASSISTANT_DEB): $(DEB)
+$(FIRST_BOOT_DEB): $(DEB)
 $(DEB): $(BUILDDIR)
 	cd $(BUILDDIR); dpkg-buildpackage -b -us -uc
-	lintian $(DEB) $(ASSISTANT_DEB)
+	lintian $(ALL_DEBS)
 
 test-$(DEB): $(INSTALLER_SOURCES)
 	rsync --exclude='test*.img' --exclude='*.deb' --exclude='build' -a * build
@@ -114,6 +120,7 @@ HTMLDIR=$(VARLIBDIR)/html/common
 install: $(INSTALLER_SOURCES) $(COMPILED_BINS)
 	$(MAKE) -C banner install
 	$(MAKE) -C Proxmox install
+	$(MAKE) -C proxmox-first-boot install
 	install -D -m 644 interfaces $(DESTDIR)/etc/network/interfaces
 	install -D -m 755 fake-start-stop-daemon $(VARLIBDIR)/fake-start-stop-daemon
 	install -D -m 755 policy-disable-rc.d $(VARLIBDIR)/policy-disable-rc.d
@@ -143,8 +150,8 @@ cargo-build:
 
 .PHONY: upload
 upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)
-upload: $(DEB) $(ASSISTANT_DEB)
-	tar cf - $(DEB) $(ASSISTANT_DEB) | ssh -X repoman@repo.proxmox.com -- upload --product pve,pmg,pbs --dist $(UPLOAD_DIST)
+upload: $(ALL_DEBS)
+	tar cf - $(ALL_DEBS) | ssh -X repoman@repo.proxmox.com -- upload --product pve,pmg,pbs --dist $(UPLOAD_DIST)
 
 %.img:
 	truncate -s 2G $@
diff --git a/debian/control b/debian/control
index ff00cc2..fd0f4df 100644
--- a/debian/control
+++ b/debian/control
@@ -62,3 +62,10 @@ Description: Assistant to help with automated installations
  Provides a helper that can assist with creating an answer file for a automated
  installation of a Proxmox project, and preparing a official ISO image to use
  this answer file.
+
+Package: proxmox-first-boot
+Architecture: any
+Depends: ${misc:Depends},
+Description: Service which runs on the first system boot for additional setup
+ Provides a service which will run on the first boot if the a script was
+ configured through the auto-installer.
diff --git a/debian/proxmox-first-boot.install b/debian/proxmox-first-boot.install
new file mode 100644
index 0000000..715f11a
--- /dev/null
+++ b/debian/proxmox-first-boot.install
@@ -0,0 +1,3 @@
+lib/systemd/system/proxmox-first-boot-network-pre.service
+lib/systemd/system/proxmox-first-boot-network-online.service
+lib/systemd/system/proxmox-first-boot-multi-user.service
diff --git a/debian/rules b/debian/rules
index 8a3f879..84d5943 100755
--- a/debian/rules
+++ b/debian/rules
@@ -19,3 +19,8 @@ override_dh_strip:
 	    -executable -type f); do \
 	  debian/scripts/elf-strip-unused-dependencies.sh "$$exe" || true; \
 	done
+
+override_dh_installsystemd:
+	# disables all services by default, as we enable them ourselves in
+	# the installer
+	dh_installsystemd --no-stop-on-upgrade --no-start --no-enable
diff --git a/proxmox-first-boot/Makefile b/proxmox-first-boot/Makefile
new file mode 100644
index 0000000..d889c67
--- /dev/null
+++ b/proxmox-first-boot/Makefile
@@ -0,0 +1,10 @@
+all:
+
+DESTDIR =
+LIBSYSTEMD_DIR = $(DESTDIR)/lib/systemd/system
+
+.PHONY: install
+install:
+	install -D -m 644 etc/proxmox-first-boot-network-pre.service $(LIBSYSTEMD_DIR)/proxmox-first-boot-network-pre.service
+	install -D -m 644 etc/proxmox-first-boot-network-online.service $(LIBSYSTEMD_DIR)/proxmox-first-boot-network-online.service
+	install -D -m 644 etc/proxmox-first-boot-multi-user.service $(LIBSYSTEMD_DIR)/proxmox-first-boot-multi-user.service
diff --git a/proxmox-first-boot/etc/proxmox-first-boot-multi-user.service b/proxmox-first-boot/etc/proxmox-first-boot-multi-user.service
new file mode 100644
index 0000000..d3c798d
--- /dev/null
+++ b/proxmox-first-boot/etc/proxmox-first-boot-multi-user.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Proxmox First Boot Setup (Fully Booted)
+After=systemd-remount-fs.service
+ConditionPathExists=/var/lib/proxmox-first-boot/pending-first-boot-setup
+ConditionPathIsReadWrite=/var/lib
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/var/lib/proxmox-first-boot/proxmox-first-boot fully-up
+ExecStartPost=/usr/bin/rm -v /var/lib/proxmox-first-boot/pending-first-boot-setup
+
+[Install]
+Alias=proxmox-first-boot.service
+WantedBy=multi-user.target
diff --git a/proxmox-first-boot/etc/proxmox-first-boot-network-online.service b/proxmox-first-boot/etc/proxmox-first-boot-network-online.service
new file mode 100644
index 0000000..8417747
--- /dev/null
+++ b/proxmox-first-boot/etc/proxmox-first-boot-network-online.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=Proxmox First Boot Setup (Network Online)
+After=systemd-remount-fs.service
+After=network-online.target
+Wants=network-online.target
+ConditionPathExists=/var/lib/proxmox-first-boot/pending-first-boot-setup
+ConditionPathIsReadWrite=/var/lib
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/var/lib/proxmox-first-boot/proxmox-first-boot network-online
+ExecStartPost=/usr/bin/rm -v /var/lib/proxmox-first-boot/pending-first-boot-setup
+
+[Install]
+Alias=proxmox-first-boot.service
+WantedBy=multi-user.target
diff --git a/proxmox-first-boot/etc/proxmox-first-boot-network-pre.service b/proxmox-first-boot/etc/proxmox-first-boot-network-pre.service
new file mode 100644
index 0000000..1b4e396
--- /dev/null
+++ b/proxmox-first-boot/etc/proxmox-first-boot-network-pre.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=Proxmox First Boot Setup (Pre-Network)
+After=systemd-remount-fs.service
+Before=network-pre.target
+Wants=network-pre.target
+ConditionPathExists=/var/lib/proxmox-first-boot/pending-first-boot-setup
+ConditionPathIsReadWrite=/var/lib
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/var/lib/proxmox-first-boot/proxmox-first-boot before-network
+ExecStartPost=/usr/bin/rm -v /var/lib/proxmox-first-boot/pending-first-boot-setup
+
+[Install]
+Alias=proxmox-first-boot.service
+WantedBy=network.target
-- 
2.47.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH installer v2 2/5] fix #5579: setup: introduce 'first_boot' low-level installer options
  2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 1/5] fix #5579: first-boot: add initial service packaging Christoph Heiss
@ 2024-11-18 12:38 ` Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 3/5] fix #5579: auto-install-assistant: enable baking in first-boot script Christoph Heiss
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Christoph Heiss @ 2024-11-18 12:38 UTC (permalink / raw)
  To: pve-devel

.. to enable the setup of the 'proxmox-first-boot' service, as well as
optionally setting the ordering.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * add tests
  * introduce `first_boot.enabled` low-level option to tell the
    installer explicitly about it

 Proxmox/Install/Config.pm                     | 20 +++++++++++++++++++
 proxmox-auto-installer/src/utils.rs           |  6 ++++--
 .../tests/resources/parse_answer/btrfs.json   |  3 ++-
 .../resources/parse_answer/disk_match.json    |  3 ++-
 .../parse_answer/disk_match_all.json          |  3 ++-
 .../parse_answer/disk_match_any.json          |  3 ++-
 .../resources/parse_answer/first-boot.json    | 19 ++++++++++++++++++
 .../resources/parse_answer/first-boot.toml    | 18 +++++++++++++++++
 .../parse_answer/hashed_root_password.json    |  3 ++-
 .../tests/resources/parse_answer/minimal.json |  3 ++-
 .../resources/parse_answer/nic_matching.json  |  3 ++-
 .../resources/parse_answer/specific_nic.json  |  3 ++-
 .../tests/resources/parse_answer/zfs.json     |  3 ++-
 proxmox-installer-common/src/setup.rs         | 17 ++++++++++++++++
 proxmox-tui-installer/src/setup.rs            |  4 +++-
 15 files changed, 99 insertions(+), 12 deletions(-)
 create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/first-boot.json
 create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/first-boot.toml

diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm
index 6d47b75..b3a257e 100644
--- a/Proxmox/Install/Config.pm
+++ b/Proxmox/Install/Config.pm
@@ -111,6 +111,14 @@ my sub init_cfg {
 	gateway => undef,
 	dns => undef,
 	target_cmdline => undef,
+
+	# proxmox-first-boot setup
+	first_boot => {
+	    enabled => 0,
+	    # Must be kept in sync with proxmox_auto_installer::answer::FirstBootHookServiceOrdering
+	    # and the service files in the proxmox-first-boot package
+	    ordering_target => 'multi-user', # one of `network-pre`, `network-online` or `multi-user`
+	},
     };
 
     $initial = parse_kernel_cmdline($initial);
@@ -279,4 +287,16 @@ sub get_target_cmdline { return get('target_cmdline'); }
 sub set_existing_storage_auto_rename { set_key('existing_storage_auto_rename', $_[0]); }
 sub get_existing_storage_auto_rename { return get('existing_storage_auto_rename'); }
 
+sub set_first_boot_opt {
+    my ($k, $v) = @_;
+    my $opts = get('first_boot');
+    croak "unknown first boot override key '$k'" if !exists($opts->{$k});
+    $opts->{$k} = $v;
+}
+sub get_first_boot_opt {
+    my ($k) = @_;
+    my $opts = get('first_boot');
+    return defined($k) ? $opts->{$k} : $opts;
+}
+
 1;
diff --git a/proxmox-auto-installer/src/utils.rs b/proxmox-auto-installer/src/utils.rs
index dd686c0..9c399a5 100644
--- a/proxmox-auto-installer/src/utils.rs
+++ b/proxmox-auto-installer/src/utils.rs
@@ -11,8 +11,8 @@ use crate::{
 use proxmox_installer_common::{
     options::{email_validate, FsType, NetworkOptions, ZfsChecksumOption, ZfsCompressOption},
     setup::{
-        InstallBtrfsOption, InstallConfig, InstallRootPassword, InstallZfsOption, LocaleInfo,
-        RuntimeInfo, SetupInfo,
+        InstallBtrfsOption, InstallConfig, InstallFirstBootSetup, InstallRootPassword,
+        InstallZfsOption, LocaleInfo, RuntimeInfo, SetupInfo,
     },
 };
 use serde::{Deserialize, Serialize};
@@ -374,6 +374,8 @@ pub fn parse_answer(
         cidr: network_settings.address,
         gateway: network_settings.gateway,
         dns: network_settings.dns_server,
+
+        first_boot: InstallFirstBootSetup::default(),
     };
 
     set_disks(answer, udev_info, runtime_info, &mut config)?;
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/btrfs.json b/proxmox-auto-installer/tests/resources/parse_answer/btrfs.json
index 0330a38..de4c6e5 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/btrfs.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/btrfs.json
@@ -20,5 +20,6 @@
   "timezone": "Europe/Vienna",
   "btrfs_opts": {
     "compress": "zlib"
-  }
+  },
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/disk_match.json b/proxmox-auto-installer/tests/resources/parse_answer/disk_match.json
index 6c8d6d9..48a82e6 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/disk_match.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/disk_match.json
@@ -26,5 +26,6 @@
       "checksum": "on",
       "compress": "on",
       "copies": 1
-  }
+  },
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/disk_match_all.json b/proxmox-auto-installer/tests/resources/parse_answer/disk_match_all.json
index 2d2e94e..f012eb1 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/disk_match_all.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/disk_match_all.json
@@ -23,5 +23,6 @@
       "checksum": "on",
       "compress": "on",
       "copies": 1
-  }
+  },
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/disk_match_any.json b/proxmox-auto-installer/tests/resources/parse_answer/disk_match_any.json
index 1f3b2eb..ad3e304 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/disk_match_any.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/disk_match_any.json
@@ -30,5 +30,6 @@
       "checksum": "on",
       "compress": "on",
       "copies": 1
-  }
+  },
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/first-boot.json b/proxmox-auto-installer/tests/resources/parse_answer/first-boot.json
new file mode 100644
index 0000000..ff3f859
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer/first-boot.json
@@ -0,0 +1,19 @@
+{
+  "autoreboot": 1,
+  "cidr": "192.168.1.114/24",
+  "country": "at",
+  "dns": "192.168.1.254",
+  "domain": "testinstall",
+  "filesys": "ext4",
+  "gateway": "192.168.1.1",
+  "hdsize": 223.57088470458984,
+  "existing_storage_auto_rename": 1,
+  "hostname": "pveauto",
+  "keymap": "de",
+  "mailto": "mail@no.invalid",
+  "mngmt_nic": "eno1",
+  "root_password": { "plain": "123456" },
+  "target_hd": "/dev/sda",
+  "timezone": "Europe/Vienna",
+  "first_boot": { "enabled": 1, "ordering_target": "network-pre" }
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/first-boot.toml b/proxmox-auto-installer/tests/resources/parse_answer/first-boot.toml
new file mode 100644
index 0000000..75c6a5d
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer/first-boot.toml
@@ -0,0 +1,18 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root_password = "123456"
+
+[first-boot]
+source = "from-iso"
+ordering = "before-network"
+
+[network]
+source = "from-dhcp"
+
+[disk-setup]
+filesystem = "ext4"
+disk_list = ["sda"]
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/hashed_root_password.json b/proxmox-auto-installer/tests/resources/parse_answer/hashed_root_password.json
index 4cae547..4e049bd 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/hashed_root_password.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/hashed_root_password.json
@@ -16,5 +16,6 @@
     "hashed": "$y$j9T$VgMv8lsz/TEvzesCZU3xD.$SK.h4QW51Jr/EmjuaTz5Bt4kYiX2Iezz6omzoqVEwj9"
   },
   "target_hd": "/dev/sda",
-  "timezone": "Europe/Vienna"
+  "timezone": "Europe/Vienna",
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/minimal.json b/proxmox-auto-installer/tests/resources/parse_answer/minimal.json
index 9fe9150..62b45c9 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/minimal.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/minimal.json
@@ -14,5 +14,6 @@
   "mngmt_nic": "eno1",
   "root_password": { "plain": "123456" },
   "target_hd": "/dev/sda",
-  "timezone": "Europe/Vienna"
+  "timezone": "Europe/Vienna",
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/nic_matching.json b/proxmox-auto-installer/tests/resources/parse_answer/nic_matching.json
index 610060e..e8b5424 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/nic_matching.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/nic_matching.json
@@ -14,5 +14,6 @@
   "mngmt_nic": "enp65s0f0",
   "root_password": { "plain": "123456" },
   "target_hd": "/dev/sda",
-  "timezone": "Europe/Vienna"
+  "timezone": "Europe/Vienna",
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/specific_nic.json b/proxmox-auto-installer/tests/resources/parse_answer/specific_nic.json
index 5f456bb..a5a4e0b 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/specific_nic.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/specific_nic.json
@@ -14,5 +14,6 @@
   "mngmt_nic": "enp129s0f1np1",
   "root_password": { "plain": "123456" },
   "target_hd": "/dev/sda",
-  "timezone": "Europe/Vienna"
+  "timezone": "Europe/Vienna",
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/zfs.json b/proxmox-auto-installer/tests/resources/parse_answer/zfs.json
index 025dd8f..090b58d 100644
--- a/proxmox-auto-installer/tests/resources/parse_answer/zfs.json
+++ b/proxmox-auto-installer/tests/resources/parse_answer/zfs.json
@@ -24,5 +24,6 @@
       "checksum": "on",
       "compress": "lz4",
       "copies": 2
-  }
+  },
+  "first_boot": { "enabled": 0 }
 }
diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs
index 79b17ed..fefedf6 100644
--- a/proxmox-installer-common/src/setup.rs
+++ b/proxmox-installer-common/src/setup.rs
@@ -339,6 +339,13 @@ where
     serializer.collect_str(value)
 }
 
+fn serialize_bool_as_u32<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    serializer.serialize_u32(if *value { 1 } else { 0 })
+}
+
 #[derive(Clone, Deserialize)]
 pub struct RuntimeInfo {
     /// Whether is system was booted in (legacy) BIOS or UEFI mode.
@@ -464,6 +471,14 @@ pub struct InstallRootPassword {
     pub hashed: Option<String>,
 }
 
+#[derive(Clone, Default, Deserialize, Serialize)]
+pub struct InstallFirstBootSetup {
+    #[serde(serialize_with = "serialize_bool_as_u32")]
+    pub enabled: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ordering_target: Option<String>,
+}
+
 pub fn spawn_low_level_installer(test_mode: bool) -> io::Result<process::Child> {
     let (path, args, envs): (&str, &[&str], Vec<(&str, &str)>) = if test_mode {
         (
@@ -530,4 +545,6 @@ pub struct InstallConfig {
     pub cidr: CidrAddress,
     pub gateway: IpAddr,
     pub dns: IpAddr,
+
+    pub first_boot: InstallFirstBootSetup,
 }
diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs
index 8146511..b2a3511 100644
--- a/proxmox-tui-installer/src/setup.rs
+++ b/proxmox-tui-installer/src/setup.rs
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
 use crate::options::InstallerOptions;
 use proxmox_installer_common::{
     options::AdvancedBootdiskOptions,
-    setup::{InstallConfig, InstallRootPassword},
+    setup::{InstallConfig, InstallFirstBootSetup, InstallRootPassword},
 };
 
 impl From<InstallerOptions> for InstallConfig {
@@ -44,6 +44,8 @@ impl From<InstallerOptions> for InstallConfig {
             cidr: options.network.address,
             gateway: options.network.gateway,
             dns: options.network.dns_server,
+
+            first_boot: InstallFirstBootSetup::default(),
         };
 
         match &options.bootdisk.advanced {
-- 
2.47.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH installer v2 3/5] fix #5579: auto-install-assistant: enable baking in first-boot script
  2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 1/5] fix #5579: first-boot: add initial service packaging Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 2/5] fix #5579: setup: introduce 'first_boot' low-level installer options Christoph Heiss
@ 2024-11-18 12:38 ` Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 4/5] fix #5579: auto-installer: add optional first-boot hook script Christoph Heiss
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Christoph Heiss @ 2024-11-18 12:38 UTC (permalink / raw)
  To: pve-devel

Adds a new parameter `--on-first-boot` to the `prepare-iso` command, to
specify a file to bake into the ISO.

To later use it with the auto-installer, the following must be set in
the answer file:

  [first-boot]
  source = "from-iso"

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * add filesize check; only allow up to 1 MiB scripts

 proxmox-auto-install-assistant/Cargo.toml  |  1 +
 proxmox-auto-install-assistant/src/main.rs | 30 +++++++++++++++++++++-
 proxmox-installer-common/src/lib.rs        |  6 +++++
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/proxmox-auto-install-assistant/Cargo.toml b/proxmox-auto-install-assistant/Cargo.toml
index c4486f8..07e6ffb 100644
--- a/proxmox-auto-install-assistant/Cargo.toml
+++ b/proxmox-auto-install-assistant/Cargo.toml
@@ -13,6 +13,7 @@ homepage = "https://www.proxmox.com"
 [dependencies]
 anyhow.workspace = true
 log.workspace = true
+proxmox-installer-common.workspace = true
 proxmox-auto-installer.workspace = true
 serde = { workspace = true, features = ["derive"] }
 serde_json.workspace = true
diff --git a/proxmox-auto-install-assistant/src/main.rs b/proxmox-auto-install-assistant/src/main.rs
index bdcf067..d7aa134 100644
--- a/proxmox-auto-install-assistant/src/main.rs
+++ b/proxmox-auto-install-assistant/src/main.rs
@@ -19,6 +19,7 @@ use proxmox_auto_installer::{
         FetchAnswerFrom, HttpOptions,
     },
 };
+use proxmox_installer_common::{FIRST_BOOT_EXEC_MAX_SIZE, FIRST_BOOT_EXEC_NAME};
 
 static PROXMOX_ISO_FLAG: &str = "/auto-installer-capable";
 
@@ -149,6 +150,13 @@ struct CommandPrepareISO {
     // so shorten "Automated Installer Source" to "AIS" to be safe.
     #[arg(long, default_value_t = { "proxmox-ais".to_owned() } )]
     partition_label: String,
+
+    /// Executable file to include, which should be run on the first system boot after the
+    /// installation. Can be used for further bootstrapping the new system.
+    ///
+    /// Must be appropriately enabled in the answer file.
+    #[arg(long)]
+    on_first_boot: Option<PathBuf>,
 }
 
 /// Show the system information that can be used to identify a host.
@@ -201,7 +209,7 @@ fn main() {
         Commands::SystemInfo(args) => show_system_info(args),
     };
     if let Err(err) = res {
-        eprintln!("{err}");
+        eprintln!("Error: {err:?}");
         std::process::exit(1);
     }
 }
@@ -305,6 +313,17 @@ fn prepare_iso(args: &CommandPrepareISO) -> Result<()> {
         bail!("You must set '--fetch-from' to 'iso' to place the answer file directly in the ISO.");
     }
 
+    if let Some(first_boot) = &args.on_first_boot {
+        let metadata = fs::metadata(first_boot)?;
+
+        if metadata.len() > FIRST_BOOT_EXEC_MAX_SIZE.try_into()? {
+            bail!(
+                "Maximum file size for first-boot executable file is {} MiB",
+                FIRST_BOOT_EXEC_MAX_SIZE / 1024 / 1024
+            )
+        }
+    }
+
     if let Some(file) = &args.answer_file {
         println!("Checking provided answer file...");
         parse_answer(file)?;
@@ -352,6 +371,15 @@ fn prepare_iso(args: &CommandPrepareISO) -> Result<()> {
         inject_file_to_iso(&tmp_iso, answer_file, "/answer.toml", &uuid)?;
     }
 
+    if let Some(first_boot) = &args.on_first_boot {
+        inject_file_to_iso(
+            &tmp_iso,
+            first_boot,
+            &format!("/{FIRST_BOOT_EXEC_NAME}"),
+            &uuid,
+        )?;
+    }
+
     println!("Moving prepared ISO to target location...");
     fs::rename(&tmp_iso, &iso_target)?;
     println!("Final ISO is available at {iso_target:?}.");
diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs
index 10b5940..13acb89 100644
--- a/proxmox-installer-common/src/lib.rs
+++ b/proxmox-installer-common/src/lib.rs
@@ -11,3 +11,9 @@ pub const RUNTIME_DIR: &str = "/run/proxmox-installer";
 
 /// Default placeholder value for the administrator email address.
 pub const EMAIL_DEFAULT_PLACEHOLDER: &str = "mail@example.invalid";
+
+/// Name of the executable for the first-boot hook.
+pub const FIRST_BOOT_EXEC_NAME: &str = "proxmox-first-boot";
+
+/// Maximum file size for the first-boot hook executable.
+pub const FIRST_BOOT_EXEC_MAX_SIZE: usize = 1024 * 1024; // 1 MiB
-- 
2.47.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH installer v2 4/5] fix #5579: auto-installer: add optional first-boot hook script
  2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
                   ` (2 preceding siblings ...)
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 3/5] fix #5579: auto-install-assistant: enable baking in first-boot script Christoph Heiss
@ 2024-11-18 12:38 ` Christoph Heiss
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 5/5] fix #5579: install: setup proxmox-first-boot service if enabled Christoph Heiss
  2024-11-18 21:35 ` [pve-devel] applied: [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Christoph Heiss @ 2024-11-18 12:38 UTC (permalink / raw)
  To: pve-devel

Users can specifying an optional file - either fetched from an URL or
backed into the ISO - to execute on the first boot after the
installation, using the 'proxmox-first-boot' oneshot service.

Essentially adds an (optional) `[first-boot]` section to the answer
file. If specified, the `source` key must be at least set, which gives
the location of the hook script.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * adapt to new low-level format
  * add settable ordering of service

 proxmox-auto-installer/Cargo.toml             |  2 +-
 proxmox-auto-installer/src/answer.rs          | 59 +++++++++++++++++++
 .../src/bin/proxmox-auto-installer.rs         | 49 +++++++++++++--
 proxmox-auto-installer/src/utils.rs           | 22 ++++++-
 4 files changed, 126 insertions(+), 6 deletions(-)

diff --git a/proxmox-auto-installer/Cargo.toml b/proxmox-auto-installer/Cargo.toml
index 21ed538..7e3d90c 100644
--- a/proxmox-auto-installer/Cargo.toml
+++ b/proxmox-auto-installer/Cargo.toml
@@ -13,7 +13,7 @@ homepage = "https://www.proxmox.com"
 [dependencies]
 anyhow.workspace = true
 log.workspace = true
-proxmox-installer-common.workspace = true
+proxmox-installer-common = { workspace = true, features = ["http"] }
 serde = { workspace = true, features = ["derive"] }
 serde_json.workspace = true
 serde_plain.workspace = true
diff --git a/proxmox-auto-installer/src/answer.rs b/proxmox-auto-installer/src/answer.rs
index 73e5869..c206fcc 100644
--- a/proxmox-auto-installer/src/answer.rs
+++ b/proxmox-auto-installer/src/answer.rs
@@ -22,6 +22,8 @@ pub struct Answer {
     pub disks: Disks,
     #[serde(default)]
     pub post_installation_webhook: Option<PostNotificationHookInfo>,
+    #[serde(default)]
+    pub first_boot: Option<FirstBootHookInfo>,
 }
 
 impl Answer {
@@ -62,6 +64,63 @@ pub struct PostNotificationHookInfo {
     pub cert_fingerprint: Option<String>,
 }
 
+/// Possible sources for the optional first-boot hook script/executable file.
+#[derive(Clone, Deserialize, Debug, PartialEq)]
+#[serde(rename_all = "kebab-case", deny_unknown_fields)]
+pub enum FirstBootHookSourceMode {
+    /// Fetch the executable file from an URL, specified in the parent.
+    FromUrl,
+    /// The executable file has been baked into the ISO at a known location,
+    /// and should be retrieved from there.
+    FromIso,
+}
+
+/// Possible orderings for the `proxmox-first-boot` systemd service.
+///
+/// Determines the final value of `Unit.Before` and `Unit.Wants` in the service
+/// file.
+// Must be kept in sync with Proxmox::Install::Config and the service files in the
+// proxmox-first-boot package.
+#[derive(Clone, Default, Deserialize, Debug, PartialEq)]
+#[serde(rename_all = "kebab-case", deny_unknown_fields)]
+pub enum FirstBootHookServiceOrdering {
+    /// Needed for bringing up the network itself, runs before any networking is attempted.
+    BeforeNetwork,
+    /// Network needs to be already online, runs after networking was brought up.
+    NetworkOnline,
+    /// Runs after the system has successfully booted up completely.
+    #[default]
+    FullyUp,
+}
+
+impl FirstBootHookServiceOrdering {
+    /// Maps the enum to the appropriate systemd target name, without the '.target' suffix.
+    pub fn as_systemd_target_name(&self) -> &str {
+        match self {
+            FirstBootHookServiceOrdering::BeforeNetwork => "network-pre",
+            FirstBootHookServiceOrdering::NetworkOnline => "network-online",
+            FirstBootHookServiceOrdering::FullyUp => "multi-user",
+        }
+    }
+}
+
+/// Describes from where to fetch the first-boot hook script, either being baked into the ISO or
+/// from a URL.
+#[derive(Clone, Deserialize, Debug)]
+#[serde(rename_all = "kebab-case", deny_unknown_fields)]
+pub struct FirstBootHookInfo {
+    /// Mode how to retrieve the first-boot executable file, either from an URL or from the ISO if
+    /// it has been baked-in.
+    pub source: FirstBootHookSourceMode,
+    /// Determines the service order when the hook will run on first boot.
+    #[serde(default)]
+    pub ordering: FirstBootHookServiceOrdering,
+    /// Retrieve the post-install script from a URL, if source == "from-url".
+    pub url: Option<String>,
+    /// SHA256 cert fingerprint if certificate pinning should be used, if source == "from-url".
+    pub cert_fingerprint: Option<String>,
+}
+
 #[derive(Clone, Deserialize, Debug, Default, PartialEq)]
 #[serde(deny_unknown_fields)]
 enum NetworkConfigMode {
diff --git a/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs b/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
index ea45c29..4b9d73d 100644
--- a/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
+++ b/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
@@ -1,18 +1,22 @@
 use anyhow::{bail, format_err, Result};
 use log::{error, info, LevelFilter};
 use std::{
-    env,
+    env, fs,
     io::{BufRead, BufReader, Write},
     path::PathBuf,
     process::ExitCode,
 };
 
-use proxmox_installer_common::setup::{
-    installer_setup, read_json, spawn_low_level_installer, LocaleInfo, RuntimeInfo, SetupInfo,
+use proxmox_installer_common::{
+    http,
+    setup::{
+        installer_setup, read_json, spawn_low_level_installer, LocaleInfo, RuntimeInfo, SetupInfo,
+    },
+    FIRST_BOOT_EXEC_MAX_SIZE, FIRST_BOOT_EXEC_NAME, RUNTIME_DIR,
 };
 
 use proxmox_auto_installer::{
-    answer::Answer,
+    answer::{Answer, FirstBootHookInfo, FirstBootHookSourceMode},
     log::AutoInstLogger,
     udevinfo::UdevInfo,
     utils::{parse_answer, LowLevelMessage},
@@ -27,6 +31,38 @@ pub fn init_log() -> Result<()> {
         .map_err(|err| format_err!(err))
 }
 
+fn setup_first_boot_executable(first_boot: &FirstBootHookInfo) -> Result<()> {
+    let content = match first_boot.source {
+        FirstBootHookSourceMode::FromUrl => {
+            if let Some(url) = &first_boot.url {
+                info!("Fetching first-boot hook from {url} ..");
+                Some(http::get(url, first_boot.cert_fingerprint.as_deref())?)
+            } else {
+                bail!("first-boot hook source set to URL, but none specified!");
+            }
+        }
+        FirstBootHookSourceMode::FromIso => Some(fs::read_to_string(format!(
+            "/cdrom/{FIRST_BOOT_EXEC_NAME}"
+        ))?),
+    };
+
+    if let Some(content) = content {
+        if content.len() > FIRST_BOOT_EXEC_MAX_SIZE {
+            bail!(
+                "Maximum file size for first-boot executable file is {} MiB",
+                FIRST_BOOT_EXEC_MAX_SIZE / 1024 / 1024
+            )
+        }
+
+        Ok(fs::write(
+            format!("/{RUNTIME_DIR}/{FIRST_BOOT_EXEC_NAME}"),
+            content,
+        )?)
+    } else {
+        Ok(())
+    }
+}
+
 fn auto_installer_setup(in_test_mode: bool) -> Result<(Answer, UdevInfo)> {
     let base_path = if in_test_mode { "./testdir" } else { "/" };
     let mut path = PathBuf::from(base_path);
@@ -43,6 +79,11 @@ fn auto_installer_setup(in_test_mode: bool) -> Result<(Answer, UdevInfo)> {
     };
 
     let answer = Answer::try_from_reader(std::io::stdin().lock())?;
+
+    if let Some(first_boot) = &answer.first_boot {
+        setup_first_boot_executable(first_boot)?;
+    }
+
     Ok((answer, udev_info))
 }
 
diff --git a/proxmox-auto-installer/src/utils.rs b/proxmox-auto-installer/src/utils.rs
index 9c399a5..ea7176a 100644
--- a/proxmox-auto-installer/src/utils.rs
+++ b/proxmox-auto-installer/src/utils.rs
@@ -5,7 +5,7 @@ use log::info;
 use std::{collections::BTreeMap, process::Command};
 
 use crate::{
-    answer::{self, Answer},
+    answer::{self, Answer, FirstBootHookSourceMode},
     udevinfo::UdevInfo,
 };
 use proxmox_installer_common::{
@@ -325,6 +325,18 @@ fn verify_email_and_root_password_settings(answer: &Answer) -> Result<()> {
     }
 }
 
+fn verify_first_boot_settings(answer: &Answer) -> Result<()> {
+    info!("Verifying first boot settings");
+
+    if let Some(first_boot) = &answer.first_boot {
+        if first_boot.source == FirstBootHookSourceMode::FromUrl && first_boot.url.is_none() {
+            bail!("first-boot executable source set to URL, but none specified!");
+        }
+    }
+
+    Ok(())
+}
+
 pub fn parse_answer(
     answer: &Answer,
     udev_info: &UdevInfo,
@@ -341,6 +353,7 @@ pub fn parse_answer(
 
     verify_locale_settings(answer, locales)?;
     verify_email_and_root_password_settings(answer)?;
+    verify_first_boot_settings(answer)?;
 
     let mut config = InstallConfig {
         autoreboot: 1_usize,
@@ -419,6 +432,13 @@ pub fn parse_answer(
             })
         }
     }
+
+    if let Some(first_boot) = &answer.first_boot {
+        config.first_boot.enabled = true;
+        config.first_boot.ordering_target =
+            Some(first_boot.ordering.as_systemd_target_name().to_owned());
+    }
+
     Ok(config)
 }
 
-- 
2.47.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH installer v2 5/5] fix #5579: install: setup proxmox-first-boot service if enabled
  2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
                   ` (3 preceding siblings ...)
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 4/5] fix #5579: auto-installer: add optional first-boot hook script Christoph Heiss
@ 2024-11-18 12:38 ` Christoph Heiss
  2024-11-18 21:35 ` [pve-devel] applied: [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Christoph Heiss @ 2024-11-18 12:38 UTC (permalink / raw)
  To: pve-devel

The auto-installer will place an executable file named
`proxmox-first-boot` in the installer runtime-directory if the user set
up.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * factor out of extract_data()
  * implement enabling correct service depending on set order

 Proxmox/Install.pm | 55 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 54 insertions(+), 1 deletion(-)

diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm
index d409577..c64e1d4 100644
--- a/Proxmox/Install.pm
+++ b/Proxmox/Install.pm
@@ -15,7 +15,7 @@ use Proxmox::Install::StorageConfig;
 
 use Proxmox::Sys::Block qw(get_cached_disks wipe_disk partition_bootable_disk);
 use Proxmox::Sys::Command qw(run_command syscmd);
-use Proxmox::Sys::File qw(file_read_firstline file_write_all);
+use Proxmox::Sys::File qw(file_read_firstline file_read_all file_write_all);
 use Proxmox::Sys::ZFS;
 use Proxmox::UI;
 
@@ -678,6 +678,55 @@ my sub setup_root_password {
     }
 }
 
+my sub setup_proxmox_first_boot_service {
+    my ($targetdir) = @_;
+
+    return if !Proxmox::Install::Config::get_first_boot_opt('enabled');
+
+    my $iso_env = Proxmox::Install::ISOEnv::get();
+    my $proxmox_rundir = $iso_env->{locations}->{run};
+
+    my $exec_name = 'proxmox-first-boot';
+    my $pending_flagfile = "pending-first-boot-setup";
+    my $targetpath = "$targetdir/var/lib/proxmox-first-boot";
+
+    die "cannot find proxmox-first-boot hook executable?\n"
+	if ! -f "$proxmox_rundir/$exec_name";
+
+    # Create /var/lib/proxmox-first-boot state directory
+    syscmd("mkdir -p $targetpath/") == 0
+	|| die "failed to create $targetpath directory\n";
+
+    syscmd("cp $proxmox_rundir/$exec_name $targetpath/") == 0
+	|| die "unable to copy $exec_name executable\n";
+    syscmd("touch $targetpath/$pending_flagfile") == 0
+	|| die "unable to create $pending_flagfile flag file\n";
+
+    # Explicitly mark the entire directory only accessible, to prevent
+    # possible secret leaks from the bootstrap script.
+    syscmd("chmod -R 0700 $targetpath") == 0
+	|| warn "failed to set permissions for $targetpath\n";
+
+    # Enable the correct unit according the requested target ordering
+    my $ordering = Proxmox::Install::Config::get_first_boot_opt('ordering_target');
+
+    # .. so do it ourselves
+    my $linktarget = "/lib/systemd/system/proxmox-first-boot-$ordering.service";
+    syscmd("ln -sf $linktarget $targetdir/etc/systemd/system/proxmox-first-boot.service") == 0
+	|| die "failed to link proxmox-first-boot-$ordering.service\n";
+
+    my $servicefile = file_read_all("$targetdir/$linktarget");
+    if ($servicefile =~ m/^WantedBy=(.+)$/m) {
+	my $wantedby = $1;
+
+	syscmd("mkdir -p $targetdir/etc/systemd/system/$wantedby.wants") == 0
+	    || die "failed to create $wantedby.wants directory\n";
+
+	syscmd("ln -sf $linktarget $targetdir/etc/systemd/system/$wantedby.wants/proxmox-first-boot-$ordering.service") == 0
+	    || die "failed to link $wantedby.wants/proxmox-first-boot-$ordering.service\n";
+    }
+}
+
 sub extract_data {
     my $iso_env = Proxmox::Install::ISOEnv::get();
     my $run_env = Proxmox::Install::RunEnv::get();
@@ -1171,6 +1220,7 @@ _EOD
 	    next if ($deb =~ /grub-efi-amd64_/ && $run_env->{boot_type} ne 'efi');
 	    next if ($deb =~ /^proxmox-grub/ && $run_env->{boot_type} ne 'efi');
 	    next if ($deb =~ /^proxmox-secure-boot-support_/ && !$run_env->{secure_boot});
+	    next if ($deb =~ /^proxmox-first-boot/ && !Proxmox::Install::Config::get_first_boot_opt('enabled'));
 
 	    update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
 
@@ -1251,6 +1301,9 @@ _EOD
 	my $ask_for_patience = "";
 	$ask_for_patience = " (multiple disks detected, please be patient)" if $diskcount > 3;
 	update_progress(0.8, 0.95, 1, "make system bootable$ask_for_patience");
+
+	setup_proxmox_first_boot_service($targetdir);
+
 	my $target_cmdline='';
 	if ($target_cmdline = Proxmox::Install::Config::get_target_cmdline()) {
 	    my $target_cmdline_snippet = '';
-- 
2.47.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] applied: [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script
  2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
                   ` (4 preceding siblings ...)
  2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 5/5] fix #5579: install: setup proxmox-first-boot service if enabled Christoph Heiss
@ 2024-11-18 21:35 ` Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Thomas Lamprecht @ 2024-11-18 21:35 UTC (permalink / raw)
  To: Proxmox VE development discussion, Christoph Heiss

Am 18.11.24 um 13:38 schrieb Christoph Heiss:
> This implements #5579 [0] as proposed by Thomas [1].
> 
> Adds a new (optional) section to the auto-installer answer file called
> `first-boot`, which can be used to the configure a script/executable
> file to run on the first boot after the installation.
> 
> To used the baked-in (via the `proxmox-auto-install-assistant prepare-iso
> --on-first-boot`) file from the ISO:
> 
>   [first-boot]
>   source = "from-iso"
> 
> Or fetching it from a URL:
> 
>   [first-boot]
>   source = "from-url"
>   url = "http://example.com/first-boot"
>   cert_fingerprint = ".." # if needed
> 
> Optionally, a ordering can be specified, when to run the script:
> 
>   [first-boot]
>   source = "from-url"
>   ordering = "fully-up" # default value
>   # one of "before-network", "network-online" or "fully-up"
> 
> The structure the section is mostly taken from the `network` section to
> provide consistency.
> 
> [0] https://bugzilla.proxmox.com/show_bug.cgi?id=5579
> [1] https://bugzilla.proxmox.com/show_bug.cgi?id=5579#c5
> 

Looks all right to me, nice work! Would be great if you could add that new feature
to our auto-installer docs.

> Diffstat
> ========
> 
> Christoph Heiss (5):
>   fix #5579: first-boot: add initial service packaging
>   fix #5579: setup: introduce 'first_boot' low-level installer options
>   fix #5579: auto-install-assistant: enable baking in first-boot script
>   fix #5579: auto-installer: add optional first-boot hook script
>   fix #5579: install: setup proxmox-first-boot service if enabled


applied, thanks!


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2024-11-18 21:35 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-11-18 12:38 [pve-devel] [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Christoph Heiss
2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 1/5] fix #5579: first-boot: add initial service packaging Christoph Heiss
2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 2/5] fix #5579: setup: introduce 'first_boot' low-level installer options Christoph Heiss
2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 3/5] fix #5579: auto-install-assistant: enable baking in first-boot script Christoph Heiss
2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 4/5] fix #5579: auto-installer: add optional first-boot hook script Christoph Heiss
2024-11-18 12:38 ` [pve-devel] [PATCH installer v2 5/5] fix #5579: install: setup proxmox-first-boot service if enabled Christoph Heiss
2024-11-18 21:35 ` [pve-devel] applied: [PATCH installer v2 0/5] fix #5579: allow specifying optional first-boot script Thomas Lamprecht

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal