@@ -66,6 +66,7 @@ type Options struct {
66
66
Filesystem afero.Fs
67
67
LogDir string
68
68
TempDir string
69
+ ScriptDataDir string
69
70
ExchangeToken func (ctx context.Context ) (string , error )
70
71
Client Client
71
72
ReconnectingPTYTimeout time.Duration
@@ -112,9 +113,19 @@ func New(options Options) Agent {
112
113
if options .LogDir == "" {
113
114
if options .TempDir != os .TempDir () {
114
115
options .Logger .Debug (context .Background (), "log dir not set, using temp dir" , slog .F ("temp_dir" , options .TempDir ))
116
+ } else {
117
+ options .Logger .Debug (context .Background (), "using log dir" , slog .F ("log_dir" , options .LogDir ))
115
118
}
116
119
options .LogDir = options .TempDir
117
120
}
121
+ if options .ScriptDataDir == "" {
122
+ if options .TempDir != os .TempDir () {
123
+ options .Logger .Debug (context .Background (), "script data dir not set, using temp dir" , slog .F ("temp_dir" , options .TempDir ))
124
+ } else {
125
+ options .Logger .Debug (context .Background (), "using script data dir" , slog .F ("script_data_dir" , options .ScriptDataDir ))
126
+ }
127
+ options .ScriptDataDir = options .TempDir
128
+ }
118
129
if options .ExchangeToken == nil {
119
130
options .ExchangeToken = func (ctx context.Context ) (string , error ) {
120
131
return "" , nil
@@ -146,12 +157,13 @@ func New(options Options) Agent {
146
157
logger : options .Logger ,
147
158
closeCancel : cancelFunc ,
148
159
closed : make (chan struct {}),
149
- envVars : options .EnvironmentVariables ,
160
+ environmentVariables : options .EnvironmentVariables ,
150
161
client : options .Client ,
151
162
exchangeToken : options .ExchangeToken ,
152
163
filesystem : options .Filesystem ,
153
164
logDir : options .LogDir ,
154
165
tempDir : options .TempDir ,
166
+ scriptDataDir : options .ScriptDataDir ,
155
167
lifecycleUpdate : make (chan struct {}, 1 ),
156
168
lifecycleReported : make (chan codersdk.WorkspaceAgentLifecycle , 1 ),
157
169
lifecycleStates : []agentsdk.PostLifecycleRequest {{State : codersdk .WorkspaceAgentLifecycleCreated }},
@@ -169,6 +181,8 @@ func New(options Options) Agent {
169
181
prometheusRegistry : prometheusRegistry ,
170
182
metrics : newAgentMetrics (prometheusRegistry ),
171
183
}
184
+ a .serviceBanner .Store (new (codersdk.ServiceBannerConfig ))
185
+ a .sessionToken .Store (new (string ))
172
186
a .init (ctx )
173
187
return a
174
188
}
@@ -181,6 +195,7 @@ type agent struct {
181
195
filesystem afero.Fs
182
196
logDir string
183
197
tempDir string
198
+ scriptDataDir string
184
199
// ignorePorts tells the api handler which ports to ignore when
185
200
// listing all listening ports. This is helpful to hide ports that
186
201
// are used by the agent, that the user does not care about.
@@ -196,7 +211,7 @@ type agent struct {
196
211
closeMutex sync.Mutex
197
212
closed chan struct {}
198
213
199
- envVars map [string ]string
214
+ environmentVariables map [string ]string
200
215
201
216
manifest atomic.Pointer [agentsdk.Manifest ] // manifest is atomic because values can change after reconnection.
202
217
reportMetadataInterval time.Duration
@@ -235,21 +250,24 @@ func (a *agent) TailnetConn() *tailnet.Conn {
235
250
}
236
251
237
252
func (a * agent ) init (ctx context.Context ) {
238
- sshSrv , err := agentssh .NewServer (ctx , a .logger .Named ("ssh-server" ), a .prometheusRegistry , a .filesystem , a .sshMaxTimeout , "" )
253
+ sshSrv , err := agentssh .NewServer (ctx , a .logger .Named ("ssh-server" ), a .prometheusRegistry , a .filesystem , & agentssh.Config {
254
+ MaxTimeout : a .sshMaxTimeout ,
255
+ MOTDFile : func () string { return a .manifest .Load ().MOTDFile },
256
+ ServiceBanner : func () * codersdk.ServiceBannerConfig { return a .serviceBanner .Load () },
257
+ UpdateEnv : a .updateCommandEnv ,
258
+ WorkingDirectory : func () string { return a .manifest .Load ().Directory },
259
+ })
239
260
if err != nil {
240
261
panic (err )
241
262
}
242
- sshSrv .Env = a .envVars
243
- sshSrv .AgentToken = func () string { return * a .sessionToken .Load () }
244
- sshSrv .Manifest = & a .manifest
245
- sshSrv .ServiceBanner = & a .serviceBanner
246
263
a .sshServer = sshSrv
247
264
a .scriptRunner = agentscripts .New (agentscripts.Options {
248
- LogDir : a .logDir ,
249
- Logger : a .logger ,
250
- SSHServer : sshSrv ,
251
- Filesystem : a .filesystem ,
252
- PatchLogs : a .client .PatchLogs ,
265
+ LogDir : a .logDir ,
266
+ DataDirBase : a .scriptDataDir ,
267
+ Logger : a .logger ,
268
+ SSHServer : sshSrv ,
269
+ Filesystem : a .filesystem ,
270
+ PatchLogs : a .client .PatchLogs ,
253
271
})
254
272
// Register runner metrics. If the prom registry is nil, the metrics
255
273
// will not report anywhere.
@@ -879,6 +897,90 @@ func (a *agent) run(ctx context.Context) error {
879
897
return eg .Wait ()
880
898
}
881
899
900
+ // updateCommandEnv updates the provided command environment with the
901
+ // following set of environment variables:
902
+ // - Predefined workspace environment variables
903
+ // - Environment variables currently set (overriding predefined)
904
+ // - Environment variables passed via the agent manifest (overriding predefined and current)
905
+ // - Agent-level environment variables (overriding all)
906
+ func (a * agent ) updateCommandEnv (current []string ) (updated []string , err error ) {
907
+ manifest := a .manifest .Load ()
908
+ if manifest == nil {
909
+ return nil , xerrors .Errorf ("no manifest" )
910
+ }
911
+
912
+ executablePath , err := os .Executable ()
913
+ if err != nil {
914
+ return nil , xerrors .Errorf ("getting os executable: %w" , err )
915
+ }
916
+ unixExecutablePath := strings .ReplaceAll (executablePath , "\\ " , "/" )
917
+
918
+ // Define environment variables that should be set for all commands,
919
+ // and then merge them with the current environment.
920
+ envs := map [string ]string {
921
+ // Set env vars indicating we're inside a Coder workspace.
922
+ "CODER" : "true" ,
923
+ "CODER_WORKSPACE_NAME" : manifest .WorkspaceName ,
924
+ "CODER_WORKSPACE_AGENT_NAME" : manifest .AgentName ,
925
+
926
+ // Specific Coder subcommands require the agent token exposed!
927
+ "CODER_AGENT_TOKEN" : * a .sessionToken .Load (),
928
+
929
+ // Git on Windows resolves with UNIX-style paths.
930
+ // If using backslashes, it's unable to find the executable.
931
+ "GIT_SSH_COMMAND" : fmt .Sprintf ("%s gitssh --" , unixExecutablePath ),
932
+ // Hide Coder message on code-server's "Getting Started" page
933
+ "CS_DISABLE_GETTING_STARTED_OVERRIDE" : "true" ,
934
+ }
935
+
936
+ // This adds the ports dialog to code-server that enables
937
+ // proxying a port dynamically.
938
+ // If this is empty string, do not set anything. Code-server auto defaults
939
+ // using its basepath to construct a path based port proxy.
940
+ if manifest .VSCodePortProxyURI != "" {
941
+ envs ["VSCODE_PROXY_URI" ] = manifest .VSCodePortProxyURI
942
+ }
943
+
944
+ // Allow any of the current env to override what we defined above.
945
+ for _ , env := range current {
946
+ parts := strings .SplitN (env , "=" , 2 )
947
+ if len (parts ) != 2 {
948
+ continue
949
+ }
950
+ if _ , ok := envs [parts [0 ]]; ! ok {
951
+ envs [parts [0 ]] = parts [1 ]
952
+ }
953
+ }
954
+
955
+ // Load environment variables passed via the agent manifest.
956
+ // These override all variables we manually specify.
957
+ for k , v := range manifest .EnvironmentVariables {
958
+ // Expanding environment variables allows for customization
959
+ // of the $PATH, among other variables. Customers can prepend
960
+ // or append to the $PATH, so allowing expand is required!
961
+ envs [k ] = os .ExpandEnv (v )
962
+ }
963
+
964
+ // Agent-level environment variables should take over all. This is
965
+ // used for setting agent-specific variables like CODER_AGENT_TOKEN
966
+ // and GIT_ASKPASS.
967
+ for k , v := range a .environmentVariables {
968
+ envs [k ] = v
969
+ }
970
+
971
+ // Prepend the agent script bin directory to the PATH
972
+ // (this is where Coder modules place their binaries).
973
+ if _ , ok := envs ["PATH" ]; ! ok {
974
+ envs ["PATH" ] = os .Getenv ("PATH" )
975
+ }
976
+ envs ["PATH" ] = fmt .Sprintf ("%s%c%s" , a .scriptRunner .ScriptBinDir (), filepath .ListSeparator , envs ["PATH" ])
977
+
978
+ for k , v := range envs {
979
+ updated = append (updated , fmt .Sprintf ("%s=%s" , k , v ))
980
+ }
981
+ return updated , nil
982
+ }
983
+
882
984
func (a * agent ) wireguardAddresses (agentID uuid.UUID ) []netip.Prefix {
883
985
if len (a .addresses ) == 0 {
884
986
return []netip.Prefix {
@@ -1314,7 +1416,7 @@ func (a *agent) manageProcessPriorityLoop(ctx context.Context) {
1314
1416
}
1315
1417
}()
1316
1418
1317
- if val := a .envVars [EnvProcPrioMgmt ]; val == "" || runtime .GOOS != "linux" {
1419
+ if val := a .environmentVariables [EnvProcPrioMgmt ]; val == "" || runtime .GOOS != "linux" {
1318
1420
a .logger .Debug (ctx , "process priority not enabled, agent will not manage process niceness/oom_score_adj " ,
1319
1421
slog .F ("env_var" , EnvProcPrioMgmt ),
1320
1422
slog .F ("value" , val ),
0 commit comments