Deployement updates

This commit is contained in:
anonpenguin23 2026-02-01 12:01:31 +02:00
parent b5109f1ee8
commit 9282fe64ee
7 changed files with 147 additions and 39 deletions

View File

@ -17,14 +17,34 @@ import (
"github.com/stretchr/testify/require"
)
func TestNamespaceIsolation_Deployments(t *testing.T) {
// Setup two test environments with different namespaces
// TestNamespaceIsolation creates two namespaces once and runs all isolation
// subtests against them. This keeps namespace usage to 2 regardless of how
// many isolation scenarios we test.
func TestNamespaceIsolation(t *testing.T) {
envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Failed to create namespace A environment")
envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Failed to create namespace B environment")
t.Run("Deployments", func(t *testing.T) {
testNamespaceIsolationDeployments(t, envA, envB)
})
t.Run("SQLiteDatabases", func(t *testing.T) {
testNamespaceIsolationSQLiteDatabases(t, envA, envB)
})
t.Run("IPFSContent", func(t *testing.T) {
testNamespaceIsolationIPFSContent(t, envA, envB)
})
t.Run("OlricCache", func(t *testing.T) {
testNamespaceIsolationOlricCache(t, envA, envB)
})
}
func testNamespaceIsolationDeployments(t *testing.T, envA, envB *e2e.E2ETestEnv) {
tarballPath := filepath.Join("../../testdata/apps/react-app")
// Create deployment in namespace-a
@ -112,13 +132,7 @@ func TestNamespaceIsolation_Deployments(t *testing.T) {
})
}
func TestNamespaceIsolation_SQLiteDatabases(t *testing.T) {
envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Should create test environment for namespace-a")
envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Should create test environment for namespace-b")
func testNamespaceIsolationSQLiteDatabases(t *testing.T, envA, envB *e2e.E2ETestEnv) {
// Create database in namespace-a
dbNameA := "users-db-a"
e2e.CreateSQLiteDB(t, envA, dbNameA)
@ -201,13 +215,7 @@ func TestNamespaceIsolation_SQLiteDatabases(t *testing.T) {
})
}
func TestNamespaceIsolation_IPFSContent(t *testing.T) {
envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Should create test environment for namespace-a")
envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Should create test environment for namespace-b")
func testNamespaceIsolationIPFSContent(t *testing.T, envA, envB *e2e.E2ETestEnv) {
// Upload file in namespace-a
cidA := e2e.UploadTestFile(t, envA, "test-file-a.txt", "Content from namespace A")
defer func() {
@ -217,8 +225,6 @@ func TestNamespaceIsolation_IPFSContent(t *testing.T) {
}()
t.Run("Namespace-B cannot GET Namespace-A IPFS content", func(t *testing.T) {
// This tests application-level access control
// IPFS content is globally accessible by CID, but our handlers should enforce namespace
req, _ := http.NewRequest("GET", envB.GatewayURL+"/v1/storage/get/"+cidA, nil)
req.Header.Set("Authorization", "Bearer "+envB.APIKey)
@ -226,7 +232,6 @@ func TestNamespaceIsolation_IPFSContent(t *testing.T) {
require.NoError(t, err, "Should execute request")
defer resp.Body.Close()
// Should return 403 or 404 (namespace doesn't own this CID)
assert.Contains(t, []int{http.StatusNotFound, http.StatusForbidden}, resp.StatusCode,
"Should block cross-namespace IPFS GET")
@ -273,13 +278,7 @@ func TestNamespaceIsolation_IPFSContent(t *testing.T) {
})
}
func TestNamespaceIsolation_OlricCache(t *testing.T) {
envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Should create test environment for namespace-a")
envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
require.NoError(t, err, "Should create test environment for namespace-b")
func testNamespaceIsolationOlricCache(t *testing.T, envA, envB *e2e.E2ETestEnv) {
dmap := "test-cache"
keyA := "user-session-123"
valueA := `{"user_id": "alice", "token": "secret-token-a"}`
@ -342,7 +341,6 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
require.NoError(t, err, "Should execute request")
defer resp.Body.Close()
// Should return 404 or success (key doesn't exist in their namespace)
assert.Contains(t, []int{http.StatusOK, http.StatusNotFound}, resp.StatusCode)
// Verify key still exists for namespace-a

View File

@ -201,18 +201,60 @@ func CollectPortsForServices(services []string, skipActive bool) ([]PortSpec, er
return ports, nil
}
// EnsurePortsAvailable checks if the specified ports are available
// EnsurePortsAvailable checks if the specified ports are available.
// If a port is in use, it identifies the process and gives actionable guidance.
func EnsurePortsAvailable(action string, ports []PortSpec) error {
var conflicts []string
for _, spec := range ports {
ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", spec.Port))
if err != nil {
if errors.Is(err, syscall.EADDRINUSE) || strings.Contains(err.Error(), "address already in use") {
return fmt.Errorf("%s cannot continue: %s (port %d) is already in use", action, spec.Name, spec.Port)
processInfo := identifyPortProcess(spec.Port)
conflicts = append(conflicts, fmt.Sprintf(" - %s (port %d): %s", spec.Name, spec.Port, processInfo))
continue
}
return fmt.Errorf("%s cannot continue: failed to inspect %s (port %d): %w", action, spec.Name, spec.Port, err)
}
_ = ln.Close()
}
if len(conflicts) > 0 {
msg := fmt.Sprintf("%s cannot continue: the following ports are already in use:\n%s\n\n", action, strings.Join(conflicts, "\n"))
msg += "Please stop the conflicting services before running this command.\n"
msg += "Common fixes:\n"
msg += " - Docker: sudo systemctl stop docker docker.socket\n"
msg += " - Old IPFS: sudo systemctl stop ipfs\n"
msg += " - systemd-resolved: already handled by installer (port 53)\n"
msg += " - Other services: sudo kill <PID> or sudo systemctl stop <service>"
return fmt.Errorf(msg)
}
return nil
}
// identifyPortProcess uses ss/lsof to find what process is using a port
func identifyPortProcess(port int) string {
// Try ss first (available on most Linux)
out, err := exec.Command("ss", "-tlnp", fmt.Sprintf("sport = :%d", port)).CombinedOutput()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
for _, line := range lines {
if strings.Contains(line, "users:") {
// Extract process info from ss output like: users:(("docker-proxy",pid=2049,fd=4))
if idx := strings.Index(line, "users:"); idx != -1 {
return strings.TrimSpace(line[idx:])
}
}
}
}
// Fallback: try lsof
out, err = exec.Command("lsof", "-i", fmt.Sprintf(":%d", port), "-sTCP:LISTEN", "-n", "-P").CombinedOutput()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
if len(lines) > 1 {
return strings.TrimSpace(lines[1]) // first data line after header
}
}
return "unknown process"
}

View File

@ -1,6 +1,7 @@
package production
import (
"fmt"
"io"
"os/exec"
@ -117,8 +118,12 @@ func (bi *BinaryInstaller) InstallAnyoneClient() error {
return bi.gateway.InstallAnyoneClient()
}
// InstallCoreDNS builds and installs CoreDNS with the custom RQLite plugin
// InstallCoreDNS builds and installs CoreDNS with the custom RQLite plugin.
// Also disables systemd-resolved's stub listener so CoreDNS can bind to port 53.
func (bi *BinaryInstaller) InstallCoreDNS() error {
if err := bi.coredns.DisableResolvedStubListener(); err != nil {
fmt.Fprintf(bi.logWriter, " ⚠️ Failed to disable systemd-resolved stub: %v\n", err)
}
return bi.coredns.Install()
}

View File

@ -171,8 +171,17 @@ func (ari *AnyoneRelayInstaller) Install() error {
return fmt.Errorf("failed to add Anyone repository: %w", err)
}
// Install the anon package
cmd := exec.Command("apt-get", "install", "-y", "anon")
// Pre-accept terms via debconf to avoid interactive prompt during apt install.
// The anon package preinst script checks "anon/terms" via debconf.
preseed := exec.Command("bash", "-c", `echo "anon anon/terms boolean true" | debconf-set-selections`)
if output, err := preseed.CombinedOutput(); err != nil {
fmt.Fprintf(ari.logWriter, " ⚠️ debconf preseed warning: %v (%s)\n", err, string(output))
}
// Install the anon package non-interactively.
// --force-confold keeps existing config files if present (e.g. during migration).
cmd := exec.Command("apt-get", "install", "-y", "-o", "Dpkg::Options::=--force-confold", "anon")
cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to install anon package: %w\n%s", err, string(output))
}
@ -180,6 +189,11 @@ func (ari *AnyoneRelayInstaller) Install() error {
// Clean up
os.Remove(installScript)
// Stop and disable the default 'anon' systemd service that the apt package
// auto-enables. We use our own 'debros-anyone-relay' service instead.
exec.Command("systemctl", "stop", "anon").Run()
exec.Command("systemctl", "disable", "anon").Run()
fmt.Fprintf(ari.logWriter, " ✓ Anyone relay binary installed\n")
// Install nyx for relay monitoring (connects to ControlPort 9051)

View File

@ -54,6 +54,46 @@ func (ci *CoreDNSInstaller) IsInstalled() bool {
}
// Install builds and installs CoreDNS with the custom RQLite plugin
// DisableResolvedStubListener disables systemd-resolved's DNS stub listener
// so CoreDNS can bind to port 53. This is required on Ubuntu/Debian systems
// where systemd-resolved listens on 127.0.0.53:53 by default.
func (ci *CoreDNSInstaller) DisableResolvedStubListener() error {
// Check if systemd-resolved is running
if err := exec.Command("systemctl", "is-active", "--quiet", "systemd-resolved").Run(); err != nil {
return nil // Not running, nothing to do
}
fmt.Fprintf(ci.logWriter, " Disabling systemd-resolved DNS stub listener (for CoreDNS)...\n")
// Disable the stub listener
resolvedConf := "/etc/systemd/resolved.conf.d/no-stub.conf"
if err := os.MkdirAll("/etc/systemd/resolved.conf.d", 0755); err != nil {
return fmt.Errorf("failed to create resolved.conf.d: %w", err)
}
conf := "[Resolve]\nDNSStubListener=no\n"
if err := os.WriteFile(resolvedConf, []byte(conf), 0644); err != nil {
return fmt.Errorf("failed to write resolved config: %w", err)
}
// Point resolv.conf to localhost (CoreDNS) and a fallback
resolvConf := "nameserver 127.0.0.1\nnameserver 8.8.8.8\n"
if err := os.Remove("/etc/resolv.conf"); err != nil && !os.IsNotExist(err) {
// It might be a symlink
fmt.Fprintf(ci.logWriter, " ⚠️ Could not remove /etc/resolv.conf: %v\n", err)
}
if err := os.WriteFile("/etc/resolv.conf", []byte(resolvConf), 0644); err != nil {
return fmt.Errorf("failed to write resolv.conf: %w", err)
}
// Restart systemd-resolved
if output, err := exec.Command("systemctl", "restart", "systemd-resolved").CombinedOutput(); err != nil {
fmt.Fprintf(ci.logWriter, " ⚠️ Failed to restart systemd-resolved: %v (%s)\n", err, string(output))
}
fmt.Fprintf(ci.logWriter, " ✓ systemd-resolved stub listener disabled\n")
return nil
}
func (ci *CoreDNSInstaller) Install() error {
if ci.IsInstalled() {
fmt.Fprintf(ci.logWriter, " ✓ CoreDNS with RQLite plugin already installed\n")

View File

@ -222,14 +222,21 @@ func (gi *GatewayInstaller) InstallDeBrosBinaries(branch string, oramaHome strin
// InstallGo downloads and installs Go toolchain
func (gi *GatewayInstaller) InstallGo() error {
if _, err := exec.LookPath("go"); err == nil {
fmt.Fprintf(gi.logWriter, " ✓ Go already installed\n")
return nil
requiredVersion := "1.22.5"
if goPath, err := exec.LookPath("go"); err == nil {
// Check version - upgrade if too old
out, _ := exec.Command(goPath, "version").Output()
if strings.Contains(string(out), "go"+requiredVersion) || strings.Contains(string(out), "go1.23") || strings.Contains(string(out), "go1.24") {
fmt.Fprintf(gi.logWriter, " ✓ Go already installed (%s)\n", strings.TrimSpace(string(out)))
return nil
}
fmt.Fprintf(gi.logWriter, " Upgrading Go (current: %s, need >= %s)...\n", strings.TrimSpace(string(out)), requiredVersion)
os.RemoveAll("/usr/local/go")
} else {
fmt.Fprintf(gi.logWriter, " Installing Go...\n")
}
fmt.Fprintf(gi.logWriter, " Installing Go...\n")
goTarball := fmt.Sprintf("go1.22.5.linux-%s.tar.gz", gi.arch)
goTarball := fmt.Sprintf("go%s.linux-%s.tar.gz", requiredVersion, gi.arch)
goURL := fmt.Sprintf("https://go.dev/dl/%s", goTarball)
// Download

View File

@ -640,9 +640,11 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
// Caddy service (for SSL/TLS with DNS-01 ACME challenges)
if _, err := os.Stat("/usr/bin/caddy"); err == nil {
// Create caddy user if it doesn't exist
exec.Command("useradd", "-r", "-s", "/sbin/nologin", "caddy").Run()
exec.Command("useradd", "-r", "-m", "-d", "/home/caddy", "-s", "/sbin/nologin", "caddy").Run()
exec.Command("mkdir", "-p", "/var/lib/caddy").Run()
exec.Command("chown", "caddy:caddy", "/var/lib/caddy").Run()
exec.Command("mkdir", "-p", "/home/caddy").Run()
exec.Command("chown", "caddy:caddy", "/home/caddy").Run()
caddyUnit := ps.serviceGenerator.GenerateCaddyService()
if err := ps.serviceController.WriteServiceUnit("caddy.service", caddyUnit); err != nil {