Fixed IPFS systemd service and deploy issue on nextjs

This commit is contained in:
anonpenguin23 2026-01-29 08:38:33 +02:00
parent 4b24b0aa6c
commit 571f8babb4
3 changed files with 239 additions and 18 deletions

View File

@ -10,6 +10,7 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
@ -94,6 +95,14 @@ func init() {
func deployStatic(cmd *cobra.Command, args []string) error { func deployStatic(cmd *cobra.Command, args []string) error {
sourcePath := args[0] sourcePath := args[0]
// Warn if source looks like it needs building
if _, err := os.Stat(filepath.Join(sourcePath, "package.json")); err == nil {
if _, err := os.Stat(filepath.Join(sourcePath, "index.html")); os.IsNotExist(err) {
fmt.Printf("⚠️ Warning: %s has package.json but no index.html. You may need to build first.\n", sourcePath)
fmt.Printf(" Try: cd %s && npm run build, then deploy the output directory (e.g. dist/ or out/)\n\n", sourcePath)
}
}
fmt.Printf("📦 Creating tarball from %s...\n", sourcePath) fmt.Printf("📦 Creating tarball from %s...\n", sourcePath)
tarball, err := createTarball(sourcePath) tarball, err := createTarball(sourcePath)
if err != nil { if err != nil {
@ -123,10 +132,67 @@ func deployStatic(cmd *cobra.Command, args []string) error {
} }
func deployNextJS(cmd *cobra.Command, args []string) error { func deployNextJS(cmd *cobra.Command, args []string) error {
sourcePath := args[0] sourcePath, err := filepath.Abs(args[0])
if err != nil {
return fmt.Errorf("failed to resolve path: %w", err)
}
fmt.Printf("📦 Creating tarball from %s...\n", sourcePath) // Verify it's a Next.js project
tarball, err := createTarball(sourcePath) if _, err := os.Stat(filepath.Join(sourcePath, "package.json")); os.IsNotExist(err) {
return fmt.Errorf("no package.json found in %s", sourcePath)
}
// Step 1: Install dependencies if needed
if _, err := os.Stat(filepath.Join(sourcePath, "node_modules")); os.IsNotExist(err) {
fmt.Printf("📦 Installing dependencies...\n")
if err := runBuildCommand(sourcePath, "npm", "install"); err != nil {
return fmt.Errorf("npm install failed: %w", err)
}
}
// Step 2: Build
fmt.Printf("🔨 Building Next.js application...\n")
if err := runBuildCommand(sourcePath, "npm", "run", "build"); err != nil {
return fmt.Errorf("build failed: %w", err)
}
var tarball string
if deploySSR {
// SSR: tarball the standalone output
standalonePath := filepath.Join(sourcePath, ".next", "standalone")
if _, err := os.Stat(standalonePath); os.IsNotExist(err) {
return fmt.Errorf(".next/standalone/ not found. Ensure next.config.js has output: 'standalone'")
}
// Copy static assets into standalone
staticSrc := filepath.Join(sourcePath, ".next", "static")
staticDst := filepath.Join(standalonePath, ".next", "static")
if _, err := os.Stat(staticSrc); err == nil {
if err := copyDir(staticSrc, staticDst); err != nil {
return fmt.Errorf("failed to copy static assets: %w", err)
}
}
// Copy public directory if it exists
publicSrc := filepath.Join(sourcePath, "public")
publicDst := filepath.Join(standalonePath, "public")
if _, err := os.Stat(publicSrc); err == nil {
if err := copyDir(publicSrc, publicDst); err != nil {
return fmt.Errorf("failed to copy public directory: %w", err)
}
}
fmt.Printf("📦 Creating tarball from standalone output...\n")
tarball, err = createTarballAll(standalonePath)
} else {
// Static export: tarball the out/ directory
outPath := filepath.Join(sourcePath, "out")
if _, err := os.Stat(outPath); os.IsNotExist(err) {
return fmt.Errorf("out/ directory not found. For static export, ensure next.config.js has output: 'export'")
}
fmt.Printf("📦 Creating tarball from static export...\n")
tarball, err = createTarball(outPath)
}
if err != nil { if err != nil {
return fmt.Errorf("failed to create tarball: %w", err) return fmt.Errorf("failed to create tarball: %w", err)
} }
@ -159,10 +225,30 @@ func deployNextJS(cmd *cobra.Command, args []string) error {
} }
func deployGo(cmd *cobra.Command, args []string) error { func deployGo(cmd *cobra.Command, args []string) error {
sourcePath := args[0] sourcePath, err := filepath.Abs(args[0])
if err != nil {
return fmt.Errorf("failed to resolve path: %w", err)
}
fmt.Printf("📦 Creating tarball from %s...\n", sourcePath) // Verify it's a Go project
tarball, err := createTarball(sourcePath) if _, err := os.Stat(filepath.Join(sourcePath, "go.mod")); os.IsNotExist(err) {
return fmt.Errorf("no go.mod found in %s", sourcePath)
}
// Cross-compile for Linux amd64 (production VPS target)
fmt.Printf("🔨 Building Go binary (linux/amd64)...\n")
buildCmd := exec.Command("go", "build", "-o", "app", ".")
buildCmd.Dir = sourcePath
buildCmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
if err := buildCmd.Run(); err != nil {
return fmt.Errorf("go build failed: %w", err)
}
defer os.Remove(filepath.Join(sourcePath, "app")) // Clean up after tarball
fmt.Printf("📦 Creating tarball...\n")
tarball, err := createTarballFiles(sourcePath, []string{"app"})
if err != nil { if err != nil {
return fmt.Errorf("failed to create tarball: %w", err) return fmt.Errorf("failed to create tarball: %w", err)
} }
@ -190,9 +276,33 @@ func deployGo(cmd *cobra.Command, args []string) error {
} }
func deployNodeJS(cmd *cobra.Command, args []string) error { func deployNodeJS(cmd *cobra.Command, args []string) error {
sourcePath := args[0] sourcePath, err := filepath.Abs(args[0])
if err != nil {
return fmt.Errorf("failed to resolve path: %w", err)
}
fmt.Printf("📦 Creating tarball from %s...\n", sourcePath) // Verify it's a Node.js project
if _, err := os.Stat(filepath.Join(sourcePath, "package.json")); os.IsNotExist(err) {
return fmt.Errorf("no package.json found in %s", sourcePath)
}
// Install dependencies if needed
if _, err := os.Stat(filepath.Join(sourcePath, "node_modules")); os.IsNotExist(err) {
fmt.Printf("📦 Installing dependencies...\n")
if err := runBuildCommand(sourcePath, "npm", "install", "--production"); err != nil {
return fmt.Errorf("npm install failed: %w", err)
}
}
// Run build script if it exists
if hasBuildScript(sourcePath) {
fmt.Printf("🔨 Building...\n")
if err := runBuildCommand(sourcePath, "npm", "run", "build"); err != nil {
return fmt.Errorf("build failed: %w", err)
}
}
fmt.Printf("📦 Creating tarball...\n")
tarball, err := createTarball(sourcePath) tarball, err := createTarball(sourcePath)
if err != nil { if err != nil {
return fmt.Errorf("failed to create tarball: %w", err) return fmt.Errorf("failed to create tarball: %w", err)
@ -220,7 +330,115 @@ func deployNodeJS(cmd *cobra.Command, args []string) error {
return nil return nil
} }
// runBuildCommand runs a command in the given directory with stdout/stderr streaming
func runBuildCommand(dir string, name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// hasBuildScript checks if package.json has a "build" script
func hasBuildScript(dir string) bool {
data, err := os.ReadFile(filepath.Join(dir, "package.json"))
if err != nil {
return false
}
var pkg map[string]interface{}
if err := json.Unmarshal(data, &pkg); err != nil {
return false
}
scripts, ok := pkg["scripts"].(map[string]interface{})
if !ok {
return false
}
_, ok = scripts["build"]
return ok
}
// copyDir recursively copies a directory
func copyDir(src, dst string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}
dstPath := filepath.Join(dst, relPath)
if info.IsDir() {
return os.MkdirAll(dstPath, info.Mode())
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
return os.WriteFile(dstPath, data, info.Mode())
})
}
// createTarballFiles creates a tarball containing only specific files from a directory
func createTarballFiles(baseDir string, files []string) (string, error) {
tmpFile, err := os.CreateTemp("", "orama-deploy-*.tar.gz")
if err != nil {
return "", err
}
defer tmpFile.Close()
gzWriter := gzip.NewWriter(tmpFile)
defer gzWriter.Close()
tarWriter := tar.NewWriter(gzWriter)
defer tarWriter.Close()
for _, f := range files {
fullPath := filepath.Join(baseDir, f)
info, err := os.Stat(fullPath)
if err != nil {
return "", fmt.Errorf("file %s not found: %w", f, err)
}
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return "", err
}
header.Name = f
if err := tarWriter.WriteHeader(header); err != nil {
return "", err
}
if !info.IsDir() {
file, err := os.Open(fullPath)
if err != nil {
return "", err
}
_, err = io.Copy(tarWriter, file)
file.Close()
if err != nil {
return "", err
}
}
}
return tmpFile.Name(), nil
}
func createTarball(sourcePath string) (string, error) { func createTarball(sourcePath string) (string, error) {
return createTarballWithOptions(sourcePath, true)
}
// createTarballAll creates a tarball including node_modules and hidden dirs (for standalone output)
func createTarballAll(sourcePath string) (string, error) {
return createTarballWithOptions(sourcePath, false)
}
func createTarballWithOptions(sourcePath string, skipNodeModules bool) (string, error) {
// Create temp file // Create temp file
tmpFile, err := os.CreateTemp("", "orama-deploy-*.tar.gz") tmpFile, err := os.CreateTemp("", "orama-deploy-*.tar.gz")
if err != nil { if err != nil {
@ -242,15 +460,17 @@ func createTarball(sourcePath string) (string, error) {
return err return err
} }
// Skip hidden files and node_modules // Skip hidden files and node_modules (unless disabled)
if strings.HasPrefix(info.Name(), ".") && info.Name() != "." { if skipNodeModules {
if info.IsDir() { if strings.HasPrefix(info.Name(), ".") && info.Name() != "." {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
if info.Name() == "node_modules" {
return filepath.SkipDir return filepath.SkipDir
} }
return nil
}
if info.Name() == "node_modules" {
return filepath.SkipDir
} }
// Create tar header // Create tar header

View File

@ -395,8 +395,8 @@ func (m *Manager) getStartCommand(deployment *deployments.Deployment, workDir st
switch deployment.Type { switch deployment.Type {
case deployments.DeploymentTypeNextJS: case deployments.DeploymentTypeNextJS:
// Next.js standalone output places server at .next/standalone/server.js // CLI tarballs the standalone output directly, so server.js is at the root
return nodePath + " .next/standalone/server.js" return nodePath + " server.js"
case deployments.DeploymentTypeNodeJSBackend: case deployments.DeploymentTypeNodeJSBackend:
// Check if ENTRY_POINT is set in environment // Check if ENTRY_POINT is set in environment
if entryPoint, ok := deployment.Environment["ENTRY_POINT"]; ok { if entryPoint, ok := deployment.Environment["ENTRY_POINT"]; ok {

View File

@ -66,7 +66,7 @@ WantedBy=multi-user.target
func (ssg *SystemdServiceGenerator) GenerateIPFSClusterService(clusterBinary string) string { func (ssg *SystemdServiceGenerator) GenerateIPFSClusterService(clusterBinary string) string {
clusterPath := filepath.Join(ssg.oramaDir, "data", "ipfs-cluster") clusterPath := filepath.Join(ssg.oramaDir, "data", "ipfs-cluster")
logFile := filepath.Join(ssg.oramaDir, "logs", "ipfs-cluster.log") logFile := filepath.Join(ssg.oramaDir, "logs", "ipfs-cluster.log")
// Read cluster secret from file to pass to daemon // Read cluster secret from file to pass to daemon
clusterSecretPath := filepath.Join(ssg.oramaDir, "secrets", "cluster-secret") clusterSecretPath := filepath.Join(ssg.oramaDir, "secrets", "cluster-secret")
clusterSecret := "" clusterSecret := ""
@ -89,6 +89,7 @@ Environment=HOME=%[1]s
Environment=IPFS_CLUSTER_PATH=%[2]s Environment=IPFS_CLUSTER_PATH=%[2]s
Environment=CLUSTER_SECRET=%[5]s Environment=CLUSTER_SECRET=%[5]s
ExecStartPre=/bin/bash -c 'mkdir -p %[2]s && chmod 700 %[2]s' ExecStartPre=/bin/bash -c 'mkdir -p %[2]s && chmod 700 %[2]s'
ExecStartPre=/bin/bash -c 'for i in $(seq 1 30); do curl -sf -X POST http://127.0.0.1:4501/api/v0/id > /dev/null 2>&1 && exit 0; sleep 1; done; echo "IPFS API not ready after 30s"; exit 1'
ExecStart=%[4]s daemon ExecStart=%[4]s daemon
Restart=always Restart=always
RestartSec=5 RestartSec=5