From c8c12be1bb8da343a7e41e1f72a454db6c359bca Mon Sep 17 00:00:00 2001 From: J the Code Monkey Date: Sat, 5 Oct 2024 06:14:33 -0400 Subject: [PATCH] feat: add nostr-rs-relay, check for git package, and add network utils (#61) --- README.md | 34 ++--- cmd/install.go | 79 +++++++---- pkg/manager/apt.go | 15 +- pkg/relays/khatru29/constants.go | 2 +- pkg/relays/khatru29/install.go | 4 +- pkg/relays/khatru29/nginx_https.go | 2 +- pkg/relays/khatru_pyramid/constants.go | 2 +- pkg/relays/khatru_pyramid/install.go | 4 +- pkg/relays/khatru_pyramid/nginx_https.go | 2 +- pkg/relays/nostr_rs_relay/constants.go | 32 +++++ pkg/relays/nostr_rs_relay/install.go | 49 +++++++ pkg/relays/nostr_rs_relay/nginx_http.go | 89 ++++++++++++ pkg/relays/nostr_rs_relay/nginx_https.go | 129 ++++++++++++++++++ pkg/relays/nostr_rs_relay/service.go | 84 ++++++++++++ pkg/relays/nostr_rs_relay/success_messages.go | 10 ++ pkg/relays/strfry/constants.go | 2 +- pkg/relays/strfry/install.go | 4 +- pkg/relays/strfry/nginx_https.go | 2 +- pkg/relays/strfry/service.go | 2 +- pkg/relays/strfry29/constants.go | 2 +- pkg/relays/strfry29/install.go | 4 +- pkg/relays/strfry29/nginx_https.go | 2 +- pkg/relays/wot_relay/constants.go | 6 +- pkg/relays/wot_relay/install.go | 4 +- pkg/relays/wot_relay/nginx_https.go | 2 +- pkg/ui/greeter.go | 2 +- pkg/utils/network/utils.go | 17 +++ pkg/utils/systemd/utils.go | 10 +- pkg/utils/templates/utils.go | 10 +- 29 files changed, 526 insertions(+), 80 deletions(-) create mode 100644 pkg/relays/nostr_rs_relay/constants.go create mode 100644 pkg/relays/nostr_rs_relay/install.go create mode 100644 pkg/relays/nostr_rs_relay/nginx_http.go create mode 100644 pkg/relays/nostr_rs_relay/nginx_https.go create mode 100644 pkg/relays/nostr_rs_relay/service.go create mode 100644 pkg/relays/nostr_rs_relay/success_messages.go create mode 100644 pkg/utils/network/utils.go diff --git a/README.md b/README.md index 2322e0b..9294abf 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,13 @@ If you prefer to manually verify the authenticity of the Relay Wizard binary bef #### Arch -```sh +```bash sudo pacman -S gnupg ``` #### Debian/Ubuntu -```sh +```bash sudo apt install -y gnupg ``` @@ -60,19 +60,19 @@ sudo apt install -y gnupg #### Arch -```sh +```bash sudo pacman -S curl ``` #### Debian/Ubuntu -```sh +```bash sudo apt install -y curl ``` Now you need to import the public key that signed the manifest file which you can do by running the following command: -```sh +```bash curl https://keybase.io/nodetec/pgp_keys.asc | gpg --import ``` @@ -80,21 +80,21 @@ You're now ready to verify the manifest file. You will need to have the `rwz-x.x To verify the manifest file run the following command: -```sh +```bash gpg --verify rwz-x.x.x-manifest.sha512sum.asc ``` Here's the command to run for the latest version of `rwz`: -```sh -gpg --verify rwz-0.3.0-alpha2-manifest.sha512sum.asc +```bash +gpg --verify rwz-0.3.0-alpha3-manifest.sha512sum.asc ``` You should see output similar to the following if the verification was successful: -```sh -gpg: assuming signed data in 'rwz-0.3.0-alpha2-manifest.sha512sum' -gpg: Signature made Thu 03 Oct 2024 07:40:12 PM UTC +```bash +gpg: assuming signed data in 'rwz-0.3.0-alpha3-manifest.sha512sum' +gpg: Signature made Sat 05 Oct 2024 10:05:41 AM UTC gpg: using RSA key 252F57B9DCD920EBF14E6151A8841CC4D10CC288 gpg: Good signature from "NODE-TEC Devs " [unknown] gpg: aka "[jpeg image of size 5143]" [unknown] @@ -104,7 +104,7 @@ Primary key fingerprint: 04BD 8C20 598F A5FD DE19 BECD 8F24 69F7 1314 FAD7 > Unless you tell GnuPG to trust the key, you'll see a warning similar to the following: -```sh +```bash gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. ``` @@ -117,20 +117,20 @@ You have now verified the signature of the manifest file which ensures the integ To verify the binary you'll need to recompute the SHA512 hash of the file, compare it with the corresponding hash in the manifest file, and ensure they match exactly which you can do by running the following command: -```sh +```bash sha512sum --check rwz-x.x.x-manifest.sha512sum ``` Here's the command to run for the latest version of `rwz`: -```sh -sha512sum --check rwz-0.3.0-alpha2-manifest.sha512sum +```bash +sha512sum --check rwz-0.3.0-alpha3-manifest.sha512sum ``` If the verification was successful you should see the output similar to the following: -```sh -rwz-0.3.0-alpha2-x86_64-linux-gnu.tar.gz: OK +```bash +rwz-0.3.0-alpha3-x86_64-linux-gnu.tar.gz: OK ``` By completing the above steps you will have successfully verified the integrity of the binary. diff --git a/cmd/install.go b/cmd/install.go index 8536314..8e5d4af 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -5,6 +5,7 @@ import ( "github.com/nodetec/rwz/pkg/network" "github.com/nodetec/rwz/pkg/relays/khatru29" "github.com/nodetec/rwz/pkg/relays/khatru_pyramid" + "github.com/nodetec/rwz/pkg/relays/nostr_rs_relay" "github.com/nodetec/rwz/pkg/relays/strfry" "github.com/nodetec/rwz/pkg/relays/strfry29" "github.com/nodetec/rwz/pkg/relays/wot_relay" @@ -18,6 +19,7 @@ var installCmd = &cobra.Command{ Short: "Install and configure your Nostr relay", Long: `Install and configure your Nostr relay, including package installation, firewall setup, Nginx configuration, SSL/TLS certificates, and starting the relay service.`, Run: func(cmd *cobra.Command, args []string) { + ThemeDefault := pterm.ThemeDefault ui.Greet() @@ -25,18 +27,30 @@ var installCmd = &cobra.Command{ pterm.Println() // Supported relay options - options := []string{khatru_pyramid.RelayName, strfry.RelayName, khatru29.RelayName, strfry29.RelayName, wot_relay.RelayName} + options := []string{khatru_pyramid.RelayName, nostr_rs_relay.RelayName, strfry.RelayName, wot_relay.RelayName, khatru29.RelayName, strfry29.RelayName} // Use PTerm's interactive select feature to present the options to the user and capture their selection // The Show() method displays the options and waits for the user's input - selectedRelayOption, _ := pterm.DefaultInteractiveSelect.WithOptions(options).Show() + relaySelector := pterm.InteractiveSelectPrinter{ + TextStyle: &ThemeDefault.PrimaryStyle, + DefaultText: "Please select an option", + Options: []string{}, + OptionStyle: &ThemeDefault.DefaultText, + DefaultOption: "", + MaxHeight: 6, + Selector: ">", + SelectorStyle: &ThemeDefault.SecondaryStyle, + Filter: true, + } + + selectedRelayOption, _ := relaySelector.WithOptions(options).Show() // Display the selected option to the user with a green color for emphasis pterm.Info.Printfln("Selected option: %s", pterm.Green(selectedRelayOption)) var privKey string var pubKey string - if selectedRelayOption == khatru_pyramid.RelayName || selectedRelayOption == wot_relay.RelayName { + if selectedRelayOption == khatru_pyramid.RelayName || selectedRelayOption == nostr_rs_relay.RelayName || selectedRelayOption == wot_relay.RelayName { pterm.Println() pubKey, _ = pterm.DefaultInteractiveTextInput.Show("Public key (hex not npub)") } else if selectedRelayOption == khatru29.RelayName || selectedRelayOption == strfry29.RelayName { @@ -46,7 +60,7 @@ var installCmd = &cobra.Command{ } var relayContact string - if selectedRelayOption == khatru_pyramid.RelayName || selectedRelayOption == khatru29.RelayName { + if selectedRelayOption == khatru_pyramid.RelayName || selectedRelayOption == nostr_rs_relay.RelayName || selectedRelayOption == khatru29.RelayName { pterm.Println() pterm.Println(pterm.Yellow("Leave email empty if you don't want to provide relay contact information.")) @@ -65,7 +79,7 @@ var installCmd = &cobra.Command{ pterm.Println() // Step 1: Install necessary packages using APT - manager.AptInstallPackages() + manager.AptInstallPackages(selectedRelayOption) // Step 2: Configure the firewall network.ConfigureFirewall() @@ -89,6 +103,25 @@ var installCmd = &cobra.Command{ // Step 8: Show success messages khatru_pyramid.SuccessMessages(relayDomain, httpsEnabled) + } else if selectedRelayOption == nostr_rs_relay.RelayName { + // Step 3: Configure Nginx for HTTP + nostr_rs_relay.ConfigureNginxHttp(relayDomain) + + // Step 4: Get SSL/TLS certificates + httpsEnabled := network.GetCertificates(relayDomain) + if httpsEnabled { + // Step 5: Configure Nginx for HTTPS + nostr_rs_relay.ConfigureNginxHttps(relayDomain) + } + + // Step 6: Download and install the relay binary + nostr_rs_relay.InstallRelayBinary() + + // Step 7: Set up the relay service + nostr_rs_relay.SetupRelayService(relayDomain, pubKey, relayContact, httpsEnabled) + + // Step 8: Show success messages + nostr_rs_relay.SuccessMessages(relayDomain, httpsEnabled) } else if selectedRelayOption == strfry.RelayName { // Step 3: Configure Nginx for HTTP strfry.ConfigureNginxHttp(relayDomain) @@ -108,63 +141,63 @@ var installCmd = &cobra.Command{ // Step 8: Show success messages strfry.SuccessMessages(relayDomain, httpsEnabled) - } else if selectedRelayOption == khatru29.RelayName { + } else if selectedRelayOption == wot_relay.RelayName { // Step 3: Configure Nginx for HTTP - khatru29.ConfigureNginxHttp(relayDomain) + wot_relay.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL/TLS certificates httpsEnabled := network.GetCertificates(relayDomain) if httpsEnabled { // Step 5: Configure Nginx for HTTPS - khatru29.ConfigureNginxHttps(relayDomain) + wot_relay.ConfigureNginxHttps(relayDomain) } // Step 6: Download and install the relay binary - khatru29.InstallRelayBinary() + wot_relay.InstallRelayBinary() // Step 7: Set up the relay service - khatru29.SetupRelayService(relayDomain, privKey, relayContact) + wot_relay.SetupRelayService(relayDomain, pubKey, relayContact, httpsEnabled) // Step 8: Show success messages - khatru29.SuccessMessages(relayDomain, httpsEnabled) - } else if selectedRelayOption == strfry29.RelayName { + wot_relay.SuccessMessages(relayDomain, httpsEnabled) + } else if selectedRelayOption == khatru29.RelayName { // Step 3: Configure Nginx for HTTP - strfry29.ConfigureNginxHttp(relayDomain) + khatru29.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL/TLS certificates httpsEnabled := network.GetCertificates(relayDomain) if httpsEnabled { // Step 5: Configure Nginx for HTTPS - strfry29.ConfigureNginxHttps(relayDomain) + khatru29.ConfigureNginxHttps(relayDomain) } // Step 6: Download and install the relay binary - strfry29.InstallRelayBinary() + khatru29.InstallRelayBinary() // Step 7: Set up the relay service - strfry29.SetupRelayService(relayDomain, privKey) + khatru29.SetupRelayService(relayDomain, privKey, relayContact) // Step 8: Show success messages - strfry29.SuccessMessages(relayDomain, httpsEnabled) - } else if selectedRelayOption == wot_relay.RelayName { + khatru29.SuccessMessages(relayDomain, httpsEnabled) + } else if selectedRelayOption == strfry29.RelayName { // Step 3: Configure Nginx for HTTP - wot_relay.ConfigureNginxHttp(relayDomain) + strfry29.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL/TLS certificates httpsEnabled := network.GetCertificates(relayDomain) if httpsEnabled { // Step 5: Configure Nginx for HTTPS - wot_relay.ConfigureNginxHttps(relayDomain) + strfry29.ConfigureNginxHttps(relayDomain) } // Step 6: Download and install the relay binary - wot_relay.InstallRelayBinary() + strfry29.InstallRelayBinary() // Step 7: Set up the relay service - wot_relay.SetupRelayService(relayDomain, pubKey, relayContact, httpsEnabled) + strfry29.SetupRelayService(relayDomain, privKey) // Step 8: Show success messages - wot_relay.SuccessMessages(relayDomain, httpsEnabled) + strfry29.SuccessMessages(relayDomain, httpsEnabled) } pterm.Println() diff --git a/pkg/manager/apt.go b/pkg/manager/apt.go index 1f19117..bd88b04 100644 --- a/pkg/manager/apt.go +++ b/pkg/manager/apt.go @@ -2,6 +2,9 @@ package manager import ( "fmt" + "github.com/nodetec/rwz/pkg/relays/nostr_rs_relay" + "github.com/nodetec/rwz/pkg/relays/strfry" + "github.com/nodetec/rwz/pkg/relays/strfry29" "github.com/pterm/pterm" "os" "os/exec" @@ -36,12 +39,20 @@ func IsPackageInstalled(packageName string) bool { } // Function to install necessary packages -func AptInstallPackages() { +func AptInstallPackages(selectedRelayOption string) { spinner, _ := pterm.DefaultSpinner.Start("Updating and installing packages...") exec.Command("apt", "update", "-qq").Run() - packages := []string{"nginx", "certbot", "python3-certbot-nginx", "ufw", "fail2ban"} + packages := []string{"nginx", "certbot", "python3-certbot-nginx", "ufw", "fail2ban", "git"} + + if selectedRelayOption == nostr_rs_relay.RelayName || selectedRelayOption == strfry.RelayName || selectedRelayOption == strfry29.RelayName { + packages = append(packages, "git") + } + + if selectedRelayOption == nostr_rs_relay.RelayName { + packages = append(packages, "sqlite3", "libsqlite3-dev") + } // Check if package is installed, install if not for _, p := range packages { diff --git a/pkg/relays/khatru29/constants.go b/pkg/relays/khatru29/constants.go index 4a3e23b..950ef99 100644 --- a/pkg/relays/khatru29/constants.go +++ b/pkg/relays/khatru29/constants.go @@ -1,6 +1,6 @@ package khatru29 -const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.3.0/relay29-0.4.0-khatru29-x86_64-linux-gnu.tar.gz" +const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/relay29-0.4.0-khatru29-x86_64-linux-gnu.tar.gz" const BinaryName = "khatru29" const BinaryFilePath = "/usr/local/bin/khatru29" const NginxConfigFilePath = "/etc/nginx/conf.d/khatru29.conf" diff --git a/pkg/relays/khatru29/install.go b/pkg/relays/khatru29/install.go index fd4c553..ae685e1 100644 --- a/pkg/relays/khatru29/install.go +++ b/pkg/relays/khatru29/install.go @@ -10,7 +10,7 @@ import ( // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start("Installing Khatru29 relay...") + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) // Determine the file name from the URL tmpFileName := filepath.Base(DownloadURL) @@ -37,5 +37,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success("Khatru29 relay installed successfully.") + spinner.Success(fmt.Sprintf("%s relay installed successfully.", RelayName)) } diff --git a/pkg/relays/khatru29/nginx_https.go b/pkg/relays/khatru29/nginx_https.go index 681eb25..dee138b 100644 --- a/pkg/relays/khatru29/nginx_https.go +++ b/pkg/relays/khatru29/nginx_https.go @@ -58,7 +58,7 @@ server { # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_protocols TLSv1.2 TLSv1.3; + ssl_protocols TLSv1.3 TLSv1.2; # For more information on the security of different cipher suites, you can refer to the following link: # https://ciphersuite.info/ diff --git a/pkg/relays/khatru_pyramid/constants.go b/pkg/relays/khatru_pyramid/constants.go index e0cddb6..022aa2d 100644 --- a/pkg/relays/khatru_pyramid/constants.go +++ b/pkg/relays/khatru_pyramid/constants.go @@ -1,6 +1,6 @@ package khatru_pyramid -const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.3.0/khatru-pyramid-0.1.0-x86_64-linux-gnu.tar.gz" +const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/khatru-pyramid-0.1.0-x86_64-linux-gnu.tar.gz" const BinaryName = "khatru-pyramid" const BinaryFilePath = "/usr/local/bin/khatru-pyramid" const NginxConfigFilePath = "/etc/nginx/conf.d/khatru_pyramid.conf" diff --git a/pkg/relays/khatru_pyramid/install.go b/pkg/relays/khatru_pyramid/install.go index eede454..c1ed1d2 100644 --- a/pkg/relays/khatru_pyramid/install.go +++ b/pkg/relays/khatru_pyramid/install.go @@ -10,7 +10,7 @@ import ( // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start("Installing Khatru Pyramid relay...") + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) // Determine the file name from the URL tmpFileName := filepath.Base(DownloadURL) @@ -37,5 +37,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success("Khatru Pyramid relay installed successfully.") + spinner.Success(fmt.Sprintf("%s relay installed successfully.", RelayName)) } diff --git a/pkg/relays/khatru_pyramid/nginx_https.go b/pkg/relays/khatru_pyramid/nginx_https.go index 32583d3..dd54e14 100644 --- a/pkg/relays/khatru_pyramid/nginx_https.go +++ b/pkg/relays/khatru_pyramid/nginx_https.go @@ -58,7 +58,7 @@ server { # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_protocols TLSv1.2 TLSv1.3; + ssl_protocols TLSv1.3 TLSv1.2; # For more information on the security of different cipher suites, you can refer to the following link: # https://ciphersuite.info/ diff --git a/pkg/relays/nostr_rs_relay/constants.go b/pkg/relays/nostr_rs_relay/constants.go new file mode 100644 index 0000000..e41374c --- /dev/null +++ b/pkg/relays/nostr_rs_relay/constants.go @@ -0,0 +1,32 @@ +package nostr_rs_relay + +const GitRepoBranch = "0.9.0" +const GitRepoURL = "https://github.com/scsibug/nostr-rs-relay" +const GitRepoTmpDirPath = "/tmp/nostr-rs-relay" +const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/nostr-rs-relay-0.9.0-x86_64-linux-gnu.tar.gz" +const BinaryName = "nostr-rs-relay" +const BinaryFilePath = "/usr/local/bin/nostr-rs-relay" +const NginxConfigFilePath = "/etc/nginx/conf.d/nostr_rs_relay.conf" +const DataDirPath = "/var/lib/nostr-rs-relay" +const TmpConfigFilePath = "/tmp/nostr-rs-relay/config.toml" +const ConfigDirPath = "/etc/nostr-rs-relay" +const ConfigFilePath = "/etc/nostr-rs-relay/config.toml" +const ServiceName = "nostr-rs-relay" +const ServiceFilePath = "/etc/systemd/system/nostr-rs-relay.service" +const ServiceFileTemplate = `[Unit] +Description=nostr-rs-relay Service +After=network.target + +[Service] +Type=simple +User=nostr +Group=nostr +WorkingDirectory=/home/nostr +ExecStart={{.BinaryFilePath}} +Restart=on-failure + +[Install] +WantedBy=multi-user.target +` +const RelayName = "nostr-rs-relay" +const GithubLink = "https://github.com/scsibug/nostr-rs-relay" diff --git a/pkg/relays/nostr_rs_relay/install.go b/pkg/relays/nostr_rs_relay/install.go new file mode 100644 index 0000000..c8f2793 --- /dev/null +++ b/pkg/relays/nostr_rs_relay/install.go @@ -0,0 +1,49 @@ +package nostr_rs_relay + +import ( + "fmt" + "github.com/nodetec/rwz/pkg/relays" + "github.com/nodetec/rwz/pkg/utils/directories" + "github.com/nodetec/rwz/pkg/utils/files" + "github.com/nodetec/rwz/pkg/utils/git" + "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)) + + // Check for and remove existing git repository + directories.RemoveDirectory(GitRepoTmpDirPath) + + // Download git repository + git.Clone(GitRepoBranch, GitRepoURL, GitRepoTmpDirPath) + + // Determine the file name from the URL + tmpFileName := filepath.Base(DownloadURL) + + // Temporary file path + tmpFilePath := fmt.Sprintf("%s/%s", relays.TmpDirPath, tmpFileName) + + // Check if the temporary file exists and remove it if it does + files.RemoveFile(tmpFilePath) + + // Download and copy the file + files.DownloadAndCopyFile(tmpFilePath, DownloadURL) + + // Extract binary + files.ExtractFile(tmpFilePath, relays.BinaryDestDir) + + // TODO + // Currently, the downloaded binary is expected to have a name that matches the BinaryName variable + // Ideally, the extracted binary file should be renamed to match the BinaryName variable + + // Define the final destination path + destPath := filepath.Join(relays.BinaryDestDir, BinaryName) + + // Make the file executable + files.SetPermissions(destPath, 0755) + + spinner.Success(fmt.Sprintf("%s relay installed successfully.", RelayName)) +} diff --git a/pkg/relays/nostr_rs_relay/nginx_http.go b/pkg/relays/nostr_rs_relay/nginx_http.go new file mode 100644 index 0000000..c2e6688 --- /dev/null +++ b/pkg/relays/nostr_rs_relay/nginx_http.go @@ -0,0 +1,89 @@ +package nostr_rs_relay + +import ( + "fmt" + "github.com/nodetec/rwz/pkg/network" + "github.com/nodetec/rwz/pkg/utils/directories" + "github.com/nodetec/rwz/pkg/utils/files" + "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/pterm/pterm" +) + +// Function to configure Nginx for HTTP +func ConfigureNginxHttp(domainName string) { + spinner, _ := pterm.DefaultSpinner.Start("Configuring Nginx for HTTP...") + + files.RemoveFile(NginxConfigFilePath) + + directories.CreateDirectory(fmt.Sprintf("%s/%s/%s/", network.WWWDirPath, domainName, network.AcmeChallengeDirPath), 0755) + + configContent := fmt.Sprintf(`map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream websocket_nostr_rs_relay { + server 127.0.0.1:8080; +} + +server { + listen 80; + listen [::]:80; + server_name %s; + + location /%s/ { + root %s/%s; + allow all; + } + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying 404. + try_files $uri $uri/ =404; + proxy_pass http://websocket_nostr_rs_relay; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + } + + # Only return Nginx in server header + server_tokens off; + + #### Security Headers #### + # Test configuration: + # https://securityheaders.com/ + # https://observatory.mozilla.org/ + add_header X-Frame-Options DENY; + + # Avoid MIME type sniffing + add_header X-Content-Type-Options nosniff always; + + add_header Referrer-Policy "no-referrer" always; + + add_header X-XSS-Protection 0 always; + + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + #### Content-Security-Policy (CSP) #### + add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none';" always; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + location / { + return 301 http://%s$request_uri; + } +} +`, domainName, network.AcmeChallengeDirPath, network.WWWDirPath, domainName, domainName, domainName) + + files.WriteFile(NginxConfigFilePath, configContent, 0644) + + systemd.RestartService("nginx") + + spinner.Success("Nginx configured for HTTP") +} diff --git a/pkg/relays/nostr_rs_relay/nginx_https.go b/pkg/relays/nostr_rs_relay/nginx_https.go new file mode 100644 index 0000000..ca9bb07 --- /dev/null +++ b/pkg/relays/nostr_rs_relay/nginx_https.go @@ -0,0 +1,129 @@ +package nostr_rs_relay + +import ( + "fmt" + "github.com/nodetec/rwz/pkg/network" + "github.com/nodetec/rwz/pkg/utils/files" + "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/pterm/pterm" +) + +// Function to configure Nginx for HTTPS +func ConfigureNginxHttps(domainName string) { + spinner, _ := pterm.DefaultSpinner.Start("Configuring Nginx for HTTPS...") + + files.RemoveFile(NginxConfigFilePath) + + configContent := fmt.Sprintf(`map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream websocket_nostr_rs_relay { + server 127.0.0.1:8080; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + root %s/%s; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying 404. + try_files $uri $uri/ =404; + proxy_pass http://websocket_nostr_rs_relay; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + } + + # Only return Nginx in server header + server_tokens off; + + #### SSL Configuration #### + # Test configuration: + # https://www.ssllabs.com/ssltest/analyze.html + # https://cryptcheck.fr/ + ssl_certificate %s/%s/%s; + ssl_certificate_key %s/%s/%s; + # Verify chain of trust of OCSP response using Root CA and Intermediate certs + ssl_trusted_certificate %s/%s/%s; + + # TODO + # Add support to generate the file in the script + #ssl_dhparam /etc/ssl/certs/dhparam.pem; + + ssl_protocols TLSv1.3 TLSv1.2; + + # For more information on the security of different cipher suites, you can refer to the following link: + # https://ciphersuite.info/ + # Compilation of the top cipher suites 2024: + # https://ssl-config.mozilla.org/#server=nginx + ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305"; + + # Perfect Forward Secrecy (PFS) is frequently compromised without this + ssl_prefer_server_ciphers on; + + ssl_session_tickets off; + + # Enable SSL session caching for improved performance + # Try setting ssl_session_timeout to 1d if performance is bad + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + + # By default, the buffer size is 16k, which corresponds to minimal overhead when sending big responses. + # To minimize Time To First Byte it may be beneficial to use smaller values + ssl_buffer_size 8k; + + # OCSP stapling + ssl_stapling on; + ssl_stapling_verify on; + + #### Security Headers #### + # Test configuration: + # https://securityheaders.com/ + # https://observatory.mozilla.org/ + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; + + add_header X-Frame-Options DENY; + + # Avoid MIME type sniffing + add_header X-Content-Type-Options nosniff always; + + add_header Referrer-Policy "no-referrer" always; + + add_header X-XSS-Protection 0 always; + + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + #### Content-Security-Policy (CSP) #### + add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests;" always; +} + +server { + listen 80; + listen [::]:80; + server_name %s; + + location /%s/ { + root %s/%s; + allow all; + } + + location / { + return 301 https://%s$request_uri; + } +} +`, domainName, network.WWWDirPath, domainName, network.CertificateDirPath, domainName, network.FullchainFile, network.CertificateDirPath, domainName, network.PrivkeyFile, network.CertificateDirPath, domainName, network.ChainFile, domainName, network.AcmeChallengeDirPath, network.WWWDirPath, domainName, domainName) + + files.WriteFile(NginxConfigFilePath, configContent, 0644) + + systemd.ReloadService("nginx") + + spinner.Success("Nginx configured for HTTPS") +} diff --git a/pkg/relays/nostr_rs_relay/service.go b/pkg/relays/nostr_rs_relay/service.go new file mode 100644 index 0000000..375938a --- /dev/null +++ b/pkg/relays/nostr_rs_relay/service.go @@ -0,0 +1,84 @@ +package nostr_rs_relay + +import ( + "fmt" + "github.com/nodetec/rwz/pkg/utils/directories" + "github.com/nodetec/rwz/pkg/utils/files" + "github.com/nodetec/rwz/pkg/utils/network" + "github.com/nodetec/rwz/pkg/utils/systemd" + "github.com/nodetec/rwz/pkg/utils/users" + "github.com/pterm/pterm" +) + +// Function to set up the relay service +func SetupRelayService(domain, pubKey, relayContact string, httpsEnabled bool) { + spinner, _ := pterm.DefaultSpinner.Start("Configuring relay service...") + + // Ensure the user for the relay service exists + if !users.UserExists("nostr") { + spinner.UpdateText("Creating user 'nostr'...") + users.CreateUser("nostr", true) + } else { + spinner.UpdateText("User 'nostr' already exists") + } + + // Ensure the data directory exists and set ownership + spinner.UpdateText("Creating data directory...") + directories.CreateDirectory(DataDirPath, 0755) + + // Use chown command to set ownership of the data directory to the nostr user + directories.SetOwnerAndGroup("nostr", "nostr", DataDirPath) + + // Ensure the config directory exists and set ownership + spinner.UpdateText("Creating config directory...") + directories.CreateDirectory(ConfigDirPath, 0755) + + // Use chown command to set ownership of the config directory to the nostr user + directories.SetOwnerAndGroup("nostr", "nostr", ConfigDirPath) + + // Check for and remove existing config file + files.RemoveFile(ConfigFilePath) + + // Check if the service file exists and remove it if it does + files.RemoveFile(ServiceFilePath) + + // Construct the sed command to change the relay url + files.InPlaceEdit(fmt.Sprintf(`s|relay_url = ".*"|relay_url = "%s://%s/"|`, network.WSEnabled(httpsEnabled), domain), TmpConfigFilePath) + + // Construct the sed command to change the pubkey + files.InPlaceEdit(fmt.Sprintf(`s|#pubkey = ".*"|pubkey = "%s"|`, pubKey), TmpConfigFilePath) + + // Construct the sed command to change the contact + files.InPlaceEdit(fmt.Sprintf(`s|#contact = ".*"|contact = "%s"|`, relayContact), TmpConfigFilePath) + + // Construct the sed command to change the data directory + files.InPlaceEdit(fmt.Sprintf(`s|#data_directory = ".*"|data_directory = "%s"|`, DataDirPath), TmpConfigFilePath) + + // Construct the sed command to change the address + files.InPlaceEdit(fmt.Sprintf(`s|address = ".*"|address = "127.0.0.1"|`), TmpConfigFilePath) + + // Construct the sed command to change the remote ip header + files.InPlaceEdit(fmt.Sprintf(`s|#remote_ip_header = "x-forwarded-for"|remote_ip_header = "x-forwarded-for"|`), TmpConfigFilePath) + + // Copy config file to config directory + files.CopyFile(TmpConfigFilePath, ConfigDirPath) + + // Use chown command to set ownership of the config file to the nostr user + files.SetOwnerAndGroup("nostr", "nostr", ConfigFilePath) + + // Create the systemd service file + spinner.UpdateText("Creating service file...") + serviceFileParams := systemd.ServiceFileParams{BinaryFilePath: BinaryFilePath} + systemd.CreateServiceFile(ServiceFilePath, ServiceFileTemplate, &serviceFileParams) + + // Reload systemd to apply the new service + spinner.UpdateText("Reloading systemd daemon...") + systemd.Reload() + + // Enable and start the Nostr relay service + spinner.UpdateText("Enabling and starting service...") + systemd.EnableService(ServiceName) + systemd.StartService(ServiceName) + + spinner.Success("Nostr relay service configured") +} diff --git a/pkg/relays/nostr_rs_relay/success_messages.go b/pkg/relays/nostr_rs_relay/success_messages.go new file mode 100644 index 0000000..9556f11 --- /dev/null +++ b/pkg/relays/nostr_rs_relay/success_messages.go @@ -0,0 +1,10 @@ +package nostr_rs_relay + +import ( + "github.com/nodetec/rwz/pkg/utils/messages" +) + +func SuccessMessages(domain string, httpsEnabled bool) { + successMsgParams := messages.SuccessMsgParams{Domain: domain, HTTPSEnabled: httpsEnabled, DataDirPath: DataDirPath, ConfigFilePath: ConfigFilePath, NginxConfigFilePath: NginxConfigFilePath, BinaryFilePath: BinaryFilePath, ServiceFilePath: ServiceFilePath, ServiceName: ServiceName, RelayName: RelayName, GitHubLink: GithubLink} + messages.SuccessMessages(&successMsgParams) +} diff --git a/pkg/relays/strfry/constants.go b/pkg/relays/strfry/constants.go index e2971bf..4fcc72b 100644 --- a/pkg/relays/strfry/constants.go +++ b/pkg/relays/strfry/constants.go @@ -3,7 +3,7 @@ package strfry const GitRepoBranch = "1.0.1" const GitRepoURL = "https://github.com/hoytech/strfry.git" const GitRepoTmpDirPath = "/tmp/strfry" -const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.3.0/strfry-1.0.1-x86_64-linux-gnu.tar.gz" +const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/strfry-1.0.1-x86_64-linux-gnu.tar.gz" const BinaryName = "strfry" const BinaryFilePath = "/usr/local/bin/strfry" const NginxConfigFilePath = "/etc/nginx/conf.d/strfry.conf" diff --git a/pkg/relays/strfry/install.go b/pkg/relays/strfry/install.go index 89391b7..f110861 100644 --- a/pkg/relays/strfry/install.go +++ b/pkg/relays/strfry/install.go @@ -12,7 +12,7 @@ import ( // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start("Installing strfry relay...") + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) // Check for and remove existing git repository directories.RemoveDirectory(GitRepoTmpDirPath) @@ -46,5 +46,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success("strfry relay installed successfully.") + spinner.Success(fmt.Sprintf("%s relay installed successfully.", RelayName)) } diff --git a/pkg/relays/strfry/nginx_https.go b/pkg/relays/strfry/nginx_https.go index f17cf0d..c208493 100644 --- a/pkg/relays/strfry/nginx_https.go +++ b/pkg/relays/strfry/nginx_https.go @@ -49,7 +49,7 @@ func ConfigureNginxHttps(domainName string) { # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_protocols TLSv1.2 TLSv1.3; + ssl_protocols TLSv1.3 TLSv1.2; # For more information on the security of different cipher suites, you can refer to the following link: # https://ciphersuite.info/ diff --git a/pkg/relays/strfry/service.go b/pkg/relays/strfry/service.go index 3a84dc4..f4cbbd5 100644 --- a/pkg/relays/strfry/service.go +++ b/pkg/relays/strfry/service.go @@ -50,7 +50,7 @@ func SetupRelayService(domain string) { // Determine preferred nofiles value files.InPlaceEdit(`s|nofiles = .*|nofiles = 0|`, TmpConfigFilePath) - // Copy config file to /etc/strfry + // Copy config file to config directory files.CopyFile(TmpConfigFilePath, ConfigDirPath) // Use chown command to set ownership of the config file to the nostr user diff --git a/pkg/relays/strfry29/constants.go b/pkg/relays/strfry29/constants.go index ac69439..d095367 100644 --- a/pkg/relays/strfry29/constants.go +++ b/pkg/relays/strfry29/constants.go @@ -3,7 +3,7 @@ package strfry29 const GitRepoBranch = "v0.4.0" const GitRepoURL = "https://github.com/fiatjaf/relay29.git" const GitRepoTmpDir = "/tmp/relay29" -const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.3.0/strfry-1.0.1-x86_64-linux-gnu.tar.gz" +const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/strfry-1.0.1-x86_64-linux-gnu.tar.gz" const BinaryName = "strfry" const BinaryFilePath = "/usr/local/bin/strfry" const BinaryPluginDownloadURL = "https://github.com/nodetec/relays/releases/download/v0.3.0/relay29-0.4.0-strfry29-x86_64-linux-gnu.tar.gz" diff --git a/pkg/relays/strfry29/install.go b/pkg/relays/strfry29/install.go index 889b2b5..6d870c9 100644 --- a/pkg/relays/strfry29/install.go +++ b/pkg/relays/strfry29/install.go @@ -12,7 +12,7 @@ import ( // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start("Installing strfry29 relay...") + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s relay...", RelayName)) // Check for and remove existing git repository directories.RemoveDirectory(GitRepoTmpDir) @@ -67,5 +67,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success("strfry29 relay installed successfully.") + spinner.Success(fmt.Sprintf("%s relay installed successfully.", RelayName)) } diff --git a/pkg/relays/strfry29/nginx_https.go b/pkg/relays/strfry29/nginx_https.go index 2943a41..b5a878d 100644 --- a/pkg/relays/strfry29/nginx_https.go +++ b/pkg/relays/strfry29/nginx_https.go @@ -49,7 +49,7 @@ func ConfigureNginxHttps(domainName string) { # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_protocols TLSv1.2 TLSv1.3; + ssl_protocols TLSv1.3 TLSv1.2; # For more information on the security of different cipher suites, you can refer to the following link: # https://ciphersuite.info/ diff --git a/pkg/relays/wot_relay/constants.go b/pkg/relays/wot_relay/constants.go index 13aebe9..32de4d6 100644 --- a/pkg/relays/wot_relay/constants.go +++ b/pkg/relays/wot_relay/constants.go @@ -1,6 +1,6 @@ package wot_relay -const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.3.0/wot-relay-0.1.12-x86_64-linux-gnu.tar.gz" +const DownloadURL = "https://github.com/nodetec/relays/releases/download/v0.4.0/wot-relay-0.1.12-x86_64-linux-gnu.tar.gz" const BinaryName = "wot-relay" const BinaryFilePath = "/usr/local/bin/wot-relay" const NginxConfigFilePath = "/etc/nginx/conf.d/wot_relay.conf" @@ -13,7 +13,7 @@ const IndexFileTemplate = ` WoT Relay - +
@@ -39,7 +39,7 @@ const EnvFilePath = "/etc/systemd/system/wot-relay.env" const EnvFileTemplate = `RELAY_NAME="WoT Relay" RELAY_DESCRIPTION="WoT Nostr Relay" RELAY_ICON="https://pfp.nostr.build/56306a93a88d4c657d8a3dfa57b55a4ed65b709eee927b5dafaab4d5330db21f.png" -RELAY_URL="{{.WSProtocol}}://{{.Domain}}" +RELAY_URL="{{.WSScheme}}://{{.Domain}}" RELAY_PUBKEY="{{.PubKey}}" RELAY_CONTACT="{{.RelayContact}}" INDEX_PATH="/etc/wot-relay/templates/index.html" diff --git a/pkg/relays/wot_relay/install.go b/pkg/relays/wot_relay/install.go index 56994c1..03f3dda 100644 --- a/pkg/relays/wot_relay/install.go +++ b/pkg/relays/wot_relay/install.go @@ -10,7 +10,7 @@ import ( // Function to download and make the binary executable func InstallRelayBinary() { - spinner, _ := pterm.DefaultSpinner.Start("Installing WoT Relay...") + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Installing %s...", RelayName)) // Determine the file name from the URL tmpFileName := filepath.Base(DownloadURL) @@ -37,5 +37,5 @@ func InstallRelayBinary() { // Make the file executable files.SetPermissions(destPath, 0755) - spinner.Success("WoT Relay installed successfully.") + spinner.Success(fmt.Sprintf("%s installed successfully.", RelayName)) } diff --git a/pkg/relays/wot_relay/nginx_https.go b/pkg/relays/wot_relay/nginx_https.go index 9dde160..55bb688 100644 --- a/pkg/relays/wot_relay/nginx_https.go +++ b/pkg/relays/wot_relay/nginx_https.go @@ -60,7 +60,7 @@ server { # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_protocols TLSv1.2 TLSv1.3; + ssl_protocols TLSv1.3 TLSv1.2; # For more information on the security of different cipher suites, you can refer to the following link: # https://ciphersuite.info/ diff --git a/pkg/ui/greeter.go b/pkg/ui/greeter.go index 5c69410..daf49c6 100644 --- a/pkg/ui/greeter.go +++ b/pkg/ui/greeter.go @@ -6,5 +6,5 @@ import ( func Greet() { pterm.DefaultCenter.WithCenterEachLineSeparately().Println( - pterm.Magenta("\nWelcome to Relay Wizard 🧙") + pterm.Gray("\nInstall and manage your relays with ease!") + pterm.Gray("\nv0.3.0 alpha2")) + pterm.Magenta("\nWelcome to Relay Wizard 🧙") + pterm.Gray("\nInstall and manage your relays with ease!") + pterm.Gray("\nv0.3.0 alpha3")) } diff --git a/pkg/utils/network/utils.go b/pkg/utils/network/utils.go new file mode 100644 index 0000000..aba5251 --- /dev/null +++ b/pkg/utils/network/utils.go @@ -0,0 +1,17 @@ +package network + +// Function to determine http scheme being used +func HTTPEnabled(httpsEnabled bool) string { + if httpsEnabled { + return "https" + } + return "http" +} + +// Function to determine ws scheme being used +func WSEnabled(httpsEnabled bool) string { + if httpsEnabled { + return "wss" + } + return "ws" +} diff --git a/pkg/utils/systemd/utils.go b/pkg/utils/systemd/utils.go index cd74527..9b5124c 100644 --- a/pkg/utils/systemd/utils.go +++ b/pkg/utils/systemd/utils.go @@ -2,6 +2,7 @@ package systemd import ( "fmt" + "github.com/nodetec/rwz/pkg/utils/network" "github.com/pterm/pterm" "os" "os/exec" @@ -38,14 +39,9 @@ func CreateEnvFile(envFilePath, envTemplate string, envFileParams *EnvFileParams os.Exit(1) } - var WSProtocol string - if envFileParams.HTTPSEnabled { - WSProtocol = "wss" - } else { - WSProtocol = "ws" - } + WSScheme := network.WSEnabled(envFileParams.HTTPSEnabled) - err = envTmpl.Execute(envFile, struct{ Domain, WSProtocol, PrivKey, PubKey, RelayContact string }{Domain: envFileParams.Domain, WSProtocol: WSProtocol, PrivKey: envFileParams.PrivKey, PubKey: envFileParams.PubKey, RelayContact: envFileParams.RelayContact}) + err = envTmpl.Execute(envFile, struct{ Domain, WSScheme, PrivKey, PubKey, RelayContact string }{Domain: envFileParams.Domain, WSScheme: WSScheme, PrivKey: envFileParams.PrivKey, PubKey: envFileParams.PubKey, RelayContact: envFileParams.RelayContact}) if err != nil { pterm.Println() pterm.Error.Println(fmt.Sprintf("Failed to execute environment template: %v", err)) diff --git a/pkg/utils/templates/utils.go b/pkg/utils/templates/utils.go index fde8fe7..3f9dab3 100644 --- a/pkg/utils/templates/utils.go +++ b/pkg/utils/templates/utils.go @@ -2,6 +2,7 @@ package templates import ( "fmt" + "github.com/nodetec/rwz/pkg/utils/network" "github.com/pterm/pterm" "os" "text/template" @@ -29,14 +30,9 @@ func CreateIndexFile(indexFilePath, indexTemplate string, indexFileParams *Index os.Exit(1) } - var HTTPProtocol string - if indexFileParams.HTTPSEnabled { - HTTPProtocol = "https" - } else { - HTTPProtocol = "http" - } + HTTPScheme := network.HTTPEnabled(indexFileParams.HTTPSEnabled) - err = indexTmpl.Execute(indexFile, struct{ Domain, HTTPProtocol, PubKey string }{Domain: indexFileParams.Domain, HTTPProtocol: HTTPProtocol, PubKey: indexFileParams.PubKey}) + err = indexTmpl.Execute(indexFile, struct{ Domain, HTTPScheme, PubKey string }{Domain: indexFileParams.Domain, HTTPScheme: HTTPScheme, PubKey: indexFileParams.PubKey}) if err != nil { pterm.Println() pterm.Error.Println(fmt.Sprintf("Failed to execute index.html template: %v", err))