Skip to content

Instantly share code, notes, and snippets.

@nickjacob
Last active March 17, 2023 16:11
Show Gist options
  • Select an option

  • Save nickjacob/9909574 to your computer and use it in GitHub Desktop.

Select an option

Save nickjacob/9909574 to your computer and use it in GitHub Desktop.
execute arbitrary bash code/variable substitution in systemd units
[Unit]
Description=Demonstrate Bash
[Service]
ExecStartPre=/usr/bin/bash -c "/usr/bin/systemctl set-environment MYVAR=$(( 2 + 2 ))"
ExecStart=/usr/bin/echo "2 + 2 = ${MYVAR}"
@gbarrancos

Copy link
Copy Markdown

thanks! saved me some good time figuring out the systemctl set-environment directive :)

@mbabineau

Copy link
Copy Markdown

👍 quelled my systemd rage

@adinapoli

Copy link
Copy Markdown

🎉 Awesome, heroes!

@flungo

flungo commented Aug 30, 2015

Copy link
Copy Markdown

What's the scope of this? I assume just within the units environment and that, environment variables set like this, won't bleed out into other units?

Thinking of using this to assign variables that are output of other commands.

@andrewmichaelsmith

Copy link
Copy Markdown

I believe all future units will have this variable in their scope

@chrissnell

Copy link
Copy Markdown

Can anybody think of an alternative to this that doesn't bleed into other units?

@LordFPL

LordFPL commented Mar 2, 2016

Copy link
Copy Markdown

Make my day happiest to have hostname ip in many units that need it to bind on correct interface ! 👍

[Unit]
Description=Set HOSTNAME_IP in global systemd environement

[Service]
ExecStart=/usr/bin/bash -c "/usr/bin/systemctl set-environment HOSTNAME_IP=$(hostname -i)"
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Thx again for this tip !

@dinacel

dinacel commented Mar 3, 2016

Copy link
Copy Markdown

Note it won't work if user is not root (i.e. if you define "User" in [Service] section). It can be fixed with "PermissionsStartOnly=true", so all commands will be executed as root, and ExecStart command will be run as the defined user as intended.

[Unit]
Description=Demonstrate Bash

[Service]
User=someuser
PermissionsStartOnly=true

ExecStartPre=/usr/bin/bash -c "/usr/bin/systemctl set-environment MYVAR=$(( 2 + 2 ))" #Run as root
ExecStart=/usr/bin/echo "2 + 2 = ${MYVAR}" #Run as "someuser"
ExecStop=#Run as root

[Install]
WantedBy=multi-user.target

@vaijab

vaijab commented May 5, 2016

Copy link
Copy Markdown

Note it won't work if user is not root

I believe you can use PassEnvironment= in this case.

@DavidXArnold

DavidXArnold commented Aug 6, 2016

Copy link
Copy Markdown

👍 many thanks

@danieldkim

Copy link
Copy Markdown

👍

@gdamjan

gdamjan commented Jul 24, 2017

Copy link
Copy Markdown

https://www.freedesktop.org/software/systemd/man/systemd.exec.html#EnvironmentFile=

The files listed with this directive will be read shortly before the process is executed (more specifically, after all processes from a previous unit state terminated. This means you can generate these files in one unit state, and read it with this option in the next).

@tzkmx

tzkmx commented Oct 18, 2017

Copy link
Copy Markdown

The scope of this variables is at systemd manager, so they are available to all systemd units, you can clean it with ExecStop directive invoking systemctl unset-environment. Also, you can use templated units, and use the variable %i to populate a different variable for every different instance thus avoiding variables collision without copypasting unit files.

https://gist.github.com/tzkmx/0ae0d2686ee5da5f245f330e3a6a3329

@katcaola

katcaola commented Jan 25, 2018

Copy link
Copy Markdown

Must you use ExecStartPre=/usr/bin/bash or ExecStart=/usr/bin/echo ?
Could you use ExecStartPre=/bin/sh?

@a0s

a0s commented Jun 14, 2018

Copy link
Copy Markdown

👍

@markstos

Copy link
Copy Markdown

@katcaola Yes. The binary just has to be given with an absolute path.

@d-baranowski

Copy link
Copy Markdown

thx 👍

@PhilipSchmid

Copy link
Copy Markdown

Thanks! Used it to dynamically set a USB Ethernet dongle to a separate namespace:

https://gist.github.com/PhilipSchmid/55e40dba4e323e9236a1101a7d0d6abb

@dtrv

dtrv commented May 22, 2020

Copy link
Copy Markdown

Note it won't work if user is not root (i.e. if you define "User" in [Service] section). It can be fixed with "PermissionsStartOnly=true", so all commands will be executed as root, and ExecStart command will be run as the defined user as intended.

add --user to systemctl and it works.

@zffocussss

Copy link
Copy Markdown

nice! I have a question why does env variables generated by ExecStartPre can be used by ExecStart?

@maffe

maffe commented Apr 27, 2022

Copy link
Copy Markdown

I prefer to write the variable to a file specific to this unit:

[Service]
EnvironmentFile=-/dev/shm/demo.env
ExecStartPre=/usr/bin/bash -c "echo MYVAR=$(( 2 + 2 )) >/dev/shm/demo.env"
ExecStart=/usr/bin/echo "2 + 2 = ${MYVAR}"

@mjtiempo

Copy link
Copy Markdown

Thanks. I use it to finally make tigervnc work with SDDM:
https://gist.github.com/mjtiempo/8662e88da74236c58115bed5cc78ceaf

@KalanaDananjaya

KalanaDananjaya commented Mar 17, 2023

Copy link
Copy Markdown

Beware that there's a pitfall if you have to use %s with your command.
echo (date +%s)
SystemD sees this as user shell and resolve to /bin/sh instead. You need to use %%s to avoid that.

EnvironmentFile=/tmp/timestamp.txt
ExecStartPre=/bin/bash -c "echo TIMESTAMP=$(date +%%s) > /tmp/timestamp.txt;"

Although this seems simple, it was quite hard to find this info. ChatGPT was not yielding a correct result either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment