diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 9207186..c6a57ab 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,4 @@ # These are supported funding model platforms -github: christianchiarulli +github: [christianchiarulli, jchiarulli] patreon: chrisatmachine diff --git a/.gitignore b/.gitignore index ac8a393..171e37b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ relaywiz + +# Ignore .DS_Store files +.DS_Store diff --git a/README.md b/README.md index a0b121d..e77383f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-

Relay Wizard 🪄

+

Relay Wizard 🧙

Latest release @@ -35,7 +35,7 @@ The program will automate the following steps: To install a relay, spin up a new Debian server, hook up a domain name, and run the following command: ```bash -curl -sL https://relayrunner.org/relaywizard.sh | bash +curl -sL https://relaywizard.com/install.sh | bash ``` ## Learn more diff --git a/cmd/install.go b/cmd/install.go index 107af41..ce09785 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -1,11 +1,12 @@ package cmd import ( - "github.com/nodetec/relaywiz/pkg/manager" - "github.com/nodetec/relaywiz/pkg/network" - "github.com/nodetec/relaywiz/pkg/relays/khatru_pyramid" - "github.com/nodetec/relaywiz/pkg/relays/strfry" - "github.com/nodetec/relaywiz/pkg/ui" + "github.com/nodetec/rwz/pkg/manager" + "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/strfry" + "github.com/nodetec/rwz/pkg/ui" "github.com/pterm/pterm" "github.com/spf13/cobra" ) @@ -23,10 +24,10 @@ var installCmd = &cobra.Command{ pterm.Println(pterm.Yellow("Leave email empty if you don't want to receive notifications from Let's Encrypt about your SSL cert.")) pterm.Println() ssl_email, _ := pterm.DefaultInteractiveTextInput.Show("Email address") - pterm.Println() + // Supported relay options - options := []string{"Khatru Pyramid", "strfry"} + options := []string{"Khatru Pyramid", "strfry", "khatru29"} // 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 @@ -35,10 +36,14 @@ var installCmd = &cobra.Command{ // 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" { pterm.Println() pubkey, _ = pterm.DefaultInteractiveTextInput.Show("Public key (hex not npub)") + } else if selectedRelayOption == "khatru29" { + pterm.Println() + privkey, _ = pterm.DefaultInteractiveTextInput.Show("Private key (hex not nsec)") } pterm.Println() @@ -48,53 +53,91 @@ var installCmd = &cobra.Command{ // Step 1: Install necessary packages using APT manager.AptInstallPackages() - if selectedRelayOption == "strfry" { - strfry.AptInstallDependencies() - } - - // Step 2: Configure the firewall - network.ConfigureFirewall() - - // Step 3: Configure Nginx for HTTP if selectedRelayOption == "Khatru Pyramid" { - khatru_pyramid.ConfigureNginxHttp(relayDomain) - } else if selectedRelayOption == "strfry" { - strfry.ConfigureNginxHttp(relayDomain) - } + // Step 2: Configure the firewall + network.ConfigureFirewall() - // Step 4: Get SSL certificates - var shouldContinue = network.GetCertificates(relayDomain, ssl_email) + // Step 3: Configure Nginx for HTTP + khatru_pyramid.ConfigureNginxHttp(relayDomain) - if !shouldContinue { - return - } + // Step 4: Get SSL certificates + var shouldContinue = network.GetCertificates(relayDomain, ssl_email) + if !shouldContinue { + return + } - // Step 5: Configure Nginx for HTTPS - if selectedRelayOption == "Khatru Pyramid" { + // Step 5: Configure Nginx for HTTPS khatru_pyramid.ConfigureNginxHttps(relayDomain) - } else if selectedRelayOption == "strfry" { - strfry.ConfigureNginxHttps(relayDomain) - } - // Step 6: Download and install the relay binary - if selectedRelayOption == "Khatru Pyramid" { + // Step 6: Download and install the relay binary khatru_pyramid.InstallRelayBinary() - } else if selectedRelayOption == "strfry" { - strfry.InstallRelayBinary() - } - // Step 7: Set up the relay service - if selectedRelayOption == "Khatru Pyramid" { + // Step 7: Set up the relay service khatru_pyramid.SetupRelayService(relayDomain, pubkey) + + // Step 8: Show success messages + khatru_pyramid.SuccessMessages(relayDomain) } else if selectedRelayOption == "strfry" { + // Step 2: Install necessary strfry package dependencies + strfry.AptInstallDependencies() + + // Step 3: Configure the firewall + network.ConfigureFirewall() + + // Step 4: Configure Nginx for HTTP + strfry.ConfigureNginxHttp(relayDomain) + + // Step 5: Get SSL certificates + var shouldContinue = network.GetCertificates(relayDomain, ssl_email) + if !shouldContinue { + return + } + + // Step 6: Configure Nginx for HTTPS + strfry.ConfigureNginxHttps(relayDomain) + + // Step 7: Download and install the relay binary + strfry.InstallRelayBinary() + + // Step 8: Set up the relay service strfry.SetupRelayService(relayDomain) + + // Step 9: Show success messages + strfry.SuccessMessages(relayDomain) + } else if selectedRelayOption == "khatru29" { + // Step 2: Configure the firewall + network.ConfigureFirewall() + + // Step 3: Configure Nginx for HTTP + khatru29.ConfigureNginxHttp(relayDomain) + + // Step 4: Get SSL certificates + var shouldContinue = network.GetCertificates(relayDomain, ssl_email) + if !shouldContinue { + return + } + + // Step 5: Configure Nginx for HTTPS + khatru29.ConfigureNginxHttps(relayDomain) + + // Step 6: Download and install the relay binary + khatru29.InstallRelayBinary() + + // Step 7: Set up the relay service + khatru29.SetupRelayService(relayDomain, privkey) + + // Step 8: Show success messages + khatru29.SuccessMessages(relayDomain) } pterm.Println() - pterm.Println(pterm.Magenta("The installation is complete.")) - pterm.Println(pterm.Magenta("You can access your relay at wss://" + relayDomain)) + pterm.Println(pterm.Magenta("Join the NODE-TEC Discord to get support:")) + pterm.Println(pterm.Magenta("https://discord.gg/J9gRK5pbWb")) + pterm.Println() + pterm.Println(pterm.Magenta("We plan to use relay groups for support in the future...")) + pterm.Println() - pterm.Println(pterm.Magenta("You can re-run this installer with `relaywiz install`.")) + pterm.Println(pterm.Magenta("You can re-run this installer with `rwz install`.")) }, } diff --git a/cmd/root.go b/cmd/root.go index e4cfdc4..e735b3d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,9 +6,9 @@ import ( ) var rootCmd = &cobra.Command{ - Use: "relaywiz", + Use: "rwz", Short: "A wizard for relay runners", - Long: `relaywiz is a CLI tool for relays operators that + Long: `rwz is a CLI tool for relays operators that helps install and configure your relay.`, } @@ -30,5 +30,5 @@ func init() { // Cobra also supports local flags, which will only run // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/go.mod b/go.mod index 52f4f1f..60f8778 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/nodetec/relaywiz +module github.com/nodetec/rwz go 1.22.4 diff --git a/main.go b/main.go index e600d09..b6092fd 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/nodetec/relaywiz/cmd" + "github.com/nodetec/rwz/cmd" ) func main() { diff --git a/pkg/network/certbot.go b/pkg/network/certbot.go index c3cfd8e..0cd3e65 100644 --- a/pkg/network/certbot.go +++ b/pkg/network/certbot.go @@ -2,7 +2,7 @@ package network import ( "fmt" - "github.com/nodetec/relaywiz/pkg/utils" + "github.com/nodetec/rwz/pkg/utils" "github.com/pterm/pterm" "log" "os" diff --git a/pkg/relays/khatru29/install.go b/pkg/relays/khatru29/install.go new file mode 100644 index 0000000..cffab49 --- /dev/null +++ b/pkg/relays/khatru29/install.go @@ -0,0 +1,84 @@ +package khatru29 + +import ( + "fmt" + "github.com/pterm/pterm" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" +) + +// Function to download and make the binary executable +func InstallRelayBinary() { + // URL of the binary to download + const downloadURL = "https://github.com/nodetec/relays/releases/download/v0.1.0/khatru29-0.4.0-x86_64-linux-gnu.tar.gz" + + // Name of the binary after downloading + const binaryName = "nostr-relay-khatru29" + + // Destination directory for the binary + const destDir = "/usr/local/bin" + + // Data directory for the relay + const dataDir = "/var/lib/nostr-relay-khatru29" + + spinner, _ := pterm.DefaultSpinner.Start("Installing khatru29 relay...") + + // Ensure the data directory exists + err := os.MkdirAll(dataDir, 0755) + if err != nil { + log.Fatalf("Error creating data directory: %v", err) + } + + // Determine the file name from the URL + tempFileName := filepath.Base(downloadURL) + + // Create the temporary file + out, err := os.Create(fmt.Sprintf("/tmp/%s", tempFileName)) + if err != nil { + log.Fatalf("Error creating temporary file: %v", err) + } + defer out.Close() + + // Download the file + resp, err := http.Get(downloadURL) + if err != nil { + log.Fatalf("Error downloading file: %v", err) + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + log.Fatalf("Bad status: %s", resp.Status) + } + + // Write the body to the temporary file + _, err = io.Copy(out, resp.Body) + if err != nil { + log.Fatalf("Error writing to temporary file: %v", err) + } + + // Extract binary + err = exec.Command("tar", "-xf", fmt.Sprintf("/tmp/%s", tempFileName), "-C", fmt.Sprintf("%s", destDir)).Run() + if err != nil { + log.Fatalf("Error extracting binary to /usr/local/bin: %v", err) + } + + // 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(destDir, binaryName) + + // Make the file executable + err = os.Chmod(destPath, 0755) + if err != nil { + log.Fatalf("Error making file executable: %v", err) + } + + spinner.Success("khatru29 relay installed successfully.") +} diff --git a/pkg/relays/khatru29/nginx_http.go b/pkg/relays/khatru29/nginx_http.go new file mode 100644 index 0000000..1afa220 --- /dev/null +++ b/pkg/relays/khatru29/nginx_http.go @@ -0,0 +1,71 @@ +package khatru29 + +import ( + "fmt" + "github.com/pterm/pterm" + "log" + "os" + "os/exec" +) + +// Function to configure nginx for HTTP +func ConfigureNginxHttp(domainName string) { + spinner, _ := pterm.DefaultSpinner.Start("Configuring nginx for HTTP...") + + err := os.MkdirAll(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) + if err != nil { + log.Fatalf("Error creating directories: %v", err) + } + + const configFile = "nostr_relay_khatru29.conf" + + err = os.Remove(fmt.Sprintf("/etc/nginx/conf.d/%s", configFile)) + if err != nil && !os.IsNotExist(err) { + log.Fatalf("Error removing existing nginx configuration: %v", err) + } + + var configContent string + + configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream websocket_khatru29 { + server 0.0.0.0:5577; +} + +# %s +server { + listen 80; + listen [::]:80; + server_name %s; + + location /.well-known/acme-challenge/ { + root /var/www/%s; + allow all; + } + + location / { + proxy_pass http://websocket_khatru29; + 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; + } +} +`, domainName, domainName, domainName) + + err = os.WriteFile(fmt.Sprintf("/etc/nginx/conf.d/%s", configFile), []byte(configContent), 0644) + if err != nil { + log.Fatalf("Error writing nginx configuration: %v", err) + } + + err = exec.Command("systemctl", "restart", "nginx").Run() + if err != nil { + log.Fatalf("Error reloading nginx: %v", err) + } + + spinner.Success("Nginx configured for HTTP") +} diff --git a/pkg/relays/khatru29/nginx_https.go b/pkg/relays/khatru29/nginx_https.go new file mode 100644 index 0000000..8137896 --- /dev/null +++ b/pkg/relays/khatru29/nginx_https.go @@ -0,0 +1,142 @@ +package khatru29 + +import ( + "fmt" + "github.com/pterm/pterm" + "log" + "os" + "os/exec" +) + +// Function to configure nginx for HTTPS +func ConfigureNginxHttps(domainName string) { + spinner, _ := pterm.DefaultSpinner.Start("Configuring nginx for HTTPS...") + + const configFile = "nostr_relay_khatru29.conf" + + err := os.Remove(fmt.Sprintf("/etc/nginx/conf.d/%s", configFile)) + if err != nil && !os.IsNotExist(err) { + log.Fatalf("Error removing existing nginx configuration: %v", err) + } + + var configContent string + + configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream websocket_khatru29 { + server 0.0.0.0:5577; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + root /var/www/%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_khatru29; + 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; + } + + #### SSL Configuration #### + # Test configuration: + # https://www.ssllabs.com/ssltest/analyze.html + # https://cryptcheck.fr/ + ssl_certificate /etc/letsencrypt/live/%s/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/%s/privkey.pem; + # Verify chain of trust of OCSP response using Root CA and Intermediate certs + ssl_trusted_certificate /etc/letsencrypt/live/%s/chain.pem; + + # Only return Nginx in server header + server_tokens off; + + # TODO + # Add support to generate the file in the script + #ssl_dhparam /etc/ssl/certs/dhparam.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + + # 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 /.well-known/acme-challenge/ { + root /var/www/%s; + allow all; + } + + location / { + return 301 https://%s$request_uri; + } +} +`, domainName, domainName, domainName, domainName, domainName, domainName, domainName, domainName) + + err = os.WriteFile(fmt.Sprintf("/etc/nginx/conf.d/%s", configFile), []byte(configContent), 0644) + if err != nil { + log.Fatalf("Error writing nginx configuration: %v", err) + } + + err = exec.Command("systemctl", "reload", "nginx").Run() + if err != nil { + log.Fatalf("Error reloading nginx: %v", err) + } + + spinner.Success("Nginx configured for HTTPS") +} diff --git a/pkg/relays/khatru29/service.go b/pkg/relays/khatru29/service.go new file mode 100644 index 0000000..4fbbcd3 --- /dev/null +++ b/pkg/relays/khatru29/service.go @@ -0,0 +1,159 @@ +package khatru29 + +import ( + "fmt" + "github.com/pterm/pterm" + "log" + "os" + "os/exec" + "text/template" +) + +// Function to check if a user exists +func userExists(username string) bool { + cmd := exec.Command("id", "-u", username) + err := cmd.Run() + return err == nil +} + +// Function to set up the relay service +func SetupRelayService(domain, privKey string) { + // Template for the systemd service file + const serviceTemplate = `[Unit] +Description=Nostr Relay Khatru29 +After=network.target + +[Service] +Type=simple +User=nostr +Group=nostr +WorkingDirectory=/home/nostr +EnvironmentFile=/etc/systemd/system/nostr-relay-khatru29.env +ExecStart=/usr/local/bin/nostr-relay-khatru29 +Restart=on-failure + +[Install] +WantedBy=multi-user.target +` + + // Template for the environment file + const envTemplate = ` +PORT=5577 +DOMAIN={{.Domain}} +RELAY_NAME=nostr-relay-khatru29 +RELAY_PRIVKEY={{.PrivKey}} +RELAY_DESCRIPTION=Khatru29 Nostr Relay +RELAY_CONTACT=devs@node-tec.com +DATABASE_PATH=/var/lib/nostr-relay-khatru29/db +` + // Path for the systemd service file + const serviceFilePath = "/etc/systemd/system/nostr-relay-khatru29.service" + + // Path for the environment file + const envFilePath = "/etc/systemd/system/nostr-relay-khatru29.env" + + // Data directory + const dataDir = "/var/lib/nostr-relay-khatru29" + + // Relay service + const relayService = "nostr-relay-khatru29" + + spinner, _ := pterm.DefaultSpinner.Start("Configuring relay service...") + + // Check if the service file exists and remove it if it does + if _, err := os.Stat(serviceFilePath); err == nil { + err = os.Remove(serviceFilePath) + if err != nil { + log.Fatalf("Error removing service file: %v", err) + } + } + + // Check if the environment file exists and remove it if it does + if _, err := os.Stat(envFilePath); err == nil { + err = os.Remove(envFilePath) + if err != nil { + log.Fatalf("Error removing environment file: %v", err) + } + } + + // Ensure the user for the relay service exists + if !userExists("nostr") { + spinner.UpdateText("Creating user 'nostr'...") + err := exec.Command("adduser", "--disabled-login", "--gecos", "", "nostr").Run() + if err != nil { + log.Fatalf("Error creating user: %v", err) + } + } else { + spinner.UpdateText("User 'nostr' already exists") + } + + // Ensure the data directory exists and set ownership + spinner.UpdateText("Creating data directory...") + err := os.MkdirAll(dataDir, 0755) + if err != nil { + log.Fatalf("Error creating data directory: %v", err) + } + + // Use chown command to set ownership of the data directory to the nostr user + err = exec.Command("chown", "-R", "nostr:nostr", dataDir).Run() + if err != nil { + log.Fatalf("Error setting ownership of the data directory: %v", err) + } + + // Create the environment file + spinner.UpdateText("Creating environment file...") + envFile, err := os.Create(envFilePath) + if err != nil { + log.Fatalf("Error creating environment file: %v", err) + } + defer envFile.Close() + + envTmpl, err := template.New("env").Parse(envTemplate) + if err != nil { + log.Fatalf("Error parsing environment template: %v", err) + } + + err = envTmpl.Execute(envFile, struct{ Domain, PrivKey string }{Domain: domain, PrivKey: privKey}) + if err != nil { + log.Fatalf("Error executing environment template: %v", err) + } + + // Create the systemd service file + spinner.UpdateText("Creating service file...") + serviceFile, err := os.Create(serviceFilePath) + if err != nil { + log.Fatalf("Error creating service file: %v", err) + } + defer serviceFile.Close() + + tmpl, err := template.New("service").Parse(serviceTemplate) + if err != nil { + log.Fatalf("Error parsing service template: %v", err) + } + + err = tmpl.Execute(serviceFile, struct{ Domain, PrivKey string }{Domain: domain, PrivKey: privKey}) + if err != nil { + log.Fatalf("Error executing service template: %v", err) + } + + // Reload systemd to apply the new service + spinner.UpdateText("Reloading systemd daemon...") + err = exec.Command("systemctl", "daemon-reload").Run() + if err != nil { + log.Fatalf("Error reloading systemd daemon: %v", err) + } + + // Enable and start the nostr relay service + spinner.UpdateText("Enabling and starting service...") + err = exec.Command("systemctl", "enable", fmt.Sprintf("%s", relayService)).Run() + if err != nil { + log.Fatalf("Error enabling nostr relay service: %v", err) + } + + err = exec.Command("systemctl", "start", fmt.Sprintf("%s", relayService)).Run() + if err != nil { + log.Fatalf("Error starting nostr relay service: %v", err) + } + + spinner.Success("Nostr relay service configured") +} diff --git a/pkg/relays/khatru29/success-messages.go b/pkg/relays/khatru29/success-messages.go new file mode 100644 index 0000000..0719eed --- /dev/null +++ b/pkg/relays/khatru29/success-messages.go @@ -0,0 +1,43 @@ +package khatru29 + +import ( + "github.com/pterm/pterm" +) + +func SuccessMessages(domain string) { + const dataDir = "/var/lib/nostr-relay-khatru29" + const envFile = "/etc/systemd/system/nostr-relay-khatru29.env" + const service = "nostr-relay-khatru29" + const githubLink = "https://github.com/fiatjaf/relay29/tree/master" + + pterm.Println() + pterm.Println(pterm.Magenta("The installation is complete.")) + + pterm.Println() + pterm.Println(pterm.Magenta("You can access your relay at:")) + pterm.Println(pterm.Magenta("wss://" + domain)) + + pterm.Println() + pterm.Println(pterm.Magenta("Your relay's data directory is located here:")) + pterm.Println(pterm.Magenta(dataDir)) + + pterm.Println() + pterm.Println(pterm.Magenta("Your relay's environment file is located here:")) + pterm.Println(pterm.Magenta(envFile)) + + pterm.Println() + pterm.Println(pterm.Magenta("To check the status of your relay run:")) + pterm.Println(pterm.Magenta("systemctl status " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("To reload the relay service run:")) + pterm.Println(pterm.Magenta("systemctl reload " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("To restart the relay service run:")) + pterm.Println(pterm.Magenta("systemctl restart " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("Khatru29 GitHub")) + pterm.Println(pterm.Magenta(githubLink)) +} diff --git a/pkg/relays/khatru_pyramid/install.go b/pkg/relays/khatru_pyramid/install.go index 9c96ff6..a1f10f1 100644 --- a/pkg/relays/khatru_pyramid/install.go +++ b/pkg/relays/khatru_pyramid/install.go @@ -1,18 +1,20 @@ package khatru_pyramid import ( + "fmt" "github.com/pterm/pterm" "io" "log" "net/http" "os" + "os/exec" "path/filepath" ) // Function to download and make the binary executable func InstallRelayBinary() { // URL of the binary to download - const downloadURL = "https://github.com/github-tijlxyz/khatru-pyramid/releases/download/v0.0.5/khatru-pyramid-v0.0.5-linux-amd64" + const downloadURL = "https://github.com/nodetec/relays/releases/download/v0.1.0/khatru-pyramid-0.0.5-x86_64-linux-gnu.tar.gz" // Name of the binary after downloading const binaryName = "nostr-relay-khatru-pyramid" @@ -24,6 +26,7 @@ func InstallRelayBinary() { const dataDir = "/var/lib/nostr-relay-khatru-pyramid" spinner, _ := pterm.DefaultSpinner.Start("Installing Khatru Pyramid relay...") + // Ensure the data directory exists err := os.MkdirAll(dataDir, 0755) if err != nil { @@ -34,7 +37,7 @@ func InstallRelayBinary() { tempFileName := filepath.Base(downloadURL) // Create the temporary file - out, err := os.Create(tempFileName) + out, err := os.Create(fmt.Sprintf("/tmp/%s", tempFileName)) if err != nil { log.Fatalf("Error creating temporary file: %v", err) } @@ -58,15 +61,19 @@ func InstallRelayBinary() { log.Fatalf("Error writing to temporary file: %v", err) } - // Define the final destination path - destPath := filepath.Join(destDir, binaryName) - - // Move the file to the destination directory - err = os.Rename(tempFileName, destPath) + // Extract binary + err = exec.Command("tar", "-xf", fmt.Sprintf("/tmp/%s", tempFileName), "-C", fmt.Sprintf("%s", destDir)).Run() if err != nil { - log.Fatalf("Error moving file to /usr/local/bin: %v", err) + log.Fatalf("Error extracting binary to /usr/local/bin: %v", err) } + // 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(destDir, binaryName) + // Make the file executable err = os.Chmod(destPath, 0755) if err != nil { diff --git a/pkg/relays/khatru_pyramid/nginx_http.go b/pkg/relays/khatru_pyramid/nginx_http.go index ae4191c..df5f9b3 100644 --- a/pkg/relays/khatru_pyramid/nginx_http.go +++ b/pkg/relays/khatru_pyramid/nginx_http.go @@ -2,7 +2,6 @@ package khatru_pyramid import ( "fmt" - // "github.com/nodetec/relaywiz/pkg/utils" "github.com/pterm/pterm" "log" "os" @@ -32,7 +31,7 @@ func ConfigureNginxHttp(domainName string) { '' close; } -upstream websocket { +upstream websocket_khatru_pyramid { server 0.0.0.0:3334; } @@ -48,7 +47,7 @@ server { } location / { - proxy_pass http://websocket; + proxy_pass http://websocket_khatru_pyramid; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; diff --git a/pkg/relays/khatru_pyramid/nginx_https.go b/pkg/relays/khatru_pyramid/nginx_https.go index cd85f2a..760cbfd 100644 --- a/pkg/relays/khatru_pyramid/nginx_https.go +++ b/pkg/relays/khatru_pyramid/nginx_https.go @@ -2,7 +2,6 @@ package khatru_pyramid import ( "fmt" - // "github.com/nodetec/relaywiz/pkg/utils" "github.com/pterm/pterm" "log" "os" @@ -27,7 +26,7 @@ func ConfigureNginxHttps(domainName string) { '' close; } -upstream websocket { +upstream websocket_khatru_pyramid { server 0.0.0.0:3334; } @@ -42,7 +41,7 @@ server { # 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; + proxy_pass http://websocket_khatru_pyramid; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; diff --git a/pkg/relays/khatru_pyramid/success-messages.go b/pkg/relays/khatru_pyramid/success-messages.go new file mode 100644 index 0000000..1005ccf --- /dev/null +++ b/pkg/relays/khatru_pyramid/success-messages.go @@ -0,0 +1,43 @@ +package khatru_pyramid + +import ( + "github.com/pterm/pterm" +) + +func SuccessMessages(domain string) { + const dataDir = "/var/lib/nostr-relay-khatru-pyramid" + const envFile = "/etc/systemd/system/nostr-relay-khatru-pyramid.env" + const service = "nostr-relay-khatru-pyramid" + const githubLink = "https://github.com/github-tijlxyz/khatru-pyramid" + + pterm.Println() + pterm.Println(pterm.Magenta("The installation is complete.")) + + pterm.Println() + pterm.Println(pterm.Magenta("You can access your relay at:")) + pterm.Println(pterm.Magenta("wss://" + domain)) + + pterm.Println() + pterm.Println(pterm.Magenta("Your relay's data directory is located here:")) + pterm.Println(pterm.Magenta(dataDir)) + + pterm.Println() + pterm.Println(pterm.Magenta("Your relay's environment file is located here:")) + pterm.Println(pterm.Magenta(envFile)) + + pterm.Println() + pterm.Println(pterm.Magenta("To check the status of your relay run:")) + pterm.Println(pterm.Magenta("systemctl status " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("To reload the relay service run:")) + pterm.Println(pterm.Magenta("systemctl reload " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("To restart the relay service run:")) + pterm.Println(pterm.Magenta("systemctl restart " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("Khatru Pyramid GitHub")) + pterm.Println(pterm.Magenta(githubLink)) +} diff --git a/pkg/relays/strfry/apt.go b/pkg/relays/strfry/apt.go index 9985051..9929b1a 100644 --- a/pkg/relays/strfry/apt.go +++ b/pkg/relays/strfry/apt.go @@ -2,7 +2,7 @@ package strfry import ( "fmt" - "github.com/nodetec/relaywiz/pkg/manager" + "github.com/nodetec/rwz/pkg/manager" "github.com/pterm/pterm" "os/exec" ) diff --git a/pkg/relays/strfry/install.go b/pkg/relays/strfry/install.go index 1480c4e..d62f126 100644 --- a/pkg/relays/strfry/install.go +++ b/pkg/relays/strfry/install.go @@ -2,24 +2,22 @@ package strfry import ( "fmt" + "github.com/pterm/pterm" + "io" "log" + "net/http" "os" "os/exec" - - "github.com/pterm/pterm" + "path/filepath" ) -// Function to download, build, and install the binary +// Function to download and make the binary executable func InstallRelayBinary() { - // TODO - // Create the binary on a different machine then download it instead of building it here - // Use these variables and model it after Khatru Pyramid installation - // Temporary directory for git repository const tempDir = "/tmp/strfry" // URL of the binary to download - // const downloadURL = "https://..." + const downloadURL = "https://github.com/nodetec/relays/releases/download/v0.1.0/strfry-0.9.7-x86_64-linux-gnu.tar.gz" // Name of the binary after downloading const binaryName = "nostr-relay-strfry" @@ -27,67 +25,67 @@ func InstallRelayBinary() { // Destination directory for the binary const destDir = "/usr/local/bin" - // Data directory for the relay - // const dataDir = "/var/lib/nostr-relay-strfry" - spinner, _ := pterm.DefaultSpinner.Start("Installing strfry relay...") - pterm.Println() - pterm.Println(pterm.Magenta("Go get coffee, this may take a few minutes...")) - pterm.Println() - - // Download - // Check for and remove existing repository + // Check for and remove existing git repository err := os.RemoveAll(fmt.Sprintf("%s", tempDir)) if err != nil && !os.IsNotExist(err) { log.Fatalf("Error removing existing repository: %v", err) } // Download git repository - err = exec.Command("git", "clone", "https://github.com/hoytech/strfry.git", fmt.Sprintf("%s", tempDir)).Run() + err = exec.Command("git", "clone", "-b", "0.9.7", "https://github.com/hoytech/strfry.git", fmt.Sprintf("%s", tempDir)).Run() if err != nil { log.Fatalf("Error downloading repository: %v", err) } - // Build - // TODO - // Check for development environment variable instead of commenting and uncommenting these lines + // Install + // Determine the file name from the URL + tempFileName := filepath.Base(downloadURL) - // Check for and remove existing binary - // When developing comment to prevent unecessary builds - err = os.Remove(fmt.Sprintf("%s/%s", destDir, binaryName)) - if err != nil && !os.IsNotExist(err) { - log.Fatalf("Error removing existing binary: %v", err) + // Create the temporary file + out, err := os.Create(fmt.Sprintf("/tmp/%s", tempFileName)) + if err != nil { + log.Fatalf("Error creating temporary file: %v", err) } + defer out.Close() - // Check if binary exists - // When developing uncomment to prevent unecessary builds - // _, err = os.Stat(fmt.Sprintf("%s/%s", destDir, binaryName)) - // if os.IsNotExist(err) { - // Intialize and update git submodule - err = exec.Command("git", "-C", fmt.Sprintf("%s", tempDir), "submodule", "update", "--init").Run() + // Download the file + resp, err := http.Get(downloadURL) if err != nil { - log.Fatalf("Error initializing and updating git submodule: %v", err) + log.Fatalf("Error downloading file: %v", err) + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + log.Fatalf("Bad status: %s", resp.Status) } - // Make setup-golpe - err = exec.Command("make", "-C", fmt.Sprintf("%s", tempDir), "setup-golpe").Run() + // Write the body to the temporary file + _, err = io.Copy(out, resp.Body) if err != nil { - log.Fatalf("Error making setup-golpe: %v", err) + log.Fatalf("Error writing to temporary file: %v", err) } - // Make -j2 - err = exec.Command("make", "-C", fmt.Sprintf("%s", tempDir), "-j2").Run() + // Extract binary + err = exec.Command("tar", "-xf", fmt.Sprintf("/tmp/%s", tempFileName), "-C", fmt.Sprintf("%s", destDir)).Run() if err != nil { - log.Fatalf("Error making -j2: %v", err) + log.Fatalf("Error extracting binary to /usr/local/bin: %v", err) } - // Install - err = exec.Command("mv", fmt.Sprintf("%s/strfry", tempDir), fmt.Sprintf("%s/%s", destDir, binaryName)).Run() + // 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(destDir, binaryName) + + // Make the file executable + err = os.Chmod(destPath, 0755) if err != nil { - log.Fatalf("Error installing binary: %v", err) + log.Fatalf("Error making file executable: %v", err) } - // } spinner.Success("strfry relay installed successfully.") } diff --git a/pkg/relays/strfry/service.go b/pkg/relays/strfry/service.go index 27e050c..dfeeec0 100644 --- a/pkg/relays/strfry/service.go +++ b/pkg/relays/strfry/service.go @@ -17,6 +17,8 @@ func userExists(username string) bool { } // Function to set up the relay service +// TODO +// Check working directory // WorkingDirectory=/home/nostr func SetupRelayService(domain string) { // Template for the systemd service file @@ -92,7 +94,7 @@ WantedBy=multi-user.target // TODO // Determine system hard limit - // Set nofiles option in config file + // Determine preferred nofiles value cmd = exec.Command("sed", "-i", `s|nofiles = .*|nofiles = 0|`, filePath) // Execute the command @@ -126,9 +128,6 @@ WantedBy=multi-user.target } defer serviceFile.Close() - // TODO - // Try setting the owner to be nostr - tmpl, err := template.New("service").Parse(serviceTemplate) if err != nil { log.Fatalf("Error parsing service template: %v", err) diff --git a/pkg/relays/strfry/success-messages.go b/pkg/relays/strfry/success-messages.go new file mode 100644 index 0000000..bdf42ab --- /dev/null +++ b/pkg/relays/strfry/success-messages.go @@ -0,0 +1,43 @@ +package strfry + +import ( + "github.com/pterm/pterm" +) + +func SuccessMessages(domain string) { + const dataDir = "/var/lib/nostr-relay-strfry" + const configFile = "/etc/strfry.conf" + const service = "nostr-relay-strfry" + const githubLink = "https://github.com/hoytech/strfry" + + pterm.Println() + pterm.Println(pterm.Magenta("The installation is complete.")) + + pterm.Println() + pterm.Println(pterm.Magenta("You can access your relay at:")) + pterm.Println(pterm.Magenta("wss://" + domain)) + + pterm.Println() + pterm.Println(pterm.Magenta("Your relay's data directory is located here:")) + pterm.Println(pterm.Magenta(dataDir)) + + pterm.Println() + pterm.Println(pterm.Magenta("Your relay's config file is located here:")) + pterm.Println(pterm.Magenta(configFile)) + + pterm.Println() + pterm.Println(pterm.Magenta("To check the status of your relay run:")) + pterm.Println(pterm.Magenta("systemctl status " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("To reload the relay service run:")) + pterm.Println(pterm.Magenta("systemctl reload " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("To restart the relay service run:")) + pterm.Println(pterm.Magenta("systemctl restart " + service)) + + pterm.Println() + pterm.Println(pterm.Magenta("strfry GitHub")) + pterm.Println(pterm.Magenta(githubLink)) +} diff --git a/pkg/ui/greeter.go b/pkg/ui/greeter.go index 80232a2..8823bac 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.1.0")) + pterm.Magenta("\nWelcome to Relay Wizard 🧙") + pterm.Gray("\nInstall and manage your relays with ease!") + pterm.Gray("\nv0.2.0")) }