Starting a script on first boot via a systemd service
| Make sure that you have completed the steps described in the initial setup page before starting this tutorial. | 
In this tutorial, we will run a script on the first boot via a systemd service. We will add the following to the Butane config from the previous scenario:
- 
Add a script at /usr/local/bin/public-ipv4.sh.
- 
Configure a systemd service to run the script on first boot. 
Writing the script
Let’s write a small script that uses icanhazip.com to create an issue file to display as a prelogin message on the console and store it in public-ipv4.sh.
| This is only an example to show how to run a service on boot. Do not use this if you don’t trust the owners of icanhazip.com. | 
cat <<'EOF' > public-ipv4.sh
#!/bin/bash
echo "Detected Public IPv4: is $(curl https://ipv4.icanhazip.com)" > \
    /etc/issue.d/50_public-ipv4.issue
EOFThis could be useful in cloud environments where you might have different public and private addresses.
We will store this script into /usr/local/bin/public-ipv4.sh when we provision the machine.
Writing the systemd service
We need to call the script from the previous section by using a systemd unit. Let’s write a systemd unit into the issuegen-public-ipv4.service file that does what we want, which is to execute on first boot and not again:
cat <<'EOF' > issuegen-public-ipv4.service
[Unit]
Before=systemd-user-sessions.service
Wants=network-online.target
After=network-online.target
ConditionPathExists=!/var/lib/issuegen-public-ipv4
[Service]
Type=oneshot
ExecStart=/usr/local/bin/public-ipv4.sh
ExecStartPost=/usr/bin/touch /var/lib/issuegen-public-ipv4
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOFWriting the Butane config and converting to Ignition
We can now create a Butane config that will include the script and systemd unit file contents by picking up the local public-ipv4.sh and issuegen-public-ipv4.service files using local file references. The final Butane config, stored in services.bu, will be:
variant: fcos
version: 1.6.0
systemd:
  units:
    - name: serial-getty@ttyS0.service
      dropins:
      - name: autologin-core.conf
        contents: |
          [Service]
          # Override Execstart in main unit
          ExecStart=
          # Add new Execstart with `-` prefix to ignore failure`
          ExecStart=-/usr/sbin/agetty --autologin core --noclear %I $TERM
    - name: issuegen-public-ipv4.service
      enabled: true
      contents_local: issuegen-public-ipv4.service
storage:
  files:
    - path: /etc/hostname
      mode: 0644
      contents:
        inline: |
          tutorial
    - path: /etc/profile.d/systemd-pager.sh
      mode: 0644
      contents:
        inline: |
          # Tell systemd to not use a pager when printing information
          export SYSTEMD_PAGER=cat
    - path: /usr/local/bin/public-ipv4.sh
      mode: 0755
      contents:
        local: public-ipv4.sh| Check the Butane Examples and Configuration specifications for more details about local file includes. | 
With the files public-ipv4.sh, issuegen-public-ipv4.service, and services.bu in the current working directory we can now convert to Ignition:
butane --pretty --strict --files-dir=./ services.bu --output services.ignTesting
Just as before we will use the following to boot the instance:
# Setup the correct SELinux label to allow access to the config
chcon --verbose --type svirt_home_t services.ign
# Start a Fedora CoreOS virtual machine
virt-install --name=fcos --vcpus=2 --ram=2048 --os-variant=fedora-coreos-stable \
    --import --network=bridge=virbr0 --graphics=none \
    --qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=${PWD}/services.ign" \
    --disk="size=20,backing_store=${PWD}/fedora-coreos.qcow2"And view on the console that the Detected Public IPv4 is shown in the console output right before you are dropped to a login prompt:
Fedora CoreOS 38.20230709.3.0 Kernel 6.3.11-200.fc38.x86_64 on an x86_64 (ttyS0) SSH host key: SHA256:tYHKk26+NZ/+ZytWLXClGz813PQJDGP/2+AiuZ8fiqk (ECDSA) SSH host key: SHA256:jJASZec/91zXd4or0uiFsvsfaLC6viLronfxIwQlNCs (ED25519) SSH host key: SHA256:2XlSZAehEu666fmXeM8d47lpIJd92MBOqgMazT4GsVw (RSA) enp1s0: 192.168.124.150 fe80::475a:7a10:2302:b670 Ignition: ran on 2023/08/03 16:40:45 UTC (this boot) Ignition: user-provided config was applied No SSH authorized keys provided by Ignition or Afterburn Detected Public IPv4: is 3.252.102.80 tutorial login: core (automatic login) Fedora CoreOS 38.20230709.3.0 [core@tutorial ~]$
And the service shows it was launched successfully:
[core@tutorial ~]$ systemctl status --full issuegen-public-ipv4.service
● issuegen-public-ipv4.service
     Loaded: loaded (/etc/systemd/system/issuegen-public-ipv4.service; enabled; preset: enabled)
    Drop-In: /usr/lib/systemd/system/service.d
             └─10-timeout-abort.conf
     Active: active (exited) since Thu 2023-08-03 16:40:55 UTC; 1min 7s ago
    Process: 1423 ExecStart=/usr/local/bin/public-ipv4.sh (code=exited, status=0/SUCCESS)
    Process: 1460 ExecStartPost=/usr/bin/touch /var/lib/issuegen-public-ipv4 (code=exited, status=0/SUCCESS)
   Main PID: 1423 (code=exited, status=0/SUCCESS)
        CPU: 84ms
Aug 03 16:40:55 tutorial systemd[1]: Starting issuegen-public-ipv4.service...
Aug 03 16:40:55 tutorial public-ipv4.sh[1424]:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
Aug 03 16:40:55 tutorial public-ipv4.sh[1424]:                                  Dload  Upload   Total   Spent    Left  Speed
Aug 03 16:40:55 tutorial public-ipv4.sh[1424]: [158B blob data]
Aug 03 16:40:55 tutorial systemd[1]: Finished issuegen-public-ipv4.service.
Cleanup
Now let’s take down the instance for the next test. First, disconnect from the serial console by pressing CTRL + ] and then destroy the machine:
virsh destroy fcos virsh undefine --remove-all-storage fcos
You may now proceed with the next tutorial.
Want to help? Learn how to contribute to Fedora Docs ›