@@ -25,13 +25,18 @@ type AgentOptions struct {
2525Fetch func (ctx context.Context ,agentID uuid.UUID ) (codersdk.WorkspaceAgent ,error )
2626FetchLogs func (ctx context.Context ,agentID uuid.UUID ,after int64 ,follow bool ) (<- chan []codersdk.WorkspaceAgentLog , io.Closer ,error )
2727Wait bool // If true, wait for the agent to be ready (startup script).
28+ DocsURL string
2829}
2930
3031// Agent displays a spinning indicator that waits for a workspace agent to connect.
3132func Agent (ctx context.Context ,writer io.Writer ,agentID uuid.UUID ,opts AgentOptions )error {
3233ctx ,cancel := context .WithCancel (ctx )
3334defer cancel ()
3435
36+ if opts .DocsURL == "" {
37+ opts .DocsURL = "https://coder.com/docs"
38+ }
39+
3540if opts .FetchInterval == 0 {
3641opts .FetchInterval = 500 * time .Millisecond
3742}
@@ -119,7 +124,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
119124if agent .Status == codersdk .WorkspaceAgentTimeout {
120125now := time .Now ()
121126sw .Log (now ,codersdk .LogLevelInfo ,"The workspace agent is having trouble connecting, wait for it to connect or restart your workspace." )
122- sw .Log (now ,codersdk .LogLevelInfo ,troubleshootingMessage (agent ,"https://coder.com/docs/ templates#agent-connection-issues" ))
127+ sw .Log (now ,codersdk .LogLevelInfo ,troubleshootingMessage (agent ,fmt . Sprintf ( "%s/ templates#agent-connection-issues", opts . DocsURL ) ))
123128for agent .Status == codersdk .WorkspaceAgentTimeout {
124129if agent ,err = fetch ();err != nil {
125130return xerrors .Errorf ("fetch: %w" ,err )
@@ -224,13 +229,13 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
224229sw .Fail (stage ,safeDuration (sw ,agent .ReadyAt ,agent .StartedAt ))
225230// Use zero time (omitted) to separate these from the startup logs.
226231sw .Log (time.Time {},codersdk .LogLevelWarn ,"Warning: A startup script exited with an error and your workspace may be incomplete." )
227- sw .Log (time.Time {},codersdk .LogLevelWarn ,troubleshootingMessage (agent ,"https://coder.com/docs/ templates/troubleshooting #startup-script-exited-with-an-error" ))
232+ sw .Log (time.Time {},codersdk .LogLevelWarn ,troubleshootingMessage (agent ,fmt . Sprintf ( "%s/ templates#startup-script-exited-with-an-error", opts . DocsURL ) ))
228233default :
229234switch {
230235case agent .LifecycleState .Starting ():
231236// Use zero time (omitted) to separate these from the startup logs.
232237sw .Log (time.Time {},codersdk .LogLevelWarn ,"Notice: The startup scripts are still running and your workspace may be incomplete." )
233- sw .Log (time.Time {},codersdk .LogLevelWarn ,troubleshootingMessage (agent ,"https://coder.com/docs/ templates/troubleshooting #your-workspace-may-be-incomplete" ))
238+ sw .Log (time.Time {},codersdk .LogLevelWarn ,troubleshootingMessage (agent ,fmt . Sprintf ( "%s/ templates#your-workspace-may-be-incomplete", opts . DocsURL ) ))
234239// Note: We don't complete or fail the stage here, it's
235240// intentionally left open to indicate this stage didn't
236241// complete.
@@ -252,7 +257,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
252257stage := "The workspace agent lost connection"
253258sw .Start (stage )
254259sw .Log (time .Now (),codersdk .LogLevelWarn ,"Wait for it to reconnect or restart your workspace." )
255- sw .Log (time .Now (),codersdk .LogLevelWarn ,troubleshootingMessage (agent ,"https://coder.com/docs/ templates/troubleshooting #agent-connection-issues" ))
260+ sw .Log (time .Now (),codersdk .LogLevelWarn ,troubleshootingMessage (agent ,fmt . Sprintf ( "%s/ templates#agent-connection-issues", opts . DocsURL ) ))
256261
257262disconnectedAt := agent .DisconnectedAt
258263for agent .Status == codersdk .WorkspaceAgentDisconnected {
@@ -351,16 +356,16 @@ func PeerDiagnostics(w io.Writer, d tailnet.PeerDiagnostics) {
351356}
352357
353358type ConnDiags struct {
354- ConnInfo workspacesdk.AgentConnectionInfo
355- PingP2P bool
356- DisableDirect bool
357- LocalNetInfo * tailcfg.NetInfo
358- LocalInterfaces * healthsdk.InterfacesReport
359- AgentNetcheck * healthsdk.AgentNetcheckReport
360- ClientIPIsAWS bool
361- AgentIPIsAWS bool
362- Verbose bool
363- // TODO: More diagnostics
359+ ConnInfo workspacesdk.AgentConnectionInfo
360+ PingP2P bool
361+ DisableDirect bool
362+ LocalNetInfo * tailcfg.NetInfo
363+ LocalInterfaces * healthsdk.InterfacesReport
364+ AgentNetcheck * healthsdk.AgentNetcheckReport
365+ ClientIPIsAWS bool
366+ AgentIPIsAWS bool
367+ Verbose bool
368+ TroubleshootingURL string
364369}
365370
366371func (d ConnDiags )Write (w io.Writer ) {
@@ -395,7 +400,7 @@ func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
395400agent = append (agent ,msg .Message )
396401}
397402if len (d .AgentNetcheck .Interfaces .Warnings )> 0 {
398- agent [len (agent )- 1 ]+= " \n https://coder.com/docs/networking/troubleshooting #low-mtu"
403+ agent [len (agent )- 1 ]+= fmt . Sprintf ( " \n %s #low-mtu", d . TroubleshootingURL )
399404}
400405}
401406
@@ -404,7 +409,7 @@ func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
404409client = append (client ,msg .Message )
405410}
406411if len (d .LocalInterfaces .Warnings )> 0 {
407- client [len (client )- 1 ]+= " \n https://coder.com/docs/networking/troubleshooting #low-mtu"
412+ client [len (client )- 1 ]+= fmt . Sprintf ( " \n %s #low-mtu", d . TroubleshootingURL )
408413}
409414}
410415
@@ -420,45 +425,45 @@ func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
420425}
421426
422427if d .ConnInfo .DisableDirectConnections {
423- general = append (general ,"❗ Your Coder administrator has blocked direct connections \n " +
424- " https://coder.com/docs/networking/troubleshooting #disabled-deployment-wide" )
428+ general = append (general ,
429+ fmt . Sprintf ( "❗ Your Coder administrator has blocked direct connections \n %s #disabled-deployment-wide", d . TroubleshootingURL ) )
425430if ! d .Verbose {
426431return general ,client ,agent
427432}
428433}
429434
430435if ! d .ConnInfo .DERPMap .HasSTUN () {
431- general = append (general ,"❗ The DERP map is not configured to use STUN \n " +
432- " https://coder.com/docs/networking/troubleshooting #no-stun-servers" )
436+ general = append (general ,
437+ fmt . Sprintf ( "❗ The DERP map is not configured to use STUN \n %s #no-stun-servers", d . TroubleshootingURL ) )
433438}else if d .LocalNetInfo != nil && ! d .LocalNetInfo .UDP {
434- client = append (client ,"Client could not connect to STUN over UDP \n " +
435- " https://coder.com/docs/networking/troubleshooting #udp-blocked" )
439+ client = append (client ,
440+ fmt . Sprintf ( "Client could not connect to STUN over UDP \n %s #udp-blocked", d . TroubleshootingURL ) )
436441}
437442
438443if d .LocalNetInfo != nil && d .LocalNetInfo .MappingVariesByDestIP .EqualBool (true ) {
439- client = append (client ,"Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n " +
440- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
444+ client = append (client ,
445+ fmt . Sprintf ( "Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
441446}
442447
443448if d .AgentNetcheck != nil && d .AgentNetcheck .NetInfo != nil {
444449if d .AgentNetcheck .NetInfo .MappingVariesByDestIP .EqualBool (true ) {
445- agent = append (agent ,"Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n " +
446- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
450+ agent = append (agent ,
451+ fmt . Sprintf ( "Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
447452}
448453if ! d .AgentNetcheck .NetInfo .UDP {
449- agent = append (agent ,"Agent could not connect to STUN over UDP \n " +
450- " https://coder.com/docs/networking/troubleshooting #udp-blocked" )
454+ agent = append (agent ,
455+ fmt . Sprintf ( "Agent could not connect to STUN over UDP \n %s #udp-blocked", d . TroubleshootingURL ) )
451456}
452457}
453458
454459if d .ClientIPIsAWS {
455- client = append (client ,"Client IP address is within an AWS range (AWS uses hard NAT) \n " +
456- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
460+ client = append (client ,
461+ fmt . Sprintf ( "Client IP address is within an AWS range (AWS uses hard NAT) \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
457462}
458463
459464if d .AgentIPIsAWS {
460- agent = append (agent ,"Agent IP address is within an AWS range (AWS uses hard NAT) \n " +
461- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
465+ agent = append (agent ,
466+ fmt . Sprintf ( "Agent IP address is within an AWS range (AWS uses hard NAT) \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
462467}
463468return general ,client ,agent
464469}