@@ -40,6 +40,7 @@ import (
4040
4141type Options struct {
4242 ReconnectingPTYTimeout time.Duration
43+ EnvironmentVariables map [string ]string
4344 Logger slog.Logger
4445}
4546
@@ -67,6 +68,7 @@ func New(dialer Dialer, options *Options) io.Closer {
6768 logger : options .Logger ,
6869 closeCancel : cancelFunc ,
6970 closed : make (chan struct {}),
71+ envVars : options .EnvironmentVariables ,
7072 }
7173 server .init (ctx )
7274 return server
@@ -84,24 +86,21 @@ type agent struct {
8486 closeMutex sync.Mutex
8587 closed chan struct {}
8688
87- // Environment variables sent by Coder to inject for shell sessions.
88- // These are atomic because values can change after reconnect.
89- envVars atomic.Value
90- directory atomic.String
91- ownerEmail atomic.String
92- ownerUsername atomic.String
89+ envVars map [string ]string
90+ // metadata is atomic because values can change after reconnection.
91+ metadata atomic.Value
9392 startupScript atomic.Bool
9493 sshServer * ssh.Server
9594}
9695
9796func (a * agent ) run (ctx context.Context ) {
98- var options Metadata
97+ var metadata Metadata
9998 var peerListener * peerbroker.Listener
10099 var err error
101100 // An exponential back-off occurs when the connection is failing to dial.
102101 // This is to prevent server spam in case of a coderd outage.
103102 for retrier := retry .New (50 * time .Millisecond , 10 * time .Second ); retrier .Wait (ctx ); {
104- options , peerListener , err = a .dialer (ctx , a .logger )
103+ metadata , peerListener , err = a .dialer (ctx , a .logger )
105104 if err != nil {
106105 if errors .Is (err , context .Canceled ) {
107106 return
@@ -120,15 +119,12 @@ func (a *agent) run(ctx context.Context) {
120119 return
121120 default :
122121 }
123- a .directory .Store (options .Directory )
124- a .envVars .Store (options .EnvironmentVariables )
125- a .ownerEmail .Store (options .OwnerEmail )
126- a .ownerUsername .Store (options .OwnerUsername )
122+ a .metadata .Store (metadata )
127123
128124 if a .startupScript .CAS (false , true ) {
129125 // The startup script has not ran yet!
130126 go func () {
131- err := a .runStartupScript (ctx , options .StartupScript )
127+ err := a .runStartupScript (ctx , metadata .StartupScript )
132128 if errors .Is (err , context .Canceled ) {
133129 return
134130 }
@@ -175,7 +171,7 @@ func (*agent) runStartupScript(ctx context.Context, script string) error {
175171 writer , err = gsyslog .NewLogger (gsyslog .LOG_INFO , "USER" , "coder-startup-script" )
176172 if err != nil {
177173 // If the syslog isn't supported or cannot be created, use a text file in temp.
178- writer , err = os .CreateTemp ("" , "coder-startup-script.txt" )
174+ writer , err = os .CreateTemp ("" , "coder-startup-script-* .txt" )
179175 if err != nil {
180176 return xerrors .Errorf ("open startup script log file: %w" , err )
181177 }
@@ -322,6 +318,15 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
322318 return nil , xerrors .Errorf ("get user shell: %w" , err )
323319 }
324320
321+ rawMetadata := a .metadata .Load ()
322+ if rawMetadata == nil {
323+ return nil , xerrors .Errorf ("no metadata was provided: %w" , err )
324+ }
325+ metadata , valid := rawMetadata .(Metadata )
326+ if ! valid {
327+ return nil , xerrors .Errorf ("metadata is the wrong type: %T" , metadata )
328+ }
329+
325330 // gliderlabs/ssh returns a command slice of zero
326331 // when a shell is requested.
327332 command := rawCommand
@@ -336,7 +341,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
336341 caller = "/c"
337342 }
338343 cmd := exec .CommandContext (ctx , shell , caller , command )
339- cmd .Dir = a . directory . Load ()
344+ cmd .Dir = metadata . Directory
340345 if cmd .Dir == "" {
341346 // Default to $HOME if a directory is not set!
342347 cmd .Dir = os .Getenv ("HOME" )
@@ -351,20 +356,24 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
351356 executablePath = strings .ReplaceAll (executablePath , "\\ " , "/" )
352357 cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_SSH_COMMAND=%s gitssh --` , executablePath ))
353358 // These prevent the user from having to specify _anything_ to successfully commit.
354- cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_AUTHOR_EMAIL=%s` , a .ownerEmail .Load ()))
355- cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_AUTHOR_NAME=%s` , a .ownerUsername .Load ()))
359+ // Both author and committer must be set!
360+ cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_AUTHOR_EMAIL=%s` , metadata .OwnerEmail ))
361+ cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_COMMITTER_EMAIL=%s` , metadata .OwnerEmail ))
362+ cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_AUTHOR_NAME=%s` , metadata .OwnerUsername ))
363+ cmd .Env = append (cmd .Env , fmt .Sprintf (`GIT_COMMITTER_NAME=%s` , metadata .OwnerUsername ))
356364
357365 // Load environment variables passed via the agent.
358366 // These should override all variables we manually specify.
359- envVars := a . envVars . Load ()
360- if envVars != nil {
361- envVarMap , ok := envVars .( map [ string ] string )
362- if ok {
363- for key , value := range envVarMap {
364- cmd . Env = append ( cmd . Env , fmt . Sprintf ( "%s=%s" , key , value ))
365- }
366- }
367+ for key , value := range metadata . EnvironmentVariables {
368+ cmd . Env = append ( cmd . Env , fmt . Sprintf ( "%s=%s" , key , value ))
369+ }
370+
371+ // Agent-level environment variables should take over all!
372+ // This is used for setting agent-specific variables like "CODER_AGENT_TOKEN".
373+ for key , value := range a . envVars {
374+ cmd . Env = append ( cmd . Env , fmt . Sprintf ( "%s=%s" , key , value ))
367375 }
376+
368377 return cmd , nil
369378}
370379
0 commit comments