From 40926179a824002be6f209baa3155517f52c4403 Mon Sep 17 00:00:00 2001 From: Fornax <23104993+fornax2@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:33:13 -0300 Subject: [PATCH] Add migrate-geth command --- README.md | 1 + rocketpool-cli/service/commands.go | 24 ++++ rocketpool-cli/service/service.go | 111 ++++++++++++++++-- .../assets/install/scripts/start-ec.sh | 5 + shared/services/rocketpool/client.go | 10 +- 5 files changed, 138 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a3d385845..0dfa93515 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,7 @@ The following commands are available via the Smart Node client: - `rocketpool service compose` - View the Rocket Pool service docker compose config - `rocketpool service version, v` - View the Rocket Pool service version information - `rocketpool service prune-eth1, n` - Shuts down the main ETH1 client and prunes its database, freeing up disk space, then restarts it when it's done. + - `rocketpool service migrate-geth` - Shuts down Geth and migrates its database from Pebble v1 to Pebble v2, then restarts it when it's done. - `rocketpool service install-update-tracker, d` - Install the update tracker that provides the available system update count to the metrics dashboard - `rocketpool service get-config-yaml` - Generate YAML that shows the current configuration schema, including all of the parameters and their descriptions - `rocketpool service resync-eth1` - Deletes the main ETH1 client's chain data and resyncs it from scratch. Only use this as a last resort! diff --git a/rocketpool-cli/service/commands.go b/rocketpool-cli/service/commands.go index c7831b5a9..bafb45005 100644 --- a/rocketpool-cli/service/commands.go +++ b/rocketpool-cli/service/commands.go @@ -414,6 +414,30 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { }, }, + { + Name: "migrate-geth", + Usage: "Shuts down Geth and migrates its database from Pebble v1 to Pebble v2, then restarts it when it's done.", + UsageText: "rocketpool service migrate-eth1", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "yes", + Aliases: []string{"y"}, + Usage: "Automatically confirm the Geth Pebble v2 migration", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + + // Validate args + if err := cliutils.ValidateArgCount(c, 0); err != nil { + return err + } + + // Run command + return migrateGethDatabase(c.Bool("yes")) + + }, + }, + { Name: "install-update-tracker", Aliases: []string{"d"}, diff --git a/rocketpool-cli/service/service.go b/rocketpool-cli/service/service.go index 465a9cd47..74c458b5b 100644 --- a/rocketpool-cli/service/service.go +++ b/rocketpool-cli/service/service.go @@ -28,15 +28,16 @@ import ( // Settings const ( - ExporterContainerSuffix string = "_exporter" - ValidatorContainerSuffix string = "_validator" - BeaconContainerSuffix string = "_eth2" - ExecutionContainerSuffix string = "_eth1" - NodeContainerSuffix string = "_node" - WatchtowerContainerSuffix string = "_watchtower" - PruneProvisionerContainerSuffix string = "_prune_provisioner" - clientDataVolumeName string = "/ethclient" - dataFolderVolumeName string = "/.rocketpool/data" + ExporterContainerSuffix string = "_exporter" + ValidatorContainerSuffix string = "_validator" + BeaconContainerSuffix string = "_eth2" + ExecutionContainerSuffix string = "_eth1" + NodeContainerSuffix string = "_node" + WatchtowerContainerSuffix string = "_watchtower" + PruneProvisionerContainerSuffix string = "_prune_provisioner" + MigrateProvisionerContainerSuffix string = "_migrate_provisioner" + clientDataVolumeName string = "/ethclient" + dataFolderVolumeName string = "/.rocketpool/data" PruneFreeSpaceRequired uint64 = 50 * 1024 * 1024 * 1024 NethermindPruneFreeSpaceRequired uint64 = 250 * 1024 * 1024 * 1024 @@ -1078,6 +1079,98 @@ func pruneExecutionClient(yes bool) error { } +// Migrates a local Geth database from Pebble v1 to Pebble v2. +func migrateGethDatabase(yes bool) error { + + rp := rocketpool.NewClient() + defer rp.Close() + + cfg, isNew, err := rp.LoadConfig() + if err != nil { + return err + } + if isNew { + return fmt.Errorf("Settings file not found. Please run `rocketpool service config` to set up your Smart Node.") + } + + if cfg.ExecutionClientMode.Value.(cfgtypes.Mode) == cfgtypes.Mode_External { + fmt.Println("You are using an externally managed Execution client.") + fmt.Println("The Smart Node cannot migrate it. Run `geth db pebble-upgrade` against that client yourself.") + return nil + } + if cfg.IsNativeMode { + fmt.Println("You are using Native Mode.") + fmt.Println("The Smart Node cannot migrate Geth for you. Stop Geth and run `geth db pebble-upgrade --datadir `.") + return nil + } + + selectedEc := cfg.ExecutionClient.Value.(cfgtypes.ExecutionClient) + if selectedEc != cfgtypes.ExecutionClient_Geth { + fmt.Println("Pebble v2 migration is only implemented for Geth.") + return nil + } + + fmt.Println("This will shut down Geth and migrate its database from Pebble v1 to Pebble v2.") + fmt.Println("New Geth databases already use v2; this is only needed for databases created before Geth 1.16+.") + + fmt.Println("Once the migration is complete, Geth will restart automatically.") + fmt.Println() + + if prompt.Declined(yes, "Are you sure you want to migrate the Geth database to Pebble v2?") { + fmt.Println("Cancelled.") + return nil + } + + prefix, err := rp.GetContainerPrefix() + if err != nil { + return fmt.Errorf("Error getting container prefix: %w", err) + } + executionContainerName := prefix + ExecutionContainerSuffix + + exists, err := rp.ContainerExists(executionContainerName) + if err != nil { + return fmt.Errorf("Error checking if main execution container exists: %w", err) + } + if !exists { + return fmt.Errorf("Main execution container [%s] does not exist", executionContainerName) + } + + fmt.Printf("Stopping %s...\n", executionContainerName) + result, err := rp.StopContainer(executionContainerName) + if err != nil { + return fmt.Errorf("Error stopping main execution container: %w", err) + } + if result != executionContainerName { + return fmt.Errorf("Unexpected output while stopping main execution container: %s", result) + } + + volume, err := rp.GetClientVolumeName(executionContainerName, clientDataVolumeName) + if err != nil { + return fmt.Errorf("Error getting execution client volume name: %w", err) + } + + fmt.Printf("Provisioning Pebble v2 migration on volume %s...\n", volume) + err = rp.TouchEthclientMarker(prefix+MigrateProvisionerContainerSuffix, volume, "migrate-v2") + if err != nil { + return fmt.Errorf("Error creating migrate-v2 marker: %w", err) + } + + fmt.Printf("Restarting %s...\n", executionContainerName) + result, err = rp.StartContainer(executionContainerName) + if err != nil { + return fmt.Errorf("Error starting main execution client: %w", err) + } + if result != executionContainerName { + return fmt.Errorf("Unexpected output while starting main execution client: %s", result) + } + + fmt.Println() + fmt.Println("Done! Geth is now migrating to Pebble v2. Follow progress with `rocketpool service logs eth1`.") + color.YellowPrintln("Do not interrupt the client until the migration finishes.") + + return nil +} + // Stops Smart Node stack containers, prunes docker, and restarts the Smart Node stack. func resetDocker(yes, all bool, composeFiles []string) error { diff --git a/shared/services/rocketpool/assets/install/scripts/start-ec.sh b/shared/services/rocketpool/assets/install/scripts/start-ec.sh index 2d75e99d1..34dea50c9 100755 --- a/shared/services/rocketpool/assets/install/scripts/start-ec.sh +++ b/shared/services/rocketpool/assets/install/scripts/start-ec.sh @@ -83,6 +83,11 @@ if [ "$CLIENT" = "geth" ]; then $PERF_PREFIX /usr/local/bin/geth snapshot prune-state $GETH_NETWORK --datadir /ethclient/geth ; rm /ethclient/prune.lock fi + elif [ -f "/ethclient/migrate-v2" ]; then + echo "Migrating Geth database from Pebble v1 to Pebble v2" + rm -f /ethclient/migrate-v2 + $PERF_PREFIX /usr/local/bin/geth db pebble-upgrade $GETH_NETWORK --datadir /ethclient/geth + # Run Geth normally else diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 834b0fbc9..ddcca0744 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -919,9 +919,12 @@ func (c *Client) GetVolumeSize(volumeName string) (string, error) { // Runs the prune provisioner func (c *Client) RunPruneProvisioner(container, volume string) error { + return c.TouchEthclientMarker(container, volume, "prune.lock") +} - // Run the prune provisioner - cmd := fmt.Sprintf("docker run --rm --name %s -v %s:/ethclient alpine:latest sh -c 'touch /ethclient/prune.lock'", container, volume) +// Creates a marker file on the execution client volume (used for prune and DB migrations). +func (c *Client) TouchEthclientMarker(container, volume, marker string) error { + cmd := fmt.Sprintf("docker run --rm --name %s -v %s:/ethclient alpine:latest sh -c 'touch /ethclient/%s'", container, volume, marker) output, err := c.readOutput(cmd) if err != nil { return err @@ -929,11 +932,10 @@ func (c *Client) RunPruneProvisioner(container, volume string) error { outputString := strings.TrimSpace(string(output)) if outputString != "" { - return fmt.Errorf("Unexpected output running the prune provisioner: %s", outputString) + return fmt.Errorf("Unexpected output creating marker %s: %s", marker, outputString) } return nil - } // Curls the Nethermind admin URL to trigger pruning