Skip to content
Merged
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
9 changes: 7 additions & 2 deletions agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The agent downloads the release binary, verifies its checksum, installs it, and
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install wireguard wireguard-tools podman -y
sudo systemctl enable --now podman.socket

curl -sSL https://railpack.com/install.sh | sh
sudo ln -s ~/.railpack/bin/railpack /usr/local/bin/railpack
Expand All @@ -67,6 +68,7 @@ curl -sSL https://github.com/moby/buildkit/releases/download/v0.26.3/buildkit-v0
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install wireguard wireguard-tools podman -y
sudo systemctl enable --now podman.socket

curl -sSL https://railpack.com/install.sh | sh
sudo ln -s ~/.railpack/bin/railpack /usr/local/bin/railpack
Expand Down Expand Up @@ -191,7 +193,8 @@ Worker node:
```ini
[Unit]
Description=Techulus Cloud Agent
After=network.target buildkitd.service
After=network.target podman.socket buildkitd.service
Wants=podman.socket

[Service]
Type=simple
Expand All @@ -208,7 +211,8 @@ Proxy node:
```ini
[Unit]
Description=Techulus Cloud Agent
After=network.target traefik.service buildkitd.service
After=network.target podman.socket traefik.service buildkitd.service
Wants=podman.socket

[Service]
Type=simple
Expand All @@ -222,6 +226,7 @@ WantedBy=multi-user.target
```

`KillMode=process` ensures only the agent process is killed on restart, not container processes.
The rootful Podman API socket at `/run/podman/podman.sock` is required for container metrics collection.

```bash
sudo systemctl daemon-reload
Expand Down
34 changes: 34 additions & 0 deletions agent/internal/container/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,40 @@ func CheckPrerequisites() error {
if _, err := exec.LookPath("podman"); err != nil {
return fmt.Errorf("podman not found: %w", err)
}
return ensurePodmanSocket(podmanSocketPath, func() ([]byte, error) {
return exec.Command("systemctl", "enable", "--now", "podman.socket").CombinedOutput()
})
}

func ensurePodmanSocket(socketPath string, enable func() ([]byte, error)) error {
if err := validatePodmanSocket(socketPath); err == nil {
return nil
}

output, err := enable()
if err != nil {
if len(output) > 4*1024 {
output = output[:4*1024]
}
if detail := strings.TrimSpace(string(output)); detail != "" {
return fmt.Errorf("failed to enable podman.socket: %s: %w", detail, err)
}
return fmt.Errorf("failed to enable podman.socket: %w", err)
}
if err := validatePodmanSocket(socketPath); err != nil {
return fmt.Errorf("podman API socket unavailable after enabling podman.socket: %w", err)
}
return nil
}

func validatePodmanSocket(socketPath string) error {
socket, err := os.Stat(socketPath)
if err != nil {
return fmt.Errorf("podman API socket unavailable at %s: %w", socketPath, err)
}
if socket.Mode()&os.ModeSocket == 0 {
return fmt.Errorf("podman API endpoint at %s is not a Unix socket", socketPath)
}
return nil
}

Expand Down
72 changes: 72 additions & 0 deletions agent/internal/container/runtime_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package container

import (
"errors"
"net"
"os"
"path/filepath"
"slices"
"strings"
"testing"
)

Expand Down Expand Up @@ -104,3 +109,70 @@ func TestBuildPodmanRunArgsDoesNotPublishStaticIPPortsByDefault(t *testing.T) {
t.Fatalf("args unexpectedly publish ports: %+v", args)
}
}

func TestEnsurePodmanSocketDoesNotEnableExistingSocket(t *testing.T) {
socketPath := testPodmanSocketPath(t)
listener, err := net.Listen("unix", socketPath)
if err != nil {
t.Fatalf("listen on test socket: %v", err)
}
defer listener.Close()

called := false
err = ensurePodmanSocket(socketPath, func() ([]byte, error) {
called = true
return nil, nil
})
if err != nil {
t.Fatalf("ensure socket: %v", err)
}
if called {
t.Fatal("activation called for an existing socket")
}
}

func TestEnsurePodmanSocketRepairsMissingSocket(t *testing.T) {
socketPath := testPodmanSocketPath(t)
var listener net.Listener
err := ensurePodmanSocket(socketPath, func() ([]byte, error) {
var err error
listener, err = net.Listen("unix", socketPath)
return nil, err
})
if listener != nil {
defer listener.Close()
}
if err != nil {
t.Fatalf("ensure socket: %v", err)
}
}

func TestEnsurePodmanSocketReportsActivationFailure(t *testing.T) {
socketPath := testPodmanSocketPath(t)
err := ensurePodmanSocket(socketPath, func() ([]byte, error) {
return []byte("permission denied"), errors.New("exit status 1")
})
if err == nil || !strings.Contains(err.Error(), "failed to enable podman.socket: permission denied") {
t.Fatalf("unexpected error: %v", err)
}
}

func TestEnsurePodmanSocketReportsInvalidSocketAfterActivation(t *testing.T) {
socketPath := testPodmanSocketPath(t)
err := ensurePodmanSocket(socketPath, func() ([]byte, error) {
return nil, os.WriteFile(socketPath, nil, 0o600)
})
if err == nil || !strings.Contains(err.Error(), "is not a Unix socket") {
t.Fatalf("unexpected error: %v", err)
}
}

func testPodmanSocketPath(t *testing.T) string {
t.Helper()
dir, err := os.MkdirTemp("/tmp", "podman-socket-")
if err != nil {
t.Fatalf("create socket test directory: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(dir) })
return filepath.Join(dir, "podman.sock")
}
Loading
Loading