style: align struct literals and promote deps to direct

Promote golang-jwt/jwt/v5, mattn/go-isatty, golang.org/x/crypto, and
golang.org/x/term from indirect to direct dependencies in go.mod.

Fix data races in firewall test fakes by guarding calls slice with sync.Mutex.

Reformat struct literals and map literals across cmd, internal/wireguard,
and internal/firewall for consistent column alignment.
This commit is contained in:
Andras Bacsai
2026-04-21 21:24:21 +02:00
parent 8341802c88
commit 346320504c
11 changed files with 304 additions and 62 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ type InitFlags struct {
// CentralHost is the SSH address of the central VM (from --central flag).
// When non-empty, phases 4+5 install Redis + broker on that host and push
// per-host JWTs to all other hosts. Default empty = no broker setup.
CentralHost string
CentralHost string
BrokerVersion string
}
+5 -5
View File
@@ -52,11 +52,11 @@ func runPlan(ctx context.Context, cmd *cobra.Command, flags *InitFlags) error {
InstallPodman: true,
Namespaces: flags.Namespaces,
DefaultDenyContainers: !flags.SkipDefaultDeny,
InstallCoold: true,
CooldVersion: flags.CooldVersion,
CorrosionVersion: flags.CorrosionVersion,
CorrosionGossipPort: flags.CorrosionGossipPort,
CorrosionAPIPort: flags.CorrosionAPIPort,
InstallCoold: true,
CooldVersion: flags.CooldVersion,
CorrosionVersion: flags.CorrosionVersion,
CorrosionGossipPort: flags.CorrosionGossipPort,
CorrosionAPIPort: flags.CorrosionAPIPort,
}
// Build SSH runner (handles passphrase resolution).
+4 -4
View File
@@ -5,12 +5,16 @@ go 1.25.0
require (
github.com/adrg/xdg v0.5.3
github.com/creativeprojects/go-selfupdate v1.5.1
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/hashicorp/go-version v1.7.0
github.com/mattn/go-isatty v0.0.20
github.com/olekukonko/tablewriter v1.1.2
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.50.0
golang.org/x/term v0.42.0
)
require (
@@ -27,14 +31,12 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
@@ -49,10 +51,8 @@ require (
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/xanzy/go-gitlab v0.115.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
-8
View File
@@ -105,8 +105,6 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -120,19 +118,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+6
View File
@@ -4,6 +4,7 @@ import (
"context"
"net"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
@@ -13,13 +14,18 @@ import (
// fakeCooldRunner is a minimal Runner for client-level tests. It captures
// every command and replies based on substring-matched canned responses.
// mu guards calls against concurrent appends from ForEachServer's parallel
// goroutines.
type fakeCooldRunner struct {
mu sync.Mutex
responses map[string]string
calls []string
}
func (f *fakeCooldRunner) Run(_ context.Context, _, _ string, _ int, cmd string) (string, string, error) {
f.mu.Lock()
f.calls = append(f.calls, cmd)
f.mu.Unlock()
for sub, resp := range f.responses {
if strings.Contains(cmd, sub) {
return resp, "", nil
+3 -3
View File
@@ -139,9 +139,9 @@ func DiscoverAllNamespaces(
concurrency int,
) ([]Container, []ssh.ServerResult[[]Container]) {
var (
all []Container
allResults []ssh.ServerResult[[]Container]
seenHosts = map[string]struct{}{}
all []Container
allResults []ssh.ServerResult[[]Container]
seenHosts = map[string]struct{}{}
)
for _, ns := range namespaces {
nsContainers, results := DiscoverAll(ctx, runner, hosts, user, port,
+6 -1
View File
@@ -3,6 +3,7 @@ package firewall
import (
"context"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
@@ -39,14 +40,18 @@ func TestParseDiscoverLine(t *testing.T) {
}
// fakeRunner is a deterministic ssh.Runner for firewall tests. Responses
// map a command substring to its canned stdout.
// map a command substring to its canned stdout. mu guards calls against
// concurrent appends from ForEachServer's parallel goroutines.
type fakeRunner struct {
mu sync.Mutex
responses map[string]string
calls []string
}
func (f *fakeRunner) Run(_ context.Context, _, _ string, _ int, cmd string) (string, string, error) {
f.mu.Lock()
f.calls = append(f.calls, cmd)
f.mu.Unlock()
for sub, resp := range f.responses {
if strings.Contains(cmd, sub) {
return resp, "", nil
+2 -2
View File
@@ -408,8 +408,8 @@ func buildNamespaceConfigs(host string, nsSorted []string, assignments map[strin
continue
}
out = append(out, services.CooldNamespace{
Name: ns,
Network: PodmanNetworkFor(ns),
Name: ns,
Network: PodmanNetworkFor(ns),
BridgeGateway: MachineIP(subnet),
})
}
+12 -12
View File
@@ -37,13 +37,13 @@ func convergedServer(host, pubkey, peerKey, mgmtIP, contSubnet string) *ServerSt
sn := mustParseCIDR(contSubnet)
firewallHash := sha256Hex([]byte(FirewallServiceUnit("wg0", []string{"default"}, []*net.IPNet{sn}, false)))
return &ServerState{
Host: host,
Installed: true,
KeysExist: true,
PublicKey: pubkey,
WireGuardMgmtIP: net.ParseIP(mgmtIP).To4(),
ListenPort: 51820,
Active: true,
Host: host,
Installed: true,
KeysExist: true,
PublicKey: pubkey,
WireGuardMgmtIP: net.ParseIP(mgmtIP).To4(),
ListenPort: 51820,
Active: true,
Peers: []Peer{{
PublicKey: peerKey,
AllowedIPs: []string{peerMgmtForPub(peerKey), peerSubnetForPub(peerKey)},
@@ -564,11 +564,11 @@ func TestBuildPlan_PodmanRequiresNamespace(t *testing.T) {
func TestBinaryVersionDrift(t *testing.T) {
tests := []struct {
name string
desiredVersion string
installed bool
haveVersion string
wantDrift bool
name string
desiredVersion string
installed bool
haveVersion string
wantDrift bool
}{
{"not installed", "nightly", false, "", true},
{"installed no marker", "nightly", true, "", true},
+26 -26
View File
@@ -167,28 +167,28 @@ func TestTruncateKey(t *testing.T) {
func TestProbe_NftAvailableAndBridgeTableExists_True(t *testing.T) {
runner := &fakeReconRunner{
responses: map[string]string{
"dpkg-query": "1\n",
"wg show": "",
"cat /etc/wireguard/": "",
"wg pubkey": "",
"ip -4 -o addr show": "",
"systemctl is-active wg-quick": "active\n",
"podman --version": "podman version 4.9.0\n",
"systemctl is-active podman.socket": "active\n",
"sysctl net.ipv4.ip_forward": "net.ipv4.ip_forward = 1\n",
"podman network inspect": `[{"name":"coolify-default-mesh","subnets":[{"subnet":"10.210.0.0/24","gateway":"10.210.0.1"}],"dns_enabled":false,"labels":{"io.coolify.managed":"true","io.coolify.namespace":"default"}}]` + "\n",
"systemctl is-active coolify-mesh-fw": "active\n",
"sha256sum /etc/systemd/system/coolify-mesh-fw.service": "",
"iptables -nL COOLIFY-INTRA": "yes\n",
"command -v nft": "yes\n",
"nft list table bridge coolify_bridge": "yes\n",
"test -x /usr/local/bin/corrosion": "yes\n",
"systemctl is-active corrosion": "active\n",
"sha256sum /etc/corrosion/config.toml": "",
"test -x /usr/local/bin/coold": "yes\n",
"systemctl is-active coold": "active\n",
"cat /etc/coolify/coold-version": "",
"cat /etc/coolify/corrosion-version": "",
"dpkg-query": "1\n",
"wg show": "",
"cat /etc/wireguard/": "",
"wg pubkey": "",
"ip -4 -o addr show": "",
"systemctl is-active wg-quick": "active\n",
"podman --version": "podman version 4.9.0\n",
"systemctl is-active podman.socket": "active\n",
"sysctl net.ipv4.ip_forward": "net.ipv4.ip_forward = 1\n",
"podman network inspect": `[{"name":"coolify-default-mesh","subnets":[{"subnet":"10.210.0.0/24","gateway":"10.210.0.1"}],"dns_enabled":false,"labels":{"io.coolify.managed":"true","io.coolify.namespace":"default"}}]` + "\n",
"systemctl is-active coolify-mesh-fw": "active\n",
"sha256sum /etc/systemd/system/coolify-mesh-fw.service": "",
"iptables -nL COOLIFY-INTRA": "yes\n",
"command -v nft": "yes\n",
"nft list table bridge coolify_bridge": "yes\n",
"test -x /usr/local/bin/corrosion": "yes\n",
"systemctl is-active corrosion": "active\n",
"sha256sum /etc/corrosion/config.toml": "",
"test -x /usr/local/bin/coold": "yes\n",
"systemctl is-active coold": "active\n",
"cat /etc/coolify/coold-version": "",
"cat /etc/coolify/corrosion-version": "",
},
}
@@ -203,10 +203,10 @@ func TestProbe_NftAvailableAndBridgeTableExists_True(t *testing.T) {
func TestProbe_NftNotAvailable_BridgeTableAbsent(t *testing.T) {
runner := &fakeReconRunner{
responses: map[string]string{
"dpkg-query": "1\n",
"iptables -nL COOLIFY-INTRA": "yes\n",
"command -v nft": "no\n",
"nft list table bridge coolify_bridge": "no\n",
"dpkg-query": "1\n",
"iptables -nL COOLIFY-INTRA": "yes\n",
"command -v nft": "no\n",
"nft list table bridge coolify_bridge": "no\n",
},
}
+239
View File
@@ -1724,6 +1724,129 @@ Parameters:
required: false
default: 0
Command: coolify firewall
Description: [ALPHA] Manage cross-host container allow rules (Coolify v5)
Parameters:
- name: --all-namespaces
type: boolean
description: Operate across every mesh namespace on each host (list/containers fan out; allow/revoke still require a specific --namespace)
required: false
default: false
- name: --concurrency
type: integer
description: Maximum number of parallel SSH connections
required: false
default: 10
- name: --coold-port
type: integer
description: TCP port coold's REST API listens on (bound to the WG mgmt IP)
required: false
default: 8443
- name: --coold-token
type: string
description: Bearer token override for coold REST API (also reads COOLIFY_COOLD_TOKEN env). When unset, CLI reads /etc/coolify/api-token over SSH per host.
required: false
- name: --namespace
type: string
description: Namespace the command operates against (must match a namespace created by `coolify init`)
required: false
default: default
- name: --servers
type: stringSlice
description: Comma-separated server IPs (required)
required: true
- name: --ssh-key
type: string
description: Path to SSH private key used to connect to servers (required)
required: true
- name: --ssh-passphrase-prompt
type: boolean
description: Prompt for SSH key passphrase (also reads COOLIFY_SSH_PASSPHRASE env var)
required: false
default: false
- name: --ssh-port
type: integer
description: SSH port
required: false
default: 22
- name: --ssh-timeout
type: string
description: SSH connection timeout (e.g. 30s, 1m)
required: false
default: 30s
- name: --ssh-user
type: string
description: SSH username
required: false
default: root
- name: --wg-interface
type: string
description: WireGuard interface name on remote hosts (must match --wg-interface at init)
required: false
default: wg0
Command: coolify firewall allow
Description: Add an allow rule (from container → to container:port)
Parameters:
- name: --bidirectional
type: boolean
description: Also install the reverse rule on the source host (default: one-way; conntrack handles replies)
required: false
default: false
- name: --from
type: string
description: Source container (name, short-id, raw IP, or host:name) — required
required: false
- name: --port
type: integer
description: Destination port (required unless --proto is empty)
required: false
default: 0
- name: --proto
type: string
description: Protocol (tcp, udp, or empty for any)
required: false
default: tcp
- name: --to
type: string
description: Destination container (name, short-id, raw IP, or host:name) — required
required: false
Command: coolify firewall containers
Description: List containers on the Coolify mesh bridge across all servers
Parameters: (None)
Command: coolify firewall list
Description: List installed allow rules across all servers
Parameters: (None)
Command: coolify firewall revoke
Description: Remove an allow rule
Parameters:
- name: --bidirectional
type: boolean
description: Also install the reverse rule on the source host (default: one-way; conntrack handles replies)
required: false
default: false
- name: --from
type: string
description: Source container (name, short-id, raw IP, or host:name) — required
required: false
- name: --port
type: integer
description: Destination port (required unless --proto is empty)
required: false
default: 0
- name: --proto
type: string
description: Protocol (tcp, udp, or empty for any)
required: false
default: tcp
- name: --to
type: string
description: Destination container (name, short-id, raw IP, or host:name) — required
required: false
Command: coolify github branches <app_uuid> <owner/repo>
Description: List branches for a repository
Parameters: (None)
@@ -1869,6 +1992,122 @@ Parameters:
description: GitHub Webhook Secret
required: false
Command: coolify init
Description: [ALPHA] Initialize WireGuard mesh for Coolify v5
Parameters:
- name: --broker-version
type: string
description: Release tag to download for coolify-broker (e.g. "nightly", "v1.2.3").
required: false
default: nightly
- name: --central
type: string
description: SSH address of the central VM that will run coolify-broker + Redis (and later Laravel).
Must be one of the --servers entries. When set, phases 4+5 install Redis + broker on that host
and push a per-host JWT to every other server. Leave empty to skip broker setup.
required: false
- name: --concurrency
type: integer
description: Maximum number of parallel SSH connections
required: false
default: 10
- name: --container-pool
type: string
description: Shared container address pool — each (namespace, host) pair gets a /<container-prefix> from here, owned by that namespace's Podman bridge
required: false
default: 10.210.0.0/16
- name: --container-prefix
type: integer
description: Prefix length of each per-host, per-namespace container subnet
required: false
default: 24
- name: --coold-version
type: string
description: Release tag to download for coold (e.g. "nightly", "v1.2.3"). nightly always re-installs on every apply.
required: false
default: nightly
- name: --corrosion-api-port
type: integer
description: Corrosion HTTP API port (bound to 127.0.0.1)
required: false
default: 8080
- name: --corrosion-gossip-port
type: integer
description: Corrosion SWIM gossip port (bound to the wg0 mgmt IP)
required: false
default: 8787
- name: --corrosion-version
type: string
description: Release tag to download for corrosion (e.g. "nightly", "v1.2.3"). nightly always re-installs on every apply.
required: false
default: nightly
- name: --namespaces
type: stringSlice
description: Comma-separated list of namespaces to create on each host. Each namespace is a separate Podman bridge network (coolify-<ns>-mesh) with its own /<container-prefix> per host
required: false
default: [default]
- name: --servers
type: stringSlice
description: Comma-separated server IPs (required)
required: true
- name: --skip-default-deny
type: boolean
description: Skip installing the default-deny firewall scaffold. By default, both cross-host and intra-host (same bridge) container traffic is blocked; coold manages the allow list at runtime
required: false
default: false
- name: --ssh-key
type: string
description: Path to SSH private key used to connect to servers (required)
required: true
- name: --ssh-passphrase-prompt
type: boolean
description: Prompt for SSH key passphrase (also reads COOLIFY_SSH_PASSPHRASE env var)
required: false
default: false
- name: --ssh-port
type: integer
description: SSH port
required: false
default: 22
- name: --ssh-timeout
type: string
description: SSH connection timeout (e.g. 30s, 1m)
required: false
default: 30s
- name: --ssh-user
type: string
description: SSH username
required: false
default: root
- name: --wg-interface
type: string
description: WireGuard interface name on the remote hosts
required: false
default: wg0
- name: --wg-listen-port
type: integer
description: WireGuard UDP listen port
required: false
default: 51820
- name: --wg-mgmt-pool
type: string
description: WireGuard management address pool — each host gets a /32 from here, assigned to wg0
required: false
default: 100.64.0.0/16
- name: --yes (-y)
type: boolean
description: Skip the interactive alpha confirmation prompt
required: false
default: false
Command: coolify init apply
Description: Bootstrap the WireGuard mesh (executes the plan)
Parameters: (None)
Command: coolify init plan
Description: Show WireGuard mesh changes without applying them
Parameters: (None)
Command: coolify private-key add <key_name> <private_key_or_file>
Description: Add a private key
Parameters: (None)