Skip to content

Commit f743707

Browse files
Jan Saidljansaidl
authored andcommitted
add vpn up flags to skip dns configuration
1 parent 0ddee6c commit f743707

11 files changed

Lines changed: 229 additions & 49 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [v1.0.59] - 2026-01-23
8+
9+
### Added
10+
- `vpn config` command to Generate VPN configuration file without connecting
11+
- `vpn up` command now supports flags to skip DNS configuration
12+
713
## [v1.0.58] - 2026-01-21
814

915
### Fixed

src/cmd/logout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func logoutCmd() *cmdBuilder.Cmd {
3333
return err
3434
}
3535
if vpnActive {
36-
_ = disconnectVpn(ctx, uxBlocks)
36+
_ = disconnectVpn(ctx, uxBlocks, false, false)
3737
}
3838
uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.LogoutSuccess)))
3939

src/cmd/vpn.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func vpnCmd() *cmdBuilder.Cmd {
1212
HelpFlag(i18n.T(i18n.CmdHelpVpn)).
1313
AddChildrenCmd(vpnUpCmd()).
1414
AddChildrenCmd(vpnDownCmd()).
15+
AddChildrenCmd(vpnConfigCmd()).
1516
AddChildrenCmd(vpnClearCmd()).
1617
AddChildrenCmd(vpnKeyCmd())
1718
}

src/cmd/vpnClear.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func vpnClearCmd() *cmdBuilder.Cmd {
2323
return err
2424
}
2525

26-
if vpnDownErr := disconnectVpn(ctx, cmdData.UxBlocks); vpnDownErr != nil {
26+
if vpnDownErr := disconnectVpn(ctx, cmdData.UxBlocks, false, false); vpnDownErr != nil {
2727
cmdData.UxBlocks.PrintWarningTextf("vpn down: %s", vpnDownErr)
2828
}
2929

src/cmd/vpnConfig.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"os"
6+
"time"
7+
8+
"github.com/zeropsio/zcli/src/cliStorage"
9+
"github.com/zeropsio/zcli/src/cmdBuilder"
10+
"github.com/zeropsio/zcli/src/constants"
11+
"github.com/zeropsio/zcli/src/entity"
12+
"github.com/zeropsio/zcli/src/file"
13+
"github.com/zeropsio/zcli/src/i18n"
14+
"github.com/zeropsio/zcli/src/uxBlock/styles"
15+
"github.com/zeropsio/zcli/src/wg"
16+
"github.com/zeropsio/zerops-go/dto/input/body"
17+
"github.com/zeropsio/zerops-go/dto/input/path"
18+
"github.com/zeropsio/zerops-go/types"
19+
"github.com/zeropsio/zerops-go/types/uuid"
20+
)
21+
22+
func vpnConfigCmd() *cmdBuilder.Cmd {
23+
return cmdBuilder.NewCmd().
24+
Use("config").
25+
Short("Generate VPN configuration file without connecting").
26+
ScopeLevel(cmdBuilder.ScopeProject()).
27+
Arg(cmdBuilder.ProjectArgName, cmdBuilder.OptionalArg()).
28+
IntFlag(vpnFlagMtu, 1420, i18n.T(i18n.VpnMtuFlag)).
29+
BoolFlag(vpnFlagSkipDnsSetup, false, "skip DNS configuration - you will need to use IP addresses to connect to services instead of domain names").
30+
StringFlag(vpnFlagOutput, "", "output file path (use '-' for stdout, empty for default location)").
31+
HelpFlag("Generate WireGuard VPN configuration file for the project without establishing connection").
32+
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
33+
dnsSetup := !cmdData.Params.GetBool(vpnFlagSkipDnsSetup)
34+
35+
uxBlocks := cmdData.UxBlocks
36+
project, err := cmdData.Project.Expect("project is null")
37+
if err != nil {
38+
return err
39+
}
40+
41+
privateKey, err := getOrCreatePrivateVpnKey(project, cmdData)
42+
if err != nil {
43+
return err
44+
}
45+
46+
publicKey := privateKey.PublicKey()
47+
48+
postProjectResponse, err := cmdData.RestApiClient.PostProjectVpn(
49+
ctx,
50+
path.ProjectId{Id: project.Id},
51+
body.PostProjectVpn{PublicKey: types.String(publicKey.String())},
52+
)
53+
if err != nil {
54+
return err
55+
}
56+
57+
vpnSettings, err := postProjectResponse.Output()
58+
if err != nil {
59+
return err
60+
}
61+
62+
outputPath := cmdData.Params.GetString(vpnFlagOutput)
63+
64+
// Determine output destination
65+
var f *os.File
66+
var filePath string
67+
var fileMode os.FileMode
68+
69+
switch outputPath {
70+
case "-":
71+
// Output to stdout
72+
f = os.Stdout
73+
filePath = "stdout"
74+
case "":
75+
// Use default location
76+
filePath, fileMode, err = constants.WgConfigFilePath()
77+
if err != nil {
78+
return err
79+
}
80+
f, err = file.Open(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
81+
if err != nil {
82+
return err
83+
}
84+
defer f.Close()
85+
default:
86+
// Use custom file path
87+
filePath = outputPath
88+
fileMode = 0600
89+
f, err = os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
90+
if err != nil {
91+
return err
92+
}
93+
defer f.Close()
94+
}
95+
96+
if err := wg.GenerateConfig(f, privateKey, vpnSettings, cmdData.Params.GetInt(vpnFlagMtu), dnsSetup); err != nil {
97+
return err
98+
}
99+
100+
if outputPath != "-" {
101+
uxBlocks.PrintInfo(styles.InfoWithValueLine(i18n.T(i18n.VpnConfigSaved), filePath))
102+
}
103+
104+
if _, err = cmdData.CliStorage.Update(func(data cliStorage.Data) cliStorage.Data {
105+
if data.ProjectVpnKeyRegistry == nil {
106+
data.ProjectVpnKeyRegistry = make(map[uuid.ProjectId]entity.VpnKey)
107+
}
108+
data.ProjectVpnKeyRegistry[project.Id] = entity.VpnKey{
109+
ProjectId: project.Id,
110+
Key: privateKey.String(),
111+
CreatedAt: time.Now(),
112+
}
113+
114+
return data
115+
}); err != nil {
116+
return err
117+
}
118+
119+
return nil
120+
})
121+
}

src/cmd/vpnDown.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ func vpnDownCmd() *cmdBuilder.Cmd {
1919
Use("down").
2020
Short(i18n.T(i18n.CmdDescVpnDown)).
2121
HelpFlag(i18n.T(i18n.CmdHelpVpnDown)).
22+
BoolFlag(vpnFlagSkipDnsSetup, false, "skip DNS configuration - you will need to use IP addresses to connect to services instead of domain names").
23+
BoolFlag(vpnFlagSkipCheckInstallation, false, "skip WireGuard installation check").
2224
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
23-
return disconnectVpn(ctx, cmdData.UxBlocks)
25+
checkInstallation := !cmdData.Params.GetBool(vpnFlagSkipCheckInstallation)
26+
dnsSetup := !cmdData.Params.GetBool(vpnFlagSkipDnsSetup)
27+
return disconnectVpn(ctx, cmdData.UxBlocks, dnsSetup, checkInstallation)
2428
})
2529
}
2630

27-
func disconnectVpn(ctx context.Context, uxBlocks uxBlock.UxBlocks) error {
28-
err := wg.CheckWgInstallation()
31+
func disconnectVpn(ctx context.Context, uxBlocks uxBlock.UxBlocks, dnsSetup, checkInstallation bool) error {
32+
err := wg.CheckWgInstallation(checkInstallation, dnsSetup)
2933
if err != nil {
3034
return err
3135
}

src/cmd/vpnUp.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,33 @@ import (
2828

2929
const vpnCheckAddress = "logger.core.zerops"
3030

31+
const (
32+
vpnFlagMtu = "mtu"
33+
vpnFlagAutoDisconnect = "auto-disconnect"
34+
vpnFlagSkipDnsSetup = "skip-dns-setup"
35+
vpnFlagSkipVpnTest = "skip-vpn-test"
36+
vpnFlagSkipCheckInstallation = "skip-check-installation"
37+
vpnFlagSkipConnect = "skip-connect"
38+
vpnFlagOutput = "output"
39+
)
40+
3141
func vpnUpCmd() *cmdBuilder.Cmd {
3242
return cmdBuilder.NewCmd().
3343
Use("up").
3444
Short(i18n.T(i18n.CmdDescVpnUp)).
3545
ScopeLevel(cmdBuilder.ScopeProject()).
3646
Arg(cmdBuilder.ProjectArgName, cmdBuilder.OptionalArg()).
37-
IntFlag("mtu", 1420, i18n.T(i18n.VpnMtuFlag)).
38-
BoolFlag("auto-disconnect", false, i18n.T(i18n.VpnAutoDisconnectFlag)).
47+
IntFlag(vpnFlagMtu, 1420, i18n.T(i18n.VpnMtuFlag)).
48+
BoolFlag(vpnFlagAutoDisconnect, false, i18n.T(i18n.VpnAutoDisconnectFlag)).
49+
BoolFlag(vpnFlagSkipDnsSetup, false, "skip DNS configuration - you will need to use IP addresses to connect to services instead of domain names").
50+
BoolFlag(vpnFlagSkipVpnTest, false, "skip VPN connectivity test after connection is established").
51+
BoolFlag(vpnFlagSkipCheckInstallation, false, "skip WireGuard installation check").
3952
HelpFlag(i18n.T(i18n.CmdHelpVpnUp)).
4053
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
54+
dnsSetup := !cmdData.Params.GetBool(vpnFlagSkipDnsSetup)
55+
vpnTest := !cmdData.Params.GetBool(vpnFlagSkipVpnTest)
56+
checkInstallation := !cmdData.Params.GetBool(vpnFlagSkipCheckInstallation)
57+
4158
uxBlocks := cmdData.UxBlocks
4259
project, err := cmdData.Project.Expect("project is null")
4360
if err != nil {
@@ -49,8 +66,8 @@ func vpnUpCmd() *cmdBuilder.Cmd {
4966
return err
5067
}
5168
if vpnActive {
52-
if cmdData.Params.GetBool("auto-disconnect") {
53-
if err := disconnectVpn(ctx, uxBlocks); err != nil {
69+
if cmdData.Params.GetBool(vpnFlagAutoDisconnect) {
70+
if err := disconnectVpn(ctx, uxBlocks, dnsSetup, checkInstallation); err != nil {
5471
return err
5572
}
5673
} else {
@@ -66,7 +83,7 @@ func vpnUpCmd() *cmdBuilder.Cmd {
6683
return nil
6784
}
6885

69-
if err := disconnectVpn(ctx, uxBlocks); err != nil {
86+
if err := disconnectVpn(ctx, uxBlocks, dnsSetup, checkInstallation); err != nil {
7087
return err
7188
}
7289
}
@@ -104,8 +121,7 @@ func vpnUpCmd() *cmdBuilder.Cmd {
104121
}
105122
defer f.Close()
106123

107-
err = wg.GenerateConfig(f, privateKey, vpnSettings, cmdData.Params.GetInt("mtu"))
108-
if err != nil {
124+
if err := wg.GenerateConfig(f, privateKey, vpnSettings, cmdData.Params.GetInt(vpnFlagMtu), dnsSetup); err != nil {
109125
return err
110126
}
111127

@@ -127,8 +143,7 @@ func vpnUpCmd() *cmdBuilder.Cmd {
127143
return err
128144
}
129145

130-
err = wg.CheckWgInstallation()
131-
if err != nil {
146+
if err := wg.CheckWgInstallation(checkInstallation, dnsSetup); err != nil {
132147
return err
133148
}
134149

@@ -138,11 +153,13 @@ func vpnUpCmd() *cmdBuilder.Cmd {
138153
return err
139154
}
140155

141-
// wait for the vpn to be up
142-
if isVpnUp(ctx, uxBlocks, 6) {
143-
uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.VpnUp)))
144-
} else {
145-
uxBlocks.PrintWarning(styles.WarningLine(i18n.T(i18n.VpnPingFailed)))
156+
if vpnTest && dnsSetup {
157+
// wait for the vpn to be up
158+
if isVpnUp(ctx, uxBlocks, 6) {
159+
uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.VpnUp)))
160+
} else {
161+
uxBlocks.PrintWarning(styles.WarningLine(i18n.T(i18n.VpnPingFailed)))
162+
}
146163
}
147164

148165
return nil

src/wg/darwin.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import (
2020

2121
const wgRunDir = "/var/run/wireguard/"
2222

23-
func CheckWgInstallation() error {
23+
func CheckWgInstallation(checkInstallation, _ bool) error {
24+
if !checkInstallation {
25+
return nil
26+
}
2427
_, err := exec.LookPath("wg-quick")
2528
if err != nil {
2629
return errors.New(i18n.T(i18n.VpnWgQuickIsNotInstalled))
@@ -29,8 +32,8 @@ func CheckWgInstallation() error {
2932
return nil
3033
}
3134

32-
func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.ProjectVpnItem, mtu int) error {
33-
data, err := defaultTemplateData(privateKey, vpnSettings, mtu)
35+
func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.ProjectVpnItem, mtu int, dnsSetup bool) error {
36+
data, err := defaultTemplateData(privateKey, vpnSettings, mtu, dnsSetup)
3437
if err != nil {
3538
return err
3639
}
@@ -71,11 +74,13 @@ PrivateKey = {{.PrivateKey}}
7174
MTU = {{.Mtu}}
7275
7376
Address = {{if .AssignedIpv4Address}}{{.AssignedIpv4Address}}/32{{end}}, {{.AssignedIpv6Address}}/128
77+
{{if .DnsSetup -}}
7478
PostUp = mkdir -p /etc/resolver
7579
PostUp = echo "nameserver {{.Ipv4NetworkGateway}}" > /etc/resolver/zerops
7680
PostUp = echo "domain zerops" >> /etc/resolver/zerops
7781
PostUp = echo "search zerops" >> /etc/resolver/zerops
7882
PostDown = rm /etc/resolver/zerops
83+
{{end}}
7984
8085
[Peer]
8186
PublicKey = {{.PublicKey}}

src/wg/linux.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@ import (
1818
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
1919
)
2020

21-
func CheckWgInstallation() error {
21+
func CheckWgInstallation(checkInstallation, dnsSetup bool) error {
22+
if !checkInstallation {
23+
return nil
24+
}
2225
if _, err := exec.LookPath("wg-quick"); err != nil {
2326
return errors.New(i18n.T(i18n.VpnWgQuickIsNotInstalled))
2427
}
2528
// Debian does not have it by default anymore
26-
if _, err := exec.LookPath("resolvectl"); err != nil {
27-
return errors.New(i18n.T(i18n.VpnResolveCtlIsNotInstalled))
29+
if dnsSetup {
30+
if _, err := exec.LookPath("resolvectl"); err != nil {
31+
return errors.New(i18n.T(i18n.VpnResolveCtlIsNotInstalled))
32+
}
2833
}
2934
return nil
3035
}
3136

32-
func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.ProjectVpnItem, mtu int) error {
33-
data, err := defaultTemplateData(privateKey, vpnSettings, mtu)
37+
func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.ProjectVpnItem, mtu int, dnsSetup bool) error {
38+
data, err := defaultTemplateData(privateKey, vpnSettings, mtu, dnsSetup)
3439
if err != nil {
3540
return err
3641
}
@@ -63,9 +68,10 @@ PrivateKey = {{.PrivateKey}}
6368
MTU = {{.Mtu}}
6469
6570
Address = {{if .AssignedIpv4Address}}{{.AssignedIpv4Address}}/32{{end}}, {{.AssignedIpv6Address}}/128
71+
{{if .DnsSetup -}}
6672
PostUp = resolvectl dns %i {{.Ipv4NetworkGateway}}
6773
PostUp = resolvectl domain %i zerops
68-
74+
{{end}}
6975
[Peer]
7076
PublicKey = {{.PublicKey}}
7177

0 commit comments

Comments
 (0)