mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-11 08:18:49 +00:00
feat: enhance production command handling and configuration generation
- Added comprehensive tests for production command flag parsing to ensure correct handling of bootstrap, VPS IP, and peer configurations. - Updated production command help output to clarify the usage of new flags, including `--vps-ip` and `--bootstrap-join`. - Modified the configuration generation logic to incorporate the new `bootstrapJoin` parameter for secondary bootstrap nodes. - Enhanced systemd service generation to include the correct advertise IP and join address for non-bootstrap nodes. - Implemented tests for RQLite service generation to verify the inclusion of join addresses and advertise IPs in the generated units.
This commit is contained in:
parent
ed80b5b023
commit
badaa920d9
34
CHANGELOG.md
34
CHANGELOG.md
@ -13,6 +13,40 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
||||
### Deprecated
|
||||
|
||||
### Fixed
|
||||
## [0.67.0] - 2025-11-11
|
||||
|
||||
### Added
|
||||
- Added support for joining a cluster as a secondary bootstrap node using the new `--bootstrap-join` flag.
|
||||
- Added a new flag `--vps-ip` to specify the public IP address for non-bootstrap nodes, which is now required for cluster joining.
|
||||
|
||||
### Changed
|
||||
- Updated the installation script to correctly download and install the CLI binary from the GitHub release archive.
|
||||
- Improved RQLite service configuration to correctly use the public IP address (`--vps-ip`) for advertising its raft and HTTP addresses.
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
- Fixed an issue where non-bootstrap nodes could be installed without specifying the required `--vps-ip`.
|
||||
|
||||
## [0.67.0] - 2025-11-11
|
||||
|
||||
### Added
|
||||
- Added support for joining a cluster as a secondary bootstrap node using the new `--bootstrap-join` flag.
|
||||
- Added a new flag `--vps-ip` to specify the public IP address for non-bootstrap nodes, which is now required for cluster joining.
|
||||
|
||||
### Changed
|
||||
- Updated the installation script to correctly download and install the CLI binary from the GitHub release archive.
|
||||
- Improved RQLite service configuration to correctly use the public IP address (`--vps-ip`) for advertising its raft and HTTP addresses.
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
- Fixed an issue where non-bootstrap nodes could be installed without specifying the required `--vps-ip`.
|
||||
|
||||
## [0.66.1] - 2025-11-11
|
||||
|
||||
### Added
|
||||
|
||||
2
Makefile
2
Makefile
@ -19,7 +19,7 @@ test-e2e:
|
||||
|
||||
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill
|
||||
|
||||
VERSION := 0.66.1
|
||||
VERSION := 0.67.0
|
||||
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
||||
|
||||
@ -49,8 +49,9 @@ func showProdHelp() {
|
||||
fmt.Printf(" Options:\n")
|
||||
fmt.Printf(" --force - Reconfigure all settings\n")
|
||||
fmt.Printf(" --bootstrap - Install as bootstrap node\n")
|
||||
fmt.Printf(" --vps-ip IP - VPS public IP address (required for non-bootstrap)\n")
|
||||
fmt.Printf(" --peers ADDRS - Comma-separated bootstrap peers (for non-bootstrap)\n")
|
||||
fmt.Printf(" --vps-ip IP - VPS public IP address\n")
|
||||
fmt.Printf(" --bootstrap-join ADDR - Bootstrap raft join address (for secondary bootstrap)\n")
|
||||
fmt.Printf(" --domain DOMAIN - Domain for HTTPS (optional)\n")
|
||||
fmt.Printf(" upgrade - Upgrade existing installation (requires root/sudo)\n")
|
||||
fmt.Printf(" status - Show status of production services\n")
|
||||
@ -59,8 +60,12 @@ func showProdHelp() {
|
||||
fmt.Printf(" --follow - Follow logs in real-time\n")
|
||||
fmt.Printf(" uninstall - Remove production services (requires root/sudo)\n\n")
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" sudo dbn prod install --bootstrap\n")
|
||||
fmt.Printf(" sudo dbn prod install --peers /ip4/1.2.3.4/tcp/4001/p2p/Qm...\n")
|
||||
fmt.Printf(" # Bootstrap node\n")
|
||||
fmt.Printf(" sudo dbn prod install --bootstrap\n\n")
|
||||
fmt.Printf(" # Join existing cluster\n")
|
||||
fmt.Printf(" sudo dbn prod install --vps-ip 10.0.0.2 --peers /ip4/10.0.0.1/tcp/4001/p2p/Qm...\n\n")
|
||||
fmt.Printf(" # Secondary bootstrap joining existing cluster\n")
|
||||
fmt.Printf(" sudo dbn prod install --bootstrap --vps-ip 10.0.0.2 --bootstrap-join 10.0.0.1:7001\n\n")
|
||||
fmt.Printf(" dbn prod status\n")
|
||||
fmt.Printf(" dbn prod logs node --follow\n")
|
||||
}
|
||||
@ -69,7 +74,7 @@ func handleProdInstall(args []string) {
|
||||
// Parse arguments
|
||||
force := false
|
||||
isBootstrap := false
|
||||
var vpsIP, domain, peersStr string
|
||||
var vpsIP, domain, peersStr, bootstrapJoin string
|
||||
|
||||
for i, arg := range args {
|
||||
switch arg {
|
||||
@ -89,6 +94,10 @@ func handleProdInstall(args []string) {
|
||||
if i+1 < len(args) {
|
||||
domain = args[i+1]
|
||||
}
|
||||
case "--bootstrap-join":
|
||||
if i+1 < len(args) {
|
||||
bootstrapJoin = args[i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +113,13 @@ func handleProdInstall(args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Enforce --vps-ip for non-bootstrap nodes
|
||||
if !isBootstrap && vpsIP == "" {
|
||||
fmt.Fprintf(os.Stderr, "❌ --vps-ip is required for non-bootstrap nodes\n")
|
||||
fmt.Fprintf(os.Stderr, " Usage: sudo dbn prod install --vps-ip <public_ip> --peers <multiaddr>\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
debrosHome := "/home/debros"
|
||||
setup := production.NewProductionSetup(debrosHome, os.Stdout, force)
|
||||
|
||||
@ -152,14 +168,14 @@ func handleProdInstall(args []string) {
|
||||
// Phase 4: Generate configs
|
||||
fmt.Printf("\n⚙️ Phase 4: Generating configurations...\n")
|
||||
enableHTTPS := domain != ""
|
||||
if err := setup.Phase4GenerateConfigs(isBootstrap, bootstrapPeers, vpsIP, enableHTTPS, domain); err != nil {
|
||||
if err := setup.Phase4GenerateConfigs(isBootstrap, bootstrapPeers, vpsIP, enableHTTPS, domain, bootstrapJoin); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Configuration generation failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Phase 5: Create systemd services
|
||||
fmt.Printf("\n🔧 Phase 5: Creating systemd services...\n")
|
||||
if err := setup.Phase5CreateSystemdServices(nodeType); err != nil {
|
||||
if err := setup.Phase5CreateSystemdServices(nodeType, vpsIP); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Service creation failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
88
pkg/cli/prod_commands_test.go
Normal file
88
pkg/cli/prod_commands_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestProdCommandFlagParsing verifies that prod command flags are parsed correctly
|
||||
func TestProdCommandFlagParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectBootstrap bool
|
||||
expectVPSIP string
|
||||
expectBootstrapJoin string
|
||||
expectPeers string
|
||||
}{
|
||||
{
|
||||
name: "bootstrap node",
|
||||
args: []string{"install", "--bootstrap"},
|
||||
expectBootstrap: true,
|
||||
},
|
||||
{
|
||||
name: "non-bootstrap with vps-ip",
|
||||
args: []string{"install", "--vps-ip", "10.0.0.2", "--peers", "multiaddr1,multiaddr2"},
|
||||
expectVPSIP: "10.0.0.2",
|
||||
expectPeers: "multiaddr1,multiaddr2",
|
||||
},
|
||||
{
|
||||
name: "secondary bootstrap",
|
||||
args: []string{"install", "--bootstrap", "--vps-ip", "10.0.0.3", "--bootstrap-join", "10.0.0.1:7001"},
|
||||
expectBootstrap: true,
|
||||
expectVPSIP: "10.0.0.3",
|
||||
expectBootstrapJoin: "10.0.0.1:7001",
|
||||
},
|
||||
{
|
||||
name: "with domain",
|
||||
args: []string{"install", "--bootstrap", "--domain", "example.com"},
|
||||
expectBootstrap: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Extract flags manually to verify parsing logic
|
||||
force := false
|
||||
isBootstrap := false
|
||||
var vpsIP, domain, peersStr, bootstrapJoin string
|
||||
|
||||
for i, arg := range tt.args {
|
||||
switch arg {
|
||||
case "--force":
|
||||
force = true
|
||||
case "--bootstrap":
|
||||
isBootstrap = true
|
||||
case "--peers":
|
||||
if i+1 < len(tt.args) {
|
||||
peersStr = tt.args[i+1]
|
||||
}
|
||||
case "--vps-ip":
|
||||
if i+1 < len(tt.args) {
|
||||
vpsIP = tt.args[i+1]
|
||||
}
|
||||
case "--domain":
|
||||
if i+1 < len(tt.args) {
|
||||
domain = tt.args[i+1]
|
||||
}
|
||||
case "--bootstrap-join":
|
||||
if i+1 < len(tt.args) {
|
||||
bootstrapJoin = tt.args[i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isBootstrap != tt.expectBootstrap {
|
||||
t.Errorf("expected bootstrap=%v, got %v", tt.expectBootstrap, isBootstrap)
|
||||
}
|
||||
if vpsIP != tt.expectVPSIP {
|
||||
t.Errorf("expected vpsIP=%q, got %q", tt.expectVPSIP, vpsIP)
|
||||
}
|
||||
if peersStr != tt.expectPeers {
|
||||
t.Errorf("expected peers=%q, got %q", tt.expectPeers, peersStr)
|
||||
}
|
||||
if bootstrapJoin != tt.expectBootstrapJoin {
|
||||
t.Errorf("expected bootstrapJoin=%q, got %q", tt.expectBootstrapJoin, bootstrapJoin)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,7 @@ func NewConfigGenerator(debrosDir string) *ConfigGenerator {
|
||||
}
|
||||
|
||||
// GenerateNodeConfig generates node.yaml configuration
|
||||
func (cg *ConfigGenerator) GenerateNodeConfig(isBootstrap bool, bootstrapPeers []string, vpsIP string) (string, error) {
|
||||
func (cg *ConfigGenerator) GenerateNodeConfig(isBootstrap bool, bootstrapPeers []string, vpsIP string, bootstrapJoin string) (string, error) {
|
||||
var nodeID string
|
||||
if isBootstrap {
|
||||
nodeID = "bootstrap"
|
||||
@ -36,19 +36,22 @@ func (cg *ConfigGenerator) GenerateNodeConfig(isBootstrap bool, bootstrapPeers [
|
||||
}
|
||||
|
||||
if isBootstrap {
|
||||
// Bootstrap node - populate peer list and optional join address
|
||||
data := templates.BootstrapConfigData{
|
||||
NodeID: nodeID,
|
||||
P2PPort: 4001,
|
||||
DataDir: filepath.Join(cg.debrosDir, "data", "bootstrap"),
|
||||
RQLiteHTTPPort: 5001,
|
||||
RQLiteRaftPort: 7001,
|
||||
ClusterAPIPort: 9094,
|
||||
IPFSAPIPort: 4501,
|
||||
NodeID: nodeID,
|
||||
P2PPort: 4001,
|
||||
DataDir: filepath.Join(cg.debrosDir, "data", "bootstrap"),
|
||||
RQLiteHTTPPort: 5001,
|
||||
RQLiteRaftPort: 7001,
|
||||
ClusterAPIPort: 9094,
|
||||
IPFSAPIPort: 4501,
|
||||
BootstrapPeers: bootstrapPeers,
|
||||
RQLiteJoinAddress: bootstrapJoin,
|
||||
}
|
||||
return templates.RenderBootstrapConfig(data)
|
||||
}
|
||||
|
||||
// Regular node
|
||||
// Regular node - must have join address
|
||||
rqliteJoinAddr := "localhost:7001"
|
||||
if vpsIP != "" {
|
||||
rqliteJoinAddr = vpsIP + ":7001"
|
||||
|
||||
@ -281,11 +281,11 @@ func (ps *ProductionSetup) Phase3GenerateSecrets(isBootstrap bool) error {
|
||||
}
|
||||
|
||||
// Phase4GenerateConfigs generates node, gateway, and service configs
|
||||
func (ps *ProductionSetup) Phase4GenerateConfigs(isBootstrap bool, bootstrapPeers []string, vpsIP string, enableHTTPS bool, domain string) error {
|
||||
func (ps *ProductionSetup) Phase4GenerateConfigs(isBootstrap bool, bootstrapPeers []string, vpsIP string, enableHTTPS bool, domain string, bootstrapJoin string) error {
|
||||
ps.logf("Phase 4: Generating configurations...")
|
||||
|
||||
// Node config
|
||||
nodeConfig, err := ps.configGenerator.GenerateNodeConfig(isBootstrap, bootstrapPeers, vpsIP)
|
||||
nodeConfig, err := ps.configGenerator.GenerateNodeConfig(isBootstrap, bootstrapPeers, vpsIP, bootstrapJoin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate node config: %w", err)
|
||||
}
|
||||
@ -337,7 +337,7 @@ func (ps *ProductionSetup) Phase4GenerateConfigs(isBootstrap bool, bootstrapPeer
|
||||
}
|
||||
|
||||
// Phase5CreateSystemdServices creates and enables systemd units
|
||||
func (ps *ProductionSetup) Phase5CreateSystemdServices(nodeType string) error {
|
||||
func (ps *ProductionSetup) Phase5CreateSystemdServices(nodeType string, vpsIP string) error {
|
||||
ps.logf("Phase 5: Creating systemd services...")
|
||||
|
||||
// IPFS service
|
||||
@ -356,8 +356,20 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(nodeType string) error {
|
||||
}
|
||||
ps.logf(" ✓ IPFS Cluster service created: %s", clusterUnitName)
|
||||
|
||||
// RQLite service (only for bootstrap in single-node, or conditionally)
|
||||
rqliteUnit := ps.serviceGenerator.GenerateRQLiteService(nodeType, 5001, 7001, "")
|
||||
// RQLite service with join address for non-bootstrap nodes
|
||||
rqliteJoinAddr := ""
|
||||
if nodeType != "bootstrap" && vpsIP != "" {
|
||||
rqliteJoinAddr = vpsIP + ":7001"
|
||||
}
|
||||
|
||||
// Log the advertise configuration for verification
|
||||
advertiseIP := vpsIP
|
||||
if advertiseIP == "" {
|
||||
advertiseIP = "127.0.0.1"
|
||||
}
|
||||
ps.logf(" RQLite will advertise: %s (advertise IP: %s)", rqliteJoinAddr, advertiseIP)
|
||||
|
||||
rqliteUnit := ps.serviceGenerator.GenerateRQLiteService(nodeType, 5001, 7001, rqliteJoinAddr, advertiseIP)
|
||||
rqliteUnitName := fmt.Sprintf("debros-rqlite-%s.service", nodeType)
|
||||
if err := ps.serviceController.WriteServiceUnit(rqliteUnitName, rqliteUnit); err != nil {
|
||||
return fmt.Errorf("failed to write RQLite service: %w", err)
|
||||
|
||||
@ -104,7 +104,7 @@ WantedBy=multi-user.target
|
||||
}
|
||||
|
||||
// GenerateRQLiteService generates the RQLite systemd unit
|
||||
func (ssg *SystemdServiceGenerator) GenerateRQLiteService(nodeType string, httpPort, raftPort int, joinAddr string) string {
|
||||
func (ssg *SystemdServiceGenerator) GenerateRQLiteService(nodeType string, httpPort, raftPort int, joinAddr string, advertiseIP string) string {
|
||||
var dataDir string
|
||||
if nodeType == "bootstrap" {
|
||||
dataDir = filepath.Join(ssg.debrosDir, "data", "bootstrap", "rqlite")
|
||||
@ -112,9 +112,14 @@ func (ssg *SystemdServiceGenerator) GenerateRQLiteService(nodeType string, httpP
|
||||
dataDir = filepath.Join(ssg.debrosDir, "data", "node", "rqlite")
|
||||
}
|
||||
|
||||
// Use public IP for advertise if provided, otherwise default to localhost
|
||||
if advertiseIP == "" {
|
||||
advertiseIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
args := fmt.Sprintf(
|
||||
`-http-addr 0.0.0.0:%d -http-adv-addr 127.0.0.1:%d -raft-adv-addr 127.0.0.1:%d -raft-addr 0.0.0.0:%d`,
|
||||
httpPort, httpPort, raftPort, raftPort,
|
||||
`-http-addr 0.0.0.0:%d -http-adv-addr %s:%d -raft-adv-addr %s:%d -raft-addr 0.0.0.0:%d`,
|
||||
httpPort, advertiseIP, httpPort, advertiseIP, raftPort, raftPort,
|
||||
)
|
||||
|
||||
if joinAddr != "" {
|
||||
|
||||
114
pkg/environments/production/services_test.go
Normal file
114
pkg/environments/production/services_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
package production
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGenerateRQLiteService verifies RQLite service generation with advertise IP and join address
|
||||
func TestGenerateRQLiteService(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeType string
|
||||
joinAddr string
|
||||
advertiseIP string
|
||||
expectJoinInUnit bool
|
||||
expectAdvertiseIP string
|
||||
}{
|
||||
{
|
||||
name: "bootstrap with localhost advertise",
|
||||
nodeType: "bootstrap",
|
||||
joinAddr: "",
|
||||
advertiseIP: "",
|
||||
expectJoinInUnit: false,
|
||||
expectAdvertiseIP: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "bootstrap with public IP advertise",
|
||||
nodeType: "bootstrap",
|
||||
joinAddr: "",
|
||||
advertiseIP: "10.0.0.1",
|
||||
expectJoinInUnit: false,
|
||||
expectAdvertiseIP: "10.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "node joining cluster",
|
||||
nodeType: "node",
|
||||
joinAddr: "10.0.0.1:7001",
|
||||
advertiseIP: "10.0.0.2",
|
||||
expectJoinInUnit: true,
|
||||
expectAdvertiseIP: "10.0.0.2",
|
||||
},
|
||||
{
|
||||
name: "node with localhost (should still include join)",
|
||||
nodeType: "node",
|
||||
joinAddr: "localhost:7001",
|
||||
advertiseIP: "127.0.0.1",
|
||||
expectJoinInUnit: true,
|
||||
expectAdvertiseIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ssg := &SystemdServiceGenerator{
|
||||
debrosHome: "/home/debros",
|
||||
debrosDir: "/home/debros/.debros",
|
||||
}
|
||||
|
||||
unit := ssg.GenerateRQLiteService(tt.nodeType, 5001, 7001, tt.joinAddr, tt.advertiseIP)
|
||||
|
||||
// Check advertise IP is present
|
||||
expectedAdvertise := tt.expectAdvertiseIP + ":5001"
|
||||
if !strings.Contains(unit, expectedAdvertise) {
|
||||
t.Errorf("expected advertise address %q in unit, got:\n%s", expectedAdvertise, unit)
|
||||
}
|
||||
|
||||
// Check raft advertise IP is present
|
||||
expectedRaftAdvertise := tt.expectAdvertiseIP + ":7001"
|
||||
if !strings.Contains(unit, expectedRaftAdvertise) {
|
||||
t.Errorf("expected raft advertise address %q in unit, got:\n%s", expectedRaftAdvertise, unit)
|
||||
}
|
||||
|
||||
// Check join flag presence
|
||||
hasJoin := strings.Contains(unit, "-join")
|
||||
if hasJoin != tt.expectJoinInUnit {
|
||||
t.Errorf("expected join in unit: %v, hasJoin: %v\nUnit:\n%s", tt.expectJoinInUnit, hasJoin, unit)
|
||||
}
|
||||
|
||||
if tt.expectJoinInUnit && tt.joinAddr != "" && !strings.Contains(unit, tt.joinAddr) {
|
||||
t.Errorf("expected join address %q in unit, not found", tt.joinAddr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateRQLiteServiceArgs verifies the ExecStart command arguments
|
||||
func TestGenerateRQLiteServiceArgs(t *testing.T) {
|
||||
ssg := &SystemdServiceGenerator{
|
||||
debrosHome: "/home/debros",
|
||||
debrosDir: "/home/debros/.debros",
|
||||
}
|
||||
|
||||
unit := ssg.GenerateRQLiteService("node", 5001, 7001, "10.0.0.1:7001", "10.0.0.2")
|
||||
|
||||
// Verify essential flags are present
|
||||
if !strings.Contains(unit, "-http-addr 0.0.0.0:5001") {
|
||||
t.Error("missing -http-addr 0.0.0.0:5001")
|
||||
}
|
||||
if !strings.Contains(unit, "-http-adv-addr 10.0.0.2:5001") {
|
||||
t.Error("missing -http-adv-addr 10.0.0.2:5001")
|
||||
}
|
||||
if !strings.Contains(unit, "-raft-addr 0.0.0.0:7001") {
|
||||
t.Error("missing -raft-addr 0.0.0.0:7001")
|
||||
}
|
||||
if !strings.Contains(unit, "-raft-adv-addr 10.0.0.2:7001") {
|
||||
t.Error("missing -raft-adv-addr 10.0.0.2:7001")
|
||||
}
|
||||
if !strings.Contains(unit, "-join 10.0.0.1:7001") {
|
||||
t.Error("missing -join 10.0.0.1:7001")
|
||||
}
|
||||
if !strings.Contains(unit, "-join-attempts 30") {
|
||||
t.Error("missing -join-attempts 30")
|
||||
}
|
||||
}
|
||||
@ -101,12 +101,11 @@ get_latest_release() {
|
||||
log "Fetching latest release..."
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
# Get the latest release (including pre-releases/nightly)
|
||||
LATEST_RELEASE=$(curl -fsSL -H "Accept: application/vnd.github+json" "$GITHUB_API/releases" | \
|
||||
jq -r '.[] | select(.prerelease == false and .draft == false) | .tag_name' | head -1)
|
||||
jq -r '.[0] | .tag_name')
|
||||
else
|
||||
LATEST_RELEASE=$(curl -fsSL "$GITHUB_API/releases" | \
|
||||
grep -v "prerelease.*true" | \
|
||||
grep -v "draft.*true" | \
|
||||
grep '"tag_name"' | \
|
||||
head -1 | \
|
||||
cut -d'"' -f4)
|
||||
@ -121,19 +120,52 @@ get_latest_release() {
|
||||
}
|
||||
|
||||
download_and_install_cli() {
|
||||
BINARY_NAME="network-cli_${LATEST_RELEASE#v}_linux_${GITHUB_ARCH}"
|
||||
BINARY_NAME="debros-network_${LATEST_RELEASE#v}_linux_${GITHUB_ARCH}.tar.gz"
|
||||
DOWNLOAD_URL="$GITHUB_REPO/releases/download/$LATEST_RELEASE/$BINARY_NAME"
|
||||
|
||||
log "Downloading dbn from GitHub releases..."
|
||||
if ! curl -fsSL -o /tmp/dbn "https://github.com/$DOWNLOAD_URL"; then
|
||||
log "URL: https://github.com/$DOWNLOAD_URL"
|
||||
|
||||
# Clean up any stale binaries
|
||||
rm -f /tmp/network-cli /tmp/dbn.tar.gz "$INSTALL_DIR/dbn"
|
||||
|
||||
if ! curl -fsSL -o /tmp/dbn.tar.gz "https://github.com/$DOWNLOAD_URL"; then
|
||||
error "Failed to download dbn"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x /tmp/dbn
|
||||
# Verify the download was successful
|
||||
if [ ! -f /tmp/dbn.tar.gz ]; then
|
||||
error "Download file not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Extracting dbn..."
|
||||
# Extract to /tmp
|
||||
tar -xzf /tmp/dbn.tar.gz -C /tmp/
|
||||
|
||||
# Check if binary exists after extraction (it's named network-cli in the archive)
|
||||
if [ ! -f /tmp/network-cli ]; then
|
||||
error "Failed to extract network-cli binary"
|
||||
ls -la /tmp/ | grep -E "(network|cli|dbn)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x /tmp/network-cli
|
||||
|
||||
log "Installing dbn to $INSTALL_DIR..."
|
||||
mv /tmp/dbn "$INSTALL_DIR/dbn"
|
||||
# Rename network-cli to dbn during installation
|
||||
mv /tmp/network-cli "$INSTALL_DIR/dbn"
|
||||
|
||||
# Sanity check: verify the installed binary is functional and reports correct version
|
||||
if ! "$INSTALL_DIR/dbn" version &>/dev/null; then
|
||||
error "Installed dbn failed sanity check (version command failed)"
|
||||
rm -f "$INSTALL_DIR/dbn"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/dbn.tar.gz
|
||||
|
||||
success "dbn installed successfully"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user