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
40 changes: 23 additions & 17 deletions cmd/red/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ func main() {
cfg = loaded
}

// --- NEW: Security Token Warning ---
if cfg.AdminToken == "" || cfg.AdminToken == "secret123" {
log.Println("=================================================================")
log.Println("⚠️ SECURITY WARNING: Using default or missing Admin Token! ⚠️")
Expand All @@ -41,7 +40,6 @@ func main() {
log.Println("Windows: .\\manage-token.ps1")
log.Println("=================================================================")
}
// -----------------------------------

// 1. Core Knowledge Base Pulling
if *pull && cfg.SourceURL != "" {
Expand All @@ -52,11 +50,11 @@ func main() {
log.Println("fetch complete")
}

// 2. Startup Sync (Ported from Legacy Gateway)
// 2. Startup Sync
if len(cfg.StartupSync) > 0 {
// Calculate the absolute path based on the config (e.g., /app/data/remote)
remoteDir := filepath.Join(cfg.DataDir, "remote")

// Check for permission errors right here before proceeding
if err := os.MkdirAll(remoteDir, 0755); err != nil {
log.Fatalf("CRITICAL: Failed to create remote directory. Check volume permissions: %v", err)
}
Expand All @@ -65,7 +63,16 @@ func main() {

for _, sync := range cfg.StartupSync {
log.Printf("Startup Sync: Fetching %s...", sync.Filename)
if err := executeSync(client, sync.URL, filepath.Join(remoteDir, sync.Filename)); err != nil {

// Auto-convert awesome-markdown shortcut to raw GitHub URL
downloadURL := sync.URL
if downloadURL == "https://github.com/mundimark/awesome-markdown" {
downloadURL = "https://raw.githubusercontent.com/mundimark/awesome-markdown/master/README.md"
}

// Pass the absolute target path directly to avoid double-appending "data"
targetPath := filepath.Join(remoteDir, sync.Filename)
if err := executeSync(client, downloadURL, targetPath); err != nil {
log.Printf("Startup Sync Error (%s): %v", sync.Filename, err)
} else {
log.Printf("Startup Sync: Successfully downloaded %s", sync.Filename)
Expand All @@ -79,29 +86,28 @@ func main() {
log.Fatalf("store: %v", err)
}

// 4. Start HTTP Server with the Refactored Router
// Start Hot Reload Watcher
if err := s.Watch(); err != nil {
log.Printf("⚠️ Warning: Could not start hot reloader: %v", err)
}

// 4. Start HTTP Server
h := router.New(s, &cfg, *cfgPath)
log.Printf("RED listening on %s", cfg.Addr)
log.Fatal(http.ListenAndServe(cfg.Addr, h))
}

func executeSync(client *http.Client, targetURL, destSubPath string) error {
// Reconstruct target file paths relative to data root directory
// Note: main.go has context of cfg.DataDir

// Let's resolve the path correctly depending on the initialization parameters
func executeSync(client *http.Client, targetURL, destPath string) error {
lowerURL := strings.ToLower(targetURL)

if strings.HasSuffix(lowerURL, ".tar.gz") || strings.HasSuffix(lowerURL, ".zip") {
srcType := "tar.gz"
if strings.HasSuffix(lowerURL, ".zip") {
srcType = "zip"
}
// Pull the dynamic folder contents using the internal archive worker
return fetch.Pull(targetURL, srcType, filepath.Join("data", destSubPath))
return fetch.Pull(targetURL, srcType, destPath)
}

// Otherwise, proceed with single file retrieval flow
req, err := http.NewRequest(http.MethodGet, targetURL, nil)
if err != nil {
return err
Expand All @@ -118,12 +124,12 @@ func executeSync(client *http.Client, targetURL, destSubPath string) error {
return os.ErrPermission
}

fullFilePath := filepath.Join("data", destSubPath)
if err := os.MkdirAll(filepath.Dir(fullFilePath), 0755); err != nil {
// Use destPath exactly as provided, removing the hardcoded "data" string
if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
return err
}

outFile, err := os.Create(fullFilePath)
outFile, err := os.Create(destPath)
if err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ networks:

services:
red_engine:
build: .
image: red-engine-image
container_name: red_engine_node
restart: unless-stopped
Expand All @@ -20,8 +21,8 @@ services:
container_name: caddy_proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "8080:80"
- "8443:443"
networks:
- clearnet-tier
volumes:
Expand Down
13 changes: 10 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ module github.com/RED-Collective/red-engine

go 1.26.2

require github.com/yuin/goldmark v1.8.2
require (
github.com/microcosm-cc/bluemonday v1.0.27
github.com/yuin/goldmark v1.8.2
)

require (
github.com/fsnotify/fsnotify v1.10.1 // direct
golang.org/x/sys v0.45.0 // indirect
)

require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // direct
golang.org/x/net v0.26.0 // indirect
golang.org/x/net v0.55.0 // indirect
)
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
117 changes: 28 additions & 89 deletions install-red-engine.ps1
Original file line number Diff line number Diff line change
@@ -1,118 +1,57 @@
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "🚀 Installing RED Engine..." -ForegroundColor Cyan
Write-Host "🚀 Installing RED Engine (Production Mode)..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan

# 1. Check if we are inside the repository; if not, clone it.
if (-Not (Test-Path "docker-compose.yml"))
# Check for Administrator privileges
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
{
Write-Host "[*] Repository not detected in current directory."

if (-Not (Get-Command "git" -ErrorAction SilentlyContinue))
{
Write-Host "❌ Error: 'git' is not installed. Please install Git for Windows to continue." -ForegroundColor Red
Exit
}
Write-Host "⚠️ Administrator privileges are required to bind network ports." -ForegroundColor Yellow
Write-Host "Re-launching as Administrator..." -ForegroundColor Yellow
Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
Exit
}

Write-Host "[*] Cloning RED Engine repository..."
# 1. Repository Check
if (-Not (Test-Path "docker-compose.yml"))
{
git clone https://github.com/RED-Collective/red-engine.git

if ($LASTEXITCODE -ne 0)
{
Write-Host "❌ Error: Failed to clone repository." -ForegroundColor Red
Exit
}

Write-Host "[*] Navigating into red-engine directory..."
Set-Location "red-engine"
} else
{
Write-Host "[*] Running from inside existing repository."
}

# 2. Create data directory safely as the standard user
# 2. Setup Directories
if (-Not (Test-Path ".\data"))
{
Write-Host "[*] Creating .\data directory..."
New-Item -ItemType Directory -Path ".\data" | Out-Null
} else
{
Write-Host "[*] .\data directory already exists."
{ New-Item -ItemType Directory -Path ".\data" | Out-Null
}

# 3. Check for or create config.json with a secure token
# 3. Handle config.json
if (-Not (Test-Path "config.json"))
{
Write-Host "[*] Generating default config.json..."

# Generate a cryptographically secure 32-character hexadecimal token
$Bytes = New-Object Byte[] 16
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Bytes)
$NewToken = [BitConverter]::ToString($Bytes) -replace '-'

$DefaultConfig = @{
addr = ":8080"
siteName = "RED Engine"
dataDir = "/app/data"
adminToken = $NewToken
startupSync = @()
}
$DefaultConfig = @{ addr = ":8080"; siteName = "RED Engine"; dataDir = "/app/data"; adminToken = $NewToken; startupSync = @() }
$DefaultConfig | ConvertTo-Json -Depth 10 | Set-Content "config.json"

Write-Host "[*] Generated secure Admin Token: $NewToken" -ForegroundColor Green
Write-Host "⚠️ PLEASE SAVE THIS TOKEN! You will need it to log in to the admin panel." -ForegroundColor Yellow
} else
{
Write-Host "[*] config.json already exists. Skipping default generation."
}

# 4. Check for or create contributors.json
# 4. Handle contributors.json
if (-Not (Test-Path "contributors.json"))
{
Write-Host "[*] Generating default contributors.json..."
"[]" | Set-Content "contributors.json"
} else
{
Write-Host "[*] contributors.json already exists."
{ "[]" | Set-Content "contributors.json"
}

# 5. Detect the container engine
$ComposeCmd = ""
$ComposeArgs = @("up", "--build", "-d")

if (Get-Command "podman-compose" -ErrorAction SilentlyContinue)
{
$ComposeCmd = "podman-compose"
} elseif (Get-Command "docker-compose" -ErrorAction SilentlyContinue)
{
$ComposeCmd = "docker-compose"
} elseif (Get-Command "docker" -ErrorAction SilentlyContinue)
{
# Check if modern 'docker compose' (V2) is available
try
{
$null = Invoke-Expression "docker compose version 2>&1"
if ($LASTEXITCODE -eq 0)
{
$ComposeCmd = "docker"
$ComposeArgs = @("compose", "up", "--build", "-d")
}
} catch
{
}
}

if ($ComposeCmd -eq "")
{
Write-Host "❌ Error: Neither podman-compose nor docker compose found on this system." -ForegroundColor Red
Write-Host "Please install Podman or Docker Desktop to continue." -ForegroundColor Red
Exit
}
# 5. Build and Deploy
Write-Host "[*] Building local image..." -ForegroundColor Green
podman build --network=host -t red-engine-image .

Write-Host "[*] Starting RED Engine..."
& $ComposeCmd $ComposeArgs
Write-Host "[*] Starting services..." -ForegroundColor Green
podman-compose up -d

# 6. Final Status
$Config = Get-Content "config.json" | ConvertFrom-Json
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "✅ Installation Complete!" -ForegroundColor Green
Write-Host "🌐 Your node is running at: http://localhost"
Write-Host "🌐 Node running at: http://localhost"
Write-Host "⚙️ Admin Panel: http://localhost/-/admin"
Write-Host "🔑 YOUR ADMIN TOKEN: $($Config.adminToken)" -ForegroundColor Yellow
Write-Host "⚠️ PLEASE SAVE THIS TOKEN!" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Cyan
Loading
Loading