feature/drive: start factoring out Taildrive, add ts_omit_drive build tag

As of this commit (per the issue), the Taildrive code remains where it
was, but in new files that are protected by the new ts_omit_drive
build tag. Future commits will move it.

Updates #17058

Change-Id: Idf0a51db59e41ae8da6ea2b11d238aefc48b219e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2025-09-08 08:13:49 -07:00
committed by Brad Fitzpatrick
parent 82c5024f03
commit a1dcf12b67
21 changed files with 582 additions and 440 deletions
-123
View File
@@ -18,8 +18,6 @@ import (
"net/http"
"net/netip"
"net/url"
"os"
"path"
"reflect"
"runtime"
"slices"
@@ -31,7 +29,6 @@ import (
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/clientupdate"
"tailscale.com/drive"
"tailscale.com/envknob"
"tailscale.com/health/healthmsg"
"tailscale.com/hostinfo"
@@ -104,8 +101,6 @@ var handler = map[string]LocalAPIHandler{
"disconnect-control": (*Handler).disconnectControl,
"dns-osconfig": (*Handler).serveDNSOSConfig,
"dns-query": (*Handler).serveDNSQuery,
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
"drive/shares": (*Handler).serveShares,
"goroutines": (*Handler).serveGoroutines,
"handle-push-message": (*Handler).serveHandlePushMessage,
"id-token": (*Handler).serveIDToken,
@@ -2661,124 +2656,6 @@ func (h *Handler) serveDNSQuery(w http.ResponseWriter, r *http.Request) {
})
}
// serveDriveServerAddr handles updates of the Taildrive file server address.
func (h *Handler) serveDriveServerAddr(w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.PUT {
http.Error(w, "only PUT allowed", http.StatusMethodNotAllowed)
return
}
b, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
h.b.DriveSetServerAddr(string(b))
w.WriteHeader(http.StatusCreated)
}
// serveShares handles the management of Taildrive shares.
//
// PUT - adds or updates an existing share
// DELETE - removes a share
// GET - gets a list of all shares, sorted by name
// POST - renames an existing share
func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
if !h.b.DriveSharingEnabled() {
http.Error(w, `taildrive sharing not enabled, please add the attribute "drive:share" to this node in your ACLs' "nodeAttrs" section`, http.StatusForbidden)
return
}
switch r.Method {
case httpm.PUT:
var share drive.Share
err := json.NewDecoder(r.Body).Decode(&share)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
share.Path = path.Clean(share.Path)
fi, err := os.Stat(share.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !fi.IsDir() {
http.Error(w, "not a directory", http.StatusBadRequest)
return
}
if drive.AllowShareAs() {
// share as the connected user
username, err := h.Actor.Username()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
share.As = username
}
err = h.b.DriveSetShare(&share)
if err != nil {
if errors.Is(err, drive.ErrInvalidShareName) {
http.Error(w, "invalid share name", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
case httpm.DELETE:
b, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = h.b.DriveRemoveShare(string(b))
if err != nil {
if os.IsNotExist(err) {
http.Error(w, "share not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
case httpm.POST:
var names [2]string
err := json.NewDecoder(r.Body).Decode(&names)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = h.b.DriveRenameShare(names[0], names[1])
if err != nil {
if os.IsNotExist(err) {
http.Error(w, "share not found", http.StatusNotFound)
return
}
if os.IsExist(err) {
http.Error(w, "share name already used", http.StatusBadRequest)
return
}
if errors.Is(err, drive.ErrInvalidShareName) {
http.Error(w, "invalid share name", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
case httpm.GET:
shares := h.b.DriveGetShares()
err := json.NewEncoder(w).Encode(shares)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
default:
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
}
}
// serveSuggestExitNode serves a POST endpoint for returning a suggested exit node.
func (h *Handler) serveSuggestExitNode(w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.GET {