Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
# [0.1.0-alpha.67](https://github.com/raghavyuva/nixopus/compare/v0.1.0-alpha.66...v0.1.0-alpha.67) (2025-11-21)


### Features

* compose as extensions ([#555](https://github.com/raghavyuva/nixopus/issues/555)) ([741aa6a](https://github.com/raghavyuva/nixopus/commit/741aa6ab30520f46cc796c6510ea9c2551c4fd8e))



# [0.1.0-alpha.66](https://github.com/raghavyuva/nixopus/compare/v0.1.0-alpha.65...v0.1.0-alpha.66) (2025-11-21)


### Bug Fixes

* allow custom ports on install setup optionally ([#580](https://github.com/raghavyuva/nixopus/issues/580)) ([972c7ac](https://github.com/raghavyuva/nixopus/commit/972c7ac4ea2aedd7810954772c4d16d7226182d6))



# [0.1.0-alpha.65](https://github.com/raghavyuva/nixopus/compare/v0.1.0-alpha.64...v0.1.0-alpha.65) (2025-11-09)


Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ You can also install the CLI and run `nixopus install` with options in a single

Nixopus is derived from the combination of "octopus" and the Linux penguin (Tux). While the name might suggest a connection to [NixOS](https://nixos.org/), Nixopus is an independent project with no direct relation to NixOS or its ecosystem.

## Sponsors

Thank you so much for helping and supporting Nixopus to grow and evolve.

| <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRgIGjpjf-iao9c_pb2udOflQRZucZ4e62kNw&s" alt="letscloud.io" width="30%"> <br/> **[Lets Cloud](https://www.letscloud.io/)** | <img src="https://media.licdn.com/dms/image/v2/D4D0BAQFC-65aZcu-8g/company-logo_200_200/company-logo_200_200/0/1711678709605/hostup_logo?e=2147483647&v=beta&t=KEegISV4VeuBUSWlLQz4h-kM2BLJz0tGhmOUtrp8two" alt="hostup" width="30%"> <br/> **[HostUp](https://hostup.se/en/)** |
| :---: | :---: |

## Contributors

<a href="https://github.com/raghavyuva/nixopus/graphs/contributors">
Expand Down
3 changes: 2 additions & 1 deletion api/internal/features/deploy/docker/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ func (s *DockerService) ComposeUp(composeFilePath string, envVars map[string]str
for k, v := range envVars {
envVarsStr += fmt.Sprintf("export %s=%s && ", k, v)
}
command := fmt.Sprintf("%sdocker compose -f %s up -d", envVarsStr, composeFilePath)
// Use --force-recreate to handle existing containers and --remove-orphans to clean up old containers
command := fmt.Sprintf("%sdocker compose -f %s up -d --force-recreate --remove-orphans 2>&1", envVarsStr, composeFilePath)
output, err := client.RunCommand(command)
if err != nil {
return fmt.Errorf("failed to start docker compose services: %v, output: %s", err, output)
Expand Down
4 changes: 3 additions & 1 deletion api/internal/features/extension/engine/docker_compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ type dockerComposeModule struct{}
func (dockerComposeModule) Type() string { return "docker_compose" }

func (dockerComposeModule) Execute(_ *ssh.SSH, step types.SpecStep, vars map[string]interface{}) (string, func(), error) {
file, _ := step.Properties["file"].(string)
fileRaw, _ := step.Properties["file"].(string)
action, _ := step.Properties["action"].(string) // up, down, pull, build, restart
_, _ = step.Properties["project"].(string)
_, _ = step.Properties["args"].(string)
revertCmdRaw, _ := step.Properties["revert_cmd"].(string)
_, _ = step.Properties["user"].(string)

file := replaceVars(fileRaw, vars)

if action == "" {
return "", nil, fmt.Errorf("docker_compose action is required")
}
Expand Down
31 changes: 29 additions & 2 deletions api/internal/features/extension/engine/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"strings"

"github.com/pkg/sftp"
"github.com/raghavyuva/nixopus-api/internal/features/ssh"
Expand Down Expand Up @@ -108,10 +109,36 @@ var actionHandlers = map[string]fileAction{
"mkdir": handleMkdir,
}

// expandTilde expands ~ to the actual home directory path for SFTP compatibility
func expandTilde(path string, sshClient *ssh.SSH) string {
if len(path) > 0 && path[0] == '~' {
if len(path) == 1 || path[1] == '/' {
// Get home directory via command
homeOutput, err := sshClient.RunCommand("echo $HOME")
if err == nil && len(homeOutput) > 0 {
home := strings.TrimSpace(homeOutput)
if len(path) == 1 {
return home
}
return home + path[1:]
}
}
}
return path
}

func (fileModule) Execute(sshClient *ssh.SSH, step types.SpecStep, vars map[string]interface{}) (string, func(), error) {
action, _ := step.Properties["action"].(string)
src, _ := step.Properties["src"].(string)
dest, _ := step.Properties["dest"].(string)
srcRaw, _ := step.Properties["src"].(string)
destRaw, _ := step.Properties["dest"].(string)

// Replace variables in src and dest paths
src := replaceVars(srcRaw, vars)
dest := replaceVars(destRaw, vars)

// Expand tilde to $HOME for SFTP compatibility
src = expandTilde(src, sshClient)
dest = expandTilde(dest, sshClient)

if action == "mkdir" && dest == "" {
return "", nil, fmt.Errorf("dest is required for mkdir action")
Expand Down
8 changes: 6 additions & 2 deletions api/internal/features/extension/engine/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ type packageModule struct{}
func (packageModule) Type() string { return "package" }

func (packageModule) Execute(sshClient *ssh.SSH, step types.SpecStep, vars map[string]interface{}) (string, func(), error) {
name, _ := step.Properties["name"].(string)
state, _ := step.Properties["state"].(string)
nameRaw, _ := step.Properties["name"].(string)
stateRaw, _ := step.Properties["state"].(string)

name := replaceVars(nameRaw, vars)
state := replaceVars(stateRaw, vars)

if name == "" {
return "", nil, fmt.Errorf("package name is required")
}
Expand Down
8 changes: 6 additions & 2 deletions api/internal/features/extension/engine/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ type serviceModule struct{}
func (serviceModule) Type() string { return "service" }

func (serviceModule) Execute(sshClient *ssh.SSH, step types.SpecStep, vars map[string]interface{}) (string, func(), error) {
name, _ := step.Properties["name"].(string)
nameRaw, _ := step.Properties["name"].(string)
action, _ := step.Properties["action"].(string)
revertAction, _ := step.Properties["revert_action"].(string)
runAsUser, _ := step.Properties["user"].(string)
runAsUserRaw, _ := step.Properties["user"].(string)

name := replaceVars(nameRaw, vars)
runAsUser := replaceVars(runAsUserRaw, vars)

if name == "" {
return "", nil, fmt.Errorf("service name is required for service step")
}
Expand Down
13 changes: 9 additions & 4 deletions api/internal/features/extension/engine/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ type userModule struct{}
func (userModule) Type() string { return "user" }

func (userModule) Execute(sshClient *ssh.SSH, step types.SpecStep, vars map[string]interface{}) (string, func(), error) {
username, _ := step.Properties["username"].(string)
usernameRaw, _ := step.Properties["username"].(string)
action, _ := step.Properties["action"].(string)
shell, _ := step.Properties["shell"].(string)
home, _ := step.Properties["home"].(string)
groups, _ := step.Properties["groups"].(string)
shellRaw, _ := step.Properties["shell"].(string)
homeRaw, _ := step.Properties["home"].(string)
groupsRaw, _ := step.Properties["groups"].(string)
revertAction, _ := step.Properties["revert_action"].(string)

username := replaceVars(usernameRaw, vars)
shell := replaceVars(shellRaw, vars)
home := replaceVars(homeRaw, vars)
groups := replaceVars(groupsRaw, vars)

if username == "" {
return "", nil, fmt.Errorf("username is required for user operations")
}
Expand Down
5 changes: 3 additions & 2 deletions api/internal/features/ssh/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,11 @@ func (s *SSH) RunCommand(cmd string) (string, error) {
if err != nil {
return "", err
}
output, err := client.Run(cmd)
defer client.Close()

output, err := client.Run(cmd)
if err != nil {
return "", err
return string(output), err
}

return string(output), nil
Expand Down
Loading
Loading