feat: Custom Tailscale client for Headscale
Changes from upstream tailscale v1.97.0: - Renamed service/pipe/registry to 'Tailscale-Custom' for coexistence - Default control URL: https://vpn.softs.business - TUN adapter: WintunTunnelType='Tailscale-Custom', unique GUID - Named pipe: Tailscale-Custom\tailscaled - Registry: SOFTWARE\Tailscale-Custom IPN - ProgramData/LocalAppData paths: Tailscale-Custom New components: - cmd/tailscale-tray: Windows system tray app with multi-profile support - installer/TailscaleCustom.wxs: WiX v5 MSI installer Modified files (~22 production files): - ipn/prefs.go, paths/paths.go, paths/paths_windows.go - cmd/tailscaled/tailscaled.go, tailscaled_windows.go - control/controlclient/direct.go - net/tstun/tun_windows.go - util/winutil/winutil_windows.go - logpolicy/logpolicy.go - and more (see git diff for full list)
This commit is contained in:
@@ -126,7 +126,7 @@ you can run the command prompt as Administrator one of these ways:
|
||||
return nil
|
||||
}
|
||||
|
||||
tsDir := filepath.Join(os.Getenv("ProgramData"), "Tailscale")
|
||||
tsDir := filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom")
|
||||
msiDir := filepath.Join(tsDir, "MSICache")
|
||||
if fi, err := os.Stat(tsDir); err != nil {
|
||||
return fmt.Errorf("expected %s to exist, got stat error: %w", tsDir, err)
|
||||
@@ -292,7 +292,7 @@ func (up *Updater) startNewLogFile(baseNamePrefix, baseNameSuffix string) string
|
||||
baseName := fmt.Sprintf("%s-%s-%s.log", baseNamePrefix,
|
||||
time.Now().Format("20060102T150405"), baseNameSuffix)
|
||||
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale", "Logs")
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom", "Logs")
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
up.Logf("failed to create log directory: %v", err)
|
||||
return filepath.Join(os.TempDir(), baseName)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
// Simple embedded icons as ICO-format byte arrays.
|
||||
// These are minimal 16x16 icons for the system tray.
|
||||
|
||||
// iconConnected is a green circle icon (connected state)
|
||||
var iconConnected = generateIcon(0x00, 0xAA, 0x55) // green
|
||||
|
||||
// iconDisconnected is a gray circle icon (disconnected state)
|
||||
var iconDisconnected = generateIcon(0x88, 0x88, 0x88) // gray
|
||||
|
||||
// generateIcon creates a minimal 16x16 ICO file with a solid circle of the given color.
|
||||
func generateIcon(r, g, b byte) []byte {
|
||||
const size = 16
|
||||
|
||||
// BMP data (32-bit BGRA, bottom-up)
|
||||
pixels := make([]byte, size*size*4)
|
||||
cx, cy := float64(size)/2.0, float64(size)/2.0
|
||||
radius := float64(size)/2.0 - 1
|
||||
|
||||
for y := 0; y < size; y++ {
|
||||
for x := 0; x < size; x++ {
|
||||
// bottom-up: row 0 is the bottom
|
||||
row := size - 1 - y
|
||||
idx := (row*size + x) * 4
|
||||
dx := float64(x) - cx + 0.5
|
||||
dy := float64(y) - cy + 0.5
|
||||
dist := dx*dx + dy*dy
|
||||
if dist <= radius*radius {
|
||||
pixels[idx+0] = b // B
|
||||
pixels[idx+1] = g // G
|
||||
pixels[idx+2] = r // R
|
||||
pixels[idx+3] = 0xFF // A
|
||||
} else {
|
||||
pixels[idx+0] = 0 // B
|
||||
pixels[idx+1] = 0 // G
|
||||
pixels[idx+2] = 0 // R
|
||||
pixels[idx+3] = 0 // A (transparent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AND mask (1bpp, all zeros = fully visible)
|
||||
andMask := make([]byte, size*size/8)
|
||||
|
||||
// BITMAPINFOHEADER (40 bytes)
|
||||
bih := make([]byte, 40)
|
||||
le32(bih[0:], 40) // biSize
|
||||
le32(bih[4:], uint32(size)) // biWidth
|
||||
le32(bih[8:], uint32(size*2)) // biHeight (doubled for ICO)
|
||||
le16(bih[12:], 1) // biPlanes
|
||||
le16(bih[14:], 32) // biBitCount
|
||||
// rest is zeros (no compression, etc.)
|
||||
|
||||
imageData := append(bih, pixels...)
|
||||
imageData = append(imageData, andMask...)
|
||||
|
||||
// ICO header
|
||||
ico := make([]byte, 6+16) // ICONDIR + 1 ICONDIRENTRY
|
||||
le16(ico[0:], 0) // reserved
|
||||
le16(ico[2:], 1) // type: icon
|
||||
le16(ico[4:], 1) // count: 1 image
|
||||
|
||||
// ICONDIRENTRY
|
||||
ico[6] = byte(size) // width
|
||||
ico[7] = byte(size) // height
|
||||
ico[8] = 0 // colors (0=no palette)
|
||||
ico[9] = 0 // reserved
|
||||
le16(ico[10:], 1) // planes
|
||||
le16(ico[12:], 32) // bit count
|
||||
le32(ico[14:], uint32(len(imageData)))
|
||||
le32(ico[18:], uint32(len(ico))) // offset
|
||||
|
||||
return append(ico, imageData...)
|
||||
}
|
||||
|
||||
func le16(b []byte, v uint16) {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
}
|
||||
|
||||
func le32(b []byte, v uint32) {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// tailscale-tray is a Windows system tray application for Tailscale-Custom.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"fyne.io/systray"
|
||||
"golang.org/x/sys/windows"
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
)
|
||||
|
||||
// app holds the systray menu state.
|
||||
type app struct {
|
||||
lc *local.Client
|
||||
|
||||
mu sync.Mutex
|
||||
status *ipnstate.Status
|
||||
curProfile ipn.LoginProfile
|
||||
allProfiles []ipn.LoginProfile
|
||||
|
||||
bgCtx context.Context
|
||||
bgCancel context.CancelFunc
|
||||
|
||||
connect *systray.MenuItem
|
||||
disconnect *systray.MenuItem
|
||||
quit *systray.MenuItem
|
||||
|
||||
rebuildCh chan struct{}
|
||||
eventCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Single instance check
|
||||
mutexName, _ := windows.UTF16PtrFromString("Global\\Tailscale-Custom-Tray-Mutex")
|
||||
handle, err := windows.CreateMutex(nil, false, mutexName)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
if windows.GetLastError() == windows.ERROR_ALREADY_EXISTS {
|
||||
windows.CloseHandle(handle)
|
||||
os.Exit(0)
|
||||
}
|
||||
defer windows.CloseHandle(handle)
|
||||
|
||||
setupLogging()
|
||||
log.Println("tailscale-tray starting")
|
||||
|
||||
a := &app{
|
||||
lc: &local.Client{},
|
||||
rebuildCh: make(chan struct{}, 1),
|
||||
}
|
||||
a.bgCtx, a.bgCancel = context.WithCancel(context.Background())
|
||||
defer a.bgCancel()
|
||||
|
||||
a.updateState()
|
||||
go a.watchIPNBus()
|
||||
systray.Run(a.onReady, a.onExit)
|
||||
}
|
||||
|
||||
func setupLogging() {
|
||||
dirs := []string{
|
||||
filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom", "Logs"),
|
||||
filepath.Join(os.Getenv("LOCALAPPDATA"), "Tailscale-Custom", "Logs"),
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
os.MkdirAll(dir, 0700)
|
||||
f, err := os.OpenFile(filepath.Join(dir, "tray.log"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err == nil {
|
||||
log.SetOutput(f)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) updateState() {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(a.bgCtx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var err error
|
||||
a.status, err = a.lc.Status(ctx)
|
||||
if err != nil {
|
||||
log.Printf("updateState: Status error: %v", err)
|
||||
a.status = nil
|
||||
}
|
||||
a.curProfile, a.allProfiles, err = a.lc.ProfileStatus(ctx)
|
||||
if err != nil {
|
||||
log.Printf("updateState: ProfileStatus error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) onReady() {
|
||||
log.Println("onReady")
|
||||
systray.SetIcon(iconDisconnected)
|
||||
systray.SetTooltip("Tailscale-Custom")
|
||||
a.rebuild()
|
||||
}
|
||||
|
||||
func (a *app) onExit() {
|
||||
log.Println("onExit")
|
||||
a.bgCancel()
|
||||
}
|
||||
|
||||
// onClick registers a per-item click handler in its own goroutine.
|
||||
// This is the same pattern used by the official Tailscale systray.
|
||||
func onClick(ctx context.Context, item *systray.MenuItem, fn func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("PANIC in onClick: %v", r)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-item.ClickedCh:
|
||||
fn()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *app) rebuild() {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
log.Println("rebuild: start")
|
||||
|
||||
if a.eventCancel != nil {
|
||||
a.eventCancel()
|
||||
}
|
||||
ctx, cancelFn := context.WithCancel(a.bgCtx)
|
||||
a.eventCancel = cancelFn
|
||||
|
||||
systray.ResetMenu()
|
||||
|
||||
// --- Status line ---
|
||||
var stateStr string
|
||||
if a.status == nil {
|
||||
stateStr = "Not connected"
|
||||
systray.SetIcon(iconDisconnected)
|
||||
systray.SetTooltip("Tailscale-Custom - Not Available")
|
||||
} else {
|
||||
switch a.status.BackendState {
|
||||
case "Running":
|
||||
if a.status.Self != nil && len(a.status.Self.TailscaleIPs) > 0 {
|
||||
stateStr = fmt.Sprintf("Connected: %s (%s)", a.status.Self.HostName, a.status.Self.TailscaleIPs[0])
|
||||
} else {
|
||||
stateStr = "Connected"
|
||||
}
|
||||
systray.SetIcon(iconConnected)
|
||||
systray.SetTooltip("Tailscale-Custom - Connected")
|
||||
case "NeedsLogin":
|
||||
stateStr = "Login required"
|
||||
systray.SetIcon(iconDisconnected)
|
||||
default:
|
||||
stateStr = "Disconnected"
|
||||
systray.SetIcon(iconDisconnected)
|
||||
systray.SetTooltip("Tailscale-Custom - Disconnected")
|
||||
}
|
||||
}
|
||||
statusItem := systray.AddMenuItem(stateStr, "")
|
||||
statusItem.Disable()
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
// --- Connect / Disconnect ---
|
||||
a.connect = systray.AddMenuItem("Connect", "Connect to Tailscale")
|
||||
a.disconnect = systray.AddMenuItem("Disconnect", "Disconnect from Tailscale")
|
||||
a.disconnect.Hide()
|
||||
|
||||
if a.status != nil && a.status.BackendState == "Running" {
|
||||
a.connect.SetTitle("Connected")
|
||||
a.connect.Disable()
|
||||
a.disconnect.Show()
|
||||
a.disconnect.Enable()
|
||||
}
|
||||
|
||||
onClick(ctx, a.connect, func() {
|
||||
log.Println("action: Connect")
|
||||
opCtx, opCancel := context.WithTimeout(a.bgCtx, 10*time.Second)
|
||||
defer opCancel()
|
||||
_, err := a.lc.EditPrefs(opCtx, &ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{WantRunning: true},
|
||||
WantRunningSet: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Connect error: %v", err)
|
||||
} else {
|
||||
log.Println("Connect: OK")
|
||||
}
|
||||
})
|
||||
|
||||
onClick(ctx, a.disconnect, func() {
|
||||
log.Println("action: Disconnect")
|
||||
opCtx, opCancel := context.WithTimeout(a.bgCtx, 10*time.Second)
|
||||
defer opCancel()
|
||||
_, err := a.lc.EditPrefs(opCtx, &ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{WantRunning: false},
|
||||
WantRunningSet: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Disconnect error: %v", err)
|
||||
} else {
|
||||
log.Println("Disconnect: OK")
|
||||
}
|
||||
})
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
// --- Profiles (flat top-level items, same as official systray) ---
|
||||
if len(a.allProfiles) > 0 {
|
||||
accountLabel := "Account"
|
||||
if a.curProfile.Name != "" {
|
||||
accountLabel = a.curProfile.Name
|
||||
}
|
||||
accounts := systray.AddMenuItem(accountLabel, "")
|
||||
time.Sleep(10 * time.Millisecond) // workaround for systray submenu race
|
||||
|
||||
for _, profile := range a.allProfiles {
|
||||
title := profileTitle(profile)
|
||||
var item *systray.MenuItem
|
||||
if profile.ID == a.curProfile.ID {
|
||||
item = accounts.AddSubMenuItemCheckbox(title, "", true)
|
||||
} else {
|
||||
item = accounts.AddSubMenuItem(title, "")
|
||||
}
|
||||
pid := profile.ID
|
||||
onClick(ctx, item, func() {
|
||||
log.Printf("action: switch profile %v", pid)
|
||||
opCtx, opCancel := context.WithTimeout(a.bgCtx, 10*time.Second)
|
||||
defer opCancel()
|
||||
if err := a.lc.SwitchProfile(opCtx, pid); err != nil {
|
||||
log.Printf("SwitchProfile error: %v", err)
|
||||
} else {
|
||||
log.Println("SwitchProfile: OK")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
// --- Add Server ---
|
||||
addServerItem := systray.AddMenuItem("Add Server...", "Connect to a new control server")
|
||||
onClick(ctx, addServerItem, func() {
|
||||
log.Println("action: Add Server")
|
||||
a.addServer()
|
||||
})
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
// --- Quit ---
|
||||
a.quit = systray.AddMenuItem("Quit", "Quit")
|
||||
onClick(ctx, a.quit, func() {
|
||||
log.Println("action: Quit")
|
||||
systray.Quit()
|
||||
})
|
||||
|
||||
// --- Rebuild listener ---
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("PANIC in rebuild listener: %v", r)
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-a.rebuildCh:
|
||||
log.Println("rebuild triggered by IPNBus")
|
||||
a.updateState()
|
||||
a.rebuild()
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("rebuild: done (state=%q, profiles=%d)", stateStr, len(a.allProfiles))
|
||||
}
|
||||
|
||||
func (a *app) addServer() {
|
||||
serverURL := inputDialog("Add Server", "Enter the control server URL (e.g. https://vpn.softs.business)")
|
||||
if serverURL == "" {
|
||||
log.Println("addServer: cancelled")
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(serverURL, "http://") && !strings.HasPrefix(serverURL, "https://") {
|
||||
serverURL = "https://" + serverURL
|
||||
}
|
||||
log.Printf("addServer: url=%s", serverURL)
|
||||
|
||||
opCtx, opCancel := context.WithTimeout(a.bgCtx, 15*time.Second)
|
||||
defer opCancel()
|
||||
|
||||
if err := a.lc.SwitchToEmptyProfile(opCtx); err != nil {
|
||||
log.Printf("addServer: SwitchToEmptyProfile error: %v", err)
|
||||
showError("Failed to create profile: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err := a.lc.EditPrefs(opCtx, &ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{
|
||||
ControlURL: serverURL,
|
||||
WantRunning: true,
|
||||
},
|
||||
ControlURLSet: true,
|
||||
WantRunningSet: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("addServer: EditPrefs error: %v", err)
|
||||
showError("Failed to set server: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.lc.StartLoginInteractive(opCtx); err != nil {
|
||||
log.Printf("addServer: StartLoginInteractive error: %v", err)
|
||||
tailscaleExe := findTailscaleExe()
|
||||
exec.Command(tailscaleExe, "login", "--login-server", serverURL).Start()
|
||||
}
|
||||
log.Println("addServer: done")
|
||||
a.triggerRebuild()
|
||||
}
|
||||
|
||||
func (a *app) watchIPNBus() {
|
||||
for {
|
||||
err := a.watchIPNBusInner()
|
||||
if err != nil {
|
||||
log.Printf("watchIPNBus error: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-a.bgCtx.Done():
|
||||
return
|
||||
case <-time.After(3 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) watchIPNBusInner() error {
|
||||
watcher, err := a.lc.WatchIPNBus(a.bgCtx, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer watcher.Close()
|
||||
for {
|
||||
select {
|
||||
case <-a.bgCtx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
n, err := watcher.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n.State != nil || n.Prefs != nil {
|
||||
a.triggerRebuild()
|
||||
}
|
||||
if url := n.BrowseToURL; url != nil {
|
||||
exec.Command("rundll32", "url.dll,FileProtocolHandler", *url).Start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) triggerRebuild() {
|
||||
select {
|
||||
case a.rebuildCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func profileTitle(p ipn.LoginProfile) string {
|
||||
name := p.Name
|
||||
if name == "" {
|
||||
name = "(new profile)"
|
||||
}
|
||||
if p.NetworkProfile.DomainName != "" {
|
||||
name += " (" + p.NetworkProfile.DisplayNameOrDefault() + ")"
|
||||
}
|
||||
if p.ControlURL != "" {
|
||||
u := strings.TrimPrefix(p.ControlURL, "https://")
|
||||
u = strings.TrimPrefix(u, "http://")
|
||||
u = strings.TrimSuffix(u, "/")
|
||||
name += " [" + u + "]"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func findTailscaleExe() string {
|
||||
exe, err := os.Executable()
|
||||
if err == nil {
|
||||
candidate := filepath.Join(filepath.Dir(exe), "tailscale.exe")
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return "tailscale.exe"
|
||||
}
|
||||
|
||||
// ============ Windows Dialogs ============
|
||||
|
||||
var (
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
pMessageBoxW = user32.NewProc("MessageBoxW")
|
||||
pDialogBoxIndirectParamW = user32.NewProc("DialogBoxIndirectParamW")
|
||||
pEndDialog = user32.NewProc("EndDialog")
|
||||
pGetDlgItemTextW = user32.NewProc("GetDlgItemTextW")
|
||||
pSetDlgItemTextW = user32.NewProc("SetDlgItemTextW")
|
||||
pGetDlgItem = user32.NewProc("GetDlgItem")
|
||||
pSendMessageW = user32.NewProc("SendMessageW")
|
||||
pSetForegroundWindow = user32.NewProc("SetForegroundWindow")
|
||||
)
|
||||
|
||||
var (
|
||||
dlgInputResult string
|
||||
inputDlgCb = windows.NewCallback(inputDlgProcFn)
|
||||
)
|
||||
|
||||
func showError(msg string) {
|
||||
log.Printf("ERROR: %s", msg)
|
||||
title, _ := windows.UTF16PtrFromString("Tailscale-Custom")
|
||||
text, _ := windows.UTF16PtrFromString(msg)
|
||||
pMessageBoxW.Call(0, uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(title)),
|
||||
uintptr(0x00000000|0x00000010)) // MB_OK | MB_ICONERROR
|
||||
}
|
||||
|
||||
func inputDlgProcFn(hwnd, msg, wParam, lParam uintptr) uintptr {
|
||||
const (
|
||||
wmInitDialog = 0x0110
|
||||
wmCommand = 0x0111
|
||||
wmClose = 0x0010
|
||||
idEdit = 101
|
||||
emSetSel = 0x00B1
|
||||
)
|
||||
switch msg {
|
||||
case wmInitDialog:
|
||||
pSetForegroundWindow.Call(hwnd)
|
||||
defText, _ := windows.UTF16PtrFromString("https://")
|
||||
pSetDlgItemTextW.Call(hwnd, idEdit, uintptr(unsafe.Pointer(defText)))
|
||||
editHwnd, _, _ := pGetDlgItem.Call(hwnd, idEdit)
|
||||
pSendMessageW.Call(editHwnd, emSetSel, 8, 8)
|
||||
return 1
|
||||
case wmCommand:
|
||||
switch int(wParam & 0xFFFF) {
|
||||
case 1: // IDOK
|
||||
buf := make([]uint16, 512)
|
||||
pGetDlgItemTextW.Call(hwnd, idEdit, uintptr(unsafe.Pointer(&buf[0])), 512)
|
||||
dlgInputResult = windows.UTF16ToString(buf)
|
||||
pEndDialog.Call(hwnd, 1)
|
||||
case 2: // IDCANCEL
|
||||
dlgInputResult = ""
|
||||
pEndDialog.Call(hwnd, 0)
|
||||
}
|
||||
case wmClose:
|
||||
dlgInputResult = ""
|
||||
pEndDialog.Call(hwnd, 0)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func inputDialog(title, prompt string) string {
|
||||
log.Printf("inputDialog: title=%q", title)
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
dlgInputResult = ""
|
||||
tmpl := buildInputDialogTemplate(title, prompt)
|
||||
|
||||
ret, _, _ := pDialogBoxIndirectParamW.Call(
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&tmpl[0])),
|
||||
0,
|
||||
inputDlgCb,
|
||||
0,
|
||||
)
|
||||
|
||||
log.Printf("inputDialog: result=%q ret=%d", dlgInputResult, ret)
|
||||
if ret == 0 {
|
||||
return ""
|
||||
}
|
||||
result := strings.TrimSpace(dlgInputResult)
|
||||
if result == "" || result == "https://" {
|
||||
return ""
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type dlgBuilder struct{ buf []byte }
|
||||
|
||||
func (d *dlgBuilder) align(n int) {
|
||||
for len(d.buf)%n != 0 {
|
||||
d.buf = append(d.buf, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dlgBuilder) w16(v uint16) {
|
||||
d.buf = append(d.buf, byte(v), byte(v>>8))
|
||||
}
|
||||
|
||||
func (d *dlgBuilder) w32(v uint32) {
|
||||
d.buf = append(d.buf, byte(v), byte(v>>8), byte(v>>16), byte(v>>24))
|
||||
}
|
||||
|
||||
func (d *dlgBuilder) ws16(v int16) { d.w16(uint16(v)) }
|
||||
|
||||
func (d *dlgBuilder) wstr(s string) {
|
||||
for _, c := range s {
|
||||
d.w16(uint16(c))
|
||||
}
|
||||
d.w16(0)
|
||||
}
|
||||
|
||||
func buildInputDialogTemplate(title, prompt string) []byte {
|
||||
const (
|
||||
wsChild = 0x40000000
|
||||
wsVisible = 0x10000000
|
||||
wsCaption = 0x00C00000
|
||||
wsSysMenu = 0x00080000
|
||||
wsPopup = 0x80000000
|
||||
bsPushBtn = 0x00000000
|
||||
wsTabStop = 0x00010000
|
||||
wsBorder = 0x00800000
|
||||
esAutoHS = 0x00000080
|
||||
dsSetFont = 0x00000040
|
||||
dsModalFrm = 0x00000080
|
||||
ds3DLook = 0x00000004
|
||||
)
|
||||
d := &dlgBuilder{}
|
||||
|
||||
// DLGTEMPLATE
|
||||
d.w32(wsPopup | wsCaption | wsSysMenu | dsSetFont | dsModalFrm | ds3DLook) // style
|
||||
d.w32(0) // exstyle
|
||||
d.w16(4) // cdit (4 controls)
|
||||
d.ws16(0); d.ws16(0); d.ws16(250); d.ws16(85) // x, y, cx, cy
|
||||
d.w16(0) // menu
|
||||
d.w16(0) // class
|
||||
d.wstr(title) // title
|
||||
d.w16(9) // font size
|
||||
d.wstr("Segoe UI") // font name
|
||||
|
||||
// Static label id=100
|
||||
d.align(4)
|
||||
d.w32(wsChild | wsVisible); d.w32(0)
|
||||
d.ws16(10); d.ws16(10); d.ws16(230); d.ws16(14)
|
||||
d.w16(100); d.w16(0xFFFF); d.w16(0x0082)
|
||||
d.wstr(prompt); d.w16(0)
|
||||
|
||||
// Edit id=101
|
||||
d.align(4)
|
||||
d.w32(wsChild | wsVisible | wsTabStop | wsBorder | esAutoHS); d.w32(0)
|
||||
d.ws16(10); d.ws16(30); d.ws16(230); d.ws16(14)
|
||||
d.w16(101); d.w16(0xFFFF); d.w16(0x0081)
|
||||
d.w16(0); d.w16(0)
|
||||
|
||||
// OK id=1
|
||||
d.align(4)
|
||||
d.w32(wsChild | wsVisible | wsTabStop | bsPushBtn); d.w32(0)
|
||||
d.ws16(127); d.ws16(58); d.ws16(50); d.ws16(14)
|
||||
d.w16(1); d.w16(0xFFFF); d.w16(0x0080)
|
||||
d.wstr("OK"); d.w16(0)
|
||||
|
||||
// Cancel id=2
|
||||
d.align(4)
|
||||
d.w32(wsChild | wsVisible | wsTabStop); d.w32(0)
|
||||
d.ws16(183); d.ws16(58); d.ws16(50); d.ws16(14)
|
||||
d.w16(2); d.w16(0xFFFF); d.w16(0x0080)
|
||||
d.wstr("Cancel"); d.w16(0)
|
||||
|
||||
return d.buf
|
||||
}
|
||||
@@ -299,7 +299,7 @@ func debugCmd() *ffcli.Command {
|
||||
ShortHelp: "Debug ts2021 protocol connectivity",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("ts2021")
|
||||
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
|
||||
fs.StringVar(&ts2021Args.host, "host", "vpn.softs.business", "hostname of control plane")
|
||||
fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version")
|
||||
fs.BoolVar(&ts2021Args.verbose, "verbose", false, "be extra verbose")
|
||||
fs.StringVar(&ts2021Args.aceHost, "ace", "", "if non-empty, use this ACE server IP/hostname as a candidate path")
|
||||
@@ -984,7 +984,7 @@ func runVia(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
var ts2021Args struct {
|
||||
host string // "controlplane.tailscale.com"
|
||||
host string // "vpn.softs.business"
|
||||
version int // 27 or whatever
|
||||
verbose bool
|
||||
aceHost string // if non-empty, FQDN of https ACE server to use ("ace.example.com")
|
||||
|
||||
@@ -151,7 +151,7 @@ func changeDeltaWatcher(ec *eventbus.Client, ctx context.Context, dump func(st *
|
||||
|
||||
func getURL(ctx context.Context, urlStr string) error {
|
||||
if urlStr == "login" {
|
||||
urlStr = "https://login.tailscale.com"
|
||||
urlStr = "https://vpn.softs.business"
|
||||
}
|
||||
log.SetOutput(os.Stdout)
|
||||
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
||||
|
||||
@@ -72,7 +72,7 @@ func defaultTunName() string {
|
||||
case "openbsd":
|
||||
return "tun"
|
||||
case "windows":
|
||||
return "Tailscale"
|
||||
return "Tailscale-Custom"
|
||||
case "darwin":
|
||||
// "utun" is recognized by wireguard-go/tun/tun_darwin.go
|
||||
// as a magic value that uses/creates any free number.
|
||||
|
||||
@@ -89,7 +89,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
const serviceName = "Tailscale"
|
||||
const serviceName = "Tailscale-Custom"
|
||||
|
||||
// Application-defined command codes between 128 and 255
|
||||
// See https://web.archive.org/web/20221007222822/https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-controlservice
|
||||
|
||||
@@ -66,7 +66,7 @@ import (
|
||||
|
||||
// Direct is the client that connects to a tailcontrol server for a node.
|
||||
type Direct struct {
|
||||
httpc *http.Client // HTTP client used to do TLS requests to control (just https://controlplane.tailscale.com/key?v=123)
|
||||
httpc *http.Client // HTTP client used to do TLS requests to control (just https://vpn.softs.business/key?v=123)
|
||||
interceptedDial *atomic.Bool // if non-nil, pointer to bool whether ScreenTime intercepted our dial
|
||||
dialer *tsdial.Dialer
|
||||
dnsCache *dnscache.Resolver
|
||||
@@ -362,7 +362,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
}
|
||||
c.serverNoiseKey = key.NewMachine().Public() // prevent early error before hitting test client
|
||||
}
|
||||
if strings.Contains(opts.ServerURL, "controlplane.tailscale.com") && envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
|
||||
if strings.Contains(opts.ServerURL, "vpn.softs.business") && envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
|
||||
c.panicOnUse = true
|
||||
}
|
||||
|
||||
@@ -503,7 +503,7 @@ func (c *Direct) TryLogout(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *Direct) TryLogin(ctx context.Context, flags LoginFlags) (url string, err error) {
|
||||
if strings.Contains(c.serverURL, "controlplane.tailscale.com") && envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
|
||||
if strings.Contains(c.serverURL, "vpn.softs.business") && envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
|
||||
panic(fmt.Sprintf("[unexpected] controlclient: TryLogin called on %s; tainted=%v", c.serverURL, c.panicOnUse))
|
||||
}
|
||||
c.logf("[v1] direct.TryLogin(flags=%v)", flags)
|
||||
|
||||
+1
-1
@@ -607,7 +607,7 @@ func getPlatformEnvFiles() []string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return []string{
|
||||
filepath.Join(os.Getenv("ProgramData"), "Tailscale", "tailscaled-env.txt"),
|
||||
filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom", "tailscaled-env.txt"),
|
||||
}
|
||||
case "linux":
|
||||
if buildfeatures.HasSynology && distro.Get() == distro.Synology {
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
|
||||
|
||||
<!-- Unique UpgradeCode for Tailscale-Custom (different from official Tailscale) -->
|
||||
<Package Name="Tailscale-Custom"
|
||||
Manufacturer="Tailscale-Custom"
|
||||
Version="1.97.176.0"
|
||||
UpgradeCode="E1F2A3B4-C5D6-4E7F-8A9B-0C1D2E3F4A5B"
|
||||
Scope="perMachine"
|
||||
Language="1033"
|
||||
Compressed="yes">
|
||||
|
||||
<SummaryInformation Description="Tailscale-Custom VPN Client" />
|
||||
|
||||
<!-- Allow major upgrades, prevent downgrades -->
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<!-- Install directory structure -->
|
||||
<StandardDirectory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Tailscale-Custom">
|
||||
<!-- All files go here -->
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
|
||||
<!-- Start Menu directory -->
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
<Directory Id="TailscaleCustomMenuFolder" Name="Tailscale-Custom" />
|
||||
</StandardDirectory>
|
||||
|
||||
<!-- Desktop directory -->
|
||||
<StandardDirectory Id="DesktopFolder" />
|
||||
|
||||
<!-- Components -->
|
||||
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
|
||||
|
||||
<!-- tailscaled.exe - the daemon/service -->
|
||||
<Component Id="TailscaledExe" Guid="A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D" Bitness="always64">
|
||||
<File Id="tailscaled.exe"
|
||||
Source="$(var.DistDir)\tailscaled.exe"
|
||||
KeyPath="yes" />
|
||||
|
||||
<!-- Register as Windows Service -->
|
||||
<ServiceInstall Id="TailscaleCustomService"
|
||||
Name="Tailscale-Custom"
|
||||
DisplayName="Tailscale-Custom"
|
||||
Description="Connects this computer to others on the Tailscale-Custom network."
|
||||
Type="ownProcess"
|
||||
Start="auto"
|
||||
ErrorControl="normal"
|
||||
Account="LocalSystem">
|
||||
<ServiceDependency Id="Dnscache" />
|
||||
<ServiceDependency Id="iphlpsvc" />
|
||||
<ServiceDependency Id="netprofm" />
|
||||
<ServiceDependency Id="WinHttpAutoProxySvc" />
|
||||
<util:ServiceConfig FirstFailureActionType="restart"
|
||||
SecondFailureActionType="restart"
|
||||
ThirdFailureActionType="restart"
|
||||
RestartServiceDelayInSeconds="5"
|
||||
ResetPeriodInDays="1" />
|
||||
</ServiceInstall>
|
||||
|
||||
<!-- Control service lifecycle during install/uninstall -->
|
||||
<ServiceControl Id="TailscaleCustomServiceControl"
|
||||
Name="Tailscale-Custom"
|
||||
Start="install"
|
||||
Stop="both"
|
||||
Remove="uninstall"
|
||||
Wait="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- tailscale.exe - the CLI -->
|
||||
<Component Id="TailscaleExe" Guid="B2C3D4E5-F6A7-4B8C-9D0E-1F2A3B4C5D6E" Bitness="always64">
|
||||
<File Id="tailscale.exe"
|
||||
Source="$(var.DistDir)\tailscale.exe"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- wintun.dll - TUN driver required by tailscaled -->
|
||||
<Component Id="WintunDll" Guid="A7B8C9D0-E1F2-4A3B-4C5D-6E7F8A9B0C1D" Bitness="always64">
|
||||
<File Id="wintun.dll"
|
||||
Source="$(var.DistDir)\wintun.dll"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- Add install dir to system PATH -->
|
||||
<Component Id="PathEnv" Guid="C3D4E5F6-A7B8-4C9D-0E1F-2A3B4C5D6E7F" Bitness="always64">
|
||||
<Environment Id="PATH"
|
||||
Name="PATH"
|
||||
Value="[INSTALLFOLDER]"
|
||||
Permanent="no"
|
||||
Part="last"
|
||||
Action="set"
|
||||
System="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- tailscale-tray.exe - the system tray GUI -->
|
||||
<Component Id="TailscaleTrayExe" Guid="D4E5F6A7-B8C9-4D0E-1F2A-3B4C5D6E7F8A" Bitness="always64">
|
||||
<File Id="tailscale_tray.exe"
|
||||
Source="$(var.DistDir)\tailscale-tray.exe"
|
||||
KeyPath="yes" />
|
||||
|
||||
<!-- Auto-start tray app on login for all users -->
|
||||
<RegistryValue Root="HKLM"
|
||||
Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
|
||||
Name="Tailscale-Custom-Tray"
|
||||
Type="string"
|
||||
Value=""[INSTALLFOLDER]tailscale-tray.exe"" />
|
||||
</Component>
|
||||
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- Start Menu Shortcut -->
|
||||
<ComponentGroup Id="StartMenuShortcuts" Directory="TailscaleCustomMenuFolder">
|
||||
<Component Id="StartMenuShortcut" Guid="E5F6A7B8-C9D0-4E1F-2A3B-4C5D6E7F8A9B" Bitness="always64">
|
||||
<Shortcut Id="TrayStartMenuShortcut"
|
||||
Name="Tailscale-Custom"
|
||||
Description="Tailscale-Custom VPN Client"
|
||||
Target="[INSTALLFOLDER]tailscale-tray.exe"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="RemoveStartMenuFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Tailscale-Custom" Name="StartMenuInstalled" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- Desktop Shortcut -->
|
||||
<ComponentGroup Id="DesktopShortcuts" Directory="DesktopFolder">
|
||||
<Component Id="DesktopShortcut" Guid="F6A7B8C9-D0E1-4F2A-3B4C-5D6E7F8A9B0C" Bitness="always64">
|
||||
<Shortcut Id="TrayDesktopShortcut"
|
||||
Name="Tailscale-Custom"
|
||||
Description="Tailscale-Custom VPN Client"
|
||||
Target="[INSTALLFOLDER]tailscale-tray.exe"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Tailscale-Custom" Name="DesktopInstalled" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- Feature definition -->
|
||||
<Feature Id="ProductFeature" Title="Tailscale-Custom" Level="1">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
<ComponentGroupRef Id="StartMenuShortcuts" />
|
||||
<ComponentGroupRef Id="DesktopShortcuts" />
|
||||
</Feature>
|
||||
|
||||
<!-- Launch tray app after install -->
|
||||
<CustomAction Id="LaunchTray"
|
||||
FileRef="tailscale_tray.exe"
|
||||
ExeCommand=""
|
||||
Impersonate="yes"
|
||||
Return="asyncNoWait" />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="LaunchTray" After="InstallFinalize" Condition="NOT Installed OR REINSTALL" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- UI: minimal (progress bar only, no wizard dialogs) -->
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
|
||||
</Package>
|
||||
</Wix>
|
||||
@@ -32,7 +32,7 @@ func SetStoreFilePath(path string) {
|
||||
func DefaultStoreFilePath() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "audit-log.json"), nil
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom", "audit-log.json"), nil
|
||||
default:
|
||||
// The auditlog package must either be omitted from the build,
|
||||
// have the platform-specific store path set with [SetStoreFilePath] (e.g., on macOS),
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ type ConfigVAlpha struct {
|
||||
Version string // "alpha0" for now
|
||||
Locked opt.Bool `json:",omitempty"` // whether the config is locked from being changed by 'tailscale set'; it defaults to true
|
||||
|
||||
ServerURL *string `json:",omitempty"` // defaults to https://controlplane.tailscale.com
|
||||
ServerURL *string `json:",omitempty"` // defaults to https://vpn.softs.business
|
||||
AuthKey *string `json:",omitempty"` // as needed if NeedsLogin. either key or path to a file (if prefixed with "file:")
|
||||
Enabled opt.Bool `json:",omitempty"` // wantRunning; empty string defaults to true
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ import (
|
||||
// DefaultControlURL is the URL base of the control plane
|
||||
// ("coordination server") for use when no explicit one is configured.
|
||||
// The default control plane is the hosted version run by Tailscale.com.
|
||||
const DefaultControlURL = "https://controlplane.tailscale.com"
|
||||
const DefaultControlURL = "https://vpn.softs.business"
|
||||
|
||||
var (
|
||||
// ErrExitNodeIDAlreadySet is returned from (*Prefs).SetExitNodeIP when the
|
||||
@@ -49,7 +49,7 @@ var (
|
||||
// IsLoginServerSynonym reports whether a URL is a drop-in replacement
|
||||
// for the primary Tailscale login server.
|
||||
func IsLoginServerSynonym(val any) bool {
|
||||
return val == "https://login.tailscale.com" || val == "https://controlplane.tailscale.com"
|
||||
return val == "https://login.tailscale.com" || val == "https://controlplane.tailscale.com" || val == "https://vpn.softs.business"
|
||||
}
|
||||
|
||||
// Prefs are the user modifiable settings of the Tailscale node agent.
|
||||
|
||||
@@ -34,7 +34,7 @@ func New(fileBasePrefix, logID string, logf logger.Logf) logger.Logf {
|
||||
if logf == nil {
|
||||
panic("nil logf")
|
||||
}
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale", "Logs")
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom", "Logs")
|
||||
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Printf("failed to create local log directory; not writing logs to disk: %v", err)
|
||||
|
||||
@@ -218,13 +218,13 @@ func LogsDir(logf logger.Logf) string {
|
||||
// as a regular user (perhaps in userspace-networking/SOCK5 mode) and we should
|
||||
// just use the %LocalAppData% instead. In a user context, %LocalAppData% isn't
|
||||
// subject to random deletions from Windows system updates.
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale")
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom")
|
||||
if winProgramDataAccessible(dir) {
|
||||
logf("logpolicy: using dir %v", dir)
|
||||
return dir
|
||||
}
|
||||
}
|
||||
dir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale")
|
||||
dir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale-Custom")
|
||||
logf("logpolicy: using LocalAppData dir %v", dir)
|
||||
return dir
|
||||
case "linux":
|
||||
@@ -254,7 +254,7 @@ func LogsDir(logf logger.Logf) string {
|
||||
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err == nil {
|
||||
d := filepath.Join(cacheDir, "Tailscale")
|
||||
d := filepath.Join(cacheDir, "Tailscale-Custom")
|
||||
logf("logpolicy: using UserCacheDir, %q", d)
|
||||
return d
|
||||
}
|
||||
@@ -578,8 +578,8 @@ func (opts Options) init(disableLogging bool) (*logtail.Config, *Policy) {
|
||||
// Machines which started using Tailscale more recently will have
|
||||
// %LocalAppData%\tailscaled.log.conf
|
||||
//
|
||||
// Attempt to migrate the log conf to C:\ProgramData\Tailscale
|
||||
oldDir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale")
|
||||
// Attempt to migrate the log conf to C:\ProgramData\Tailscale-Custom
|
||||
oldDir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale-Custom")
|
||||
|
||||
oldPath := filepath.Join(oldDir, "tailscaled.log.conf")
|
||||
if fi, err := os.Stat(oldPath); err != nil || !fi.Mode().IsRegular() {
|
||||
|
||||
@@ -121,7 +121,7 @@ func availableEndpoints(derpMap *tailcfg.DERPMap, preferredDERPRegionID int, log
|
||||
}
|
||||
endpoints = append(endpoints, Endpoint{u, http.StatusNoContent, "", false, Tailscale})
|
||||
}
|
||||
appendTailscaleEndpoint("http://controlplane.tailscale.com/generate_204")
|
||||
appendTailscaleEndpoint("http://vpn.softs.business/generate_204")
|
||||
appendTailscaleEndpoint("http://login.tailscale.com/generate_204")
|
||||
|
||||
// Sort the endpoints by provider so that we can prioritize DERP nodes in the preferred region, followed by
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ var forceAllIPv6Endpoints = envknob.RegisterBool("TS_DEBUG_FORCE_ALL_IPV6_ENDPOI
|
||||
|
||||
// LoginEndpointForProxyDetermination is the URL used for testing
|
||||
// which HTTP proxy the system should use.
|
||||
var LoginEndpointForProxyDetermination = "https://controlplane.tailscale.com/"
|
||||
var LoginEndpointForProxyDetermination = "https://vpn.softs.business/"
|
||||
|
||||
func isUp(nif *net.Interface) bool { return nif.Flags&net.FlagUp != 0 }
|
||||
func isLoopback(nif *net.Interface) bool { return nif.Flags&net.FlagLoopback != 0 }
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
tun.WintunTunnelType = "Tailscale"
|
||||
guid, err := windows.GUIDFromString("{37217669-42da-4657-a55b-0d995d328250}")
|
||||
tun.WintunTunnelType = "Tailscale-Custom"
|
||||
guid, err := windows.GUIDFromString("{47317669-42da-4657-a55b-0d995d328250}")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@ var AppSharedDir syncs.AtomicValue[string]
|
||||
// or the empty string if there's no reasonable default.
|
||||
func DefaultTailscaledSocket() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return `\\.\pipe\ProtectedPrefix\Administrators\Tailscale\tailscaled`
|
||||
return `\\.\pipe\ProtectedPrefix\Administrators\Tailscale-Custom\tailscaled`
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
return "/var/run/tailscaled.socket"
|
||||
@@ -66,7 +66,7 @@ func DefaultTailscaledStateFile() string {
|
||||
return f()
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "server-state.conf")
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale-Custom", "server-state.conf")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func MkStateDir(dirPath string) error {
|
||||
// It is only called on Windows.
|
||||
func LegacyStateFilePath() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
|
||||
return filepath.Join(os.Getenv("LocalAppData"), "Tailscale-Custom", "server-state.conf")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func ensureStateDirPermsWindows(dirPath string) error {
|
||||
if !fi.IsDir() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if strings.ToLower(filepath.Base(dirPath)) != "tailscale" {
|
||||
if strings.ToLower(filepath.Base(dirPath)) != "tailscale-custom" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
const (
|
||||
softwareKeyName = `Software`
|
||||
tsPoliciesSubkey = `Policies\Tailscale`
|
||||
tsIPNSubkey = `Tailscale IPN` // the legacy key we need to fallback to
|
||||
tsPoliciesSubkey = `Policies\Tailscale-Custom`
|
||||
tsIPNSubkey = `Tailscale-Custom IPN` // the legacy key we need to fallback to
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -178,7 +178,7 @@ func convertPolicySettingValueTo[T setting.ValueType](value any, def T) (T, erro
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/2798 for some background.
|
||||
func SelectControlURL(reg, disk string) string {
|
||||
const def = "https://controlplane.tailscale.com"
|
||||
const def = "https://vpn.softs.business"
|
||||
|
||||
// Prior to Dec 2020's commit 739b02e6, the installer
|
||||
// wrote a LoginURL value of https://login.tailscale.com to the registry.
|
||||
|
||||
@@ -120,7 +120,7 @@ func GetDurationPolicy(name string, defaultValue time.Duration) time.Duration {
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/2798 for some background.
|
||||
func SelectControlURL(reg, disk string) string {
|
||||
const def = "https://controlplane.tailscale.com"
|
||||
const def = "https://vpn.softs.business"
|
||||
|
||||
// Prior to Dec 2020's commit 739b02e6, the installer
|
||||
// wrote a LoginURL value of https://login.tailscale.com to the registry.
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
regBase = `SOFTWARE\Tailscale IPN`
|
||||
regPolicyBase = `SOFTWARE\Policies\Tailscale`
|
||||
regBase = `SOFTWARE\Tailscale-Custom IPN`
|
||||
regPolicyBase = `SOFTWARE\Policies\Tailscale-Custom`
|
||||
)
|
||||
|
||||
// ErrNoShell is returned when the shell process is not found.
|
||||
|
||||
Reference in New Issue
Block a user