feat: implement resource validation checks for production deployment

- Added a new ResourceChecker type to validate system resources including disk space, RAM, and CPU cores.
- Implemented CheckDiskSpace, CheckRAM, and CheckCPU methods to ensure minimum requirements are met for production deployment.
- Integrated resource checks into the ProductionSetup's Phase1CheckPrerequisites method to enhance deployment reliability.
- Updated systemd service generation to log output to specific log files instead of the journal for better log management.
This commit is contained in:
anonpenguin23 2025-11-11 05:44:40 +02:00
parent 52a726ffd4
commit e9bf94ba96
No known key found for this signature in database
GPG Key ID: 1CBB1FE35AFBEE30
5 changed files with 147 additions and 21 deletions

View File

@ -13,6 +13,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
### Deprecated
### Fixed
## [0.66.0] - 2025-11-11
### Added
- Pre-installation checks for minimum system resources (10GB disk space, 2GB RAM, 2 CPU cores) are now performed during setup.
- All systemd services (IPFS, RQLite, Olric, Node, Gateway) now log directly to dedicated files in the logs directory instead of using the system journal.
### Changed
- Improved logging instructions in the setup completion message to reference the new dedicated log files.
### Deprecated
### Removed
### Fixed
\n
## [0.65.0] - 2025-11-11
### Added

View File

@ -19,7 +19,7 @@ test-e2e:
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill
VERSION := 0.65.0
VERSION := 0.66.0
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'

View File

@ -5,7 +5,9 @@ import (
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
)
// OSInfo contains detected operating system information
@ -213,3 +215,74 @@ func (etc *ExternalToolChecker) CheckGoAvailable() bool {
_, err := exec.LookPath("go")
return err == nil
}
// ResourceChecker validates system resources for production deployment
type ResourceChecker struct{}
// NewResourceChecker creates a new resource checker
func NewResourceChecker() *ResourceChecker {
return &ResourceChecker{}
}
// CheckDiskSpace validates sufficient disk space (minimum 10GB free)
func (rc *ResourceChecker) CheckDiskSpace(path string) error {
var stat syscall.Statfs_t
if err := syscall.Statfs(path, &stat); err != nil {
return fmt.Errorf("failed to check disk space: %w", err)
}
// Available space in bytes
availableBytes := stat.Bavail * uint64(stat.Bsize)
minRequiredBytes := uint64(10 * 1024 * 1024 * 1024) // 10GB
if availableBytes < minRequiredBytes {
availableGB := float64(availableBytes) / (1024 * 1024 * 1024)
return fmt.Errorf("insufficient disk space: %.1fGB available, minimum 10GB required", availableGB)
}
return nil
}
// CheckRAM validates sufficient RAM (minimum 2GB total)
func (rc *ResourceChecker) CheckRAM() error {
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
return fmt.Errorf("failed to read memory info: %w", err)
}
lines := strings.Split(string(data), "\n")
totalKB := uint64(0)
for _, line := range lines {
if strings.HasPrefix(line, "MemTotal:") {
parts := strings.Fields(line)
if len(parts) >= 2 {
if kb, err := strconv.ParseUint(parts[1], 10, 64); err == nil {
totalKB = kb
break
}
}
}
}
if totalKB == 0 {
return fmt.Errorf("could not determine total RAM")
}
minRequiredKB := uint64(2 * 1024 * 1024) // 2GB in KB
if totalKB < minRequiredKB {
totalGB := float64(totalKB) / (1024 * 1024)
return fmt.Errorf("insufficient RAM: %.1fGB total, minimum 2GB required", totalGB)
}
return nil
}
// CheckCPU validates sufficient CPU cores (minimum 2 cores)
func (rc *ResourceChecker) CheckCPU() error {
cores := runtime.NumCPU()
if cores < 2 {
return fmt.Errorf("insufficient CPU cores: %d available, minimum 2 required", cores)
}
return nil
}

View File

@ -21,6 +21,7 @@ type ProductionSetup struct {
privChecker *PrivilegeChecker
osDetector *OSDetector
archDetector *ArchitectureDetector
resourceChecker *ResourceChecker
fsProvisioner *FilesystemProvisioner
userProvisioner *UserProvisioner
stateDetector *StateDetector
@ -48,6 +49,7 @@ func NewProductionSetup(debrosHome string, logWriter io.Writer, forceReconfigure
privChecker: &PrivilegeChecker{},
osDetector: &OSDetector{},
archDetector: &ArchitectureDetector{},
resourceChecker: NewResourceChecker(),
fsProvisioner: NewFilesystemProvisioner(debrosHome),
userProvisioner: NewUserProvisioner("debros", debrosHome, "/bin/bash"),
stateDetector: NewStateDetector(debrosDir),
@ -115,6 +117,25 @@ func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
}
ps.logf(" ✓ Basic dependencies available")
// Check system resources
if err := ps.resourceChecker.CheckDiskSpace(ps.debrosHome); err != nil {
ps.logf(" ❌ %v", err)
return err
}
ps.logf(" ✓ Sufficient disk space available")
if err := ps.resourceChecker.CheckRAM(); err != nil {
ps.logf(" ❌ %v", err)
return err
}
ps.logf(" ✓ Sufficient RAM available")
if err := ps.resourceChecker.CheckCPU(); err != nil {
ps.logf(" ❌ %v", err)
return err
}
ps.logf(" ✓ Sufficient CPU cores available")
return nil
}
@ -426,8 +447,15 @@ func (ps *ProductionSetup) LogSetupComplete(peerID string) {
ps.logf("\nNode Peer ID: %s", peerID)
ps.logf("\nService Management:")
ps.logf(" systemctl status debros-ipfs-bootstrap")
ps.logf(" systemctl logs debros-node-bootstrap")
ps.logf(" sudo tail -f %s/logs/node.log", ps.debrosDir)
ps.logf(" journalctl -u debros-node-bootstrap -f")
ps.logf(" tail -f %s/logs/node-bootstrap.log", ps.debrosDir)
ps.logf("\nLog Files:")
ps.logf(" %s/logs/ipfs-bootstrap.log", ps.debrosDir)
ps.logf(" %s/logs/ipfs-cluster-bootstrap.log", ps.debrosDir)
ps.logf(" %s/logs/rqlite-bootstrap.log", ps.debrosDir)
ps.logf(" %s/logs/olric.log", ps.debrosDir)
ps.logf(" %s/logs/node-bootstrap.log", ps.debrosDir)
ps.logf(" %s/logs/gateway.log", ps.debrosDir)
ps.logf("\nStart All Services:")
ps.logf(" systemctl start debros-ipfs-bootstrap debros-ipfs-cluster-bootstrap debros-rqlite-bootstrap debros-olric debros-node-bootstrap debros-gateway")
ps.logf("\nVerify Installation:")

View File

@ -31,6 +31,8 @@ func (ssg *SystemdServiceGenerator) GenerateIPFSService(nodeType string) string
ipfsRepoPath = filepath.Join(ssg.debrosDir, "data", "node", "ipfs", "repo")
}
logFile := filepath.Join(ssg.debrosDir, "logs", fmt.Sprintf("ipfs-%s.log", nodeType))
return fmt.Sprintf(`[Unit]
Description=IPFS Daemon (%s)
After=network-online.target
@ -46,8 +48,8 @@ ExecStartPre=/bin/bash -c 'if [ -f %s/secrets/swarm.key ] && [ ! -f %s/swarm.key
ExecStart=/usr/bin/ipfs daemon --enable-pubsub-experiment --repo-dir=%s
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
StandardOutput=file:%s
StandardError=file:%s
SyslogIdentifier=ipfs-%s
NoNewPrivileges=yes
@ -57,7 +59,7 @@ ReadWritePaths=%s
[Install]
WantedBy=multi-user.target
`, nodeType, ssg.debrosHome, ipfsRepoPath, ssg.debrosDir, ipfsRepoPath, ssg.debrosDir, ipfsRepoPath, ipfsRepoPath, ipfsRepoPath, nodeType, ssg.debrosDir)
`, nodeType, ssg.debrosHome, ipfsRepoPath, ssg.debrosDir, ipfsRepoPath, ssg.debrosDir, ipfsRepoPath, ipfsRepoPath, ipfsRepoPath, logFile, logFile, nodeType, ssg.debrosDir)
}
// GenerateIPFSClusterService generates the IPFS Cluster systemd unit
@ -69,6 +71,8 @@ func (ssg *SystemdServiceGenerator) GenerateIPFSClusterService(nodeType string)
clusterPath = filepath.Join(ssg.debrosDir, "data", "node", "ipfs-cluster")
}
logFile := filepath.Join(ssg.debrosDir, "logs", fmt.Sprintf("ipfs-cluster-%s.log", nodeType))
return fmt.Sprintf(`[Unit]
Description=IPFS Cluster Service (%s)
After=debros-ipfs-%s.service
@ -85,8 +89,8 @@ Environment=IPFS_CLUSTER_PATH=%s
ExecStart=/usr/local/bin/ipfs-cluster-service daemon
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
StandardOutput=file:%s
StandardError=file:%s
SyslogIdentifier=ipfs-cluster-%s
NoNewPrivileges=yes
@ -96,7 +100,7 @@ ReadWritePaths=%s
[Install]
WantedBy=multi-user.target
`, nodeType, nodeType, nodeType, nodeType, ssg.debrosHome, ssg.debrosHome, clusterPath, nodeType, ssg.debrosDir)
`, nodeType, nodeType, nodeType, nodeType, ssg.debrosHome, ssg.debrosHome, clusterPath, logFile, logFile, nodeType, ssg.debrosDir)
}
// GenerateRQLiteService generates the RQLite systemd unit
@ -119,6 +123,8 @@ func (ssg *SystemdServiceGenerator) GenerateRQLiteService(nodeType string, httpP
args += fmt.Sprintf(` %s`, dataDir)
logFile := filepath.Join(ssg.debrosDir, "logs", fmt.Sprintf("rqlite-%s.log", nodeType))
return fmt.Sprintf(`[Unit]
Description=RQLite Database (%s)
After=network-online.target
@ -132,8 +138,8 @@ Environment=HOME=%s
ExecStart=/usr/local/bin/rqlited %s
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
StandardOutput=file:%s
StandardError=file:%s
SyslogIdentifier=rqlite-%s
NoNewPrivileges=yes
@ -143,12 +149,13 @@ ReadWritePaths=%s
[Install]
WantedBy=multi-user.target
`, nodeType, ssg.debrosHome, args, nodeType, ssg.debrosDir)
`, nodeType, ssg.debrosHome, args, logFile, logFile, nodeType, ssg.debrosDir)
}
// GenerateOlricService generates the Olric systemd unit
func (ssg *SystemdServiceGenerator) GenerateOlricService() string {
olricConfigPath := filepath.Join(ssg.debrosDir, "configs", "olric", "config.yaml")
logFile := filepath.Join(ssg.debrosDir, "logs", "olric.log")
return fmt.Sprintf(`[Unit]
Description=Olric Cache Server
@ -164,8 +171,8 @@ Environment=OLRIC_SERVER_CONFIG=%s
ExecStart=/usr/local/bin/olric-server
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
StandardOutput=file:%s
StandardError=file:%s
SyslogIdentifier=olric
NoNewPrivileges=yes
@ -175,7 +182,7 @@ ReadWritePaths=%s
[Install]
WantedBy=multi-user.target
`, ssg.debrosHome, olricConfigPath, ssg.debrosDir)
`, ssg.debrosHome, olricConfigPath, logFile, logFile, ssg.debrosDir)
}
// GenerateNodeService generates the DeBros Node systemd unit
@ -187,6 +194,8 @@ func (ssg *SystemdServiceGenerator) GenerateNodeService(nodeType string) string
configFile = "node.yaml"
}
logFile := filepath.Join(ssg.debrosDir, "logs", fmt.Sprintf("node-%s.log", nodeType))
return fmt.Sprintf(`[Unit]
Description=DeBros Network Node (%s)
After=debros-ipfs-cluster-%s.service
@ -202,8 +211,8 @@ Environment=HOME=%s
ExecStart=%s/bin/node --config %s/configs/%s
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
StandardOutput=file:%s
StandardError=file:%s
SyslogIdentifier=debros-node-%s
NoNewPrivileges=yes
@ -213,12 +222,13 @@ ReadWritePaths=%s
[Install]
WantedBy=multi-user.target
`, nodeType, nodeType, nodeType, nodeType, ssg.debrosHome, ssg.debrosHome, ssg.debrosHome, ssg.debrosDir, configFile, nodeType, ssg.debrosDir)
`, nodeType, nodeType, nodeType, nodeType, ssg.debrosHome, ssg.debrosHome, ssg.debrosHome, ssg.debrosDir, configFile, logFile, logFile, nodeType, ssg.debrosDir)
}
// GenerateGatewayService generates the DeBros Gateway systemd unit
func (ssg *SystemdServiceGenerator) GenerateGatewayService(nodeType string) string {
nodeService := fmt.Sprintf("debros-node-%s.service", nodeType)
logFile := filepath.Join(ssg.debrosDir, "logs", "gateway.log")
return fmt.Sprintf(`[Unit]
Description=DeBros Gateway
After=%s
@ -233,8 +243,8 @@ Environment=HOME=%s
ExecStart=%s/bin/gateway --config %s/configs/gateway.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
StandardOutput=file:%s
StandardError=file:%s
SyslogIdentifier=debros-gateway
AmbientCapabilities=CAP_NET_BIND_SERVICE
@ -247,7 +257,7 @@ ReadWritePaths=%s
[Install]
WantedBy=multi-user.target
`, nodeService, nodeService, ssg.debrosHome, ssg.debrosHome, ssg.debrosHome, ssg.debrosDir, ssg.debrosDir)
`, nodeService, nodeService, ssg.debrosHome, ssg.debrosHome, ssg.debrosHome, ssg.debrosDir, logFile, logFile, ssg.debrosDir)
}
// SystemdController manages systemd service operations