mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 05:13:01 +00:00
- Invalidate plaintext refresh tokens (migration 019) - Add `--sign` flag to `orama build` for rootwallet manifest signing - Add `--ca-fingerprint` TOFU verification for production joins/invites - Save cluster secrets from join (RQLite auth, Olric key, IPFS peers) - Add RQLite auth config fields
192 lines
6.0 KiB
Go
192 lines
6.0 KiB
Go
package production
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// FilesystemProvisioner manages directory creation and permissions
|
|
type FilesystemProvisioner struct {
|
|
oramaHome string
|
|
oramaDir string
|
|
logWriter interface{} // Can be io.Writer for logging
|
|
}
|
|
|
|
// NewFilesystemProvisioner creates a new provisioner
|
|
func NewFilesystemProvisioner(oramaHome string) *FilesystemProvisioner {
|
|
return &FilesystemProvisioner{
|
|
oramaHome: oramaHome,
|
|
oramaDir: filepath.Join(oramaHome, ".orama"),
|
|
}
|
|
}
|
|
|
|
// EnsureDirectoryStructure creates all required directories (unified structure)
|
|
func (fp *FilesystemProvisioner) EnsureDirectoryStructure() error {
|
|
// All directories needed for unified node structure
|
|
dirs := []string{
|
|
fp.oramaDir,
|
|
filepath.Join(fp.oramaDir, "configs"),
|
|
filepath.Join(fp.oramaDir, "secrets"),
|
|
filepath.Join(fp.oramaDir, "data"),
|
|
filepath.Join(fp.oramaDir, "data", "ipfs", "repo"),
|
|
filepath.Join(fp.oramaDir, "data", "ipfs-cluster"),
|
|
filepath.Join(fp.oramaDir, "data", "rqlite"),
|
|
filepath.Join(fp.oramaDir, "data", "vault"),
|
|
filepath.Join(fp.oramaDir, "logs"),
|
|
filepath.Join(fp.oramaDir, "tls-cache"),
|
|
filepath.Join(fp.oramaDir, "backups"),
|
|
filepath.Join(fp.oramaHome, "bin"),
|
|
filepath.Join(fp.oramaHome, "src"),
|
|
filepath.Join(fp.oramaHome, ".npm"),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
// Remove any stray cluster-secret file from root .orama directory
|
|
// The correct location is .orama/secrets/cluster-secret
|
|
strayClusterSecret := filepath.Join(fp.oramaDir, "cluster-secret")
|
|
if _, err := os.Stat(strayClusterSecret); err == nil {
|
|
if err := os.Remove(strayClusterSecret); err != nil {
|
|
return fmt.Errorf("failed to remove stray cluster-secret file: %w", err)
|
|
}
|
|
}
|
|
|
|
// Create log files with correct permissions so systemd can write to them
|
|
logsDir := filepath.Join(fp.oramaDir, "logs")
|
|
logFiles := []string{
|
|
"olric.log",
|
|
"gateway.log",
|
|
"ipfs.log",
|
|
"ipfs-cluster.log",
|
|
"node.log",
|
|
"vault.log",
|
|
"anyone-client.log",
|
|
}
|
|
|
|
for _, logFile := range logFiles {
|
|
logPath := filepath.Join(logsDir, logFile)
|
|
// Create empty file if it doesn't exist
|
|
if _, err := os.Stat(logPath); os.IsNotExist(err) {
|
|
if err := os.WriteFile(logPath, []byte{}, 0644); err != nil {
|
|
return fmt.Errorf("failed to create log file %s: %w", logPath, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureOramaUser creates the 'orama' system user and group for running services.
|
|
// Sets ownership of the orama data directory to the new user.
|
|
func (fp *FilesystemProvisioner) EnsureOramaUser() error {
|
|
// Check if user already exists
|
|
if err := exec.Command("id", "orama").Run(); err == nil {
|
|
return nil // user already exists
|
|
}
|
|
|
|
// Create system user with no login shell and home at /opt/orama
|
|
cmd := exec.Command("useradd", "--system", "--no-create-home",
|
|
"--home-dir", fp.oramaHome, "--shell", "/usr/sbin/nologin", "orama")
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to create orama user: %w\n%s", err, string(output))
|
|
}
|
|
|
|
// Set ownership of orama directories
|
|
chown := exec.Command("chown", "-R", "orama:orama", fp.oramaDir)
|
|
if output, err := chown.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to chown %s: %w\n%s", fp.oramaDir, err, string(output))
|
|
}
|
|
|
|
// Also chown the bin directory
|
|
binDir := filepath.Join(fp.oramaHome, "bin")
|
|
if _, err := os.Stat(binDir); err == nil {
|
|
chown = exec.Command("chown", "-R", "orama:orama", binDir)
|
|
if output, err := chown.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to chown %s: %w\n%s", binDir, err, string(output))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StateDetector checks for existing production state
|
|
type StateDetector struct {
|
|
oramaDir string
|
|
}
|
|
|
|
// NewStateDetector creates a state detector
|
|
func NewStateDetector(oramaDir string) *StateDetector {
|
|
return &StateDetector{
|
|
oramaDir: oramaDir,
|
|
}
|
|
}
|
|
|
|
// IsConfigured checks if basic configs exist
|
|
func (sd *StateDetector) IsConfigured() bool {
|
|
nodeConfig := filepath.Join(sd.oramaDir, "configs", "node.yaml")
|
|
gatewayConfig := filepath.Join(sd.oramaDir, "configs", "gateway.yaml")
|
|
_, err1 := os.Stat(nodeConfig)
|
|
_, err2 := os.Stat(gatewayConfig)
|
|
return err1 == nil || err2 == nil
|
|
}
|
|
|
|
// HasSecrets checks if cluster secret and swarm key exist
|
|
func (sd *StateDetector) HasSecrets() bool {
|
|
clusterSecret := filepath.Join(sd.oramaDir, "secrets", "cluster-secret")
|
|
swarmKey := filepath.Join(sd.oramaDir, "secrets", "swarm.key")
|
|
_, err1 := os.Stat(clusterSecret)
|
|
_, err2 := os.Stat(swarmKey)
|
|
return err1 == nil && err2 == nil
|
|
}
|
|
|
|
// HasIPFSData checks if IPFS repo is initialized (unified path)
|
|
func (sd *StateDetector) HasIPFSData() bool {
|
|
// Check unified path first
|
|
ipfsRepoPath := filepath.Join(sd.oramaDir, "data", "ipfs", "repo", "config")
|
|
if _, err := os.Stat(ipfsRepoPath); err == nil {
|
|
return true
|
|
}
|
|
// Fallback: check legacy bootstrap path for migration
|
|
legacyPath := filepath.Join(sd.oramaDir, "data", "bootstrap", "ipfs", "repo", "config")
|
|
_, err := os.Stat(legacyPath)
|
|
return err == nil
|
|
}
|
|
|
|
// HasRQLiteData checks if RQLite data exists (unified path)
|
|
func (sd *StateDetector) HasRQLiteData() bool {
|
|
// Check unified path first
|
|
rqliteDataPath := filepath.Join(sd.oramaDir, "data", "rqlite")
|
|
if info, err := os.Stat(rqliteDataPath); err == nil && info.IsDir() {
|
|
return true
|
|
}
|
|
// Fallback: check legacy bootstrap path for migration
|
|
legacyPath := filepath.Join(sd.oramaDir, "data", "bootstrap", "rqlite")
|
|
info, err := os.Stat(legacyPath)
|
|
return err == nil && info.IsDir()
|
|
}
|
|
|
|
// CheckBinaryInstallation checks if required binaries are in PATH
|
|
func (sd *StateDetector) CheckBinaryInstallation() error {
|
|
binaries := []string{"ipfs", "ipfs-cluster-service", "rqlited", "olric-server"}
|
|
var missing []string
|
|
|
|
for _, bin := range binaries {
|
|
if _, err := exec.LookPath(bin); err != nil {
|
|
missing = append(missing, bin)
|
|
}
|
|
}
|
|
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("missing binaries: %s", strings.Join(missing, ", "))
|
|
}
|
|
|
|
return nil
|
|
}
|