Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions cmd/nigiri/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ var rememberFlag = cli.BoolFlag{
Value: false,
}

var composeFlag = cli.StringFlag{
Name: "compose",
Usage: "use a custom docker-compose file instead of the embedded one",
Value: "",
}

var start = cli.Command{
Name: "start",
Usage: "start nigiri",
Expand All @@ -39,16 +45,18 @@ var start = cli.Command{
&arkFlag,
&ciFlag,
&rememberFlag,
&composeFlag,
},
}

const savedFlagsFileName = "flags.json"

type savedFlags struct {
Liquid bool `json:"liquid"`
Ln bool `json:"ln"`
Ark bool `json:"ark"`
Ci bool `json:"ci"`
Liquid bool `json:"liquid"`
Ln bool `json:"ln"`
Ark bool `json:"ark"`
Ci bool `json:"ci"`
Compose string `json:"compose,omitempty"`
}

func loadFlags(datadir string) (*savedFlags, error) {
Expand Down Expand Up @@ -88,19 +96,19 @@ func startAction(ctx *cli.Context) error {
}

datadir := ctx.String("datadir")
composePath := filepath.Join(datadir, config.DefaultCompose)


loadedFlags, err := loadFlags(datadir)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not load saved flags: %v\n", err)
loadedFlags = &savedFlags{}
}

effectiveFlags := savedFlags{
Liquid: loadedFlags.Liquid,
Ln: loadedFlags.Ln,
Ark: loadedFlags.Ark,
Ci: loadedFlags.Ci,
Liquid: loadedFlags.Liquid,
Ln: loadedFlags.Ln,
Ark: loadedFlags.Ark,
Ci: loadedFlags.Ci,
Compose: loadedFlags.Compose,
}

if ctx.IsSet("liquid") {
Expand All @@ -115,6 +123,23 @@ func startAction(ctx *cli.Context) error {
if ctx.IsSet("ci") {
effectiveFlags.Ci = ctx.Bool("ci")
}
if ctx.IsSet("compose") {
effectiveFlags.Compose = ctx.String("compose")
}

// Use custom compose file if provided (either from CLI or saved flags), otherwise use default
composePath := effectiveFlags.Compose
if composePath == "" {
composePath = filepath.Join(datadir, config.DefaultCompose)
} else {
// Expand and clean the custom compose path
composePath = cleanAndExpandPath(composePath)

// Verify the custom compose file exists
if _, err := os.Stat(composePath); os.IsNotExist(err) {
return fmt.Errorf("custom compose file not found: %s", composePath)
}
}

if ctx.Bool("remember") {
if err := saveFlags(datadir, &effectiveFlags); err != nil {
Expand Down
62 changes: 62 additions & 0 deletions cmd/nigiri/update.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
package main

import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"syscall"

"github.com/urfave/cli/v2"
"github.com/vulpemventures/nigiri/internal/config"
)

const defaultUpdateScriptURL = "https://getnigiri.vulpem.com"

var update = cli.Command{
Name: "update",
Usage: "check for updates and pull new docker images",
Action: updateAction,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "force",
Usage: "force update the nigiri binary to the latest version",
},
},
}

func updateAction(ctx *cli.Context) error {
if ctx.Bool("force") {
return updateBinary(ctx)
}

datadir := ctx.String("datadir")
composePath := filepath.Join(datadir, config.DefaultCompose)

Expand All @@ -28,3 +44,49 @@ func updateAction(ctx *cli.Context) error {

return nil
}

func updateBinary(ctx *cli.Context) error {
// Get the update script URL from environment variable or use default
updateURL := os.Getenv("NIGIRI_TEST_UPDATE_SCRIPT_URL")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
updateURL := os.Getenv("NIGIRI_TEST_UPDATE_SCRIPT_URL")
updateURL := os.Getenv("NIGIRI_UPDATE_SCRIPT_URL")

if updateURL == "" {
updateURL = defaultUpdateScriptURL
}

fmt.Printf("Downloading update script from %s...\n", updateURL)

// Download the update script
resp, err := http.Get(updateURL)
if err != nil {
return fmt.Errorf("failed to download update script: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to download update script: HTTP %d", resp.StatusCode)
}

// Create a temporary file for the update script
tmpFile, err := os.CreateTemp("", "nigiri-update-*.sh")
if err != nil {
return fmt.Errorf("failed to create temporary file: %w", err)
}
tmpPath := tmpFile.Name()
defer os.Remove(tmpPath)

// Write the script to the temporary file
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
tmpFile.Close()
return fmt.Errorf("failed to write update script: %w", err)
}
tmpFile.Close()

// Make the script executable
if err := os.Chmod(tmpPath, 0755); err != nil {
return fmt.Errorf("failed to make script executable: %w", err)
}

fmt.Println("Executing update script...")

// Execute the update script, replacing the current process
return syscall.Exec(tmpPath, []string{tmpPath}, os.Environ())
}
145 changes: 145 additions & 0 deletions test/start_stop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package test
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -612,3 +614,146 @@ func TestRememberForget(t *testing.T) {
t.Log("Running: stop")
_ = runNigiri(t, "stop")
}

func TestUpdateForce(t *testing.T) {
// Create a mock update script
mockScript := `#!/bin/bash
echo "Mock update script executed successfully"
exit 0
`

// Create a test HTTP server that serves the mock script
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte(mockScript))
}))
defer server.Close()

// Set the environment variable to use the test server URL
originalURL := os.Getenv("NIGIRI_TEST_UPDATE_SCRIPT_URL")
os.Setenv("NIGIRI_TEST_UPDATE_SCRIPT_URL", server.URL)
defer func() {
if originalURL == "" {
os.Unsetenv("NIGIRI_TEST_UPDATE_SCRIPT_URL")
} else {
os.Setenv("NIGIRI_TEST_UPDATE_SCRIPT_URL", originalURL)
}
}()

// Run the update --force command
// Note: This will replace the current process with the mock script,
// so we need to run it in a separate process
cmd := exec.Command(nigiriBinaryPath, "--datadir", tmpDatadir, "update", "--force")
cmd.Env = append(os.Environ(), "NIGIRI_TEST_UPDATE_SCRIPT_URL="+server.URL)

output, err := cmd.CombinedOutput()

// The command should execute the mock script successfully
// Since syscall.Exec replaces the process, we expect the output to be from the mock script
if err != nil {
t.Logf("Command output: %s", string(output))
// Check if the output contains our expected message
if !strings.Contains(string(output), "Mock update script executed successfully") {
t.Fatalf("update --force failed: %v\nOutput: %s", err, string(output))
}
}

// Verify that the download message was printed
if !strings.Contains(string(output), "Downloading update script") {
t.Errorf("Expected download message in output, got: %s", string(output))
}
}

func TestCustomCompose(t *testing.T) {
// Clean up any previous state
stateFilePath := filepath.Join(tmpDatadir, config.DefaultName)
flagsFilePath := filepath.Join(tmpDatadir, "flags.json")
_ = os.Remove(stateFilePath)
_ = os.Remove(flagsFilePath)

// Create a custom docker-compose file
customComposePath := filepath.Join(tmpDatadir, "custom-compose.yml")
customComposeContent := `name: nigiri-custom
services:
bitcoin:
image: getumbrel/bitcoind:v28.0
container_name: bitcoin-custom
electrs:
image: vulpemventures/electrs:latest
chopsticks:
image: vulpemventures/nigiri-chopsticks:latest
esplora:
image: vulpemventures/esplora:latest
`

if err := os.WriteFile(customComposePath, []byte(customComposeContent), 0644); err != nil {
t.Fatalf("Failed to create custom compose file: %v", err)
}
defer os.Remove(customComposePath)

// Test 1: Start with custom compose and --remember
t.Log("Running: start --compose custom-compose.yml --remember")
cmd := exec.Command(nigiriBinaryPath, "--datadir", tmpDatadir, "start", "--compose", customComposePath, "--remember")
output, err := cmd.CombinedOutput()

// We expect it to fail because docker-compose isn't available in CI,
// but it should at least accept the flag and find the file
if err != nil {
// Check that it's not failing due to file not found
if strings.Contains(string(output), "custom compose file not found") {
t.Fatalf("Custom compose file should have been found: %s", string(output))
}
// Expected error is docker-compose not found, which is fine
t.Logf("Expected docker-compose error (first start): %s", string(output))
}

// Verify flags.json was created with the compose path
expectedFlags := map[string]interface{}{
"liquid": false,
"ln": false,
"ark": false,
"ci": false,
"compose": customComposePath,
}
expectedJsonBytes, _ := json.MarshalIndent(expectedFlags, "", " ")
checkFlagsFile(t, true, string(expectedJsonBytes))

// Stop
t.Log("Running: stop")
_ = runNigiri(t, "stop")

// Test 2: Start again without --compose flag (should use remembered path)
t.Log("Running: start (expecting remembered compose path)")
cmd = exec.Command(nigiriBinaryPath, "--datadir", tmpDatadir, "start")
output, err = cmd.CombinedOutput()

if err != nil {
if strings.Contains(string(output), "custom compose file not found") {
t.Fatalf("Should have used remembered compose path: %s", string(output))
}
t.Logf("Expected docker-compose error (second start): %s", string(output))
}

// Flags should still contain the compose path
checkFlagsFile(t, true, string(expectedJsonBytes))

// Stop
t.Log("Running: stop")
_ = runNigiri(t, "stop")

// Test 3: Test with non-existent file
cmd = exec.Command(nigiriBinaryPath, "--datadir", tmpDatadir, "start", "--compose", "/nonexistent/compose.yml")
output, err = cmd.CombinedOutput()

if err == nil {
t.Fatal("Expected error for non-existent compose file")
}

if !strings.Contains(string(output), "custom compose file not found") {
t.Errorf("Expected 'custom compose file not found' error, got: %s", string(output))
}

// Cleanup
_ = runNigiri(t, "forget")
}