Compare commits

..

1 Commits

Author SHA1 Message Date
coderabbitai[bot]
8c5648bb7b 📝 Add docstrings to feature/k8s-api-auth-proxy
Docstrings generation was requested by @shyamganesh-tide.

* https://github.com/netbirdio/netbird/pull/4975#issuecomment-3685447791

The following files were modified:

* `client/cmd/kubeconfig.go`
* `client/cmd/root.go`
* `management/internals/shared/grpc/conversion.go`
2025-12-23 07:06:53 +00:00
10 changed files with 224 additions and 534 deletions

View File

@@ -9,7 +9,7 @@ on:
pull_request:
env:
SIGN_PIPE_VER: "v0.1.0"
SIGN_PIPE_VER: "v0.0.23"
GORELEASER_VER: "v2.3.2"
PRODUCT_NAME: "NetBird"
COPYRIGHT: "NetBird GmbH"
@@ -19,100 +19,6 @@ concurrency:
cancel-in-progress: true
jobs:
release_freebsd_port:
name: "FreeBSD Port / Build & Test"
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate FreeBSD port diff
run: bash release_files/freebsd-port-diff.sh
- name: Generate FreeBSD port issue body
run: bash release_files/freebsd-port-issue-body.sh
- name: Check if diff was generated
id: check_diff
run: |
if ls netbird-*.diff 1> /dev/null 2>&1; then
echo "diff_exists=true" >> $GITHUB_OUTPUT
else
echo "diff_exists=false" >> $GITHUB_OUTPUT
echo "No diff file generated (port may already be up to date)"
fi
- name: Extract version
if: steps.check_diff.outputs.diff_exists == 'true'
id: version
run: |
VERSION=$(ls netbird-*.diff | sed 's/netbird-\(.*\)\.diff/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Generated files for version: $VERSION"
cat netbird-*.diff
- name: Test FreeBSD port
if: steps.check_diff.outputs.diff_exists == 'true'
uses: vmactions/freebsd-vm@v1
with:
usesh: true
copyback: false
release: "15.0"
prepare: |
# Install required packages
pkg install -y git curl portlint go
# Install Go for building
GO_TARBALL="go1.24.10.freebsd-amd64.tar.gz"
GO_URL="https://go.dev/dl/$GO_TARBALL"
curl -LO "$GO_URL"
tar -C /usr/local -xzf "$GO_TARBALL"
# Clone ports tree (shallow, only what we need)
git clone --depth 1 --filter=blob:none https://git.FreeBSD.org/ports.git /usr/ports
cd /usr/ports
run: |
set -e -x
export PATH=$PATH:/usr/local/go/bin
# Find the diff file
echo "Finding diff file..."
DIFF_FILE=$(find $PWD -name "netbird-*.diff" -type f 2>/dev/null | head -1)
echo "Found: $DIFF_FILE"
if [[ -z "$DIFF_FILE" ]]; then
echo "ERROR: Could not find diff file"
find ~ -name "*.diff" -type f 2>/dev/null || true
exit 1
fi
# Apply the generated diff from /usr/ports (diff has a/security/netbird/... paths)
cd /usr/ports
patch -p1 -V none < "$DIFF_FILE"
# Show patched Makefile
version=$(cat security/netbird/Makefile | grep -E '^DISTVERSION=' | awk '{print $NF}')
cd /usr/ports/security/netbird
export BATCH=yes
make package
pkg add ./work/pkg/netbird-*.pkg
netbird version | grep "$version"
echo "FreeBSD port test completed successfully!"
- name: Upload FreeBSD port files
if: steps.check_diff.outputs.diff_exists == 'true'
uses: actions/upload-artifact@v4
with:
name: freebsd-port-files
path: |
./netbird-*-issue.txt
./netbird-*.diff
retention-days: 30
release:
runs-on: ubuntu-latest-m
env:

View File

@@ -1,3 +1,4 @@
<div align="center">
<br/>
<br/>
@@ -112,7 +113,7 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
<p float="left" align="middle">
<img src="https://docs.netbird.io/docs-static/img/about-netbird/high-level-dia.png" width="700"/>
<img src="https://docs.netbird.io/docs-static/img/architecture/high-level-dia.png" width="700"/>
</p>
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.

136
client/cmd/kubeconfig.go Normal file
View File

@@ -0,0 +1,136 @@
package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/proto"
)
var (
kubeconfigOutput string
kubeconfigCluster string
kubeconfigContext string
kubeconfigUser string
kubeconfigServer string
kubeconfigNamespace string
)
var kubeconfigCmd = &cobra.Command{
Use: "kubeconfig",
Short: "Generate kubeconfig for accessing Kubernetes via NetBird",
Long: `Generate a kubeconfig file that points to a Kubernetes cluster accessible via NetBird.
The generated kubeconfig uses a dummy bearer token for authentication when the
cluster's auth proxy is running in 'auth' mode. The actual authentication is
handled by the NetBird network - the auth proxy identifies users by their
NetBird peer IP and impersonates them in the Kubernetes API.
Example:
netbird kubeconfig --server https://k8s.example.netbird.cloud:6443 --cluster my-cluster
netbird kubeconfig --server https://10.100.0.1:6443 -o ~/.kube/netbird-config`,
RunE: kubeconfigFunc,
}
// init configures command-line flags for the kubeconfig command.
// It registers flags for output path, cluster, context, user, server, and namespace
// and marks the server flag as required.
func init() {
kubeconfigCmd.Flags().StringVarP(&kubeconfigOutput, "output", "o", "", "Output file path (default: stdout)")
kubeconfigCmd.Flags().StringVar(&kubeconfigCluster, "cluster", "netbird-cluster", "Cluster name in kubeconfig")
kubeconfigCmd.Flags().StringVar(&kubeconfigContext, "context", "netbird", "Context name in kubeconfig")
kubeconfigCmd.Flags().StringVar(&kubeconfigUser, "user", "netbird-user", "User name in kubeconfig")
kubeconfigCmd.Flags().StringVar(&kubeconfigServer, "server", "", "Kubernetes API server URL (required)")
kubeconfigCmd.Flags().StringVar(&kubeconfigNamespace, "namespace", "default", "Default namespace")
_ = kubeconfigCmd.MarkFlagRequired("server")
}
// kubeconfigFunc generates a kubeconfig file for accessing Kubernetes via the NetBird auth proxy.
// KUBECONFIG and running kubectl.
func kubeconfigFunc(cmd *cobra.Command, args []string) error {
ctx := context.Background()
// Get current NetBird status to verify connection
conn, err := DialClientGRPCServer(ctx, daemonAddr)
if err != nil {
cmd.PrintErrf("Warning: Could not connect to NetBird daemon: %v\n", err)
cmd.PrintErrln("Generating kubeconfig anyway, but make sure NetBird is running before using it.")
} else {
defer conn.Close()
resp, err := proto.NewDaemonServiceClient(conn).Status(ctx, &proto.StatusRequest{})
if err != nil {
cmd.PrintErrf("Warning: Could not get NetBird status: %v\n", status.Convert(err).Message())
} else if resp.Status != "Connected" {
cmd.PrintErrf("Warning: NetBird is not connected (status: %s)\n", resp.Status)
cmd.PrintErrln("Make sure to run 'netbird up' before using the generated kubeconfig.")
}
}
kubeconfig := generateKubeconfig(kubeconfigServer, kubeconfigCluster, kubeconfigContext, kubeconfigUser, kubeconfigNamespace)
if kubeconfigOutput == "" {
fmt.Println(kubeconfig)
return nil
}
// Expand ~ in path
if strings.HasPrefix(kubeconfigOutput, "~/") {
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home directory: %w", err)
}
kubeconfigOutput = filepath.Join(home, kubeconfigOutput[2:])
}
// Create directory if needed
dir := filepath.Dir(kubeconfigOutput)
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
}
if err := os.WriteFile(kubeconfigOutput, []byte(kubeconfig), 0600); err != nil {
return fmt.Errorf("failed to write kubeconfig: %w", err)
}
cmd.Printf("Kubeconfig written to %s\n", kubeconfigOutput)
cmd.PrintErrln("\nWarning: TLS verification is disabled (insecure-skip-tls-verify: true).")
cmd.PrintErrln("This is safe when traffic is encrypted via NetBird's WireGuard tunnel.")
cmd.Printf("\nTo use this kubeconfig:\n")
cmd.Printf(" export KUBECONFIG=%s\n", kubeconfigOutput)
cmd.Printf(" kubectl get nodes\n")
return nil
}
// generateKubeconfig creates a kubeconfig YAML string with the given parameters.
// generateKubeconfig generates a kubeconfig YAML for accessing the specified Kubernetes API server via NetBird.
// The returned config sets the current context to the provided context, includes the given cluster, user, and namespace,
// enables `insecure-skip-tls-verify: true`, and embeds the static token `netbird-auth-proxy`.
func generateKubeconfig(server, cluster, context, user, namespace string) string {
return fmt.Sprintf(`apiVersion: v1
kind: Config
clusters:
- cluster:
insecure-skip-tls-verify: true
server: %s
name: %s
contexts:
- context:
cluster: %s
namespace: %s
user: %s
name: %s
current-context: %s
users:
- name: %s
user:
token: netbird-auth-proxy
`, server, cluster, cluster, namespace, user, context, context, user)
}

View File

@@ -85,12 +85,16 @@ var (
// Execute executes the root command.
func Execute() error {
if isUpdateBinary() {
return updateCmd.Execute()
}
return rootCmd.Execute()
}
// init initializes package-level defaults and configures the root CLI command.
// It sets OS-specific default paths for configuration and logs, determines the default
// daemon address, registers persistent CLI flags (daemon address, management/admin URLs,
// logging, setup key options, pre-shared key, hostname, anonymization, and config path),
// and wires up all top-level and nested subcommands. It also defines upCmd-specific
// flags for external IP mapping, custom DNS resolver address, Rosenpass options,
// auto-connect control, and lazy connection.
func init() {
defaultConfigPathDir = "/etc/netbird/"
defaultLogFileDir = "/var/log/netbird/"
@@ -144,6 +148,7 @@ func init() {
rootCmd.AddCommand(forwardingRulesCmd)
rootCmd.AddCommand(debugCmd)
rootCmd.AddCommand(profileCmd)
rootCmd.AddCommand(kubeconfigCmd)
networksCMD.AddCommand(routesListCmd)
networksCMD.AddCommand(routesSelectCmd, routesDeselectCmd)
@@ -396,4 +401,4 @@ func getClient(cmd *cobra.Command) (*grpc.ClientConn, error) {
}
return conn, nil
}
}

View File

@@ -80,7 +80,6 @@ type DefaultServer struct {
updateSerial uint64
previousConfigHash uint64
currentConfig HostDNSConfig
currentConfigHash uint64
handlerChain *HandlerChain
extraDomains map[domain.Domain]int
@@ -208,7 +207,6 @@ func newDefaultServer(
hostsDNSHolder: newHostsDNSHolder(),
hostManager: &noopHostConfigurator{},
mgmtCacheResolver: mgmtCacheResolver,
currentConfigHash: ^uint64(0), // Initialize to max uint64 to ensure first config is always applied
}
// register with root zone, handler chain takes care of the routing
@@ -588,29 +586,8 @@ func (s *DefaultServer) applyHostConfig() {
log.Debugf("extra match domains: %v", maps.Keys(s.extraDomains))
hash, err := hashstructure.Hash(config, hashstructure.FormatV2, &hashstructure.HashOptions{
ZeroNil: true,
IgnoreZeroValue: true,
SlicesAsSets: true,
UseStringer: true,
})
if err != nil {
log.Warnf("unable to hash the host dns configuration, will apply config anyway: %s", err)
// Fall through to apply config anyway (fail-safe approach)
} else if s.currentConfigHash == hash {
log.Debugf("not applying host config as there are no changes")
return
}
log.Debugf("applying host config as there are changes")
if err := s.hostManager.applyDNSConfig(config, s.stateManager); err != nil {
log.Errorf("failed to apply DNS host manager update: %v", err)
return
}
// Only update hash if it was computed successfully and config was applied
if err == nil {
s.currentConfigHash = hash
}
s.registerFallback(config)

View File

@@ -1602,10 +1602,7 @@ func TestExtraDomains(t *testing.T) {
"other.example.com.",
"duplicate.example.com.",
},
// Expect 3 calls instead of 4 because when deregistering duplicate.example.com,
// the domain remains in the config (ref count goes from 2 to 1), so the host
// config hash doesn't change and applyDNSConfig is not called.
applyHostConfigCall: 3,
applyHostConfigCall: 4,
},
{
name: "Config update with new domains after registration",
@@ -1660,10 +1657,7 @@ func TestExtraDomains(t *testing.T) {
expectedMatchOnly: []string{
"extra.example.com.",
},
// Expect 2 calls instead of 3 because when deregistering protected.example.com,
// it's removed from extraDomains but still remains in the config (from customZones),
// so the host config hash doesn't change and applyDNSConfig is not called.
applyHostConfigCall: 2,
applyHostConfigCall: 3,
},
{
name: "Register domain that is part of nameserver group",

View File

@@ -312,8 +312,6 @@ type serviceClient struct {
daemonVersion string
updateIndicationLock sync.Mutex
isUpdateIconActive bool
settingsEnabled bool
profilesEnabled bool
showNetworks bool
wNetworks fyne.Window
wProfiles fyne.Window
@@ -909,7 +907,7 @@ func (s *serviceClient) updateStatus() error {
var systrayIconState bool
switch {
case status.Status == string(internal.StatusConnected) && !s.mUp.Disabled():
case status.Status == string(internal.StatusConnected):
s.connected = true
s.sendNotification = true
if s.isUpdateIconActive {
@@ -923,7 +921,6 @@ func (s *serviceClient) updateStatus() error {
s.mUp.Disable()
s.mDown.Enable()
s.mNetworks.Enable()
s.mExitNode.Enable()
go s.updateExitNodes()
systrayIconState = true
case status.Status == string(internal.StatusConnecting):
@@ -1277,22 +1274,19 @@ func (s *serviceClient) checkAndUpdateFeatures() {
return
}
s.updateIndicationLock.Lock()
defer s.updateIndicationLock.Unlock()
// Update settings menu based on current features
settingsEnabled := features == nil || !features.DisableUpdateSettings
if s.settingsEnabled != settingsEnabled {
s.settingsEnabled = settingsEnabled
s.setSettingsEnabled(settingsEnabled)
if features != nil && features.DisableUpdateSettings {
s.setSettingsEnabled(false)
} else {
s.setSettingsEnabled(true)
}
// Update profile menu based on current features
if s.mProfile != nil {
profilesEnabled := features == nil || !features.DisableProfiles
if s.profilesEnabled != profilesEnabled {
s.profilesEnabled = profilesEnabled
s.mProfile.setEnabled(profilesEnabled)
if features != nil && features.DisableProfiles {
s.mProfile.setEnabled(false)
} else {
s.mProfile.setEnabled(true)
}
}
}

View File

@@ -7,7 +7,6 @@ import (
"strings"
integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
@@ -84,6 +83,10 @@ func toNetbirdConfig(config *nbconfig.Config, turnCredentials *Token, relayToken
return nbConfig
}
// toPeerConfig builds a proto.PeerConfig from internal peer, network, DNS name, and settings.
//
// The returned PeerConfig includes the peer's IP with network mask, FQDN, SSH configuration
// (including JWT config when SSH is enabled), and flags for routing DNS resolution and lazy connections.
func toPeerConfig(peer *nbpeer.Peer, network *types.Network, dnsName string, settings *types.Settings, httpConfig *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfig.DeviceAuthorizationFlow) *proto.PeerConfig {
netmask, _ := network.Net.Mask.Size()
fqdn := peer.FQDN(dnsName)
@@ -102,20 +105,25 @@ func toPeerConfig(peer *nbpeer.Peer, network *types.Network, dnsName string, set
Fqdn: fqdn,
RoutingPeerDnsResolutionEnabled: settings.RoutingPeerDNSResolutionEnabled,
LazyConnectionEnabled: settings.LazyConnectionEnabled,
AutoUpdate: &proto.AutoUpdateSettings{
Version: settings.AutoUpdateVersion,
},
}
}
func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfig.DeviceAuthorizationFlow, peer *nbpeer.Peer, turnCredentials *Token, relayCredentials *Token, networkMap *types.NetworkMap, dnsName string, checks []*posture.Checks, dnsCache *cache.DNSConfigCache, settings *types.Settings, extraSettings *types.ExtraSettings, peerGroups []string, dnsFwdPort int64) *proto.SyncResponse {
// ToSyncResponse constructs a proto.SyncResponse that bundles the peer's runtime configuration,
// network map (routes, DNS, peer lists, firewall and forwarding rules), Netbird configuration,
// and posture checks for a sync operation.
//
// The response includes PeerConfig, NetworkMap (with Serial, Routes, DNSConfig, PeerConfig,
// RemotePeers, OfflinePeers, FirewallRules, RoutesFirewallRules, and ForwardingRules when present),
// NetbirdConfig (extended with integrations), and Checks. Remote peer lists' "IsEmpty" flags are
// set based on their lengths. If remotePeerGroupsLookup is non-nil, each remote peer's Groups and
// UserId fields are populated using GetPeerGroupNames for that peer.
func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfig.DeviceAuthorizationFlow, peer *nbpeer.Peer, turnCredentials *Token, relayCredentials *Token, networkMap *types.NetworkMap, dnsName string, checks []*posture.Checks, dnsCache *cache.DNSConfigCache, settings *types.Settings, extraSettings *types.ExtraSettings, peerGroups []string, dnsFwdPort int64, remotePeerGroupsLookup PeerGroupsLookup) *proto.SyncResponse {
response := &proto.SyncResponse{
PeerConfig: toPeerConfig(peer, networkMap.Network, dnsName, settings, httpConfig, deviceFlowConfig),
NetworkMap: &proto.NetworkMap{
Serial: networkMap.Network.CurrentSerial(),
Routes: toProtocolRoutes(networkMap.Routes),
DNSConfig: toProtocolDNSConfig(networkMap.DNSConfig, dnsCache, dnsFwdPort),
PeerConfig: toPeerConfig(peer, networkMap.Network, dnsName, settings, httpConfig, deviceFlowConfig),
Serial: networkMap.Network.CurrentSerial(),
Routes: toProtocolRoutes(networkMap.Routes),
DNSConfig: toProtocolDNSConfig(networkMap.DNSConfig, dnsCache, dnsFwdPort),
},
Checks: toProtocolChecks(ctx, checks),
}
@@ -127,13 +135,13 @@ func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nb
response.NetworkMap.PeerConfig = response.PeerConfig
remotePeers := make([]*proto.RemotePeerConfig, 0, len(networkMap.Peers)+len(networkMap.OfflinePeers))
remotePeers = appendRemotePeerConfig(remotePeers, networkMap.Peers, dnsName)
remotePeers = appendRemotePeerConfig(remotePeers, networkMap.Peers, dnsName, remotePeerGroupsLookup)
response.RemotePeers = remotePeers
response.NetworkMap.RemotePeers = remotePeers
response.RemotePeersIsEmpty = len(remotePeers) == 0
response.NetworkMap.RemotePeersIsEmpty = response.RemotePeersIsEmpty
response.NetworkMap.OfflinePeers = appendRemotePeerConfig(nil, networkMap.OfflinePeers, dnsName)
response.NetworkMap.OfflinePeers = appendRemotePeerConfig(nil, networkMap.OfflinePeers, dnsName, remotePeerGroupsLookup)
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
response.NetworkMap.FirewallRules = firewallRules
@@ -154,14 +162,58 @@ func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nb
return response
}
func appendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
// PeerGroupsLookup provides group names for a peer ID
type PeerGroupsLookup interface {
GetPeerGroupNames(peerID string) []string
}
// AccountPeerGroupsLookup implements PeerGroupsLookup using a pre-built reverse index
// for O(1) lookup performance instead of O(N*M) iteration.
type AccountPeerGroupsLookup struct {
peerToGroups map[string][]string
}
// NewAccountPeerGroupsLookup creates a new AccountPeerGroupsLookup from an Account.
// NewAccountPeerGroupsLookup builds an AccountPeerGroupsLookup containing a reverse index
// from peer ID to the names of groups that include that peer. If account is nil, it returns nil.
func NewAccountPeerGroupsLookup(account *types.Account) *AccountPeerGroupsLookup {
if account == nil {
return nil
}
peerToGroups := make(map[string][]string)
for _, group := range account.Groups {
for _, peerID := range group.Peers {
peerToGroups[peerID] = append(peerToGroups[peerID], group.Name)
}
}
return &AccountPeerGroupsLookup{peerToGroups: peerToGroups}
}
// GetPeerGroupNames returns the group names for a given peer ID.
// Returns nil if the peer is not found in any group.
func (a *AccountPeerGroupsLookup) GetPeerGroupNames(peerID string) []string {
if a == nil || a.peerToGroups == nil {
return nil
}
return a.peerToGroups[peerID]
}
// appendRemotePeerConfig appends a RemotePeerConfig for each peer in peers to dst.
// For each peer it adds a RemotePeerConfig populated with the WireGuard public key, an /32 allowed IP derived from the peer IP, SSH public key, FQDN (computed using dnsName), agent version, group names retrieved from groupsLookup when provided, and the user ID, and returns the extended slice.
func appendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*nbpeer.Peer, dnsName string, groupsLookup PeerGroupsLookup) []*proto.RemotePeerConfig {
for _, rPeer := range peers {
var groups []string
if groupsLookup != nil {
groups = groupsLookup.GetPeerGroupNames(rPeer.ID)
}
dst = append(dst, &proto.RemotePeerConfig{
WgPubKey: rPeer.Key,
AllowedIps: []string{rPeer.IP.String() + "/32"},
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
Fqdn: rPeer.FQDN(dnsName),
AgentVersion: rPeer.Meta.WtVersion,
Groups: groups,
UserId: rPeer.UserID,
})
}
return dst
@@ -407,4 +459,4 @@ func deriveIssuerFromTokenEndpoint(tokenEndpoint string) string {
}
return fmt.Sprintf("%s://%s/", u.Scheme, u.Host)
}
}

View File

@@ -1,216 +0,0 @@
#!/bin/bash
#
# FreeBSD Port Diff Generator for NetBird
#
# This script generates the diff file required for submitting a FreeBSD port update.
# It works on macOS, Linux, and FreeBSD by fetching files from FreeBSD cgit and
# computing checksums from the Go module proxy.
#
# Usage: ./freebsd-port-diff.sh [new_version]
# Example: ./freebsd-port-diff.sh 0.60.7
#
# If no version is provided, it fetches the latest from GitHub.
set -e
GITHUB_REPO="netbirdio/netbird"
PORTS_CGIT_BASE="https://cgit.freebsd.org/ports/plain/security/netbird"
GO_PROXY="https://proxy.golang.org/github.com/netbirdio/netbird/@v"
OUTPUT_DIR="${OUTPUT_DIR:-.}"
AWK_FIRST_FIELD='{print $1}'
fetch_all_tags() {
curl -sL "https://github.com/${GITHUB_REPO}/tags" 2>/dev/null | \
grep -oE '/releases/tag/v[0-9]+\.[0-9]+\.[0-9]+' | \
sed 's/.*\/v//' | \
sort -u -V
return 0
}
fetch_current_ports_version() {
echo "Fetching current version from FreeBSD ports..." >&2
curl -sL "${PORTS_CGIT_BASE}/Makefile" 2>/dev/null | \
grep -E "^DISTVERSION=" | \
sed 's/DISTVERSION=[[:space:]]*//' | \
tr -d '\t '
return 0
}
fetch_latest_github_release() {
echo "Fetching latest release from GitHub..." >&2
fetch_all_tags | tail -1
return 0
}
fetch_ports_file() {
local filename="$1"
curl -sL "${PORTS_CGIT_BASE}/${filename}" 2>/dev/null
return 0
}
compute_checksums() {
local version="$1"
local tmpdir
tmpdir=$(mktemp -d)
# shellcheck disable=SC2064
trap "rm -rf '$tmpdir'" EXIT
echo "Downloading files from Go module proxy for v${version}..." >&2
local mod_file="${tmpdir}/v${version}.mod"
local zip_file="${tmpdir}/v${version}.zip"
curl -sL "${GO_PROXY}/v${version}.mod" -o "$mod_file" 2>/dev/null
curl -sL "${GO_PROXY}/v${version}.zip" -o "$zip_file" 2>/dev/null
if [[ ! -s "$mod_file" ]] || [[ ! -s "$zip_file" ]]; then
echo "Error: Could not download files from Go module proxy" >&2
return 1
fi
local mod_sha256 mod_size zip_sha256 zip_size
if command -v sha256sum &>/dev/null; then
mod_sha256=$(sha256sum "$mod_file" | awk "$AWK_FIRST_FIELD")
zip_sha256=$(sha256sum "$zip_file" | awk "$AWK_FIRST_FIELD")
elif command -v shasum &>/dev/null; then
mod_sha256=$(shasum -a 256 "$mod_file" | awk "$AWK_FIRST_FIELD")
zip_sha256=$(shasum -a 256 "$zip_file" | awk "$AWK_FIRST_FIELD")
else
echo "Error: No sha256 command found" >&2
return 1
fi
if [[ "$OSTYPE" == "darwin"* ]]; then
mod_size=$(stat -f%z "$mod_file")
zip_size=$(stat -f%z "$zip_file")
else
mod_size=$(stat -c%s "$mod_file")
zip_size=$(stat -c%s "$zip_file")
fi
echo "TIMESTAMP = $(date +%s)"
echo "SHA256 (go/security_netbird/netbird-v${version}/v${version}.mod) = ${mod_sha256}"
echo "SIZE (go/security_netbird/netbird-v${version}/v${version}.mod) = ${mod_size}"
echo "SHA256 (go/security_netbird/netbird-v${version}/v${version}.zip) = ${zip_sha256}"
echo "SIZE (go/security_netbird/netbird-v${version}/v${version}.zip) = ${zip_size}"
return 0
}
generate_new_makefile() {
local new_version="$1"
local old_makefile="$2"
# Check if old version had PORTREVISION
if echo "$old_makefile" | grep -q "^PORTREVISION="; then
# Remove PORTREVISION line and update DISTVERSION
echo "$old_makefile" | \
sed "s/^DISTVERSION=.*/DISTVERSION= ${new_version}/" | \
grep -v "^PORTREVISION="
else
# Just update DISTVERSION
echo "$old_makefile" | \
sed "s/^DISTVERSION=.*/DISTVERSION= ${new_version}/"
fi
return 0
}
# Parse arguments
NEW_VERSION="${1:-}"
# Auto-detect versions if not provided
OLD_VERSION=$(fetch_current_ports_version)
if [[ -z "$OLD_VERSION" ]]; then
echo "Error: Could not fetch current version from FreeBSD ports" >&2
exit 1
fi
echo "Current FreeBSD ports version: ${OLD_VERSION}" >&2
if [[ -z "$NEW_VERSION" ]]; then
NEW_VERSION=$(fetch_latest_github_release)
if [[ -z "$NEW_VERSION" ]]; then
echo "Error: Could not fetch latest release from GitHub" >&2
exit 1
fi
fi
echo "Target version: ${NEW_VERSION}" >&2
if [[ "$OLD_VERSION" = "$NEW_VERSION" ]]; then
echo "Port is already at version ${NEW_VERSION}. Nothing to do." >&2
exit 0
fi
echo "" >&2
# Fetch current files
echo "Fetching current Makefile from FreeBSD ports..." >&2
OLD_MAKEFILE=$(fetch_ports_file "Makefile")
if [[ -z "$OLD_MAKEFILE" ]]; then
echo "Error: Could not fetch Makefile" >&2
exit 1
fi
echo "Fetching current distinfo from FreeBSD ports..." >&2
OLD_DISTINFO=$(fetch_ports_file "distinfo")
if [[ -z "$OLD_DISTINFO" ]]; then
echo "Error: Could not fetch distinfo" >&2
exit 1
fi
# Generate new files
echo "Generating new Makefile..." >&2
NEW_MAKEFILE=$(generate_new_makefile "$NEW_VERSION" "$OLD_MAKEFILE")
echo "Computing checksums for new version..." >&2
NEW_DISTINFO=$(compute_checksums "$NEW_VERSION")
if [[ -z "$NEW_DISTINFO" ]]; then
echo "Error: Could not compute checksums" >&2
exit 1
fi
# Create temp files for diff
TMPDIR=$(mktemp -d)
# shellcheck disable=SC2064
trap "rm -rf '$TMPDIR'" EXIT
mkdir -p "${TMPDIR}/a/security/netbird" "${TMPDIR}/b/security/netbird"
echo "$OLD_MAKEFILE" > "${TMPDIR}/a/security/netbird/Makefile"
echo "$OLD_DISTINFO" > "${TMPDIR}/a/security/netbird/distinfo"
echo "$NEW_MAKEFILE" > "${TMPDIR}/b/security/netbird/Makefile"
echo "$NEW_DISTINFO" > "${TMPDIR}/b/security/netbird/distinfo"
# Generate diff
OUTPUT_FILE="${OUTPUT_DIR}/netbird-${NEW_VERSION}.diff"
echo "" >&2
echo "Generating diff..." >&2
# Generate diff and clean up temp paths to show standard a/b paths
(cd "${TMPDIR}" && diff -ruN "a/security/netbird" "b/security/netbird") > "$OUTPUT_FILE" || true
if [[ ! -s "$OUTPUT_FILE" ]]; then
echo "Error: Generated diff is empty" >&2
exit 1
fi
echo "" >&2
echo "========================================="
echo "Diff saved to: ${OUTPUT_FILE}"
echo "========================================="
echo ""
cat "$OUTPUT_FILE"
echo ""
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Review the diff above"
echo "2. Submit to https://bugs.freebsd.org/bugzilla/"
echo "3. Use ./freebsd-port-issue-body.sh to generate the issue content"
echo ""
echo "For FreeBSD testing (optional but recommended):"
echo " cd /usr/ports/security/netbird"
echo " patch < ${OUTPUT_FILE}"
echo " make stage && make stage-qa && make package && make install"
echo " netbird status"
echo " make deinstall"

View File

@@ -1,159 +0,0 @@
#!/bin/bash
#
# FreeBSD Port Issue Body Generator for NetBird
#
# This script generates the issue body content for submitting a FreeBSD port update
# to the FreeBSD Bugzilla at https://bugs.freebsd.org/bugzilla/
#
# Usage: ./freebsd-port-issue-body.sh [old_version] [new_version]
# Example: ./freebsd-port-issue-body.sh 0.56.0 0.59.1
#
# If no versions are provided, the script will:
# - Fetch OLD version from FreeBSD ports cgit (current version in ports tree)
# - Fetch NEW version from latest NetBird GitHub release tag
set -e
GITHUB_REPO="netbirdio/netbird"
PORTS_CGIT_URL="https://cgit.freebsd.org/ports/plain/security/netbird/Makefile"
fetch_current_ports_version() {
echo "Fetching current version from FreeBSD ports..." >&2
local makefile_content
makefile_content=$(curl -sL "$PORTS_CGIT_URL" 2>/dev/null)
if [[ -z "$makefile_content" ]]; then
echo "Error: Could not fetch Makefile from FreeBSD ports" >&2
return 1
fi
echo "$makefile_content" | grep -E "^DISTVERSION=" | sed 's/DISTVERSION=[[:space:]]*//' | tr -d '\t '
return 0
}
fetch_all_tags() {
# Fetch tags from GitHub tags page (no rate limiting, no auth needed)
curl -sL "https://github.com/${GITHUB_REPO}/tags" 2>/dev/null | \
grep -oE '/releases/tag/v[0-9]+\.[0-9]+\.[0-9]+' | \
sed 's/.*\/v//' | \
sort -u -V
return 0
}
fetch_latest_github_release() {
echo "Fetching latest release from GitHub..." >&2
local latest
# Fetch from GitHub tags page
latest=$(fetch_all_tags | tail -1)
if [[ -z "$latest" ]]; then
# Fallback to GitHub API
latest=$(curl -sL "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" 2>/dev/null | \
grep '"tag_name"' | sed 's/.*"tag_name": *"v\([^"]*\)".*/\1/')
fi
if [[ -z "$latest" ]]; then
echo "Error: Could not fetch latest release from GitHub" >&2
return 1
fi
echo "$latest"
return 0
}
OLD_VERSION="${1:-}"
NEW_VERSION="${2:-}"
if [[ -z "$OLD_VERSION" ]]; then
OLD_VERSION=$(fetch_current_ports_version)
if [[ -z "$OLD_VERSION" ]]; then
echo "Error: Could not determine old version. Please provide it manually." >&2
echo "Usage: $0 <old_version> <new_version>" >&2
exit 1
fi
echo "Detected OLD version from FreeBSD ports: $OLD_VERSION" >&2
fi
if [[ -z "$NEW_VERSION" ]]; then
NEW_VERSION=$(fetch_latest_github_release)
if [[ -z "$NEW_VERSION" ]]; then
echo "Error: Could not determine new version. Please provide it manually." >&2
echo "Usage: $0 <old_version> <new_version>" >&2
exit 1
fi
echo "Detected NEW version from GitHub: $NEW_VERSION" >&2
fi
if [[ "$OLD_VERSION" = "$NEW_VERSION" ]]; then
echo "Warning: OLD and NEW versions are the same ($OLD_VERSION). Port may already be up to date." >&2
fi
echo "" >&2
OUTPUT_DIR="${OUTPUT_DIR:-.}"
fetch_releases_between_versions() {
echo "Fetching release history from GitHub..." >&2
# Fetch all tags and filter to those between OLD and NEW versions
fetch_all_tags | \
while read -r ver; do
if [[ "$(printf '%s\n' "$OLD_VERSION" "$ver" | sort -V | head -n1)" = "$OLD_VERSION" ]] && \
[[ "$(printf '%s\n' "$ver" "$NEW_VERSION" | sort -V | head -n1)" = "$ver" ]] && \
[[ "$ver" != "$OLD_VERSION" ]]; then
echo "$ver"
fi
done
return 0
}
generate_changelog_section() {
local releases
releases=$(fetch_releases_between_versions)
echo "Changelogs:"
if [[ -n "$releases" ]]; then
echo "$releases" | while read -r ver; do
echo "https://github.com/${GITHUB_REPO}/releases/tag/v${ver}"
done
else
echo "https://github.com/${GITHUB_REPO}/releases/tag/v${NEW_VERSION}"
fi
return 0
}
OUTPUT_FILE="${OUTPUT_DIR}/netbird-${NEW_VERSION}-issue.txt"
cat << EOF > "$OUTPUT_FILE"
BUGZILLA ISSUE DETAILS
======================
Severity: Affects Some People
Summary: security/netbird: Update to ${NEW_VERSION}
Description:
------------
security/netbird: Update ${OLD_VERSION} => ${NEW_VERSION}
$(generate_changelog_section)
Commit log:
https://github.com/${GITHUB_REPO}/compare/v${OLD_VERSION}...v${NEW_VERSION}
EOF
echo "========================================="
echo "Issue body saved to: ${OUTPUT_FILE}"
echo "========================================="
echo ""
cat "$OUTPUT_FILE"
echo ""
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Go to https://bugs.freebsd.org/bugzilla/ and login"
echo "2. Click 'Report an update or defect to a port'"
echo "3. Fill in:"
echo " - Severity: Affects Some People"
echo " - Summary: security/netbird: Update to ${NEW_VERSION}"
echo " - Description: Copy content from ${OUTPUT_FILE}"
echo "4. Attach diff file: netbird-${NEW_VERSION}.diff"
echo "5. Submit the bug report"