diff --git a/pkg/manager/apt.go b/pkg/manager/apt.go index 685e59f..5739a94 100644 --- a/pkg/manager/apt.go +++ b/pkg/manager/apt.go @@ -46,7 +46,7 @@ func AptInstallPackages(selectedRelayOption string) { exec.Command("apt", "update", "-qq").Run() - packages := []string{"ufw", "fail2ban", "nginx", "certbot", "python3-certbot-nginx"} + packages := []string{"curl", "gnupg", "ufw", "fail2ban", "nginx", "certbot", "python3-certbot-nginx"} if selectedRelayOption == nostr_rs_relay.RelayName || selectedRelayOption == strfry.RelayName || selectedRelayOption == wot_relay.RelayName || selectedRelayOption == strfry29.RelayName { packages = append(packages, "git") diff --git a/pkg/network/certbot.go b/pkg/network/certbot.go index fed1605..93deff0 100644 --- a/pkg/network/certbot.go +++ b/pkg/network/certbot.go @@ -2,13 +2,12 @@ package network import ( "fmt" - "os" - "os/exec" - "strings" - "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/pterm/pterm" + "os" + "os/exec" + "strings" ) func setDomainCertDirPerms(domainName string) { diff --git a/pkg/relays/khatru29/install.go b/pkg/relays/khatru29/install.go index 7264449..a5bbb54 100644 --- a/pkg/relays/khatru29/install.go +++ b/pkg/relays/khatru29/install.go @@ -5,13 +5,14 @@ import ( "github.com/nodetec/rwz/pkg/relays" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/verification" "github.com/pterm/pterm" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) + downloadSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Downloading %s relay binary...", RelayName)) // Determine the file name from the URL tmpFileName := filepath.Base(DownloadURL) @@ -25,14 +26,21 @@ func InstallRelayBinary() { // Download and copy the file files.DownloadAndCopyFile(tmpFilePath, DownloadURL) + downloadSpinner.Success(fmt.Sprintf("%s relay binary downloaded", RelayName)) + + // Verify relay binary + verification.VerifyRelayBinary(tmpFilePath) + + installSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay binary...", RelayName)) + // Check if the service file exists and disable and stop the service if it does if files.FileExists(ServiceFilePath) { // Disable and stop the Nostr relay service - spinner.UpdateText("Disabling and stopping service...") + installSpinner.UpdateText("Disabling and stopping service...") systemd.DisableService(ServiceName) systemd.StopService(ServiceName) } else { - spinner.UpdateText("Service file not found...") + installSpinner.UpdateText("Service file not found...") } // Extract binary @@ -48,5 +56,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success(fmt.Sprintf("%s relay binary downloaded and installed", RelayName)) + installSpinner.Success(fmt.Sprintf("%s relay binary installed", RelayName)) } diff --git a/pkg/relays/khatru_pyramid/install.go b/pkg/relays/khatru_pyramid/install.go index 69a04f1..4cc554e 100644 --- a/pkg/relays/khatru_pyramid/install.go +++ b/pkg/relays/khatru_pyramid/install.go @@ -6,13 +6,14 @@ import ( "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/verification" "github.com/pterm/pterm" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary(pubKey string) { - spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) + downloadSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Downloading %s relay binary...", RelayName)) // Determine the file name from the URL tmpFileName := filepath.Base(DownloadURL) @@ -26,28 +27,35 @@ func InstallRelayBinary(pubKey string) { // Download and copy the file files.DownloadAndCopyFile(tmpFilePath, DownloadURL) + downloadSpinner.Success(fmt.Sprintf("%s relay binary downloaded", RelayName)) + + // Verify relay binary + verification.VerifyRelayBinary(tmpFilePath) + + installSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay binary...", RelayName)) + // Check if the service file exists and disable and stop the service if it does if files.FileExists(ServiceFilePath) { // Disable and stop the Nostr relay service - spinner.UpdateText("Disabling and stopping service...") + installSpinner.UpdateText("Disabling and stopping service...") systemd.DisableService(ServiceName) systemd.StopService(ServiceName) } else { - spinner.UpdateText("Service file not found...") + installSpinner.UpdateText("Service file not found...") } // Check if users.json file exists if files.FileExists(UsersFilePath) { // Check if the pubKey exists in the users.json file - spinner.UpdateText("Checking for public key in users.json file...") + installSpinner.UpdateText("Checking for public key in users.json file...") lineExists := files.LineExists(fmt.Sprintf(`"%s":""`, pubKey), UsersFilePath) // If false remove data directory if !lineExists { - spinner.UpdateText("Public key not found, removing data directory...") + installSpinner.UpdateText("Public key not found, removing data directory...") directories.RemoveDirectory(DataDirPath) } else { - spinner.UpdateText("Public key found, keeping data directory.") + installSpinner.UpdateText("Public key found, keeping data directory.") } } @@ -64,5 +72,5 @@ func InstallRelayBinary(pubKey string) { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success(fmt.Sprintf("%s relay binary downloaded and installed", RelayName)) + installSpinner.Success(fmt.Sprintf("%s relay binary installed", RelayName)) } diff --git a/pkg/relays/nostr_rs_relay/install.go b/pkg/relays/nostr_rs_relay/install.go index 2a4285e..fe033a5 100644 --- a/pkg/relays/nostr_rs_relay/install.go +++ b/pkg/relays/nostr_rs_relay/install.go @@ -7,13 +7,14 @@ import ( "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/git" "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/verification" "github.com/pterm/pterm" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) + downloadSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Downloading %s relay binary...", RelayName)) // Check for and remove existing git repository directories.RemoveDirectory(GitRepoTmpDirPath) @@ -35,14 +36,21 @@ func InstallRelayBinary() { // Download and copy the file files.DownloadAndCopyFile(tmpFilePath, DownloadURL) + downloadSpinner.Success(fmt.Sprintf("%s relay binary downloaded", RelayName)) + + // Verify relay binary + verification.VerifyRelayBinary(tmpFilePath) + + installSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay binary...", RelayName)) + // Check if the service file exists and disable and stop the service if it does if files.FileExists(ServiceFilePath) { // Disable and stop the Nostr relay service - spinner.UpdateText("Disabling and stopping service...") + installSpinner.UpdateText("Disabling and stopping service...") systemd.DisableService(ServiceName) systemd.StopService(ServiceName) } else { - spinner.UpdateText("Service file not found...") + installSpinner.UpdateText("Service file not found...") } // Extract binary @@ -58,5 +66,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success(fmt.Sprintf("%s relay binary downloaded and installed", RelayName)) + installSpinner.Success(fmt.Sprintf("%s relay binary installed", RelayName)) } diff --git a/pkg/relays/strfry/install.go b/pkg/relays/strfry/install.go index 6ed05c5..5dd40d7 100644 --- a/pkg/relays/strfry/install.go +++ b/pkg/relays/strfry/install.go @@ -7,13 +7,14 @@ import ( "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/git" "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/verification" "github.com/pterm/pterm" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) + downloadSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Downloading %s relay binary...", RelayName)) // Check for and remove existing git repository directories.RemoveDirectory(GitRepoTmpDirPath) @@ -36,14 +37,21 @@ func InstallRelayBinary() { // Download and copy the file files.DownloadAndCopyFile(tmpFilePath, DownloadURL) + downloadSpinner.Success(fmt.Sprintf("%s relay binary downloaded", RelayName)) + + // Verify relay binary + verification.VerifyRelayBinary(tmpFilePath) + + installSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay binary...", RelayName)) + // Check if the service file exists and disable and stop the service if it does if files.FileExists(ServiceFilePath) { // Disable and stop the Nostr relay service - spinner.UpdateText("Disabling and stopping service...") + installSpinner.UpdateText("Disabling and stopping service...") systemd.DisableService(ServiceName) systemd.StopService(ServiceName) } else { - spinner.UpdateText("Service file not found...") + installSpinner.UpdateText("Service file not found...") } // Extract binary @@ -59,5 +67,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success(fmt.Sprintf("%s relay binary downloaded and installed", RelayName)) + installSpinner.Success(fmt.Sprintf("%s relay binary installed", RelayName)) } diff --git a/pkg/relays/strfry29/install.go b/pkg/relays/strfry29/install.go index ea22903..97256a8 100644 --- a/pkg/relays/strfry29/install.go +++ b/pkg/relays/strfry29/install.go @@ -7,13 +7,14 @@ import ( "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/git" "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/verification" "github.com/pterm/pterm" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) + downloadSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Downloading %s relay binary...", RelayName)) // Check for and remove existing git repository directories.RemoveDirectory(GitRepoTmpDirPath) @@ -51,14 +52,21 @@ func InstallRelayBinary() { // Download and copy the file files.DownloadAndCopyFile(tmpFilePath, BinaryPluginDownloadURL) + downloadSpinner.Success(fmt.Sprintf("%s relay binary downloaded", RelayName)) + + // Verify relay binary + verification.VerifyRelayBinary(tmpFilePath) + + installSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay binary...", RelayName)) + // Check if the service file exists and disable and stop the service if it does if files.FileExists(ServiceFilePath) { // Disable and stop the Nostr relay service - spinner.UpdateText("Disabling and stopping service...") + installSpinner.UpdateText("Disabling and stopping service...") systemd.DisableService(ServiceName) systemd.StopService(ServiceName) } else { - spinner.UpdateText("Service file not found...") + installSpinner.UpdateText("Service file not found...") } // Extract binary @@ -80,5 +88,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success(fmt.Sprintf("%s relay binary downloaded and installed", RelayName)) + installSpinner.Success(fmt.Sprintf("%s relay binary installed", RelayName)) } diff --git a/pkg/relays/wot_relay/install.go b/pkg/relays/wot_relay/install.go index cf16e25..73ce193 100644 --- a/pkg/relays/wot_relay/install.go +++ b/pkg/relays/wot_relay/install.go @@ -7,13 +7,14 @@ import ( "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/git" "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/verification" "github.com/pterm/pterm" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary(pubKey string) { - spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s...", RelayName)) + downloadSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Downloading %s relay binary...", RelayName)) // Check for and remove existing git repository directories.RemoveDirectory(GitRepoTmpDirPath) @@ -35,28 +36,35 @@ func InstallRelayBinary(pubKey string) { // Download and copy the file files.DownloadAndCopyFile(tmpFilePath, DownloadURL) + downloadSpinner.Success(fmt.Sprintf("%s relay binary downloaded", RelayName)) + + // Verify relay binary + verification.VerifyRelayBinary(tmpFilePath) + + installSpinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay binary...", RelayName)) + // Check if the service file exists and disable and stop the service if it does if files.FileExists(ServiceFilePath) { // Disable and stop the Nostr relay service - spinner.UpdateText("Disabling and stopping service...") + installSpinner.UpdateText("Disabling and stopping service...") systemd.DisableService(ServiceName) systemd.StopService(ServiceName) } else { - spinner.UpdateText("Service file not found...") + installSpinner.UpdateText("Service file not found...") } // Check if environment file exists if files.FileExists(EnvFilePath) { // Check if the pubKey exists in the environment file - spinner.UpdateText(fmt.Sprintf("Checking for public key in the %s file...", EnvFilePath)) + installSpinner.UpdateText(fmt.Sprintf("Checking for public key in the %s file...", EnvFilePath)) lineExists := files.LineExists(fmt.Sprintf(`RELAY_PUBKEY="%s"`, pubKey), EnvFilePath) // If false remove data directory if !lineExists { - spinner.UpdateText("Public key not found, removing data directory...") + installSpinner.UpdateText("Public key not found, removing data directory...") directories.RemoveDirectory(DataDirPath) } else { - spinner.UpdateText("Public key found, keeping data directory.") + installSpinner.UpdateText("Public key found, keeping data directory.") } } @@ -73,5 +81,5 @@ func InstallRelayBinary(pubKey string) { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success(fmt.Sprintf("%s relay binary downloaded and installed", RelayName)) + installSpinner.Success(fmt.Sprintf("%s relay binary installed", RelayName)) } diff --git a/pkg/utils/commands/utils.go b/pkg/utils/commands/utils.go new file mode 100644 index 0000000..3e59521 --- /dev/null +++ b/pkg/utils/commands/utils.go @@ -0,0 +1,30 @@ +package commands + +import ( + "fmt" + "github.com/pterm/pterm" + "os" + "os/exec" +) + +func PipeTwoCommands(commandOne, commandTwo *exec.Cmd, errMsg string) { + r, w, err := os.Pipe() + if err != nil { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to create pipe: %v", err)) + os.Exit(1) + } + defer r.Close() + commandOne.Stdout = w + err = commandOne.Start() + if err != nil { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("%s %v", errMsg, err)) + os.Exit(1) + } + defer commandOne.Wait() + w.Close() + commandTwo.Stdin = r + commandTwo.Stdout = os.Stdout + commandTwo.Run() +} diff --git a/pkg/verification/constants.go b/pkg/verification/constants.go new file mode 100644 index 0000000..a401cbd --- /dev/null +++ b/pkg/verification/constants.go @@ -0,0 +1,8 @@ +package verification + +const NodeTecKeybasePGPKeyURL = "https://keybase.io/nodetec/pgp_keys.asc" +const RelaysManifestFileURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/relays-0.4.0-manifest.sha512sum" +const RelaysManifestSigFileURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/relays-0.4.0-manifest.sha512sum.asc" +const NodeTecGoodSigMsg = `Good signature from "NODE-TEC Devs "` +const NodeTecPrimaryKeyFingerprint = "04BD8C20598FA5FDDE19BECD8F2469F71314FAD7" +const NodeTecSigningSubkeyFingerprint = "252F57B9DCD920EBF14E6151A8841CC4D10CC288" diff --git a/pkg/verification/verify.go b/pkg/verification/verify.go new file mode 100644 index 0000000..60a11f4 --- /dev/null +++ b/pkg/verification/verify.go @@ -0,0 +1,149 @@ +package verification + +import ( + "fmt" + "github.com/nodetec/rwz/pkg/relays" + "github.com/nodetec/rwz/pkg/utils/commands" + "github.com/nodetec/rwz/pkg/utils/files" + "github.com/pterm/pterm" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// Function to verify relay binaries +func VerifyRelayBinary(path string) { + ThemeDefault := pterm.ThemeDefault + + prompt := pterm.InteractiveContinuePrinter{ + DefaultValueIndex: 0, + DefaultText: "Do you want to continue with the installation?", + TextStyle: &ThemeDefault.PrimaryStyle, + Options: []string{"no", "yes"}, + OptionsStyle: &ThemeDefault.SuccessMessageStyle, + SuffixStyle: &ThemeDefault.SecondaryStyle, + Delimiter: ": ", + } + + spinner, _ := pterm.DefaultSpinner.Start("Verifying relay binary...") + pterm.Println() + + // Import NODE-TEC PGP key + commands.PipeTwoCommands(exec.Command("curl", NodeTecKeybasePGPKeyURL), exec.Command("gpg", "--import"), "Failed to import NODE-TEC PGP key:") + + spinner.UpdateText("Imported NODE-TEC PGP key") + + // Determine the file name from the URL + relaysManifestSigFile := filepath.Base(RelaysManifestSigFileURL) + + // Temporary file path + relaysManifestSigFilePath := fmt.Sprintf("%s/%s", relays.TmpDirPath, relaysManifestSigFile) + + // Check if the relay manifest signature file exists and remove it if it does + files.RemoveFile(relaysManifestSigFilePath) + + // Download and copy the file + files.DownloadAndCopyFile(relaysManifestSigFilePath, RelaysManifestSigFileURL) + + // Determine the file name from the URL + relaysManifestFile := filepath.Base(RelaysManifestFileURL) + + // Temporary file path + relaysManifestFilePath := fmt.Sprintf("%s/%s", relays.TmpDirPath, relaysManifestFile) + + // Check if the temporary file exists and remove it if it does + files.RemoveFile(relaysManifestFilePath) + + // Download and copy the file + files.DownloadAndCopyFile(relaysManifestFilePath, RelaysManifestFileURL) + + cmd := exec.Command("gpg", "--verify", relaysManifestSigFilePath) + + out, err := cmd.CombinedOutput() + if err != nil { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to run the gpg verify command on the %s file: %v", relaysManifestSigFilePath, err)) + os.Exit(1) + } + + gpgVerifyOutput := string(out) + + goodSig := strings.Contains(gpgVerifyOutput, NodeTecGoodSigMsg) + + // Extract the formatted primary key and subkey fingerprints from the output + _, formattedPrimaryAndSubKeyFingerprints, foundPrimaryKeyText := strings.Cut(gpgVerifyOutput, "Primary key fingerprint: ") + + formattedPrimaryKeyFingerprint, formattedSubkeyFingerprint, foundSubkeyText := strings.Cut(formattedPrimaryAndSubKeyFingerprints, "Subkey fingerprint: ") + + if foundPrimaryKeyText && foundSubkeyText { + formattedPrimaryKeyFingerprint = strings.ReplaceAll(formattedPrimaryKeyFingerprint, " ", "") + formattedSubkeyFingerprint = strings.ReplaceAll(formattedSubkeyFingerprint, " ", "") + + primaryKeyFingerprint := strings.ReplaceAll(formattedPrimaryKeyFingerprint, "\n", "") + subkeyFingerprint := strings.ReplaceAll(formattedSubkeyFingerprint, "\n", "") + + if goodSig && primaryKeyFingerprint == NodeTecPrimaryKeyFingerprint && subkeyFingerprint == NodeTecSigningSubkeyFingerprint { + spinner.UpdateText(fmt.Sprintf("Verified the signature of the %s file and the fingerprints", relaysManifestFilePath)) + } else { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to verify the signature of the %s file", relaysManifestFilePath)) + os.Exit(1) + } + } else { + if goodSig { + spinner.UpdateText(fmt.Sprintf("Verified the signature of the %s file", relaysManifestFilePath)) + } else { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to verify the signature of the %s file", relaysManifestFilePath)) + os.Exit(1) + } + } + + // Compute the SHA512 hash of the compressed relay binary file + out, err = exec.Command("sha512sum", path).Output() + + if err != nil { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to compute the SHA512 hash of the %s file: %v", path, err)) + os.Exit(1) + } + + // Extract the SHA512 hash from the output + sha512Hash, _, _ := strings.Cut(string(out), fmt.Sprintf(" %s", path)) + + // Read the manifest file + data, err := os.ReadFile(relaysManifestFilePath) + if err != nil { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to read the %s file: %v", relaysManifestFilePath, err)) + os.Exit(1) + } + + // Search the manifest file for the hash + if strings.Contains(string(data), sha512Hash) { + spinner.UpdateText(fmt.Sprintf("Verified the SHA512 hash of the %s file", path)) + pterm.Println() + + // Prompt user if they want to continue with installation without verifying fingerprints + if !foundPrimaryKeyText || !foundSubkeyText { + pterm.Println() + spinner.Warning(fmt.Sprintf("Warning: The signature of the %s file was valid but the fingerprints were not checked.", relaysManifestFilePath)) + + pterm.Println() + + result, _ := prompt.Show() + + if result == "no" { + os.Exit(1) + } + } + + pterm.Println() + spinner.Success("Relay binary verified") + } else { + pterm.Println() + pterm.Error.Println(fmt.Sprintf("Failed to verify the %s file, the SHA512 hash doesn't match the SHA512 hash in the %s file", path, relaysManifestFilePath)) + os.Exit(1) + } +}