44Contains functions to start, stop & poll systemd services.
55Probably not very useful outside this spawner.
66"""
7+
78import asyncio
9+ import os
10+ import re
811import shlex
12+ import warnings
13+
14+ # light validation of environment variable keys
15+ env_pat = re .compile ("[A-Za-z_]+" )
16+
17+ RUN_ROOT = "/run"
18+
19+ def ensure_environment_directory (environment_file_directory ):
20+ """Ensure directory for environment files exists and is private"""
21+ # ensure directory exists
22+ os .makedirs (environment_file_directory , mode = 0o700 , exist_ok = True )
23+ # validate permissions
24+ mode = os .stat (environment_file_directory ).st_mode
25+ if mode & 0o077 :
26+ warnings .warn (
27+ f"Fixing permissions on environment directory { environment_file_directory } : { oct (mode )} " ,
28+ RuntimeWarning ,
29+ )
30+ os .chmod (environment_file_directory , 0o700 )
31+ else :
32+ return
33+ # Check again after supposedly fixing.
34+ # Some filesystems can have weird issues, preventing this from having desired effect
35+ mode = os .stat (environment_file_directory ).st_mode
36+ if mode & 0o077 :
37+ warnings .warn (
38+ f"Bad permissions on environment directory { environment_file_directory } : { oct (mode )} " ,
39+ RuntimeWarning ,
40+ )
41+
42+
43+ def make_environment_file (environment_file_directory , unit_name , environment_variables ):
44+ """Make a systemd environment file
45+
46+ - ensures environment directory exists and is private
47+ - writes private environment file
48+ - returns path to created environment file
49+ """
50+ ensure_environment_directory (environment_file_directory )
51+ env_file = os .path .join (environment_file_directory , f"{ unit_name } .env" )
52+ env_lines = []
53+ for key , value in sorted (environment_variables .items ()):
54+ assert env_pat .match (key ), f"{ key } not a valid environment variable"
55+ env_lines .append (f"{ key } ={ shlex .quote (value )} " )
56+ env_lines .append ("" ) # trailing newline
57+ with open (env_file , mode = "w" ) as f :
58+ # make the file itself private as well
59+ os .fchmod (f .fileno (), 0o400 )
60+ f .write ("\n " .join (env_lines ))
61+
62+ return env_file
963
1064
1165async def start_transient_service (
@@ -20,14 +74,32 @@ async def start_transient_service(
2074 slice = None ,
2175):
2276 """
23- Start a systemd transient service with given paramters
77+ Start a systemd transient service with given parameters
2478 """
2579
2680 run_cmd = [
2781 'systemd-run' ,
2882 '--unit' , unit_name ,
2983 ]
3084
85+ if properties is None :
86+ properties = {}
87+ else :
88+ properties = properties .copy ()
89+
90+ # ensure there is a runtime directory where we can put our env file
91+ # If already set, can be space-separated list of paths
92+ runtime_directories = properties .setdefault ("RuntimeDirectory" , unit_name ).split ()
93+
94+ # runtime directories are always resolved relative to `/run`
95+ # grab the first item, if more than one
96+ runtime_dir = os .path .join (RUN_ROOT , runtime_directories [0 ])
97+ # make runtime directories private by default
98+ properties .setdefault ("RuntimeDirectoryMode" , "700" )
99+ # preserve runtime directories across restarts
100+ # allows `systemctl restart` to load the env
101+ properties .setdefault ("RuntimeDirectoryPreserve" , "restart" )
102+
31103 if properties :
32104 for key , value in properties .items ():
33105 if isinstance (value , list ):
@@ -37,10 +109,10 @@ async def start_transient_service(
37109 run_cmd .append ('--property={}={}' .format (key , value ))
38110
39111 if environment_variables :
40- run_cmd += [
41- '--setenv={}={}' . format ( key , value )
42- for key , value in environment_variables . items ( )
43- ]
112+ environment_file = make_environment_file (
113+ runtime_dir , unit_name , environment_variables
114+ )
115+ run_cmd . append ( f"--property=EnvironmentFile= { environment_file } " )
44116
45117 # Explicitly check if uid / gid are not None, since 0 is valid value for both
46118 if uid is not None :
@@ -51,7 +123,7 @@ async def start_transient_service(
51123
52124 if slice is not None :
53125 run_cmd += ['--slice={}' .format (slice )]
54-
126+
55127 # We unfortunately have to resort to doing cd with bash, since WorkingDirectory property
56128 # of systemd units can't be set for transient units via systemd-run until systemd v227.
57129 # Centos 7 has systemd 219, and will probably never upgrade - so we need to support them.
0 commit comments