Skip to content

Commit 6f7e871

Browse files
committed
Merge branch 'dev'
2 parents 24cee79 + 382baf5 commit 6f7e871

10 files changed

Lines changed: 171 additions & 29 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ slipgate stats # Live dashboard (CPU, RAM, traffic, connections
7979
8080
# Tunnel management
8181
slipgate tunnel add # Add tunnel(s) — supports multi-select and "both" backend
82-
slipgate tunnel edit [tag] # Edit tunnel settings (MTU, keys)
82+
slipgate tunnel edit [tag] # Edit tunnel settings (tag, MTU, keys)
8383
slipgate tunnel remove [tag] # Remove a tunnel
8484
slipgate tunnel start [tag] # Start a tunnel
8585
slipgate tunnel stop [tag] # Stop a tunnel
@@ -168,6 +168,9 @@ sudo slipgate tunnel add \
168168
sudo slipgate tunnel add --transport direct-ssh --tag myssh
169169
sudo slipgate tunnel add --transport direct-socks5 --tag mysocks
170170

171+
# Rename a tunnel
172+
sudo slipgate tunnel edit --tag mydnstt --new-tag my-tunnel
173+
171174
# Change MTU on a DNSTT tunnel
172175
sudo slipgate tunnel edit --tag mydnstt --mtu 1232
173176

internal/actions/tunnel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func init() {
8383
Category: "tunnel",
8484
Inputs: []InputField{
8585
{Key: "tag", Label: "Tunnel tag", Required: true},
86+
{Key: "new-tag", Label: "New tag name"},
8687
{Key: "domain", Label: "Domain"},
8788
{Key: "mtu", Label: "MTU"},
8889
{Key: "private-key", Label: "Private key (hex)"},

internal/config/config.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"sync"
@@ -123,6 +124,20 @@ func (c *Config) GetTunnel(tag string) *TunnelConfig {
123124
return nil
124125
}
125126

127+
// UniqueTag returns a tag that doesn't conflict with existing tunnels.
128+
// If base is available it is returned as-is, otherwise a numeric suffix is appended.
129+
func (c *Config) UniqueTag(base string) string {
130+
if c.GetTunnel(base) == nil {
131+
return base
132+
}
133+
for i := 2; ; i++ {
134+
candidate := fmt.Sprintf("%s-%d", base, i)
135+
if c.GetTunnel(candidate) == nil {
136+
return candidate
137+
}
138+
}
139+
}
140+
126141
// AddTunnel adds a tunnel to the config.
127142
func (c *Config) AddTunnel(t TunnelConfig) {
128143
c.mu.Lock()

internal/config/validation.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ func (c *Config) ValidateNewTunnel(t *TunnelConfig) error {
7676
return validateTransportBackend(t.Transport, t.Backend)
7777
}
7878

79+
// ValidateTagName checks if a tag name is valid.
80+
func ValidateTagName(tag string) error {
81+
return validateTag(tag)
82+
}
83+
7984
func validateTag(tag string) error {
8085
if tag == "" {
8186
return fmt.Errorf("tag is required")

internal/dnsrouter/service.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
const serviceName = "slipgate-dnsrouter"
1111

12-
// CreateRouterService creates the systemd service for the DNS router.
12+
// CreateRouterService creates and enables the systemd service for the DNS router.
1313
func CreateRouterService() error {
1414
execPath, err := os.Executable()
1515
if err != nil {
@@ -26,7 +26,10 @@ func CreateRouterService() error {
2626
Restart: "always",
2727
}
2828

29-
return service.Create(unit)
29+
if err := service.Create(unit); err != nil {
30+
return err
31+
}
32+
return service.Start(serviceName)
3033
}
3134

3235
// StartRouterService starts the DNS router service.

internal/handlers/quick_wizard.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,11 @@ func handleQuickWizard(ctx *actions.Context) error {
224224

225225
for _, s := range allSettings {
226226
for _, b := range s.backends {
227-
tag := s.transport
227+
tag := cfg.UniqueTag(s.transport)
228228
tunnelDomain := s.domain
229229

230230
if s.backend == "both" {
231-
tag = s.transport + "-" + b
231+
tag = cfg.UniqueTag(s.transport + "-" + b)
232232
if b == config.BackendSSH && s.transport != config.TransportNaive {
233233
parentDomain := baseDomain(s.domain)
234234
sshHint := "ts." + parentDomain

internal/handlers/system_install.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,13 @@ func handleSystemInstall(ctx *actions.Context) error {
131131
}
132132
}
133133

134-
// Write default config
135-
cfg := config.Default()
136-
if err := cfg.Save(); err != nil {
137-
return actions.NewError(actions.SystemInstall, "failed to write config", err)
134+
// Load existing config or create defaults for fresh install
135+
cfg, err := config.Load()
136+
if err != nil {
137+
cfg = config.Default()
138+
if err := cfg.Save(); err != nil {
139+
return actions.NewError(actions.SystemInstall, "failed to write config", err)
140+
}
138141
}
139142

140143
out.Print("")
@@ -189,7 +192,7 @@ func handleSystemInstall(ctx *actions.Context) error {
189192
implicitBackend = config.BackendSOCKS
190193
}
191194

192-
tag := selectedTransport
195+
tag := cfg.UniqueTag(selectedTransport)
193196
tunnel := config.TunnelConfig{
194197
Tag: tag,
195198
Transport: selectedTransport,
@@ -275,10 +278,10 @@ func handleSystemInstall(ctx *actions.Context) error {
275278
}
276279

277280
for bIdx, b := range backends {
278-
tag := selectedTransport
281+
tag := cfg.UniqueTag(selectedTransport)
279282
tunnelDomain := domain
280283
if backend == "both" {
281-
tag = selectedTransport + "-" + b
284+
tag = cfg.UniqueTag(selectedTransport + "-" + b)
282285
// SSH backend needs its own subdomain (separate dnstt/slipstream instance)
283286
if b == config.BackendSSH && selectedTransport != config.TransportNaive {
284287
parentDomain := baseDomain(domain)
@@ -625,6 +628,9 @@ func handleSystemInstall(ctx *actions.Context) error {
625628
if len(allTunnels) > 0 && allTunnels[0].DNSTT != nil {
626629
out.Print(fmt.Sprintf(" Public Key: %s", allTunnels[0].DNSTT.PublicKey))
627630
out.Print(fmt.Sprintf(" MTU : %d", allTunnels[0].DNSTT.MTU))
631+
} else if len(allTunnels) > 0 && allTunnels[0].VayDNS != nil {
632+
out.Print(fmt.Sprintf(" Public Key: %s", allTunnels[0].VayDNS.PublicKey))
633+
out.Print(fmt.Sprintf(" MTU : %d", allTunnels[0].VayDNS.MTU))
628634
}
629635

630636
out.Print("")

internal/handlers/system_update.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,21 @@ func handleSystemUpdate(ctx *actions.Context) error {
6262

6363
out.Info(fmt.Sprintf(" Updating %s...", name))
6464

65-
// Remove old binary and re-download
66-
os.Remove(binPath)
65+
// Backup old binary before replacing so we can restore on failure
66+
backupPath := binPath + ".bak"
67+
if err := os.Rename(binPath, backupPath); err != nil {
68+
out.Warning(fmt.Sprintf(" Failed to backup %s: %v", name, err))
69+
continue
70+
}
6771
if err := binary.EnsureInstalled(name); err != nil {
68-
out.Warning(fmt.Sprintf(" Failed to update %s: %v", name, err))
72+
// Restore from backup
73+
if restoreErr := os.Rename(backupPath, binPath); restoreErr != nil {
74+
out.Warning(fmt.Sprintf(" Failed to restore %s backup: %v", name, restoreErr))
75+
}
76+
out.Warning(fmt.Sprintf(" Failed to update %s: %v (kept old version)", name, err))
6977
continue
7078
}
79+
os.Remove(backupPath)
7180
out.Success(fmt.Sprintf(" %s updated", name))
7281
}
7382

@@ -140,18 +149,7 @@ func handleSystemUpdate(ctx *actions.Context) error {
140149
}
141150
}
142151

143-
// Restart infrastructure services first (DNS router needs port 53 before tunnels)
144-
for _, svc := range []string{"slipgate-dnsrouter", "slipgate-socks5"} {
145-
if service.Exists(svc) {
146-
if err := service.Restart(svc); err != nil {
147-
out.Warning(fmt.Sprintf("Failed to restart %s: %v", svc, err))
148-
} else {
149-
out.Success(fmt.Sprintf(" %s restarted", svc))
150-
}
151-
}
152-
}
153-
154-
// Then regenerate and restart tunnel services
152+
// Regenerate and restart tunnel services first
155153
for i := range cfg.Tunnels {
156154
t := &cfg.Tunnels[i]
157155
if t.IsDirectTransport() {
@@ -174,6 +172,18 @@ func handleSystemUpdate(ctx *actions.Context) error {
174172
}
175173
}
176174

175+
// Restart infrastructure services last so tunnels are ready when
176+
// the DNS router and SOCKS5 proxy come back up
177+
for _, svc := range []string{"slipgate-socks5", "slipgate-dnsrouter"} {
178+
if service.Exists(svc) {
179+
if err := service.Restart(svc); err != nil {
180+
out.Warning(fmt.Sprintf("Failed to restart %s: %v", svc, err))
181+
} else {
182+
out.Success(fmt.Sprintf(" %s restarted", svc))
183+
}
184+
}
185+
}
186+
177187
out.Print("")
178188
out.Success("Update complete!")
179189
return nil

internal/handlers/tunnel_add.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"strings"
78

89
"github.com/anonvector/slipgate/internal/actions"
910
"github.com/anonvector/slipgate/internal/certs"
@@ -70,6 +71,8 @@ func handleTunnelAdd(ctx *actions.Context) error {
7071
backends = []string{config.BackendSOCKS, config.BackendSSH}
7172
}
7273

74+
var failed, succeeded []string
75+
var sharedKeyDir string
7376
for _, b := range backends {
7477
tunnelTag := tag
7578
tunnelDomain := domain
@@ -93,15 +96,31 @@ func handleTunnelAdd(ctx *actions.Context) error {
9396
}
9497
}
9598

96-
if err := addSingleTunnel(ctx, cfg, transport_, b, tunnelTag, tunnelDomain); err != nil {
99+
if err := addSingleTunnel(ctx, cfg, transport_, b, tunnelTag, tunnelDomain, sharedKeyDir); err != nil {
97100
out.Warning(fmt.Sprintf("Failed to add %s: %v", tunnelTag, err))
101+
failed = append(failed, tunnelTag)
102+
} else {
103+
succeeded = append(succeeded, tunnelTag)
104+
if sharedKeyDir == "" {
105+
sharedKeyDir = config.TunnelDir(tunnelTag)
106+
}
98107
}
99108
}
100109

110+
// Final summary
111+
out.Print("")
112+
if len(failed) == 0 {
113+
out.Success(fmt.Sprintf("All %d tunnel(s) added successfully", len(succeeded)))
114+
} else if len(succeeded) == 0 {
115+
return actions.NewError(actions.TunnelAdd, fmt.Sprintf("all %d tunnel(s) failed to add", len(failed)), nil)
116+
} else {
117+
out.Warning(fmt.Sprintf("%d succeeded, %d failed", len(succeeded), len(failed)))
118+
}
119+
101120
return nil
102121
}
103122

104-
func addSingleTunnel(ctx *actions.Context, cfg *config.Config, transport_, backend, tag, domain string) error {
123+
func addSingleTunnel(ctx *actions.Context, cfg *config.Config, transport_, backend, tag, domain, sharedKeyDir string) error {
105124
out := ctx.Output
106125

107126
tunnel := config.TunnelConfig{
@@ -147,6 +166,19 @@ func addSingleTunnel(ctx *actions.Context, cfg *config.Config, transport_, backe
147166
case privKeyHex != "":
148167
out.Info("Importing private key and deriving public key...")
149168
pubKey, err = keys.ImportDNSTTKeys(privKeyHex, privKeyPath, pubKeyPath)
169+
case sharedKeyDir != "":
170+
out.Info("Reusing shared keypair...")
171+
if err := copyFile(filepath.Join(sharedKeyDir, "server.key"), privKeyPath); err != nil {
172+
return actions.NewError(actions.TunnelAdd, "failed to copy shared private key", err)
173+
}
174+
if err := copyFile(filepath.Join(sharedKeyDir, "server.pub"), pubKeyPath); err != nil {
175+
return actions.NewError(actions.TunnelAdd, "failed to copy shared public key", err)
176+
}
177+
pubBytes, err := os.ReadFile(pubKeyPath)
178+
if err != nil {
179+
return actions.NewError(actions.TunnelAdd, "failed to read shared public key", err)
180+
}
181+
pubKey = strings.TrimSpace(string(pubBytes))
150182
default:
151183
out.Info("Generating Curve25519 keypair...")
152184
pubKey, err = keys.GenerateDNSTTKeys(privKeyPath, pubKeyPath)
@@ -191,6 +223,19 @@ func addSingleTunnel(ctx *actions.Context, cfg *config.Config, transport_, backe
191223
case privKeyHex != "":
192224
out.Info("Importing private key and deriving public key...")
193225
pubKey, err = keys.ImportDNSTTKeys(privKeyHex, privKeyPath, pubKeyPath)
226+
case sharedKeyDir != "":
227+
out.Info("Reusing shared keypair...")
228+
if err := copyFile(filepath.Join(sharedKeyDir, "server.key"), privKeyPath); err != nil {
229+
return actions.NewError(actions.TunnelAdd, "failed to copy shared private key", err)
230+
}
231+
if err := copyFile(filepath.Join(sharedKeyDir, "server.pub"), pubKeyPath); err != nil {
232+
return actions.NewError(actions.TunnelAdd, "failed to copy shared public key", err)
233+
}
234+
pubBytes, err := os.ReadFile(pubKeyPath)
235+
if err != nil {
236+
return actions.NewError(actions.TunnelAdd, "failed to read shared public key", err)
237+
}
238+
pubKey = strings.TrimSpace(string(pubBytes))
194239
default:
195240
out.Info("Generating Curve25519 keypair...")
196241
pubKey, err = keys.GenerateDNSTTKeys(privKeyPath, pubKeyPath)

internal/handlers/tunnel_edit.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package handlers
22

33
import (
44
"fmt"
5+
"os"
56
"path/filepath"
67

78
"github.com/anonvector/slipgate/internal/actions"
@@ -32,6 +33,59 @@ func handleTunnelEdit(ctx *actions.Context) error {
3233
out.Print(fmt.Sprintf(" Editing tunnel %q (%s/%s)", tag, tunnel.Transport, tunnel.Backend))
3334
out.Print(fmt.Sprintf(" Press Enter to keep current value\n"))
3435

36+
// Tag rename
37+
newTag := ctx.GetArg("new-tag")
38+
if newTag == "" {
39+
var err error
40+
newTag, err = prompt.String("Tag", tag)
41+
if err != nil {
42+
return err
43+
}
44+
}
45+
if newTag != tag {
46+
if err := config.ValidateTagName(newTag); err != nil {
47+
return actions.NewError(actions.TunnelEdit, "invalid tag name", err)
48+
}
49+
if cfg.GetTunnel(newTag) != nil {
50+
return actions.NewError(actions.TunnelEdit, fmt.Sprintf("tag %q already exists", newTag), nil)
51+
}
52+
53+
// Rename tunnel directory
54+
oldDir := config.TunnelDir(tag)
55+
newDir := config.TunnelDir(newTag)
56+
if _, err := os.Stat(oldDir); err == nil {
57+
if err := os.Rename(oldDir, newDir); err != nil {
58+
return actions.NewError(actions.TunnelEdit, "failed to rename tunnel directory", err)
59+
}
60+
}
61+
62+
// Update key file paths
63+
if tunnel.DNSTT != nil {
64+
tunnel.DNSTT.PrivateKey = filepath.Join(newDir, "server.key")
65+
}
66+
if tunnel.VayDNS != nil {
67+
tunnel.VayDNS.PrivateKey = filepath.Join(newDir, "server.key")
68+
}
69+
70+
// Stop old systemd service
71+
oldSvcName := service.TunnelServiceName(tag)
72+
_ = service.Stop(oldSvcName)
73+
_ = service.Remove(oldSvcName)
74+
75+
// Update route references
76+
if cfg.Route.Active == tag {
77+
cfg.Route.Active = newTag
78+
}
79+
if cfg.Route.Default == tag {
80+
cfg.Route.Default = newTag
81+
}
82+
83+
tunnel.Tag = newTag
84+
tag = newTag
85+
changed = true
86+
out.Success(fmt.Sprintf("Tag renamed to %q", newTag))
87+
}
88+
3589
// Domain (non-direct transports)
3690
if !tunnel.IsDirectTransport() {
3791
newDomain := ctx.GetArg("domain")

0 commit comments

Comments
 (0)