From 0334c5d599e243beb8b7b55fb52dfb1f00b3cb53 Mon Sep 17 00:00:00 2001 From: Iskander Date: Tue, 14 Jan 2025 22:07:59 +0100 Subject: [PATCH 01/15] Add dnsmasq as a dependency and remove all code related to domains and the hosts file --- .github/workflows/release.yml | 4 +- README.md | 6 +- app/project.go | 8 +- build/DEBIAN/postinst | 19 ++ cli/project.go | 34 ++- common/constants.go | 2 + common/helpers.go | 4 + config/config.go | 16 -- config/config_linux_test.go | 12 -- config/config_test.go | 8 - config/hosts.go | 215 ------------------- config/hosts_test.go | 279 ------------------------- config/nginx.go | 14 +- config/nginx_linux_test.go | 2 +- config/nginx_test.go | 8 +- config/unix.go | 10 - config/windows.go | 55 ----- core/core_test.go | 55 ----- core/domain_alias.go | 22 +- core/domain_alias_test.go | 105 +--------- core/init.go | 5 - core/project.go | 66 ++---- core/project_test.go | 145 +------------ core/run.go | 2 +- core/run_test.go | 2 +- core/variable_test.go | 12 +- database/migrations/001_init.up.sql | 1 - database/queries/projects.sql | 8 +- database/sqlc/models.go | 9 +- database/sqlc/projects.sql.go | 36 ++-- frontend/src/routes/project-form.tsx | 22 +- frontend/src/sections/project-info.tsx | 22 +- frontend/src/stores/projectsStore.ts | 8 +- 33 files changed, 143 insertions(+), 1073 deletions(-) delete mode 100644 config/hosts.go delete mode 100644 config/hosts_test.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1cef3ce..ea41f8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -159,9 +159,9 @@ jobs: echo -e "\nVersion: $SPINUP_VERSION" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control if [ "$webkit_version" -eq "40" ]; then - echo "Depends: libgtk-3-0, libwebkit2gtk-4.0-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control + echo "Depends: dnsmasq, libgtk-3-0, libwebkit2gtk-4.0-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control else - echo "Depends: libgtk-3-0, libwebkit2gtk-4.1-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control + echo "Depends: dnsmasq, libgtk-3-0, libwebkit2gtk-4.1-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control fi sudo chown -R root:root deb/spinup-${SPINUP_VERSION}${os_version} diff --git a/README.md b/README.md index 0d04333..35ece97 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Commands are templates, so we can use variables that are then defined in the pro spinup command add example "npm run dev -- --port {{port}}" ``` -`port` and `domain` are reserved variables that are used to define the port and domain of the project. These are required to be when adding a project. +`port` and `domain` are reserved variables that are used to define the port and domain (based on the name) of the project. These are required to be when adding a project. More information on variables can be found in the [Variables](#variables) section. @@ -83,7 +83,7 @@ More information on variables can be found in the [Variables](#variables) sectio To add a project you can use the following command: ```bash -spinup project add [commands...] +spinup project add [commands...] ``` This will create a configuration for the project in the sqlite database for spinup located in your `.config/spinup` folder. @@ -91,7 +91,7 @@ This will create a configuration for the project in the sqlite database for spin Example: ```bash -spinup project add example example.local 8001 example1 example2 +spinup project add example 8001 example1 example2 ``` #### Removing a project diff --git a/app/project.go b/app/project.go index b99b301..1c8c6ae 100644 --- a/app/project.go +++ b/app/project.go @@ -33,7 +33,7 @@ func (a *App) GetProjects() []core.Project { return projects } -func (a *App) AddProject(name string, domain string, port int64, commandNames []string) error { +func (a *App) AddProject(name string, port int64, commandNames []string) error { err := a.core.FetchCommands() if err != nil { @@ -46,7 +46,7 @@ func (a *App) AddProject(name string, domain string, port int64, commandNames [] return fmt.Errorf("error getting projects config: %s", err) } - msg := a.core.AddProject(name, domain, port, commandNames) + msg := a.core.AddProject(name, port, commandNames) if _, ok := msg.(*common.ErrMsg); ok { fmt.Println(msg.GetText()) @@ -56,7 +56,7 @@ func (a *App) AddProject(name string, domain string, port int64, commandNames [] return nil } -func (a *App) UpdateProject(name string, domain string, port int64, commandNames []string) error { +func (a *App) UpdateProject(name string, port int64, commandNames []string) error { err := a.core.FetchCommands() if err != nil { @@ -69,7 +69,7 @@ func (a *App) UpdateProject(name string, domain string, port int64, commandNames return fmt.Errorf("error getting projects config: %s", err) } - msg := a.core.UpdateProject(name, domain, port, commandNames) + msg := a.core.UpdateProject(name, port, commandNames) if _, ok := msg.(*common.ErrMsg); ok { fmt.Println(msg.GetText()) diff --git a/build/DEBIAN/postinst b/build/DEBIAN/postinst index 70681b4..23d0cd8 100755 --- a/build/DEBIAN/postinst +++ b/build/DEBIAN/postinst @@ -1,5 +1,24 @@ #!/usr/bin/env bash +if [ "$(id -u)" -ne 0 ]; then + echo "This script must be run as root" >&2 + exit 1 +fi + # Remove the old symlink if it exists and create a new one rm -f /usr/bin/spinup ln -s /usr/share/spinup/bin/spinup /usr/bin/spinup + +# Create the spinup user if it doesn't exist +if ! id -u spinup &>/dev/null; then + useradd -r -s /bin/false spinup +fi + +# Add the spinup user to the sudo group +usermod -aG sudo spinup + +# Set the correct permissions on the spinup directory +chown -R spinup:spinup /usr/share/spinup + +# Set the correct permissions on the spinup binary +chown root:spinup /usr/share/spinup/bin/spinup diff --git a/cli/project.go b/cli/project.go index f052d40..47423ab 100644 --- a/cli/project.go +++ b/cli/project.go @@ -37,7 +37,7 @@ func (c *CLI) listProjects() { c.sendMsg( common.NewRegularMsg("%-10s %-30s %-10d %-20s\n", project.Name, - project.Domain, + fmt.Sprintf("%s.%s", project.Name, common.TLD), project.Port, commands, ), @@ -46,18 +46,17 @@ func (c *CLI) listProjects() { } // Add a project and display a loading message. -func (c *CLI) addProject(name string, domain string, port int64, commandNames []string) { +func (c *CLI) addProject(name string, port int64, commandNames []string) { c.Loading(fmt.Sprintf("Adding project %s...", name), func() common.Msg { - return c.core.AddProject(name, domain, port, commandNames) + return c.core.AddProject(name, port, commandNames) }, ) } -// Add a project interactively by asking the user for the name, domain, port and commands. +// Add a project interactively by asking the user for the name, port and commands. func (c *CLI) addProjectInteractive() { name := c.Input("Project name:", "") - domain := c.Input("Domain:", "") port := c.Input("Port:", "") portInt, err := strconv.ParseInt(port, 10, 64) @@ -78,7 +77,7 @@ func (c *CLI) addProjectInteractive() { return } - c.addProject(name, domain, portInt, selectedCommands) + c.addProject(name, portInt, selectedCommands) } // Remove a project and display a loading message. @@ -116,10 +115,10 @@ func (c *CLI) removeProjectInteractive() { } // Edit a project and display a loading message. -func (c *CLI) editProject(name string, domain string, port int64, commandNames []string) { +func (c *CLI) editProject(name string, port int64, commandNames []string) { c.Loading(fmt.Sprintf("Updating project %s...", name), func() common.Msg { - return c.core.UpdateProject(name, domain, port, commandNames) + return c.core.UpdateProject(name, port, commandNames) }, ) } @@ -149,7 +148,6 @@ func (c *CLI) editProjectInteractive() { return } - domain := c.Input("Domain:", project.Domain) port := c.Input("Port:", strconv.FormatInt(project.Port, 10)) portInt, err := strconv.ParseInt(port, 10, 64) @@ -177,7 +175,7 @@ func (c *CLI) editProjectInteractive() { return } - c.sendMsg(c.core.UpdateProject(name, domain, portInt, selectedCommands)) + c.sendMsg(c.core.UpdateProject(name, portInt, selectedCommands)) } // Handle the project subcommand. @@ -196,19 +194,19 @@ func (c *CLI) handleProject() { return } - if len(os.Args) < 6 { - c.sendMsg(common.NewRegularMsg("Usage: %s project add [command names...]\n", common.ProgramName)) + if len(os.Args) < 5 { + c.sendMsg(common.NewRegularMsg("Usage: %s project add [command names...]\n", common.ProgramName)) return } - port, err := strconv.ParseInt(os.Args[5], 10, 64) + port, err := strconv.ParseInt(os.Args[4], 10, 64) if err != nil { c.ErrorPrint("Port must be an integer") return } - c.addProject(os.Args[3], os.Args[4], port, os.Args[6:]) + c.addProject(os.Args[3], port, os.Args[5:]) case "remove", "rm": if len(os.Args) == 3 { c.removeProjectInteractive() @@ -227,19 +225,19 @@ func (c *CLI) handleProject() { return } - if len(os.Args) < 6 { - c.sendMsg(common.NewRegularMsg("Usage: %s project edit [command names...]\n", common.ProgramName)) + if len(os.Args) < 5 { + c.sendMsg(common.NewRegularMsg("Usage: %s project edit [command names...]\n", common.ProgramName)) return } - port, err := strconv.ParseInt(os.Args[5], 10, 64) + port, err := strconv.ParseInt(os.Args[4], 10, 64) if err != nil { c.ErrorPrint("Port must be an integer") return } - c.editProject(os.Args[3], os.Args[4], port, os.Args[6:]) + c.editProject(os.Args[3], port, os.Args[5:]) case "rename", "mv": if len(os.Args) < 5 { c.sendMsg(common.NewRegularMsg("Usage: %s project rename|mv \n", common.ProgramName)) diff --git a/common/constants.go b/common/constants.go index 4f6b3d0..90a5a79 100644 --- a/common/constants.go +++ b/common/constants.go @@ -6,5 +6,7 @@ import ( const ProgramName = "spinup" +const TLD = "test" + //go:embed .version var Version string diff --git a/common/helpers.go b/common/helpers.go index 77c2be7..44a4805 100644 --- a/common/helpers.go +++ b/common/helpers.go @@ -11,3 +11,7 @@ func IsWindows() bool { func IsMacOS() bool { return runtime.GOOS == "darwin" } + +func GetDomain(projectName string) string { + return projectName + "." + TLD +} diff --git a/config/config.go b/config/config.go index d049409..db79959 100644 --- a/config/config.go +++ b/config/config.go @@ -14,8 +14,6 @@ import ( type Config struct { configDir string nginxConfigDir string - hostsFile string - hostsBackupDir string testing bool } @@ -46,8 +44,6 @@ func New() (*Config, error) { return &Config{ configDir: configDir, nginxConfigDir: nginxConfigDir, - hostsFile: hostsFile, - hostsBackupDir: hostsBackupDir, testing: false, }, nil } @@ -58,8 +54,6 @@ func NewTesting(testingConfigDir string) *Config { return &Config{ configDir: testingConfigDir, nginxConfigDir: path.Join(testingConfigDir, "/nginx/conf.d"), - hostsFile: path.Join(testingConfigDir, "hosts"), - hostsBackupDir: path.Join(testingConfigDir, "hosts_bak"), testing: true, } } @@ -88,16 +82,6 @@ func (c *Config) GetNginxConfigDir() string { return c.nginxConfigDir } -// Returns the path to the hosts file. -func (c *Config) GetHostsFile() string { - return c.hostsFile -} - -// Returns the path to the hosts backup directory. -func (c *Config) GetHostsBackupDir() string { - return c.hostsBackupDir -} - // Returns whether the application is in testing mode. func (c *Config) IsTesting() bool { return c.testing diff --git a/config/config_linux_test.go b/config/config_linux_test.go index 2223d49..3968b9f 100644 --- a/config/config_linux_test.go +++ b/config/config_linux_test.go @@ -31,15 +31,3 @@ func TestWithSudo(t *testing.T) { t.Errorf("Expected sudo, got %s", cmd.Args[0]) } } - -func TestInitHostsBackupError(t *testing.T) { - c := TestingConfig("init_hosts_backup_error") - - os.Mkdir(c.GetHostsBackupDir(), 0444) - - err := c.InitHosts() - - if err == nil { - t.Errorf("Expected error, got nil") - } -} diff --git a/config/config_test.go b/config/config_test.go index d3a191d..9a1de42 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -71,14 +71,6 @@ func TestGetters(t *testing.T) { t.Error("Expected nginx config dir, got empty string") } - if c.GetHostsFile() == "" { - t.Error("Expected hosts file, got empty string") - } - - if c.GetHostsBackupDir() == "" { - t.Error("Expected hosts backup dir, got empty string") - } - if !c.IsTesting() { t.Error("Expected testing to be false, got true") } diff --git a/config/hosts.go b/config/hosts.go deleted file mode 100644 index 270cc77..0000000 --- a/config/hosts.go +++ /dev/null @@ -1,215 +0,0 @@ -package config - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/iskandervdh/spinup/common" -) - -// Markers that indicate the beginning and end of the custom hosts section. -var HostsBeginMarker = fmt.Sprintf("### BEGIN_%s_HOSTS", strings.ToUpper(common.ProgramName)) -var HostsEndMarker = fmt.Sprintf("\n### END_%s_HOSTS", strings.ToUpper(common.ProgramName)) - -// Backup the hosts file with a timestamp so in case of an error the original file can be restored. -func (c *Config) backupHosts() error { - // Create a directory to store the backups if it doesn't exist - err := c.createHostsBackupDir() - - if err != nil { - return err - } - - // Backup the hosts file with a timestamp - fileName := filepath.Join(c.hostsBackupDir, fmt.Sprintf("hosts_%s.bak", time.Now().Format("2006-01-02_15-04-05"))) - err = c.copyFile(c.hostsFile, fileName) - - if err != nil { - return err - } - - return nil -} - -// Get the content of the hosts file and the begin and end of the custom hosts section. -func (c *Config) getHostsContent() (string, int, int, error) { - hosts, err := os.ReadFile(c.hostsFile) - - if err != nil { - return "", 0, 0, err - } - - hostsContent := string(hosts) - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - hostsContent = strings.ReplaceAll(hostsContent, "\r\n", "\n") - } - - beginIndex := strings.Index(hostsContent, HostsBeginMarker) - endIndex := strings.Index(hostsContent, HostsEndMarker) - - if beginIndex == -1 || endIndex == -1 || beginIndex >= endIndex { - return "", beginIndex, endIndex, fmt.Errorf("%s hosts section not found", common.ProgramName) - } - - return hostsContent, beginIndex, endIndex, nil -} - -// Add a domain to the custom hosts section. -func (c *Config) AddDomain(domain string) error { - if domain == "" { - return fmt.Errorf("domain is empty") - } - - hostsContent, beginIndex, endIndex, err := c.getHostsContent() - - if err != nil { - return err - } - - customHosts := hostsContent[beginIndex+len(HostsBeginMarker) : endIndex] - - // Check if host already exists - if strings.Contains(customHosts, fmt.Sprintf("%s127.0.0.1\t%s", "\n", domain)) { - return fmt.Errorf("domain %s already exists", domain) - } - - // Add domain to hosts file - customHosts += fmt.Sprintf("%s127.0.0.1\t%s", "\n", domain) - - // Save hosts file - newHostsContent := hostsContent[:beginIndex+len(HostsBeginMarker)] + customHosts + hostsContent[endIndex:] - - err = c.backupHosts() - - if err != nil { - return err - } - - return c.writeToFile(c.hostsFile, newHostsContent) -} - -// Remove a domain from the custom hosts section. -func (c *Config) RemoveDomain(domain string) error { - if domain == "" { - return fmt.Errorf("domain is empty") - } - - hostsContent, beginIndex, endIndex, err := c.getHostsContent() - - if err != nil { - return err - } - - customHosts := hostsContent[beginIndex+len(HostsBeginMarker) : endIndex] - - // Remove domain to hosts file - customHosts = strings.Replace( - customHosts, - fmt.Sprintf("%s127.0.0.1\t%s", "\n", domain), - "", - -1, - ) - - // Save hosts file - newHostsContent := hostsContent[:beginIndex+len(HostsBeginMarker)] + customHosts + hostsContent[endIndex:] - - c.backupHosts() - - return c.writeToFile(c.hostsFile, newHostsContent) -} - -// Update a domain in the custom hosts section. -func (c *Config) UpdateHost(oldDomain string, newDomain string) error { - if oldDomain == "" { - return fmt.Errorf("old domain is empty") - } - - if newDomain == "" { - return fmt.Errorf("new domain is empty") - } - - hostsContent, beginIndex, endIndex, err := c.getHostsContent() - - if err != nil { - return err - } - - customHosts := hostsContent[beginIndex+len(HostsBeginMarker) : endIndex] - - // Update domain in hosts file - customHosts = strings.Replace( - customHosts, - fmt.Sprintf("%s127.0.0.1\t%s", "\n", oldDomain), - fmt.Sprintf("%s127.0.0.1\t%s", "\n", newDomain), - -1, - ) - - // Save hosts file - newHostsContent := hostsContent[:beginIndex+len(HostsBeginMarker)] + customHosts + hostsContent[endIndex:] - - c.backupHosts() - - return c.writeToFile(c.hostsFile, newHostsContent) -} - -// Initialize the custom hosts section in the hosts file -// so domains can be added without overwriting the other contents of the file. -func (c *Config) InitHosts() error { - // Check if hosts file exists - fileInfo, err := os.Stat(c.hostsFile) - - // Create hosts file if it doesn't exist - if err != nil { - if os.IsNotExist(err) { - _, err := os.Create(c.hostsFile) - - if err != nil { - return err - } - - // Check if hosts file was created - fileInfo, err = os.Stat(c.hostsFile) - - if err != nil { - return err - } - } else { - return err - } - } - - if fileInfo.IsDir() { - return fmt.Errorf("hosts file is a directory") - } - - hostsContent, beginIndex, endIndex, _ := c.getHostsContent() - - if beginIndex != -1 && endIndex != -1 { - fmt.Printf("Hosts file already initialized%sSkipping initialization...%s", "\n", "\n") - return nil - } - - hostsContent = strings.TrimSpace(hostsContent) - hostsContent += "\n\n" - hostsContent += HostsBeginMarker - hostsContent += HostsEndMarker - - err = c.backupHosts() - - if err != nil { - return err - } - - err = c.writeToFile(c.hostsFile, hostsContent) - - if err != nil { - return err - } - - return nil -} diff --git a/config/hosts_test.go b/config/hosts_test.go deleted file mode 100644 index d68823b..0000000 --- a/config/hosts_test.go +++ /dev/null @@ -1,279 +0,0 @@ -package config - -import ( - "io" - "os" - "strings" - "testing" - - "github.com/iskandervdh/spinup/common" -) - -func TestInitHosts(t *testing.T) { - c := TestingConfig("init_hosts") - - err := c.InitHosts() - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } -} - -func TestInitHostsFileError(t *testing.T) { - c := TestingConfig("init_hosts_file_error") - - testingConfigDir := TestingConfigDir("init_hosts_file_error") - err := os.RemoveAll(testingConfigDir) - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - err = c.InitHosts() - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestInitHostsAlreadyInitialized(t *testing.T) { - c := TestingConfig("init_hosts_already_initialized") - - r, w, err := os.Pipe() - - stdout := os.Stdout - stderr := os.Stderr - - os.Stdout = w - os.Stderr = w - - defer func() { - os.Stdout = stdout - os.Stderr = stderr - }() - - if err != nil { - t.Fatal(err) - } - - err = c.InitHosts() - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - err = c.InitHosts() - - if err != nil { - t.Error("Expected error, got nil") - } - - err = c.InitHosts() - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - w.Close() - out, err := io.ReadAll(r) - - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(string(out), "already initialized") { - t.Errorf("Expected warning message, got none %s", string(out)) - } -} - -func TestInitHostsPermissionError(t *testing.T) { - c := TestingConfig("init_hosts_permission_error") - - os.Create(c.GetHostsFile()) - os.Chmod(c.GetHostsFile(), 0444) - - err := c.InitHosts() - - if err == nil { - t.Errorf("Expected error, got nil") - } -} - -func TestInitHostsStatError(t *testing.T) { - c := TestingConfig("init_hosts_stat_error") - - err := os.MkdirAll(c.GetHostsFile(), 0755) - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - err = c.InitHosts() - - if err == nil { - t.Errorf("Expected error, got nil") - } -} - -func TestBackupHosts(t *testing.T) { - c := TestingConfig("backup_hosts") - c.InitHosts() - - err := c.backupHosts() - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } -} - -func TestBackupHostsDirError(t *testing.T) { - c := TestingConfig("backup_hosts_dir_error") - os.Chmod(c.GetConfigDir(), 0444) - - err := c.backupHosts() - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestBackupHostsFileError(t *testing.T) { - c := TestingConfig("backup_hosts_file_error") - - err := c.backupHosts() - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestGetHostsContentFileError(t *testing.T) { - c := TestingConfig("get_hosts_content_file_error") - - content, beginIndex, endIndex, err := c.getHostsContent() - - if content != "" { - t.Errorf("Expected empty content, got %s", content) - } - - if beginIndex != 0 { - t.Errorf("Expected beginIndex to be 0, got %d", beginIndex) - } - - if endIndex != 0 { - t.Errorf("Expected endIndex to be 0, got %d", endIndex) - } - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestAddDomain(t *testing.T) { - c := TestingConfig("add_domain") - c.InitHosts() - - err := c.AddDomain("test.local") - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - hostsContent, _, _, _ := c.getHostsContent() - - expected := "\n\n" + HostsBeginMarker + "\n127.0.0.1\ttest.local" + HostsEndMarker - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Errorf("Expected %s, got %s", expected, hostsContent) - } -} - -func TestAddDomainDuplicate(t *testing.T) { - c := TestingConfig("add_host_duplicate") - c.InitHosts() - - err := c.AddDomain("test.local") - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - err = c.AddDomain("test.local") - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestAddDomainFileError(t *testing.T) { - c := TestingConfig("add_host_file_error") - - err := c.AddDomain("test.local") - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestAddDomainPermissionError(t *testing.T) { - c := TestingConfig("add_host_permission_error") - c.InitHosts() - - os.Chmod(c.GetHostsFile(), 0444) - - err := c.AddDomain("test.local") - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestAddDomainEmpty(t *testing.T) { - c := TestingConfig("add_host_empty") - c.InitHosts() - - err := c.AddDomain("") - - if err == nil { - t.Error("Expected error, got nil") - } -} - -func TestRemoveDomain(t *testing.T) { - c := TestingConfig("remove_host") - c.InitHosts() - c.AddDomain("test.local") - - err := c.RemoveDomain("test.local") - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - hostsContent, _, _, _ := c.getHostsContent() - - expected := "\n\n" + HostsBeginMarker + HostsEndMarker - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Errorf("Expected %s, got %s", expected, hostsContent) - } -} - -func TestRemoveDomainEmpty(t *testing.T) { - c := TestingConfig("remove_host_empty") - c.InitHosts() - - err := c.RemoveDomain("") - - if err == nil { - t.Error("Expected error, got nil") - } -} diff --git a/config/nginx.go b/config/nginx.go index 879886f..75a6f9e 100644 --- a/config/nginx.go +++ b/config/nginx.go @@ -19,12 +19,12 @@ func (c *Config) restartNginx() error { return exec.Command("sudo", "systemctl", "restart", "nginx").Run() } -// Add a new Nginx configuration file with the given name, domain and port. -func (c *Config) AddNginxConfig(name string, domain string, port int64) error { +// Add a new Nginx configuration file with the given name and port. +func (c *Config) AddNginxConfig(name string, port int64) error { config := fmt.Sprintf(`server { listen 80; - server_name %s; + server_name %s.test; location / { proxy_pass http://127.0.0.1:%d/; @@ -34,7 +34,7 @@ func (c *Config) AddNginxConfig(name string, domain string, port int64) error { proxy_set_header X-Forwarded-Proto $scheme; } } -`, domain, port) +`, name, port) nginxConfigFilePath := fmt.Sprintf("%s/%s.conf", c.nginxConfigDir, name) @@ -79,15 +79,15 @@ func (c *Config) RemoveNginxConfig(name string) error { return nil } -// Update a Nginx configuration file with the given name, domain and port. -func (c *Config) UpdateNginxConfig(name string, domain string, port int64) error { +// Update a Nginx configuration file with the given name and port. +func (c *Config) UpdateNginxConfig(name string, port int64) error { err := c.RemoveNginxConfig(name) if err != nil { return err } - return c.AddNginxConfig(name, domain, port) + return c.AddNginxConfig(name, port) } // Rename a Nginx configuration file with the given old and new name. diff --git a/config/nginx_linux_test.go b/config/nginx_linux_test.go index d53c1ab..b668c20 100644 --- a/config/nginx_linux_test.go +++ b/config/nginx_linux_test.go @@ -16,7 +16,7 @@ func TestAddNginxConfigPermissionError(t *testing.T) { os.Chmod(c.nginxConfigDir, 0444) - err = c.AddNginxConfig("test", "test.local", 8080) + err = c.AddNginxConfig("test", 8080) if err == nil { t.Errorf("Expected error, got nil") diff --git a/config/nginx_test.go b/config/nginx_test.go index 7940118..2ebf9b0 100644 --- a/config/nginx_test.go +++ b/config/nginx_test.go @@ -23,7 +23,7 @@ func TestAddNginxConfig(t *testing.T) { t.Errorf("Expected no error, got %s", err) } - err = c.AddNginxConfig("test", "test.local", 8080) + err = c.AddNginxConfig("test", 8080) if err != nil { t.Errorf("Expected no error, got %s", err) @@ -39,13 +39,13 @@ func TestAddNginxConfigExists(t *testing.T) { t.Errorf("Expected no error, got %s", err) } - err = c.AddNginxConfig("test", "test.local", 8080) + err = c.AddNginxConfig("test", 8080) if err != nil { t.Errorf("Expected no error, got %s", err) } - err = c.AddNginxConfig("test", "test.local", 8080) + err = c.AddNginxConfig("test", 8080) if err == nil { t.Errorf("Expected error, got nil") @@ -61,7 +61,7 @@ func TestRemoveNginxConfig(t *testing.T) { t.Errorf("Expected no error, got %s", err) } - err = c.AddNginxConfig("test", "test.local", 8080) + err = c.AddNginxConfig("test", 8080) if err != nil { t.Errorf("Expected no error, got %s", err) diff --git a/config/unix.go b/config/unix.go index 09284d5..b0a23f8 100644 --- a/config/unix.go +++ b/config/unix.go @@ -5,16 +5,6 @@ package config import "strings" var nginxConfigDir = "/etc/nginx/conf.d" -var hostsFile = "/etc/hosts" -var hostsBackupDir = "/etc/hosts_bak" - -func (c *Config) createHostsBackupDir() error { - return c.withSudo("mkdir", "-p", c.hostsBackupDir).Run() -} - -func (c *Config) copyFile(from string, to string) error { - return c.withSudo("cp", from, to).Run() -} func (c *Config) writeToFile(filePath string, content string) error { saveNewHosts := c.withSudo("tee", filePath) diff --git a/config/windows.go b/config/windows.go index 5cd8e0b..3dd8c61 100644 --- a/config/windows.go +++ b/config/windows.go @@ -3,65 +3,10 @@ package config import ( - "io" "os" - "path/filepath" ) -func getHostsFilePath() string { - home, err := os.UserHomeDir() - - if err != nil { - return "" - } - - return filepath.Join(home, "Documents", "hosts") -} - -func getHostsBackupDir() string { - home, err := os.UserHomeDir() - - if err != nil { - return "" - } - - return filepath.Join(home, "Documents", "hosts_bak") -} - var nginxConfigDir = "C:\\nginx\\conf\\conf.d" -var hostsFile = getHostsFilePath() -var hostsBackupDir = getHostsBackupDir() - -func (c *Config) createHostsBackupDir() error { - return os.MkdirAll(c.hostsBackupDir, os.ModeDir) -} - -func (c *Config) copyFile(from string, to string) error { - r, err := os.Open(from) - - if err != nil { - return err - } - - defer r.Close() - - w, err := os.Create(to) - - if err != nil { - return err - } - - defer func() { - // Only return the error of closing the file if there was no error before - if c := w.Close(); err == nil { - err = c - } - }() - - _, err = io.Copy(w, r) - - return err -} func (c *Config) writeToFile(filePath string, contents string) error { f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) diff --git a/core/core_test.go b/core/core_test.go index e1c84f3..915da0f 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -3,7 +3,6 @@ package core import ( "os" "path" - "strings" "testing" "github.com/iskandervdh/spinup/common" @@ -75,57 +74,3 @@ func TestInit(t *testing.T) { t.Error("Expected ConfigDir to exist, got", err) } } - -func TestHostsConfigInit(t *testing.T) { - c := TestingCore("hosts_config_init") - - hostsFilePath := c.GetConfig().GetHostsFile() - - if _, err := os.Stat(hostsFilePath); os.IsNotExist(err) { - t.Error("Expected hosts file to exist, got", err) - return - } - - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Expected to open hosts file, got", err) - return - } - - defer hostsFile.Close() - - // Expect hosts file to contain start and end comments - expected := "\n\n" + config.HostsBeginMarker + config.HostsEndMarker - - buf := make([]byte, len(expected)) - - _, err = hostsFile.Read(buf) - - if err != nil { - t.Error("Expected to read hosts file, got", err) - return - } - - hostsContent := string(buf) - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Error("Expected hosts file to contain", expected, "got", hostsContent) - return - } -} - -func TestHostsBackupDirInit(t *testing.T) { - c := TestingCore("hosts_backup_dir_init") - - hostsBackupDir := c.GetConfig().GetHostsBackupDir() - - if _, err := os.Stat(hostsBackupDir); os.IsNotExist(err) { - t.Error("Expected hosts backup dir to exist, got", err) - return - } -} diff --git a/core/domain_alias.go b/core/domain_alias.go index b56dca9..b897892 100644 --- a/core/domain_alias.go +++ b/core/domain_alias.go @@ -24,13 +24,13 @@ func (c *Core) AddDomainAlias(projectName string, domainAlias string) common.Msg } // Check if the domain alias is already defined as the domain of the project - if project.Domain == domainAlias { + if common.GetDomain(project.Name) == domainAlias { return common.NewErrMsg("Domain alias '%s' is already the domain of project '%s'", domainAlias, projectName) } for projectName, project := range c.projects { // Check if the domain alias is the domain of another project - if project.Domain == domainAlias { + if common.GetDomain(project.Name) == domainAlias { return common.NewErrMsg("Domain alias '%s' is already the domain of project '%s'", domainAlias, projectName) } @@ -48,15 +48,6 @@ func (c *Core) AddDomainAlias(projectName string, domainAlias string) common.Msg return common.NewErrMsg(fmt.Sprintln("Error trying to add domain alias to nginx config file", err)) } - err = c.config.AddDomain(domainAlias) - - if err != nil { - // Remove the domain alias from the nginx config file if adding domain alias to hosts file fails - c.config.NginxRemoveDomainAlias(projectName, domainAlias) - - return common.NewErrMsg(fmt.Sprintln("Error trying to add domain to hosts file", err)) - } - err = c.dbQueries.CreateDomainAlias(c.dbContext, sqlc.CreateDomainAliasParams{ Value: domainAlias, ProjectID: project.ID, @@ -87,15 +78,6 @@ func (c *Core) RemoveDomainAlias(projectName string, domainAlias string) common. return common.NewErrMsg(fmt.Sprintln("Error trying to remove domain alias from nginx config file", err)) } - err = c.config.RemoveDomain(domainAlias) - - if err != nil { - // Readd the domain alias from the nginx config file if removing the domain alias from hosts file fails - c.config.NginxRemoveDomainAlias(projectName, domainAlias) - - return common.NewErrMsg(fmt.Sprintln("Error trying to add domain to hosts file", err)) - } - for i, alias := range project.DomainAliases { if alias.Value == domainAlias { // Remove the given domain alias from the domain aliases array of the project diff --git a/core/domain_alias_test.go b/core/domain_alias_test.go index 3c297c1..609f253 100644 --- a/core/domain_alias_test.go +++ b/core/domain_alias_test.go @@ -1,12 +1,9 @@ package core import ( - "os" - "strings" "testing" "github.com/iskandervdh/spinup/common" - "github.com/iskandervdh/spinup/config" ) func TestAddDomainAlias(t *testing.T) { @@ -16,12 +13,12 @@ func TestAddDomainAlias(t *testing.T) { c.FetchCommands() c.FetchProjects() - c.AddProject("test", "test.local", 1234, []string{}) + c.AddProject("test", 1234, []string{}) // "Refetch" the projects from the config file c.FetchProjects() - c.AddDomainAlias("test", "test.test") + c.AddDomainAlias("test", "test.test.test") // "Refetch" the projects from the config file c.FetchProjects() @@ -43,47 +40,8 @@ func TestAddDomainAlias(t *testing.T) { return } - if project.DomainAliases[0].Value != "test.test" { - t.Error("Expected domain alias to be 'test.test', got", project.DomainAliases[0]) - } - - // Check if the domain alias is added to the hosts file - hostsFilePath := c.GetConfig().GetHostsFile() - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Error to open hosts file, got:", err) - return - } - - defer hostsFile.Close() - - fileInfo, err := os.Stat(hostsFilePath) - - if err != nil { - t.Error("Expected to stat hosts file, got", err) - return - } - - buf := make([]byte, fileInfo.Size()) - - _, err = hostsFile.Read(buf) - - if err != nil { - t.Error("Expected to read hosts file, got", err) - return - } - - hostsContent := string(buf) - expected := "\n\n" + config.HostsBeginMarker + "\n127.0.0.1\ttest.local\n127.0.0.1\ttest.test" + config.HostsEndMarker - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Error("Expected hosts file to contain", expected, "got", hostsContent) - return + if project.DomainAliases[0].Value != "test.test.test" { + t.Error("Expected domain alias to be 'test.test.test', got", project.DomainAliases[0]) } } @@ -94,7 +52,7 @@ func TestRemoveDomainAlias(t *testing.T) { c.FetchCommands() c.FetchProjects() - c.AddProject("test", "test.local", 1234, []string{}) + c.AddProject("test", 1234, []string{}) err := c.FetchProjects() @@ -103,14 +61,14 @@ func TestRemoveDomainAlias(t *testing.T) { return } - msg := c.AddDomainAlias("test", "test.test") + msg := c.AddDomainAlias("test", "test.test.test") if _, ok := msg.(*common.ErrMsg); ok { t.Error("Expected to add domain alias, got: ", msg.GetText()) return } - msg = c.AddDomainAlias("test", "test.tst") + msg = c.AddDomainAlias("test", "tst.test.test") if _, ok := msg.(*common.ErrMsg); ok { t.Error("Expected to add domain alias, got: ", msg.GetText()) @@ -120,7 +78,7 @@ func TestRemoveDomainAlias(t *testing.T) { // "Refetch" the projects from the config file c.FetchProjects() - c.RemoveDomainAlias("test", "test.test") + c.RemoveDomainAlias("test", "test.test.test") // "Refetch" the projects from the config file c.FetchProjects() @@ -137,50 +95,7 @@ func TestRemoveDomainAlias(t *testing.T) { return } - if project.DomainAliases[0].Value != "test.tst" { - t.Error("Expected domain alias to be 'test.tst', got", project.DomainAliases[0]) - } - - // Check if the domain alias is removed from the hosts file - hostsFilePath := c.GetConfig().GetHostsFile() - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Error to open hosts file, got:", err) - return - } - - defer hostsFile.Close() - - fileInfo, err := os.Stat(hostsFilePath) - - if err != nil { - t.Error("Expected to stat hosts file, got", err) - return - } - - buf := make([]byte, fileInfo.Size()) - - _, err = hostsFile.Read(buf) - - if err != nil { - t.Error("Expected to read hosts file, got", err) - return - } - - hostsContent := string(buf) - expected := "\n\n" + - config.HostsBeginMarker + - "\n127.0.0.1\ttest.local" + - "\n127.0.0.1\ttest.tst" + - config.HostsEndMarker - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Error("Expected hosts file to contain", expected, "got", hostsContent) - return + if project.DomainAliases[0].Value != "tst.test.test" { + t.Error("Expected domain alias to be 'tst.test.test', got", project.DomainAliases[0]) } } diff --git a/core/init.go b/core/init.go index 80be04f..903f5e9 100644 --- a/core/init.go +++ b/core/init.go @@ -38,11 +38,6 @@ func (c *Core) Init() common.Msg { } c.RequireSudo() - err = c.config.InitHosts() - - if err != nil { - return common.NewErrMsg("Error initializing hosts file: %v", err) - } err = c.config.InitNginx() diff --git a/core/project.go b/core/project.go index 38e9d89..333f47b 100644 --- a/core/project.go +++ b/core/project.go @@ -103,8 +103,8 @@ func (c *Core) ProjectExists(name string) (bool, Project) { return true, c.projects[index] } -// Add a project with the given name, domain, port and command names. -func (c *Core) AddProject(name string, domain string, port int64, commandNames []string) common.Msg { +// Add a project with the given name, port and command names. +func (c *Core) AddProject(name string, port int64, commandNames []string) common.Msg { c.RequireSudo() // Check if commands exist @@ -120,41 +120,26 @@ func (c *Core) AddProject(name string, domain string, port int64, commandNames [ commandIDs = append(commandIDs, command.ID) } - // Check if project already exists or if domain or port is already in use + // Check if project already exists or the port is already in use for _, project := range c.projects { if project.Name == name { return common.NewErrMsg("Project '" + name + "' already exists") } - if project.Domain == domain { - return common.NewErrMsg("Project with domain '" + domain + "' already exists: " + project.Name) - - } - if project.Port == port { return common.NewErrMsg("Project with port " + strconv.FormatInt(port, 10) + " already exists: " + project.Name) } } - err := c.config.AddNginxConfig(name, domain, port) + err := c.config.AddNginxConfig(name, port) if err != nil { return common.NewErrMsg(fmt.Sprintln("Error trying to create nginx config file", err)) } - err = c.config.AddDomain(domain) - - if err != nil { - // Remove nginx config file if adding domain to hosts file fails - c.config.RemoveNginxConfig(name) - - return common.NewErrMsg(fmt.Sprintln("Error trying to add domain to hosts file", err)) - } - project, err := c.dbQueries.CreateProject(c.dbContext, sqlc.CreateProjectParams{ - Name: name, - Domain: domain, - Port: int64(port), + Name: name, + Port: int64(port), }) if err != nil { @@ -179,7 +164,7 @@ func (c *Core) AddProject(name string, domain string, port int64, commandNames [ func (c *Core) RemoveProject(name string) common.Msg { c.RequireSudo() - exists, project := c.ProjectExists(name) + exists, _ := c.ProjectExists(name) if !exists { return common.NewErrMsg("Project '" + name + "' does not exist, nothing to remove") @@ -191,22 +176,13 @@ func (c *Core) RemoveProject(name string) common.Msg { return common.NewErrMsg("Could not remove nginx config file: " + err.Error()) } - err = c.config.RemoveDomain(project.Domain) - - if err != nil { - // Remove nginx config file if adding domain to hosts file fails - c.config.RemoveNginxConfig(name) - - return common.NewErrMsg("Error trying to remove domain from hosts file: " + err.Error()) - } - c.dbQueries.DeleteProject(c.dbContext, name) return common.NewSuccessMsg(fmt.Sprintf("Removed project '%s'", name)) } -// Update the project with the given name to the given domain, port and command names. -func (c *Core) UpdateProject(name string, domain string, port int64, commandNames []string) common.Msg { +// Update the project with the given name to the given port and command names. +func (c *Core) UpdateProject(name string, port int64, commandNames []string) common.Msg { c.RequireSudo() exists, project := c.ProjectExists(name) @@ -224,47 +200,33 @@ func (c *Core) UpdateProject(name string, domain string, port int64, commandName } } - // Check if domain or port is already in use + // Check if port is already in use by another project for _, project := range c.projects { if project.Name == name { continue } - if project.Domain == domain { - return common.NewErrMsg("Project with domain '%s' already exists: %s", domain, project.Name) - } - if project.Port == port { return common.NewErrMsg("Project with port %d already exists: %s", port, project.Name) } } - err := c.config.UpdateNginxConfig(name, domain, port) + err := c.config.UpdateNginxConfig(name, port) if err != nil { return common.NewErrMsg("Error trying to update nginx config file: %s", err) } - err = c.config.UpdateHost(project.Domain, domain) - - if err != nil { - // Undo editing of nginx config file if adding new domain to hosts file fails - c.config.UpdateNginxConfig(name, project.Domain, project.Port) - - return common.NewErrMsg("Error trying to update domain in hosts file: %s", err) - } - c.dbQueries.UpdateProject(c.dbContext, sqlc.UpdateProjectParams{ - Name: name, - Domain: domain, - Port: port, + Name: name, + Port: port, Dir: sql.NullString{ String: project.Dir.String, Valid: project.Dir.Valid, }, }) - return common.NewSuccessMsg("Updated project '%s' with domain '%s', port %d and commands %s", name, domain, port, commandNames) + return common.NewSuccessMsg("Updated project '%s' with domain '%s', port %d and commands %s", name, port, commandNames) } // Rename the project with the given old name to the given new name. diff --git a/core/project_test.go b/core/project_test.go index 960058a..f91428e 100644 --- a/core/project_test.go +++ b/core/project_test.go @@ -3,11 +3,7 @@ package core import ( "fmt" "os" - "strings" "testing" - - "github.com/iskandervdh/spinup/common" - "github.com/iskandervdh/spinup/config" ) func TestGetProjectNamesEmpty(t *testing.T) { @@ -28,7 +24,7 @@ func TestAddProject(t *testing.T) { c.FetchCommands() c.FetchProjects() - c.AddProject("test", "test.local", 1234, []string{}) + c.AddProject("test", 1234, []string{}) // "Refetch" the projects from the config file c.FetchProjects() @@ -51,46 +47,6 @@ func TestAddProject(t *testing.T) { t.Error("Expected nginx config file to exist, got", err) return } - - // Check if hosts file was updated - hostsFilePath := c.GetConfig().GetHostsFile() - - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Expected to open hosts file, got", err) - return - } - - defer hostsFile.Close() - - fileInfo, err := os.Stat(hostsFilePath) - - if err != nil { - t.Error("Expected to stat hosts file, got", err) - return - } - - buf := make([]byte, fileInfo.Size()) - - _, err = hostsFile.Read(buf) - - if err != nil { - t.Error("Expected to read hosts file, got", err) - return - } - - hostsContent := string(buf) - expected := "\n\n" + config.HostsBeginMarker + "\n127.0.0.1\ttest.local" + config.HostsEndMarker - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Error("Expected hosts file to contain", expected, "got", hostsContent) - return - } } func TestAddProjectWithCommands(t *testing.T) { @@ -103,7 +59,7 @@ func TestAddProjectWithCommands(t *testing.T) { c.AddCommand("ls", "ls") c.AddCommand("pwd", "pwd") - c.AddProject("test", "test.local", 1234, []string{"ls", "pwd"}) + c.AddProject("test", 1234, []string{"ls", "pwd"}) // "Refetch" the projects from the config file c.FetchProjects() @@ -145,7 +101,7 @@ func TestRemoveCommandFromProject(t *testing.T) { c.AddCommand("ls", "ls") c.AddCommand("pwd", "pwd") - c.AddProject("test", "test.local", 1234, []string{"ls", "pwd"}) + c.AddProject("test", 1234, []string{"ls", "pwd"}) // "Refetch" the projects from the config file c.FetchProjects() @@ -174,7 +130,7 @@ func TestRemoveProject(t *testing.T) { c.FetchCommands() c.FetchProjects() - c.AddProject("test", "test.local", 1234, []string{}) + c.AddProject("test", 1234, []string{}) // "Refetch" the projects from the config file c.FetchProjects() @@ -198,17 +154,6 @@ func TestRemoveProject(t *testing.T) { t.Error("Expected nginx config file to not exist, got", err) return } - - // Check if hosts file was updated - hostsFilePath := c.GetConfig().GetHostsFile() - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Expected to open hosts file, got", err) - return - } - - defer hostsFile.Close() } func TestEditProject(t *testing.T) { @@ -218,12 +163,12 @@ func TestEditProject(t *testing.T) { c.FetchCommands() c.FetchProjects() - c.AddProject("test", "test.local", 1234, []string{}) + c.AddProject("test", 1234, []string{}) // "Refetch" the projects from the config file c.FetchProjects() - c.UpdateProject("test", "example.local", 1235, []string{}) + c.UpdateProject("test", 1235, []string{}) // "Refetch" the projects from the config file c.FetchProjects() @@ -242,46 +187,6 @@ func TestEditProject(t *testing.T) { t.Error("Expected nginx config file to exist, got", err) return } - - // Check if hosts file was updated - hostsFilePath := c.GetConfig().GetHostsFile() - - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Expected to open hosts file, got", err) - return - } - - defer hostsFile.Close() - - fileInfo, err := os.Stat(hostsFilePath) - - if err != nil { - t.Error("Expected to stat hosts file, got", err) - return - } - - buf := make([]byte, fileInfo.Size()) - - _, err = hostsFile.Read(buf) - - if err != nil { - t.Error("Expected to read hosts file, got", err) - return - } - - hostsContent := string(buf) - expected := "\n\n" + config.HostsBeginMarker + "\n127.0.0.1\texample.local" + config.HostsEndMarker - - if common.IsWindows() { - hostsContent = strings.ReplaceAll(hostsContent, "\u0000", "") - } - - if hostsContent != expected { - t.Error("Expected hosts file to contain", expected, "got", hostsContent) - return - } } func TestRenameProject(t *testing.T) { @@ -291,7 +196,7 @@ func TestRenameProject(t *testing.T) { c.FetchCommands() c.FetchProjects() - c.AddProject("test", "test.local", 1234, []string{}) + c.AddProject("test", 1234, []string{}) // "Refetch" the projects from the config file c.FetchProjects() @@ -322,40 +227,4 @@ func TestRenameProject(t *testing.T) { t.Error("Expected nginx config file to exist, got", err) return } - - // Check if hosts file was updated - hostsFilePath := c.GetConfig().GetHostsFile() - - hostsFile, err := os.Open(hostsFilePath) - - if err != nil { - t.Error("Expected to open hosts file, got", err) - return - } - - defer hostsFile.Close() - - fileInfo, err := os.Stat(hostsFilePath) - - if err != nil { - t.Error("Expected to stat hosts file, got", err) - return - } - - buf := make([]byte, fileInfo.Size()) - - _, err = hostsFile.Read(buf) - - if err != nil { - t.Error("Expected to read hosts file, got", err) - return - } - - hostsContent := string(buf) - expected := "\n\n" + config.HostsBeginMarker + "\n127.0.0.1\ttest.local" + config.HostsEndMarker - - if hostsContent != expected { - t.Error("Expected hosts file to contain", expected, "got", hostsContent) - return - } } diff --git a/core/run.go b/core/run.go index 6d19726..960fba0 100644 --- a/core/run.go +++ b/core/run.go @@ -23,7 +23,7 @@ type runningCommand struct { func (c *Core) commandTemplate(command string, project Project) string { // Replace placeholders in command with project values command = strings.ReplaceAll(command, "{{port}}", fmt.Sprintf("%d", project.Port)) - command = strings.ReplaceAll(command, "{{domain}}", project.Domain) + command = strings.ReplaceAll(command, "{{domain}}", common.GetDomain(project.Name)) for _, variable := range project.Variables { command = strings.ReplaceAll(command, fmt.Sprintf("{{%s}}", variable.Name), variable.Value) diff --git a/core/run_test.go b/core/run_test.go index fd6200d..6d79c35 100644 --- a/core/run_test.go +++ b/core/run_test.go @@ -12,7 +12,7 @@ func TestRun(t *testing.T) { c.AddCommand("ls", "ls") - c.AddProject("test", "test.local", 1234, []string{"ls"}) + c.AddProject("test", 1234, []string{"ls"}) // "Refetch" the projects from the config file c.FetchProjects() diff --git a/core/variable_test.go b/core/variable_test.go index d8c6e35..97190aa 100644 --- a/core/variable_test.go +++ b/core/variable_test.go @@ -12,7 +12,7 @@ func TestListVariablesEmpty(t *testing.T) { c := TestingCore(testName) c.FetchProjects() - c.AddProject("test", "test.local", 8000, []string{}) + c.AddProject("test", 8000, []string{}) // Refetch projects c.FetchProjects() @@ -50,7 +50,7 @@ func TestListVariablesEmpty(t *testing.T) { // s := TestingCore(testName) // s.FetchProjects() -// s.AddProject("test", "test.local", 8000, []string{}) +// s.AddProject("test", 8000, []string{}) // s.AddVariable("test", "key", "value") // // Refetch projects @@ -65,7 +65,7 @@ func TestAddVariable(t *testing.T) { s := TestingCore(testName) s.FetchProjects() - s.AddProject("test", "test.local", 8000, []string{}) + s.AddProject("test", 8000, []string{}) // Refetch projects s.FetchProjects() @@ -123,7 +123,7 @@ func TestAddVariableAlreadyExists(t *testing.T) { s := TestingCore(testName) s.FetchProjects() - s.AddProject("test", "test.local", 8000, []string{}) + s.AddProject("test", 8000, []string{}) s.AddVariable("test", "key", "value") // Refetch projects @@ -142,7 +142,7 @@ func TestAddVariableErrorWriting(t *testing.T) { s := TestingCore(testName) s.FetchProjects() - s.AddProject("test", "test.local", 8000, []string{}) + s.AddProject("test", 8000, []string{}) // Refetch projects s.FetchProjects() @@ -163,7 +163,7 @@ func TestRemoveVariable(t *testing.T) { s := TestingCore(testName) s.FetchProjects() - s.AddProject("test", "test.local", 8000, []string{}) + s.AddProject("test", 8000, []string{}) s.AddVariable("test", "key", "value") // Refetch projects diff --git a/database/migrations/001_init.up.sql b/database/migrations/001_init.up.sql index c6404f5..f687fad 100644 --- a/database/migrations/001_init.up.sql +++ b/database/migrations/001_init.up.sql @@ -7,7 +7,6 @@ CREATE TABLE commands ( CREATE TABLE projects ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, - domain TEXT NOT NULL, port INT NOT NULL, dir TEXT ); diff --git a/database/queries/projects.sql b/database/queries/projects.sql index 9d6d1cc..e626e43 100644 --- a/database/queries/projects.sql +++ b/database/queries/projects.sql @@ -1,5 +1,5 @@ -- name: GetProject :one -SELECT id, name, domain, port, dir +SELECT id, name, port, dir FROM projects WHERE name = ? LIMIT 1; @@ -25,9 +25,9 @@ WHERE project_id = ?; -- name: CreateProject :one INSERT INTO projects ( - name, domain, port + name, port ) VALUES ( - ?, ?, ? + ?, ? ) RETURNING *; @@ -57,7 +57,7 @@ WHERE project_id = ? AND command_id = ?; -- name: UpdateProject :exec UPDATE projects -SET domain = ?, port = ?, dir = ? +SET port = ?, dir = ? WHERE name = ?; -- name: RenameProject :exec diff --git a/database/sqlc/models.go b/database/sqlc/models.go index e6ae616..def3560 100644 --- a/database/sqlc/models.go +++ b/database/sqlc/models.go @@ -21,11 +21,10 @@ type DomainAlias struct { } type Project struct { - ID int64 - Name string - Domain string - Port int64 - Dir sql.NullString + ID int64 + Name string + Port int64 + Dir sql.NullString } type ProjectCommand struct { diff --git a/database/sqlc/projects.sql.go b/database/sqlc/projects.sql.go index e50bd2c..81c51df 100644 --- a/database/sqlc/projects.sql.go +++ b/database/sqlc/projects.sql.go @@ -12,26 +12,24 @@ import ( const createProject = `-- name: CreateProject :one INSERT INTO projects ( - name, domain, port + name, port ) VALUES ( - ?, ?, ? + ?, ? ) -RETURNING id, name, domain, port, dir +RETURNING id, name, port, dir ` type CreateProjectParams struct { - Name string - Domain string - Port int64 + Name string + Port int64 } func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) { - row := q.db.QueryRowContext(ctx, createProject, arg.Name, arg.Domain, arg.Port) + row := q.db.QueryRowContext(ctx, createProject, arg.Name, arg.Port) var i Project err := row.Scan( &i.ID, &i.Name, - &i.Domain, &i.Port, &i.Dir, ) @@ -94,7 +92,7 @@ func (q *Queries) DeleteProject(ctx context.Context, name string) error { } const getProject = `-- name: GetProject :one -SELECT id, name, domain, port, dir +SELECT id, name, port, dir FROM projects WHERE name = ? LIMIT 1 ` @@ -105,7 +103,6 @@ func (q *Queries) GetProject(ctx context.Context, name string) (Project, error) err := row.Scan( &i.ID, &i.Name, - &i.Domain, &i.Port, &i.Dir, ) @@ -206,7 +203,7 @@ func (q *Queries) GetProjectVariables(ctx context.Context, projectID int64) ([]V } const getProjects = `-- name: GetProjects :many -SELECT id, name, domain, port, dir +SELECT id, name, port, dir FROM projects ` @@ -222,7 +219,6 @@ func (q *Queries) GetProjects(ctx context.Context) ([]Project, error) { if err := rows.Scan( &i.ID, &i.Name, - &i.Domain, &i.Port, &i.Dir, ); err != nil { @@ -273,23 +269,17 @@ func (q *Queries) SetProjectDir(ctx context.Context, arg SetProjectDirParams) er const updateProject = `-- name: UpdateProject :exec UPDATE projects -SET domain = ?, port = ?, dir = ? +SET port = ?, dir = ? WHERE name = ? ` type UpdateProjectParams struct { - Domain string - Port int64 - Dir sql.NullString - Name string + Port int64 + Dir sql.NullString + Name string } func (q *Queries) UpdateProject(ctx context.Context, arg UpdateProjectParams) error { - _, err := q.db.ExecContext(ctx, updateProject, - arg.Domain, - arg.Port, - arg.Dir, - arg.Name, - ) + _, err := q.db.ExecContext(ctx, updateProject, arg.Port, arg.Dir, arg.Name) return err } diff --git a/frontend/src/routes/project-form.tsx b/frontend/src/routes/project-form.tsx index 6e808b2..878ed9c 100644 --- a/frontend/src/routes/project-form.tsx +++ b/frontend/src/routes/project-form.tsx @@ -20,7 +20,6 @@ export function ProjectFormPage() { const { projects, projectFormSubmit, editingProject } = useProjectsStore(); const [name, setName] = useState(''); - const [domain, setDomain] = useState(''); const [port, setPort] = useState(3000); const [commandNames, setCommandNames] = useState([]); @@ -36,7 +35,7 @@ export function ProjectFormPage() { e.preventDefault(); await toast - .promise(projectFormSubmit(name, domain, port, commandNames), { + .promise(projectFormSubmit(name, port, commandNames), { loading: editingProject ? 'Saving project...' : 'Creating project...', success: editingProject ? Project saved : Project created, error: (err: any) => @@ -58,7 +57,7 @@ export function ProjectFormPage() { navigate({ to: '/projects' }); }); }, - [name, domain, port, commandNames, editingProject, projectFormSubmit] + [name, port, commandNames, editingProject, projectFormSubmit] ); useEffect(() => { @@ -71,12 +70,11 @@ export function ProjectFormPage() { if (project) { setName(editingProject); - setDomain(project.Domain); setPort(project.Port); setCommandNames(project.Commands.map((c) => c.Name)); } } - }, [editingProject, setName, setDomain, setPort, setCommandNames]); + }, [editingProject, setName, setPort, setCommandNames]); return (
@@ -92,20 +90,6 @@ export function ProjectFormPage() { setName(e.target.value)} /> -
- - setDomain(e.target.value)} - /> -
-
) : ( -
{project.Domain}
+
{projectDomain}
)}
Port
@@ -169,8 +171,8 @@ export function ProjectInfo({ project }: { project: core.Project }) { )} -
Variables
-
{variables || '-'}
+ {/*
Variables
+
{variables || '-'}
*/}
Domain aliases
{domainAliases || '-'}
diff --git a/frontend/src/stores/projectsStore.ts b/frontend/src/stores/projectsStore.ts index 12bb9df..97c9078 100644 --- a/frontend/src/stores/projectsStore.ts +++ b/frontend/src/stores/projectsStore.ts @@ -21,7 +21,7 @@ interface ProjectsState { runProject: (projectName: string) => Promise; stopProject: (projectName: string) => Promise; selectProjectDir: (projectName: string, defaultDir: string | undefined) => Promise; - projectFormSubmit: (projectName: string, domain: string, port: number, commandNames: string[]) => Promise; + projectFormSubmit: (projectName: string, port: number, commandNames: string[]) => Promise; removeProject: (projectName: string) => Promise; currentProject: string | null; @@ -52,12 +52,12 @@ export const useProjectsStore = create((set, get) => ({ const projects = await GetProjects(); set(() => ({ projects })); }, - async projectFormSubmit(projectName, domain, port, commandNames) { + async projectFormSubmit(projectName, port, commandNames) { if (get().editingProject === projectName) { - await UpdateProject(projectName, domain, port, commandNames); + await UpdateProject(projectName, port, commandNames); set(() => ({ editingProject: null })); } else { - await AddProject(projectName, domain, port, commandNames); + await AddProject(projectName, port, commandNames); } const projects = await GetProjects(); From 54965bc409178ea774cf8fd7a39c41ae03ece966 Mon Sep 17 00:00:00 2001 From: Iskander Date: Tue, 14 Jan 2025 22:08:26 +0100 Subject: [PATCH 02/15] Disable macos and windows builds for now since they are not supported --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea41f8e..ed7955b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,12 +26,12 @@ jobs: - name: 'spinup' platform: 'linux/amd64' os: 'ubuntu-22.04' - - name: 'spinup' - platform: 'darwin/universal' - os: 'macos-latest' - - name: 'Spinup' - platform: 'windows/amd64' - os: 'windows-latest' + # - name: 'spinup' + # platform: 'darwin/universal' + # os: 'macos-latest' + # - name: 'Spinup' + # platform: 'windows/amd64' + # os: 'windows-latest' runs-on: ${{ matrix.build.os }} steps: From 108c8f27e2a244c14d72136b8900c00e00dff8bb Mon Sep 17 00:00:00 2001 From: Iskander Date: Wed, 15 Jan 2025 00:12:33 +0100 Subject: [PATCH 03/15] Refactor unix paths and add nginx and dnsmasq configuration files for Unix builds --- .github/workflows/release.yml | 6 +-- .../etc/systemd/system/nginx-spinup.service | 18 ++++++++ .../share/applications/spinup-app.desktop | 0 .../unix/usr/share/spinup/config/dnsmasq.conf | 13 ++++++ build/unix/usr/share/spinup/config/nginx.conf | 46 +++++++++++++++++++ build/unix/usr/share/spinup/tmp/.tmp | 0 generate-icons.sh | 11 +++-- 7 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 build/unix/etc/systemd/system/nginx-spinup.service rename build/{ => unix/usr}/share/applications/spinup-app.desktop (100%) create mode 100644 build/unix/usr/share/spinup/config/dnsmasq.conf create mode 100644 build/unix/usr/share/spinup/config/nginx.conf create mode 100644 build/unix/usr/share/spinup/tmp/.tmp diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed7955b..5c46607 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -133,8 +133,8 @@ jobs: with: key: ${{ runner.os }}-icons-${{ hashFiles('images/icon-large.png') }} path: | - build/share/icons - build/share/pixmaps + build/unix/usr/share/icons + build/unix/usr/share/pixmaps - name: Generate icons # if: (runner.os == 'Linux' || runner.os == 'macOS') && steps.cache-icons.outputs.cache-hit != 'true' if: runner.os == 'Linux' && steps.cache-icons.outputs.cache-hit != 'true' @@ -154,7 +154,7 @@ jobs: mkdir -p deb/spinup-${SPINUP_VERSION}${os_version}/usr/share/spinup/bin cp build/bin/spinup-${SPINUP_VERSION}${os_version} deb/spinup-${SPINUP_VERSION}${os_version}/usr/share/spinup/bin/spinup cp build/DEBIAN/* deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN - cp -r build/share deb/spinup-${SPINUP_VERSION}${os_version}/usr + cp -r build/unix/usr deb/spinup-${SPINUP_VERSION}${os_version} echo -e "\nVersion: $SPINUP_VERSION" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control diff --git a/build/unix/etc/systemd/system/nginx-spinup.service b/build/unix/etc/systemd/system/nginx-spinup.service new file mode 100644 index 0000000..9a0cef0 --- /dev/null +++ b/build/unix/etc/systemd/system/nginx-spinup.service @@ -0,0 +1,18 @@ +[Unit] +Description=A high performance web server and a reverse proxy server +Documentation=man:nginx(8) +After=network-online.target remote-fs.target nss-lookup.target +Wants=network-online.target + +[Service] +Type=forking +PIDFile=/usr/share/spinup/tmp/nginx.pid +ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' +ExecStart=/usr/sbin/nginx -c /usr/share/spinup/config/nginx.conf -g 'daemon on; master_process on;' +ExecReload=/usr/sbin/nginx -c /usr/share/spinup/config/nginx.conf -g 'daemon on; master_process on;' -s reload +ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /usr/share/spinup/tmp/nginx.pid +TimeoutStopSec=5 +KillMode=mixed + +[Install] +WantedBy=multi-user.target diff --git a/build/share/applications/spinup-app.desktop b/build/unix/usr/share/applications/spinup-app.desktop similarity index 100% rename from build/share/applications/spinup-app.desktop rename to build/unix/usr/share/applications/spinup-app.desktop diff --git a/build/unix/usr/share/spinup/config/dnsmasq.conf b/build/unix/usr/share/spinup/config/dnsmasq.conf new file mode 100644 index 0000000..a863d5a --- /dev/null +++ b/build/unix/usr/share/spinup/config/dnsmasq.conf @@ -0,0 +1,13 @@ +# Spinup configuration file for dnsmasq + +# Require requests to have an actual domain unless specified in /etc/hosts +domain-needed + +# Prevent reverse lookups to private IP ranges from being forwarded upstream +bogus-priv + +# Set to your DNS server +server=127.0.0.53 + +# Forward all requests with TLD '.test' to localhost +address=/test/127.0.0.1 diff --git a/build/unix/usr/share/spinup/config/nginx.conf b/build/unix/usr/share/spinup/config/nginx.conf new file mode 100644 index 0000000..9506f45 --- /dev/null +++ b/build/unix/usr/share/spinup/config/nginx.conf @@ -0,0 +1,46 @@ +worker_processes auto; +pid /run/nginx.pid; + +events { + worker_connections 1024; + # multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + # server_tokens off; + + gzip on; + gzip_disable "msie6"; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + + gzip_types + application/atom+xml + application/javascript + application/json + application/rss+xml + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/svg+xml + image/x-icon + text/css + text/plain + text/x-component; + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/build/unix/usr/share/spinup/tmp/.tmp b/build/unix/usr/share/spinup/tmp/.tmp new file mode 100644 index 0000000..e69de29 diff --git a/generate-icons.sh b/generate-icons.sh index 1315cf5..cfb6a5f 100755 --- a/generate-icons.sh +++ b/generate-icons.sh @@ -1,14 +1,15 @@ #!/bin/bash -SOURCE=images/icon-large.png +SOURCE="./images/icon-large.png" SIZES=(1024 512 256 128 64 32 16) -PIXMAPS_DIR=./build/share/pixmaps +SHARE_DIR="./build/unix/usr/share" +PIXMAPS_DIR="$SHARE_DIR/pixmaps" -mkdir -p ./build/share/icons/hicolor +mkdir -p $SHARE_DIR/icons/hicolor for size in "${SIZES[@]}"; do - dir="./build/share/icons/hicolor/${size}x${size}/apps" - dir_2x="./build/share/icons/hicolor/${size}x${size}@2x/apps" + dir="$SHARE_DIR/icons/hicolor/${size}x${size}/apps" + dir_2x="$SHARE_DIR/icons/hicolor/${size}x${size}@2x/apps" # Create the normal icon directory and convert the icon to the correct size mkdir -p $dir From 58958546db3e41aa91d6914b56661f5df14bb5ca Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 16:42:37 +0100 Subject: [PATCH 04/15] Move all bash scripts to separate folder --- .github/workflows/pr.yml | 2 +- .github/workflows/release.yml | 2 +- generate-icons.sh => scripts/generate-icons.sh | 0 run-tests.sh => scripts/run-tests.sh | 0 update-version.sh => scripts/update-version.sh | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename generate-icons.sh => scripts/generate-icons.sh (100%) rename run-tests.sh => scripts/run-tests.sh (100%) rename update-version.sh => scripts/update-version.sh (100%) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c1d3986..7437780 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -66,4 +66,4 @@ jobs: # Run tests - name: Run tests - run: ./run-tests.sh + run: ./scripts/run-tests.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c46607..ace70c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -139,7 +139,7 @@ jobs: # if: (runner.os == 'Linux' || runner.os == 'macOS') && steps.cache-icons.outputs.cache-hit != 'true' if: runner.os == 'Linux' && steps.cache-icons.outputs.cache-hit != 'true' working-directory: ${{ env.WORKING_DIRECTORY }} - run: bash ./generate-icons.sh + run: bash ./scripts/generate-icons.sh shell: bash # Package as .deb for Ubuntu diff --git a/generate-icons.sh b/scripts/generate-icons.sh similarity index 100% rename from generate-icons.sh rename to scripts/generate-icons.sh diff --git a/run-tests.sh b/scripts/run-tests.sh similarity index 100% rename from run-tests.sh rename to scripts/run-tests.sh diff --git a/update-version.sh b/scripts/update-version.sh similarity index 100% rename from update-version.sh rename to scripts/update-version.sh From 008727a69c6c29a433c4aba24e896ce55a6b4229 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 16:53:01 +0100 Subject: [PATCH 05/15] Remove nginx service and configuration files from Unix build --- .../etc/systemd/system/nginx-spinup.service | 18 -------- build/unix/usr/share/spinup/config/nginx.conf | 46 ------------------- 2 files changed, 64 deletions(-) delete mode 100644 build/unix/etc/systemd/system/nginx-spinup.service delete mode 100644 build/unix/usr/share/spinup/config/nginx.conf diff --git a/build/unix/etc/systemd/system/nginx-spinup.service b/build/unix/etc/systemd/system/nginx-spinup.service deleted file mode 100644 index 9a0cef0..0000000 --- a/build/unix/etc/systemd/system/nginx-spinup.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=A high performance web server and a reverse proxy server -Documentation=man:nginx(8) -After=network-online.target remote-fs.target nss-lookup.target -Wants=network-online.target - -[Service] -Type=forking -PIDFile=/usr/share/spinup/tmp/nginx.pid -ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' -ExecStart=/usr/sbin/nginx -c /usr/share/spinup/config/nginx.conf -g 'daemon on; master_process on;' -ExecReload=/usr/sbin/nginx -c /usr/share/spinup/config/nginx.conf -g 'daemon on; master_process on;' -s reload -ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /usr/share/spinup/tmp/nginx.pid -TimeoutStopSec=5 -KillMode=mixed - -[Install] -WantedBy=multi-user.target diff --git a/build/unix/usr/share/spinup/config/nginx.conf b/build/unix/usr/share/spinup/config/nginx.conf deleted file mode 100644 index 9506f45..0000000 --- a/build/unix/usr/share/spinup/config/nginx.conf +++ /dev/null @@ -1,46 +0,0 @@ -worker_processes auto; -pid /run/nginx.pid; - -events { - worker_connections 1024; - # multi_accept on; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - # server_tokens off; - - gzip on; - gzip_disable "msie6"; - gzip_comp_level 5; - gzip_min_length 256; - gzip_proxied any; - gzip_vary on; - - gzip_types - application/atom+xml - application/javascript - application/json - application/rss+xml - application/vnd.ms-fontobject - application/x-font-ttf - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/svg+xml - image/x-icon - text/css - text/plain - text/x-component; - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} From d2b636075a2ff17f3bbadfec4114fe040eae2d38 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 16:53:32 +0100 Subject: [PATCH 06/15] Enhance post-install script to manage spinup user permissions and include sudoers.d directory --- build/DEBIAN/postinst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/build/DEBIAN/postinst b/build/DEBIAN/postinst index 23d0cd8..13f0a05 100755 --- a/build/DEBIAN/postinst +++ b/build/DEBIAN/postinst @@ -12,13 +12,21 @@ ln -s /usr/share/spinup/bin/spinup /usr/bin/spinup # Create the spinup user if it doesn't exist if ! id -u spinup &>/dev/null; then useradd -r -s /bin/false spinup -fi -# Add the spinup user to the sudo group -usermod -aG sudo spinup + # Add the spinup user to the sudo group + usermod -aG sudo spinup +fi # Set the correct permissions on the spinup directory chown -R spinup:spinup /usr/share/spinup # Set the correct permissions on the spinup binary chown root:spinup /usr/share/spinup/bin/spinup + +if ! grep -q "^@includedir /etc/sudoers.d[[:space:]]*$" /etc/sudoers; then + # Make a backup of the sudoers file + cp /etc/sudoers /etc/sudoers.bak + + # Add the sudoers.d directory to the sudoers file if it has not been included + echo "@includedir /etc/sudoers.d" | visudo -c -f - &>/dev/null && echo "@includedir /etc/sudoers.d" | EDITOR='tee -a' visudo &>/dev/null +fi From bd44c48c9e12be5a916559161f6b087b59b6112b Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 17:59:15 +0100 Subject: [PATCH 07/15] Enhance post-install script to manage user permissions, configure dnsmasq and nginx, and add sudoers.d support --- build/DEBIAN/postinst | 73 ++++++++++++++++--- build/unix/etc/sudoers.d/spinup | 3 + .../unix/usr/share/spinup/config/dnsmasq.conf | 3 - build/unix/usr/share/spinup/tmp/.tmp | 0 4 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 build/unix/etc/sudoers.d/spinup delete mode 100644 build/unix/usr/share/spinup/tmp/.tmp diff --git a/build/DEBIAN/postinst b/build/DEBIAN/postinst index 13f0a05..2ce1dae 100755 --- a/build/DEBIAN/postinst +++ b/build/DEBIAN/postinst @@ -1,32 +1,85 @@ #!/usr/bin/env bash +# Make sure the script is being run as root if [ "$(id -u)" -ne 0 ]; then echo "This script must be run as root" >&2 exit 1 fi +# Get the home directory of the user that ran the script +USER_HOME=$(getent passwd $SUDO_USER | cut -d: -f6) + +# Make sure the user's home directory exists +if [ ! -d "$USER_HOME" ]; then + echo "The user's home directory does not exist" >&2 + exit 1 +fi + +##################### +### General setup ### +##################### + # Remove the old symlink if it exists and create a new one rm -f /usr/bin/spinup ln -s /usr/share/spinup/bin/spinup /usr/bin/spinup -# Create the spinup user if it doesn't exist -if ! id -u spinup &>/dev/null; then - useradd -r -s /bin/false spinup +# Set the correct permissions on the spinup directory +chown -R root:root /usr/share/spinup + +############### +### Dnsmasq ### +############### - # Add the spinup user to the sudo group - usermod -aG sudo spinup +if [ ! -d /etc/dnsmasq.d ]; then + # Create the dnsmasq.d directory if it doesn't exist + mkdir /etc/dnsmasq.d fi -# Set the correct permissions on the spinup directory -chown -R spinup:spinup /usr/share/spinup +# Check if the spinup.conf already exists in the dnsmasq.d directory +if [ ! -f /etc/dnsmasq.d/spinup.conf ]; then + # Link the dnsmasq configuration file for spinup to the dnsmasq.d directory if it doesn't exist + ln -s /usr/share/spinup/config/dnsmasq.conf /etc/dnsmasq.d/spinup.conf + + # Restart the dnsmasq service + systemctl restart dnsmasq +fi + +############# +### Nginx ### +############# + +USER_SPINUP_NGINX_DIR="$USER_HOME/.config/spinup/nginx" +SPINUP_NGINX_DIR="/usr/share/spinup/config/nginx" + +# Create the user's spinup nginx directory if it doesn't exist +mkdir -p $USER_SPINUP_NGINX_DIR + +if [ ! -d $SPINUP_NGINX_DIR ]; then + # Link the user's spinup nginx directory to the spinup config directory if it doesn't exist + ln -s $USER_SPINUP_NGINX_DIR $SPINUP_NGINX_DIR +fi + +# Add the user's spinup nginx directory to the nginx configuration +if ! grep -q "include ${SPINUP_NGINX_DIR}/\*.conf;" /etc/nginx/nginx.conf; then + # Make a backup of the nginx configuration file + cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak + + # Add the include directive to the bottom of the http section in the nginx configuration file + sed -i '/http {/!b; :a; N; /}/!ba; s/}/ include '"${SPINUP_NGINX_DIR//\//\\/}"'\/\*.conf;\n}/' /etc/nginx/nginx.conf +fi + +# Restart the nginx service +systemctl restart nginx -# Set the correct permissions on the spinup binary -chown root:spinup /usr/share/spinup/bin/spinup +############### +### Sudoers ### +############### +# Add an include for the sudoers.d directory to the sudoers file if it doesn't already exist if ! grep -q "^@includedir /etc/sudoers.d[[:space:]]*$" /etc/sudoers; then # Make a backup of the sudoers file cp /etc/sudoers /etc/sudoers.bak - # Add the sudoers.d directory to the sudoers file if it has not been included + # Add the include directive to the sudoers file echo "@includedir /etc/sudoers.d" | visudo -c -f - &>/dev/null && echo "@includedir /etc/sudoers.d" | EDITOR='tee -a' visudo &>/dev/null fi diff --git a/build/unix/etc/sudoers.d/spinup b/build/unix/etc/sudoers.d/spinup new file mode 100644 index 0000000..0e60d0d --- /dev/null +++ b/build/unix/etc/sudoers.d/spinup @@ -0,0 +1,3 @@ +# Allow sudo users to reload nginx without having to enter a password. +%sudo ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload nginx +%sudo ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload nginx.service diff --git a/build/unix/usr/share/spinup/config/dnsmasq.conf b/build/unix/usr/share/spinup/config/dnsmasq.conf index a863d5a..b26c113 100644 --- a/build/unix/usr/share/spinup/config/dnsmasq.conf +++ b/build/unix/usr/share/spinup/config/dnsmasq.conf @@ -6,8 +6,5 @@ domain-needed # Prevent reverse lookups to private IP ranges from being forwarded upstream bogus-priv -# Set to your DNS server -server=127.0.0.53 - # Forward all requests with TLD '.test' to localhost address=/test/127.0.0.1 diff --git a/build/unix/usr/share/spinup/tmp/.tmp b/build/unix/usr/share/spinup/tmp/.tmp deleted file mode 100644 index e69de29..0000000 From 76dd4ad23b76dcb2fbabe23aec6bb0e922919733 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 17:59:50 +0100 Subject: [PATCH 08/15] Refactor configuration handling by removing sudo dependencies and updating Nginx reload logic --- config/config.go | 30 ++++++++++++++++++------------ config/config_linux_test.go | 14 -------------- config/config_test.go | 10 ---------- config/nginx.go | 32 ++++++++++++++++---------------- config/unix.go | 21 +++++---------------- config/windows.go | 30 ++---------------------------- core/core.go | 25 +++---------------------- core/domain_alias.go | 2 -- core/init.go | 2 -- core/project.go | 6 ------ 10 files changed, 44 insertions(+), 128 deletions(-) diff --git a/config/config.go b/config/config.go index db79959..792c60f 100644 --- a/config/config.go +++ b/config/config.go @@ -4,7 +4,6 @@ import ( _ "embed" "fmt" "os" - "os/exec" "path" "github.com/iskandervdh/spinup/common" @@ -43,7 +42,7 @@ func New() (*Config, error) { return &Config{ configDir: configDir, - nginxConfigDir: nginxConfigDir, + nginxConfigDir: getNginxConfigDir(configDir), testing: false, }, nil } @@ -53,20 +52,11 @@ func New() (*Config, error) { func NewTesting(testingConfigDir string) *Config { return &Config{ configDir: testingConfigDir, - nginxConfigDir: path.Join(testingConfigDir, "/nginx/conf.d"), + nginxConfigDir: path.Join(testingConfigDir, "/config/nginx/"), testing: true, } } -// Add sudo to a command if not in testing mode. -func (c *Config) withSudo(name string, args ...string) *exec.Cmd { - if c.IsTesting() || common.IsWindows() { - return exec.Command(name, args...) - } - - return exec.Command("sudo", append([]string{name}, args...)...) -} - // Returns the path to the configuration directory. func (c *Config) GetConfigDir() string { return c.configDir @@ -86,3 +76,19 @@ func (c *Config) GetNginxConfigDir() string { func (c *Config) IsTesting() bool { return c.testing } + +func (c *Config) writeToFile(filePath string, contents string) error { + f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + + if err != nil { + return err + } + + _, err = f.Write([]byte(contents)) + + if err != nil { + return err + } + + return f.Close() +} diff --git a/config/config_linux_test.go b/config/config_linux_test.go index 3968b9f..f47bb0f 100644 --- a/config/config_linux_test.go +++ b/config/config_linux_test.go @@ -17,17 +17,3 @@ func TestNewError(t *testing.T) { os.Setenv("HOME", home) } - -func TestWithSudo(t *testing.T) { - c, err := New() - - if err != nil { - t.Errorf("Expected no error, got %s", err) - } - - cmd := c.withSudo("ls") - - if cmd.Args[0] != "sudo" { - t.Errorf("Expected sudo, got %s", cmd.Args[0]) - } -} diff --git a/config/config_test.go b/config/config_test.go index 9a1de42..40c3b1f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -50,16 +50,6 @@ func TestNewTesting(t *testing.T) { } } -func TestWithSudoTesting(t *testing.T) { - c := TestingConfig("config_test") - - cmd := c.withSudo("ls") - - if cmd.Args[0] != "ls" { - t.Errorf("Expected ls, got %s", cmd.Args[0]) - } -} - func TestGetters(t *testing.T) { c := TestingConfig("config_getters") diff --git a/config/nginx.go b/config/nginx.go index 75a6f9e..e4856c0 100644 --- a/config/nginx.go +++ b/config/nginx.go @@ -14,9 +14,9 @@ import ( // Regex to match the server_name directive in a Nginx config file. var serverNameRegex = regexp.MustCompile(`server_name\s+(.*);`) -// Restart the Nginx service. -func (c *Config) restartNginx() error { - return exec.Command("sudo", "systemctl", "restart", "nginx").Run() +// Reload the Nginx service. +func (c *Config) reloadNginx() error { + return exec.Command("systemctl", "reload", "nginx").Run() } // Add a new Nginx configuration file with the given name and port. @@ -44,12 +44,6 @@ func (c *Config) AddNginxConfig(name string, port int64) error { return fmt.Errorf("failed to check if config file exists: %v", err) } - // err := c.withSudo("touch", nginxConfigFilePath).Run() - - // if err != nil { - // return err - // } - err := c.writeToFile(nginxConfigFilePath, config) if err != nil { @@ -57,7 +51,7 @@ func (c *Config) AddNginxConfig(name string, port int64) error { } if !c.IsTesting() { - c.restartNginx() + c.reloadNginx() } return nil @@ -66,14 +60,14 @@ func (c *Config) AddNginxConfig(name string, port int64) error { // Remove a Nginx configuration file with the given name. func (c *Config) RemoveNginxConfig(name string) error { nginxConfigFilePath := fmt.Sprintf("%s/%s.conf", c.nginxConfigDir, name) - err := c.removeFile(nginxConfigFilePath) + err := os.Remove(nginxConfigFilePath) if err != nil { return err } if !c.IsTesting() { - c.restartNginx() + c.reloadNginx() } return nil @@ -95,14 +89,14 @@ func (c *Config) RenameNginxConfig(oldName string, newName string) error { oldNginxConfigFilePath := fmt.Sprintf("%s/%s.conf", c.nginxConfigDir, oldName) newNginxConfigFilePath := fmt.Sprintf("%s/%s.conf", c.nginxConfigDir, newName) - err := c.moveFile(oldNginxConfigFilePath, newNginxConfigFilePath) + err := os.Rename(oldNginxConfigFilePath, newNginxConfigFilePath) if err != nil { return err } if !c.IsTesting() { - c.restartNginx() + c.reloadNginx() } return nil @@ -140,7 +134,7 @@ func (c *Config) NginxAddDomainAlias(name string, domainAlias string) error { } if !c.IsTesting() { - c.restartNginx() + c.reloadNginx() } return nil @@ -179,7 +173,7 @@ func (c *Config) NginxRemoveDomainAlias(name string, domainAlias string) error { } if !c.IsTesting() { - c.restartNginx() + c.reloadNginx() } return nil @@ -201,6 +195,12 @@ func (c *Config) InitNginx() error { filepath.Join(c.nginxConfigDir, "..", "nginx.conf"), ) fmt.Printf("\nhttp {\n\t...\n\n\t%s\n}\n", "include \"C:/nginx/conf/conf.d/*.conf\";") + } else if !c.IsTesting() { + fmt.Printf( + "\n!!! Please add the following include directive to http section of your nginx.conf file located at %s like this:\n", + filepath.Join(c.nginxConfigDir, "..", "nginx.conf"), + ) + fmt.Printf("\nhttp {\n\t...\n\n\t%s\n}\n", "include /usr/share/spinup/config/nginx/*.conf;") } return nil diff --git a/config/unix.go b/config/unix.go index b0a23f8..2192898 100644 --- a/config/unix.go +++ b/config/unix.go @@ -2,21 +2,10 @@ package config -import "strings" +import ( + "path" +) -var nginxConfigDir = "/etc/nginx/conf.d" - -func (c *Config) writeToFile(filePath string, content string) error { - saveNewHosts := c.withSudo("tee", filePath) - saveNewHosts.Stdin = strings.NewReader(content) - - return saveNewHosts.Run() -} - -func (c *Config) moveFile(oldFilePath string, newFilePath string) error { - return c.withSudo("mv", oldFilePath, newFilePath).Run() -} - -func (c *Config) removeFile(filePath string) error { - return c.withSudo("rm", filePath).Run() +func getNginxConfigDir(configDir string) string { + return path.Join(configDir, "nginx") } diff --git a/config/windows.go b/config/windows.go index 3dd8c61..ad2aabd 100644 --- a/config/windows.go +++ b/config/windows.go @@ -2,32 +2,6 @@ package config -import ( - "os" -) - -var nginxConfigDir = "C:\\nginx\\conf\\conf.d" - -func (c *Config) writeToFile(filePath string, contents string) error { - f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - - if err != nil { - return err - } - - _, err = f.Write([]byte(contents)) - - if err != nil { - return err - } - - return f.Close() -} - -func (c *Config) moveFile(oldFilePath string, newFilePath string) error { - return os.Rename(oldFilePath, newFilePath) -} - -func (c *Config) removeFile(filePath string) error { - return os.Remove(filePath) +func getNginxConfigDir(_ string) string { + return "C:\\nginx\\conf\\conf.d" } diff --git a/core/core.go b/core/core.go index 668f7ad..1faced5 100644 --- a/core/core.go +++ b/core/core.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "os" - "os/exec" "slices" _ "github.com/golang-migrate/migrate/v4/source/file" @@ -37,8 +36,8 @@ type Core struct { projects Projects } -func (c *Core) connectToDB(config *config.Config) (*sql.DB, error) { - databasePath := config.GetDatabasePath() +func (c *Core) connectToDB() (*sql.DB, error) { + databasePath := c.config.GetDatabasePath() _, err := os.Stat(databasePath) @@ -88,7 +87,7 @@ func New(options ...func(*Core)) *Core { option(c) } - db, err := c.connectToDB(c.config) + db, err := c.connectToDB() if err != nil { fmt.Println("Error connecting to database:", err) @@ -133,24 +132,6 @@ func (c *Core) GetConfig() *config.Config { return c.config } -// RequireSudo checks if the user has sudo permissions. -// This is needed to update some system files. -// -// It returns an error if the user does not have sudo permissions. -func (c *Core) RequireSudo() error { - if c.config.IsTesting() || common.IsWindows() { - return nil - } - - err := exec.Command("sudo", "-v").Run() - - if err != nil { - return fmt.Errorf("this command requires sudo") - } - - return nil -} - // Get all the names of the commands. func (c *Core) GetCommandNames() []string { if c.commands == nil { diff --git a/core/domain_alias.go b/core/domain_alias.go index b897892..32c5e39 100644 --- a/core/domain_alias.go +++ b/core/domain_alias.go @@ -11,8 +11,6 @@ type DomainAlias = sqlc.DomainAlias // Add a domain alias to the given project. func (c *Core) AddDomainAlias(projectName string, domainAlias string) common.Msg { - c.RequireSudo() - if c.projects == nil { return common.NewErrMsg("No projects found") } diff --git a/core/init.go b/core/init.go index 903f5e9..172dbba 100644 --- a/core/init.go +++ b/core/init.go @@ -37,8 +37,6 @@ func (c *Core) Init() common.Msg { return common.NewErrMsg("Error creating config directory: %v", err) } - c.RequireSudo() - err = c.config.InitNginx() if err != nil { diff --git a/core/project.go b/core/project.go index 333f47b..f255819 100644 --- a/core/project.go +++ b/core/project.go @@ -105,8 +105,6 @@ func (c *Core) ProjectExists(name string) (bool, Project) { // Add a project with the given name, port and command names. func (c *Core) AddProject(name string, port int64, commandNames []string) common.Msg { - c.RequireSudo() - // Check if commands exist commandIDs := make([]int64, 0, len(commandNames)) @@ -162,8 +160,6 @@ func (c *Core) AddProject(name string, port int64, commandNames []string) common // Remove the project with the given name. func (c *Core) RemoveProject(name string) common.Msg { - c.RequireSudo() - exists, _ := c.ProjectExists(name) if !exists { @@ -183,8 +179,6 @@ func (c *Core) RemoveProject(name string) common.Msg { // Update the project with the given name to the given port and command names. func (c *Core) UpdateProject(name string, port int64, commandNames []string) common.Msg { - c.RequireSudo() - exists, project := c.ProjectExists(name) if !exists { From 1c1e2efd811308e4596671d1fc8604d7e1d1d7e5 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 18:04:53 +0100 Subject: [PATCH 09/15] Sudo is actually still required when reloading nginx --- config/nginx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/nginx.go b/config/nginx.go index e4856c0..90984ca 100644 --- a/config/nginx.go +++ b/config/nginx.go @@ -16,7 +16,7 @@ var serverNameRegex = regexp.MustCompile(`server_name\s+(.*);`) // Reload the Nginx service. func (c *Config) reloadNginx() error { - return exec.Command("systemctl", "reload", "nginx").Run() + return exec.Command("sudo", "systemctl", "reload", "nginx").Run() } // Add a new Nginx configuration file with the given name and port. From ab8a191ef9d70f305fec8472245f3a0dff2a216f Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 18:30:19 +0100 Subject: [PATCH 10/15] Add post-remove script to clean up spinup installation and configuration --- build/DEBIAN/postrm | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 build/DEBIAN/postrm diff --git a/build/DEBIAN/postrm b/build/DEBIAN/postrm new file mode 100755 index 0000000..f06b002 --- /dev/null +++ b/build/DEBIAN/postrm @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Make sure the script is being run as root +if [ "$(id -u)" -ne 0 ]; then + echo "This script must be run as root" >&2 + exit 1 +fi + +# Remove the link to the binary +rm -f /usr/bin/spinup + +# Remove the spinup directory +rm -rf /usr/share/spinup + +# Remove the link to the dnsmasq configuration file +rm -f /etc/dnsmasq.d/spinup.conf + +# Restart the dnsmasq service +systemctl restart dnsmasq + +# Remove include directive in the nginx configuration file +sed -i '/include \/usr\/share\/spinup\/config\/nginx\/\*.conf;/d' /etc/nginx/nginx.conf + +# Restart the nginx service +systemctl restart nginx + +# Remove the sudoers file +rm -f /etc/sudoers.d/spinup From 0719c1e4515ca33b1638b794e87bf8f785be8b78 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 19:02:45 +0100 Subject: [PATCH 11/15] Try to package a .zip or .dmg file for macOS --- .github/workflows/release.yml | 38 +++++++++++----------------------- scripts/release/package-deb.sh | 23 ++++++++++++++++++++ scripts/release/package-dmg.sh | 22 ++++++++++++++++++++ 3 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 scripts/release/package-deb.sh create mode 100644 scripts/release/package-dmg.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ace70c7..5fb3e4b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,9 +26,9 @@ jobs: - name: 'spinup' platform: 'linux/amd64' os: 'ubuntu-22.04' - # - name: 'spinup' - # platform: 'darwin/universal' - # os: 'macos-latest' + - name: 'spinup' + platform: 'darwin/universal' + os: 'macos-latest' # - name: 'Spinup' # platform: 'windows/amd64' # os: 'windows-latest' @@ -100,7 +100,7 @@ jobs: SPINUP_VERSION=$(cat ./common/.version | sed 's/^v//') sed -i.bak "s/{{version}}/${SPINUP_VERSION}/g" wails.json && rm wails.json.bak - wails build --platform ${{ matrix.build.platform }} -webview2 ${{ env.WEBVIEW2 }} -o ${{ matrix.build.name }}-${SPINUP_VERSION} + wails build --platform ${{ matrix.build.platform }} -webview2 ${{ env.WEBVIEW2 }} -o ${{ matrix.build.name }} shell: bash - name: Build Windows App + Installer if: runner.os == 'Windows' @@ -146,29 +146,13 @@ jobs: - name: Package as .deb for Ubuntu if: runner.os == 'Linux' working-directory: ${{ env.WORKING_DIRECTORY }} - run: | - SPINUP_VERSION=$(cat ./common/.version | sed 's/^v//') - - for os_version in "" "-ubuntu24.04"; do - mkdir -p deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN - mkdir -p deb/spinup-${SPINUP_VERSION}${os_version}/usr/share/spinup/bin - cp build/bin/spinup-${SPINUP_VERSION}${os_version} deb/spinup-${SPINUP_VERSION}${os_version}/usr/share/spinup/bin/spinup - cp build/DEBIAN/* deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN - cp -r build/unix/usr deb/spinup-${SPINUP_VERSION}${os_version} - - echo -e "\nVersion: $SPINUP_VERSION" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control + run: bash ./scripts/release/package-deb.sh - if [ "$webkit_version" -eq "40" ]; then - echo "Depends: dnsmasq, libgtk-3-0, libwebkit2gtk-4.0-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control - else - echo "Depends: dnsmasq, libgtk-3-0, libwebkit2gtk-4.1-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control - fi - - sudo chown -R root:root deb/spinup-${SPINUP_VERSION}${os_version} - - dpkg-deb --build deb/spinup-${SPINUP_VERSION}${os_version} - done - shell: bash + # Package as .dmg for macOS + - name: Package as .dmg for macOS + if: runner.os == 'macOS' + working-directory: ${{ env.WORKING_DIRECTORY }} + run: bash ./scripts/release/package-dmg.sh # Upload build assets - uses: actions/upload-artifact@v4 @@ -179,6 +163,8 @@ jobs: */bin/*.exe *\bin\*.exe */spinup-*.deb + */bin/*.zip + */bin/*.dmg - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') diff --git a/scripts/release/package-deb.sh b/scripts/release/package-deb.sh new file mode 100644 index 0000000..4aa01fa --- /dev/null +++ b/scripts/release/package-deb.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +SPINUP_VERSION=$(cat ./common/.version | sed 's/^v//') + +for os_version in "" "-ubuntu24.04"; do + mkdir -p deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN + mkdir -p deb/spinup-${SPINUP_VERSION}${os_version}/usr/share/spinup/bin + cp build/bin/spinup-${SPINUP_VERSION}${os_version} deb/spinup-${SPINUP_VERSION}${os_version}/usr/share/spinup/bin/spinup + cp build/DEBIAN/* deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN + cp -r build/unix/usr deb/spinup-${SPINUP_VERSION}${os_version} + + echo -e "\nVersion: $SPINUP_VERSION" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control + + if [ "$webkit_version" -eq "40" ]; then + echo "Depends: dnsmasq, libgtk-3-0, libwebkit2gtk-4.0-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control + else + echo "Depends: dnsmasq, libgtk-3-0, libwebkit2gtk-4.1-dev" >> deb/spinup-${SPINUP_VERSION}${os_version}/DEBIAN/control + fi + + sudo chown -R root:root deb/spinup-${SPINUP_VERSION}${os_version} + + dpkg-deb --build deb/spinup-${SPINUP_VERSION}${os_version} +done diff --git a/scripts/release/package-dmg.sh b/scripts/release/package-dmg.sh new file mode 100644 index 0000000..f71e705 --- /dev/null +++ b/scripts/release/package-dmg.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +SPINUP_VERSION=$(cat ./common/.version | sed 's/^v//') + +MAC_OS_FOLDER="./build/MacOS" + +# Define the path to the binary file +BIN_FILE="./build/bin/spinup/Contents/MacOS/spinup" + +# Create a MacOS directory +mkdir -p $MAC_OS_FOLDER + +# Copy the contents of the build/unix directory to the MacOS directory +cp -r "./build/unix/" $MAC_OS_FOLDER + +# Copy the binary file to the MacOS directory +cp $BIN_FILE "$MAC_OS_FOLDER/usr/share/spinup/bin/spinup" + +# Create a zip file containing the contents of the MacOS directory +zip -r "spinup-${SPINUP_VERSION}-macos.zip" $MAC_OS_FOLDER + +hdiutil create -volname "Spinup" -srcfolder $MAC_OS_FOLDER -ov -format UDZO "spinup-${SPINUP_VERSION}.dmg" From 5780a7296bd004f3ba45c0062b95612cd7e53445 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 19:13:59 +0100 Subject: [PATCH 12/15] Update release workflow to include zip and dmg files from any directory --- .github/workflows/release.yml | 4 ++-- scripts/release/package-dmg.sh | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5fb3e4b..706b86c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -163,8 +163,8 @@ jobs: */bin/*.exe *\bin\*.exe */spinup-*.deb - */bin/*.zip - */bin/*.dmg + */*.zip + */*.dmg - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') diff --git a/scripts/release/package-dmg.sh b/scripts/release/package-dmg.sh index f71e705..69b26e7 100644 --- a/scripts/release/package-dmg.sh +++ b/scripts/release/package-dmg.sh @@ -7,6 +7,8 @@ MAC_OS_FOLDER="./build/MacOS" # Define the path to the binary file BIN_FILE="./build/bin/spinup/Contents/MacOS/spinup" +ls ./build/bin/ + # Create a MacOS directory mkdir -p $MAC_OS_FOLDER From 3a0c58a726de12c8a2d176d430740c7d32b743fb Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 21:14:09 +0100 Subject: [PATCH 13/15] Actually publish zip and dmg files --- .github/workflows/release.yml | 2 ++ scripts/release/package-dmg.sh | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 706b86c..11e9d35 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -173,3 +173,5 @@ jobs: */bin/*/Contents/MacOS/* */bin/*.exe */spinup-*.deb + */spinup-*.zip + */spinup-*.dmg diff --git a/scripts/release/package-dmg.sh b/scripts/release/package-dmg.sh index 69b26e7..0d4f7d7 100644 --- a/scripts/release/package-dmg.sh +++ b/scripts/release/package-dmg.sh @@ -5,9 +5,7 @@ SPINUP_VERSION=$(cat ./common/.version | sed 's/^v//') MAC_OS_FOLDER="./build/MacOS" # Define the path to the binary file -BIN_FILE="./build/bin/spinup/Contents/MacOS/spinup" - -ls ./build/bin/ +BIN_FILE="./build/bin/Spinup.app/Contents/MacOS/spinup" # Create a MacOS directory mkdir -p $MAC_OS_FOLDER From 0e5309015fbf246fc65ef6c89709780a2514f1d5 Mon Sep 17 00:00:00 2001 From: Iskander Date: Sat, 18 Jan 2025 22:06:13 +0100 Subject: [PATCH 14/15] Rename packaging script from .dmg to .zip for macOS and update release workflow accordingly --- .github/workflows/release.yml | 11 ++++------- scripts/release/{package-dmg.sh => package-zip.sh} | 13 ++++++++----- 2 files changed, 12 insertions(+), 12 deletions(-) rename scripts/release/{package-dmg.sh => package-zip.sh} (54%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11e9d35..890b3a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,23 +148,21 @@ jobs: working-directory: ${{ env.WORKING_DIRECTORY }} run: bash ./scripts/release/package-deb.sh - # Package as .dmg for macOS - - name: Package as .dmg for macOS + # Package as .zip for macOS + - name: Package as .zip for macOS if: runner.os == 'macOS' working-directory: ${{ env.WORKING_DIRECTORY }} - run: bash ./scripts/release/package-dmg.sh + run: bash ./scripts/release/package-zip.sh # Upload build assets - uses: actions/upload-artifact@v4 with: name: ${{ matrix.build.name }} (${{ runner.os }}) path: | - */bin/*/Contents/MacOS/* */bin/*.exe *\bin\*.exe */spinup-*.deb - */*.zip - */*.dmg + */spinup-*.zip - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') @@ -174,4 +172,3 @@ jobs: */bin/*.exe */spinup-*.deb */spinup-*.zip - */spinup-*.dmg diff --git a/scripts/release/package-dmg.sh b/scripts/release/package-zip.sh similarity index 54% rename from scripts/release/package-dmg.sh rename to scripts/release/package-zip.sh index 0d4f7d7..acad0ad 100644 --- a/scripts/release/package-dmg.sh +++ b/scripts/release/package-zip.sh @@ -11,12 +11,15 @@ BIN_FILE="./build/bin/Spinup.app/Contents/MacOS/spinup" mkdir -p $MAC_OS_FOLDER # Copy the contents of the build/unix directory to the MacOS directory -cp -r "./build/unix/" $MAC_OS_FOLDER +cp -r "./build/unix/." $MAC_OS_FOLDER # Copy the binary file to the MacOS directory -cp $BIN_FILE "$MAC_OS_FOLDER/usr/share/spinup/bin/spinup" +mkdir -p "$MAC_OS_FOLDER/usr/share/spinup/bin" +cp $BIN_FILE "$MAC_OS_FOLDER/usr/share/spinup/bin" -# Create a zip file containing the contents of the MacOS directory -zip -r "spinup-${SPINUP_VERSION}-macos.zip" $MAC_OS_FOLDER +# Copy postinstall script to the MacOS directory +cp "./build/DEBIAN/postinst" $MAC_OS_FOLDER +mv "$MAC_OS_FOLDER/postinst" "$MAC_OS_FOLDER/post_install.sh" -hdiutil create -volname "Spinup" -srcfolder $MAC_OS_FOLDER -ov -format UDZO "spinup-${SPINUP_VERSION}.dmg" +# Create a zip file containing the contents of the MacOS directory +(cd $MAC_OS_FOLDER && zip -r "../../spinup-${SPINUP_VERSION}-macos.zip" .) From 05ec76aa579998ac7131bb702d3def15249eda1d Mon Sep 17 00:00:00 2001 From: Iskander Date: Sun, 19 Jan 2025 12:09:33 +0100 Subject: [PATCH 15/15] Add execution permission to release packaging scripts and try to match any zip file --- .github/workflows/release.yml | 5 ++--- scripts/release/package-deb.sh | 0 scripts/release/package-zip.sh | 18 +++++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) mode change 100644 => 100755 scripts/release/package-deb.sh mode change 100644 => 100755 scripts/release/package-zip.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 890b3a3..dec2302 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -162,13 +162,12 @@ jobs: */bin/*.exe *\bin\*.exe */spinup-*.deb - */spinup-*.zip + *.zip - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: | - */bin/*/Contents/MacOS/* */bin/*.exe */spinup-*.deb - */spinup-*.zip + *.zip diff --git a/scripts/release/package-deb.sh b/scripts/release/package-deb.sh old mode 100644 new mode 100755 diff --git a/scripts/release/package-zip.sh b/scripts/release/package-zip.sh old mode 100644 new mode 100755 index acad0ad..b408e27 --- a/scripts/release/package-zip.sh +++ b/scripts/release/package-zip.sh @@ -2,24 +2,24 @@ SPINUP_VERSION=$(cat ./common/.version | sed 's/^v//') -MAC_OS_FOLDER="./build/MacOS" +MAC_OS_DIR="./build/macos" # Define the path to the binary file BIN_FILE="./build/bin/Spinup.app/Contents/MacOS/spinup" -# Create a MacOS directory -mkdir -p $MAC_OS_FOLDER +# Create the mac os directory +mkdir -p $MAC_OS_DIR # Copy the contents of the build/unix directory to the MacOS directory -cp -r "./build/unix/." $MAC_OS_FOLDER +cp -r "./build/unix/." $MAC_OS_DIR # Copy the binary file to the MacOS directory -mkdir -p "$MAC_OS_FOLDER/usr/share/spinup/bin" -cp $BIN_FILE "$MAC_OS_FOLDER/usr/share/spinup/bin" +mkdir -p "$MAC_OS_DIR/usr/share/spinup/bin" +cp $BIN_FILE "$MAC_OS_DIR/usr/share/spinup/bin" # Copy postinstall script to the MacOS directory -cp "./build/DEBIAN/postinst" $MAC_OS_FOLDER -mv "$MAC_OS_FOLDER/postinst" "$MAC_OS_FOLDER/post_install.sh" +cp "./build/DEBIAN/postinst" $MAC_OS_DIR +mv "$MAC_OS_DIR/postinst" "$MAC_OS_DIR/post_install.sh" # Create a zip file containing the contents of the MacOS directory -(cd $MAC_OS_FOLDER && zip -r "../../spinup-${SPINUP_VERSION}-macos.zip" .) +(cd $MAC_OS_DIR && zip -r "../../spinup-${SPINUP_VERSION}-macos.zip" .)