@@ -50,6 +50,9 @@ const (
5050 // Offline can mean a user is not logged in
5151 // or is logged in but their key has expired.
5252 Offline = "OFFLINE"
53+ // RequiredSudo for when LocalBackend is run
54+ // with sudo but tsrelay is not
55+ RequiredSudo = "REQUIRES_SUDO"
5356)
5457
5558func main () {
@@ -148,13 +151,22 @@ type serveStatus struct {
148151
149152// RelayError is a wrapper for Error
150153type RelayError struct {
151- Errors []Error
154+ statusCode int
155+ Errors []Error
156+ }
157+
158+ // Error implements error. It returns a
159+ // static string as it is only needed to be
160+ // used for programatic type assertion.
161+ func (RelayError ) Error () string {
162+ return "relay error"
152163}
153164
154165// Error is a programmable error returned
155166// to the typescript client
156167type Error struct {
157- Type string `json:",omitempty"`
168+ Type string `json:",omitempty"`
169+ Command string `json:",omitempty"`
158170}
159171
160172type peerStatus struct {
@@ -208,6 +220,12 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
208220 switch r .Method {
209221 case http .MethodPost :
210222 if err := h .createServe (r .Context (), r .Body ); err != nil {
223+ var re RelayError
224+ if errors .As (err , & re ) {
225+ w .WriteHeader (re .statusCode )
226+ json .NewEncoder (w ).Encode (re )
227+ return
228+ }
211229 h .l .Println ("error creating serve:" , err )
212230 http .Error (w , err .Error (), 500 )
213231 return
@@ -441,6 +459,8 @@ func (h *httpHandler) deleteServe(ctx context.Context, body io.Reader) error {
441459 return nil
442460}
443461
462+ // createServe is the programtic equivalent of "tailscale serve --set-raw"
463+ // it returns the config as json in case of an error.
444464func (h * httpHandler ) createServe (ctx context.Context , body io.Reader ) error {
445465 var req serveRequest
446466 err := json .NewDecoder (body ).Decode (& req )
@@ -466,6 +486,23 @@ func (h *httpHandler) createServe(ctx context.Context, body io.Reader) error {
466486 }
467487 err = h .lc .SetServeConfig (ctx , sc )
468488 if err != nil {
489+ if tailscale .IsAccessDeniedError (err ) {
490+ cfgJSON , err := json .Marshal (sc )
491+ if err != nil {
492+ return fmt .Errorf ("error marshaling own config: %w" , err )
493+ }
494+ re := RelayError {
495+ statusCode : http .StatusForbidden ,
496+ Errors : []Error {{
497+ Type : RequiredSudo ,
498+ Command : fmt .Sprintf (`echo %s | sudo tailscale serve --set-raw` , cfgJSON ),
499+ }},
500+ }
501+ return re
502+ }
503+ if err != nil {
504+ return fmt .Errorf ("error marshaling config: %w" , err )
505+ }
469506 return fmt .Errorf ("error setting serve config: %w" , err )
470507 }
471508 return nil
0 commit comments