mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 19:16:58 +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
830 lines
22 KiB
Go
830 lines
22 KiB
Go
package build
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/constants"
|
|
)
|
|
|
|
// oramaBinary defines a binary to cross-compile from the project source.
|
|
type oramaBinary struct {
|
|
Name string // output binary name
|
|
Package string // Go package path relative to project root
|
|
// Extra ldflags beyond the standard ones
|
|
ExtraLDFlags string
|
|
}
|
|
|
|
// Builder orchestrates the entire build process.
|
|
type Builder struct {
|
|
flags *Flags
|
|
projectDir string
|
|
tmpDir string
|
|
binDir string
|
|
version string
|
|
commit string
|
|
date string
|
|
}
|
|
|
|
// NewBuilder creates a new Builder.
|
|
func NewBuilder(flags *Flags) *Builder {
|
|
return &Builder{flags: flags}
|
|
}
|
|
|
|
// Build runs the full build pipeline.
|
|
func (b *Builder) Build() error {
|
|
start := time.Now()
|
|
|
|
// Find project root
|
|
projectDir, err := findProjectRoot()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.projectDir = projectDir
|
|
|
|
// Read version from Makefile or use "dev"
|
|
b.version = b.readVersion()
|
|
b.commit = b.readCommit()
|
|
b.date = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
|
|
|
// Create temp build directory
|
|
b.tmpDir, err = os.MkdirTemp("", "orama-build-*")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temp dir: %w", err)
|
|
}
|
|
defer os.RemoveAll(b.tmpDir)
|
|
|
|
b.binDir = filepath.Join(b.tmpDir, "bin")
|
|
if err := os.MkdirAll(b.binDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create bin dir: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Building orama %s for linux/%s\n", b.version, b.flags.Arch)
|
|
fmt.Printf("Project: %s\n\n", b.projectDir)
|
|
|
|
// Step 1: Cross-compile Orama binaries
|
|
if err := b.buildOramaBinaries(); err != nil {
|
|
return fmt.Errorf("failed to build orama binaries: %w", err)
|
|
}
|
|
|
|
// Step 2: Cross-compile Vault Guardian (Zig)
|
|
if err := b.buildVaultGuardian(); err != nil {
|
|
return fmt.Errorf("failed to build vault-guardian: %w", err)
|
|
}
|
|
|
|
// Step 3: Cross-compile Olric
|
|
if err := b.buildOlric(); err != nil {
|
|
return fmt.Errorf("failed to build olric: %w", err)
|
|
}
|
|
|
|
// Step 4: Cross-compile IPFS Cluster
|
|
if err := b.buildIPFSCluster(); err != nil {
|
|
return fmt.Errorf("failed to build ipfs-cluster: %w", err)
|
|
}
|
|
|
|
// Step 5: Build CoreDNS with RQLite plugin
|
|
if err := b.buildCoreDNS(); err != nil {
|
|
return fmt.Errorf("failed to build coredns: %w", err)
|
|
}
|
|
|
|
// Step 6: Build Caddy with Orama DNS module
|
|
if err := b.buildCaddy(); err != nil {
|
|
return fmt.Errorf("failed to build caddy: %w", err)
|
|
}
|
|
|
|
// Step 7: Download pre-built IPFS Kubo
|
|
if err := b.downloadIPFS(); err != nil {
|
|
return fmt.Errorf("failed to download ipfs: %w", err)
|
|
}
|
|
|
|
// Step 8: Download pre-built RQLite
|
|
if err := b.downloadRQLite(); err != nil {
|
|
return fmt.Errorf("failed to download rqlite: %w", err)
|
|
}
|
|
|
|
// Step 9: Copy systemd templates
|
|
if err := b.copySystemdTemplates(); err != nil {
|
|
return fmt.Errorf("failed to copy systemd templates: %w", err)
|
|
}
|
|
|
|
// Step 10: Generate manifest
|
|
manifest, err := b.generateManifest()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate manifest: %w", err)
|
|
}
|
|
|
|
// Step 11: Sign manifest (optional)
|
|
if b.flags.Sign {
|
|
if err := b.signManifest(manifest); err != nil {
|
|
return fmt.Errorf("failed to sign manifest: %w", err)
|
|
}
|
|
}
|
|
|
|
// Step 12: Create archive
|
|
outputPath := b.flags.Output
|
|
if outputPath == "" {
|
|
outputPath = fmt.Sprintf("/tmp/orama-%s-linux-%s.tar.gz", b.version, b.flags.Arch)
|
|
}
|
|
|
|
if err := b.createArchive(outputPath, manifest); err != nil {
|
|
return fmt.Errorf("failed to create archive: %w", err)
|
|
}
|
|
|
|
elapsed := time.Since(start).Round(time.Second)
|
|
fmt.Printf("\nBuild complete in %s\n", elapsed)
|
|
fmt.Printf("Archive: %s\n", outputPath)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) buildOramaBinaries() error {
|
|
fmt.Println("[1/8] Cross-compiling Orama binaries...")
|
|
|
|
ldflags := fmt.Sprintf("-s -w -X 'main.version=%s' -X 'main.commit=%s' -X 'main.date=%s'",
|
|
b.version, b.commit, b.date)
|
|
|
|
gatewayLDFlags := fmt.Sprintf("%s -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildVersion=%s' -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildCommit=%s' -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildTime=%s'",
|
|
ldflags, b.version, b.commit, b.date)
|
|
|
|
binaries := []oramaBinary{
|
|
{Name: "orama", Package: "./cmd/cli/"},
|
|
{Name: "orama-node", Package: "./cmd/node/"},
|
|
{Name: "gateway", Package: "./cmd/gateway/", ExtraLDFlags: gatewayLDFlags},
|
|
{Name: "identity", Package: "./cmd/identity/"},
|
|
{Name: "sfu", Package: "./cmd/sfu/"},
|
|
{Name: "turn", Package: "./cmd/turn/"},
|
|
}
|
|
|
|
for _, bin := range binaries {
|
|
flags := ldflags
|
|
if bin.ExtraLDFlags != "" {
|
|
flags = bin.ExtraLDFlags
|
|
}
|
|
|
|
output := filepath.Join(b.binDir, bin.Name)
|
|
cmd := exec.Command("go", "build",
|
|
"-ldflags", flags,
|
|
"-trimpath",
|
|
"-o", output,
|
|
bin.Package)
|
|
cmd.Dir = b.projectDir
|
|
cmd.Env = b.crossEnv()
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if b.flags.Verbose {
|
|
fmt.Printf(" go build -o %s %s\n", bin.Name, bin.Package)
|
|
}
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to build %s: %w", bin.Name, err)
|
|
}
|
|
fmt.Printf(" ✓ %s\n", bin.Name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) buildVaultGuardian() error {
|
|
fmt.Println("[2/8] Cross-compiling Vault Guardian (Zig)...")
|
|
|
|
// Ensure zig is available
|
|
if _, err := exec.LookPath("zig"); err != nil {
|
|
return fmt.Errorf("zig not found in PATH — install from https://ziglang.org/download/")
|
|
}
|
|
|
|
// Vault source is sibling to orama project
|
|
vaultDir := filepath.Join(b.projectDir, "..", "orama-vault")
|
|
if _, err := os.Stat(filepath.Join(vaultDir, "build.zig")); err != nil {
|
|
return fmt.Errorf("vault source not found at %s — expected orama-vault as sibling directory: %w", vaultDir, err)
|
|
}
|
|
|
|
// Map Go arch to Zig target triple
|
|
var zigTarget string
|
|
switch b.flags.Arch {
|
|
case "amd64":
|
|
zigTarget = "x86_64-linux-musl"
|
|
case "arm64":
|
|
zigTarget = "aarch64-linux-musl"
|
|
default:
|
|
return fmt.Errorf("unsupported architecture for vault: %s", b.flags.Arch)
|
|
}
|
|
|
|
if b.flags.Verbose {
|
|
fmt.Printf(" zig build -Dtarget=%s -Doptimize=ReleaseSafe\n", zigTarget)
|
|
}
|
|
|
|
cmd := exec.Command("zig", "build",
|
|
fmt.Sprintf("-Dtarget=%s", zigTarget),
|
|
"-Doptimize=ReleaseSafe")
|
|
cmd.Dir = vaultDir
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("zig build failed: %w", err)
|
|
}
|
|
|
|
// Copy output binary to build bin dir
|
|
src := filepath.Join(vaultDir, "zig-out", "bin", "vault-guardian")
|
|
dst := filepath.Join(b.binDir, "vault-guardian")
|
|
if err := copyFile(src, dst); err != nil {
|
|
return fmt.Errorf("failed to copy vault-guardian binary: %w", err)
|
|
}
|
|
|
|
fmt.Println(" ✓ vault-guardian")
|
|
return nil
|
|
}
|
|
|
|
// copyFile copies a file from src to dst, preserving executable permissions.
|
|
func copyFile(src, dst string) error {
|
|
srcFile, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dstFile.Close()
|
|
|
|
if _, err := srcFile.WriteTo(dstFile); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) buildOlric() error {
|
|
fmt.Printf("[3/8] Cross-compiling Olric %s...\n", constants.OlricVersion)
|
|
|
|
// go install doesn't support cross-compilation with GOBIN set,
|
|
// so we create a temporary module and use go build -o instead.
|
|
tmpDir, err := os.MkdirTemp("", "olric-build-*")
|
|
if err != nil {
|
|
return fmt.Errorf("create temp dir: %w", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
modInit := exec.Command("go", "mod", "init", "olric-build")
|
|
modInit.Dir = tmpDir
|
|
modInit.Stderr = os.Stderr
|
|
if err := modInit.Run(); err != nil {
|
|
return fmt.Errorf("go mod init: %w", err)
|
|
}
|
|
|
|
modGet := exec.Command("go", "get",
|
|
fmt.Sprintf("github.com/olric-data/olric/cmd/olric-server@%s", constants.OlricVersion))
|
|
modGet.Dir = tmpDir
|
|
modGet.Env = append(os.Environ(),
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
modGet.Stderr = os.Stderr
|
|
if err := modGet.Run(); err != nil {
|
|
return fmt.Errorf("go get olric: %w", err)
|
|
}
|
|
|
|
cmd := exec.Command("go", "build",
|
|
"-ldflags", "-s -w",
|
|
"-trimpath",
|
|
"-o", filepath.Join(b.binDir, "olric-server"),
|
|
fmt.Sprintf("github.com/olric-data/olric/cmd/olric-server"))
|
|
cmd.Dir = tmpDir
|
|
cmd.Env = append(b.crossEnv(),
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(" ✓ olric-server")
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) buildIPFSCluster() error {
|
|
fmt.Printf("[4/8] Cross-compiling IPFS Cluster %s...\n", constants.IPFSClusterVersion)
|
|
|
|
tmpDir, err := os.MkdirTemp("", "ipfs-cluster-build-*")
|
|
if err != nil {
|
|
return fmt.Errorf("create temp dir: %w", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
modInit := exec.Command("go", "mod", "init", "ipfs-cluster-build")
|
|
modInit.Dir = tmpDir
|
|
modInit.Stderr = os.Stderr
|
|
if err := modInit.Run(); err != nil {
|
|
return fmt.Errorf("go mod init: %w", err)
|
|
}
|
|
|
|
modGet := exec.Command("go", "get",
|
|
fmt.Sprintf("github.com/ipfs-cluster/ipfs-cluster/cmd/ipfs-cluster-service@%s", constants.IPFSClusterVersion))
|
|
modGet.Dir = tmpDir
|
|
modGet.Env = append(os.Environ(),
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
modGet.Stderr = os.Stderr
|
|
if err := modGet.Run(); err != nil {
|
|
return fmt.Errorf("go get ipfs-cluster: %w", err)
|
|
}
|
|
|
|
cmd := exec.Command("go", "build",
|
|
"-ldflags", "-s -w",
|
|
"-trimpath",
|
|
"-o", filepath.Join(b.binDir, "ipfs-cluster-service"),
|
|
"github.com/ipfs-cluster/ipfs-cluster/cmd/ipfs-cluster-service")
|
|
cmd.Dir = tmpDir
|
|
cmd.Env = append(b.crossEnv(),
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(" ✓ ipfs-cluster-service")
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) buildCoreDNS() error {
|
|
fmt.Printf("[5/8] Building CoreDNS %s with RQLite plugin...\n", constants.CoreDNSVersion)
|
|
|
|
buildDir := filepath.Join(b.tmpDir, "coredns-build")
|
|
|
|
// Clone CoreDNS
|
|
fmt.Println(" Cloning CoreDNS...")
|
|
cmd := exec.Command("git", "clone", "--depth", "1",
|
|
"--branch", "v"+constants.CoreDNSVersion,
|
|
"https://github.com/coredns/coredns.git", buildDir)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to clone coredns: %w", err)
|
|
}
|
|
|
|
// Copy RQLite plugin from local source
|
|
pluginSrc := filepath.Join(b.projectDir, "pkg", "coredns", "rqlite")
|
|
pluginDst := filepath.Join(buildDir, "plugin", "rqlite")
|
|
if err := os.MkdirAll(pluginDst, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
entries, err := os.ReadDir(pluginSrc)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read rqlite plugin source at %s: %w", pluginSrc, err)
|
|
}
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || filepath.Ext(entry.Name()) != ".go" {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(pluginSrc, entry.Name()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(filepath.Join(pluginDst, entry.Name()), data, 0644); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write plugin.cfg (same as build-linux-coredns.sh)
|
|
pluginCfg := `metadata:metadata
|
|
cancel:cancel
|
|
tls:tls
|
|
reload:reload
|
|
nsid:nsid
|
|
bufsize:bufsize
|
|
root:root
|
|
bind:bind
|
|
debug:debug
|
|
trace:trace
|
|
ready:ready
|
|
health:health
|
|
pprof:pprof
|
|
prometheus:metrics
|
|
errors:errors
|
|
log:log
|
|
dnstap:dnstap
|
|
local:local
|
|
dns64:dns64
|
|
acl:acl
|
|
any:any
|
|
chaos:chaos
|
|
loadbalance:loadbalance
|
|
cache:cache
|
|
rewrite:rewrite
|
|
header:header
|
|
dnssec:dnssec
|
|
autopath:autopath
|
|
minimal:minimal
|
|
template:template
|
|
transfer:transfer
|
|
hosts:hosts
|
|
file:file
|
|
auto:auto
|
|
secondary:secondary
|
|
loop:loop
|
|
forward:forward
|
|
grpc:grpc
|
|
erratic:erratic
|
|
whoami:whoami
|
|
on:github.com/coredns/caddy/onevent
|
|
sign:sign
|
|
view:view
|
|
rqlite:rqlite
|
|
`
|
|
if err := os.WriteFile(filepath.Join(buildDir, "plugin.cfg"), []byte(pluginCfg), 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Add dependencies
|
|
fmt.Println(" Adding dependencies...")
|
|
goPath := os.Getenv("PATH")
|
|
baseEnv := append(os.Environ(),
|
|
"PATH="+goPath,
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
|
|
for _, dep := range []string{"github.com/miekg/dns@latest", "go.uber.org/zap@latest"} {
|
|
cmd := exec.Command("go", "get", dep)
|
|
cmd.Dir = buildDir
|
|
cmd.Env = baseEnv
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to get %s: %w", dep, err)
|
|
}
|
|
}
|
|
|
|
cmd = exec.Command("go", "mod", "tidy")
|
|
cmd.Dir = buildDir
|
|
cmd.Env = baseEnv
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("go mod tidy failed: %w", err)
|
|
}
|
|
|
|
// Generate plugin code
|
|
fmt.Println(" Generating plugin code...")
|
|
cmd = exec.Command("go", "generate")
|
|
cmd.Dir = buildDir
|
|
cmd.Env = baseEnv
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("go generate failed: %w", err)
|
|
}
|
|
|
|
// Cross-compile
|
|
fmt.Println(" Building binary...")
|
|
cmd = exec.Command("go", "build",
|
|
"-ldflags", "-s -w",
|
|
"-trimpath",
|
|
"-o", filepath.Join(b.binDir, "coredns"))
|
|
cmd.Dir = buildDir
|
|
cmd.Env = append(baseEnv,
|
|
"GOOS=linux",
|
|
fmt.Sprintf("GOARCH=%s", b.flags.Arch),
|
|
"CGO_ENABLED=0")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("build failed: %w", err)
|
|
}
|
|
|
|
fmt.Println(" ✓ coredns")
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) buildCaddy() error {
|
|
fmt.Printf("[6/8] Building Caddy %s with Orama DNS module...\n", constants.CaddyVersion)
|
|
|
|
// Ensure xcaddy is available
|
|
if _, err := exec.LookPath("xcaddy"); err != nil {
|
|
return fmt.Errorf("xcaddy not found in PATH — install with: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest")
|
|
}
|
|
|
|
moduleDir := filepath.Join(b.tmpDir, "caddy-dns-orama")
|
|
if err := os.MkdirAll(moduleDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write go.mod
|
|
goMod := fmt.Sprintf(`module github.com/DeBrosOfficial/caddy-dns-orama
|
|
|
|
go 1.22
|
|
|
|
require (
|
|
github.com/caddyserver/caddy/v2 v2.%s
|
|
github.com/libdns/libdns v1.1.0
|
|
)
|
|
`, constants.CaddyVersion[2:])
|
|
if err := os.WriteFile(filepath.Join(moduleDir, "go.mod"), []byte(goMod), 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write provider.go — read from the caddy installer's generated code
|
|
// We inline the same provider code used by the VPS-side caddy installer
|
|
providerCode := generateCaddyProviderCode()
|
|
if err := os.WriteFile(filepath.Join(moduleDir, "provider.go"), []byte(providerCode), 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
// go mod tidy
|
|
cmd := exec.Command("go", "mod", "tidy")
|
|
cmd.Dir = moduleDir
|
|
cmd.Env = append(os.Environ(),
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("go mod tidy failed: %w", err)
|
|
}
|
|
|
|
// Build with xcaddy
|
|
fmt.Println(" Building binary...")
|
|
cmd = exec.Command("xcaddy", "build",
|
|
"v"+constants.CaddyVersion,
|
|
"--with", "github.com/DeBrosOfficial/caddy-dns-orama="+moduleDir,
|
|
"--output", filepath.Join(b.binDir, "caddy"))
|
|
cmd.Env = append(os.Environ(),
|
|
"GOOS=linux",
|
|
fmt.Sprintf("GOARCH=%s", b.flags.Arch),
|
|
"GOPROXY=https://proxy.golang.org|direct",
|
|
"GONOSUMDB=*")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("xcaddy build failed: %w", err)
|
|
}
|
|
|
|
fmt.Println(" ✓ caddy")
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) downloadIPFS() error {
|
|
fmt.Printf("[7/8] Downloading IPFS Kubo %s...\n", constants.IPFSKuboVersion)
|
|
|
|
arch := b.flags.Arch
|
|
tarball := fmt.Sprintf("kubo_%s_linux-%s.tar.gz", constants.IPFSKuboVersion, arch)
|
|
url := fmt.Sprintf("https://dist.ipfs.tech/kubo/%s/%s", constants.IPFSKuboVersion, tarball)
|
|
tarPath := filepath.Join(b.tmpDir, tarball)
|
|
|
|
if err := downloadFile(url, tarPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Extract ipfs binary from kubo/ipfs
|
|
if err := extractFileFromTarball(tarPath, "kubo/ipfs", filepath.Join(b.binDir, "ipfs")); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(" ✓ ipfs")
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) downloadRQLite() error {
|
|
fmt.Printf("[8/8] Downloading RQLite %s...\n", constants.RQLiteVersion)
|
|
|
|
arch := b.flags.Arch
|
|
tarball := fmt.Sprintf("rqlite-v%s-linux-%s.tar.gz", constants.RQLiteVersion, arch)
|
|
url := fmt.Sprintf("https://github.com/rqlite/rqlite/releases/download/v%s/%s", constants.RQLiteVersion, tarball)
|
|
tarPath := filepath.Join(b.tmpDir, tarball)
|
|
|
|
if err := downloadFile(url, tarPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Extract rqlited binary
|
|
extractDir := fmt.Sprintf("rqlite-v%s-linux-%s", constants.RQLiteVersion, arch)
|
|
if err := extractFileFromTarball(tarPath, extractDir+"/rqlited", filepath.Join(b.binDir, "rqlited")); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(" ✓ rqlited")
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) copySystemdTemplates() error {
|
|
systemdSrc := filepath.Join(b.projectDir, "systemd")
|
|
systemdDst := filepath.Join(b.tmpDir, "systemd")
|
|
if err := os.MkdirAll(systemdDst, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
entries, err := os.ReadDir(systemdSrc)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read systemd dir: %w", err)
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".service") {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(systemdSrc, entry.Name()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(filepath.Join(systemdDst, entry.Name()), data, 0644); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// crossEnv returns the environment for cross-compilation.
|
|
func (b *Builder) crossEnv() []string {
|
|
return append(os.Environ(),
|
|
"GOOS=linux",
|
|
fmt.Sprintf("GOARCH=%s", b.flags.Arch),
|
|
"CGO_ENABLED=0")
|
|
}
|
|
|
|
func (b *Builder) readVersion() string {
|
|
// Try to read from Makefile
|
|
data, err := os.ReadFile(filepath.Join(b.projectDir, "Makefile"))
|
|
if err != nil {
|
|
return "dev"
|
|
}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, "VERSION") {
|
|
parts := strings.SplitN(line, ":=", 2)
|
|
if len(parts) == 2 {
|
|
return strings.TrimSpace(parts[1])
|
|
}
|
|
}
|
|
}
|
|
return "dev"
|
|
}
|
|
|
|
func (b *Builder) readCommit() string {
|
|
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
|
|
cmd.Dir = b.projectDir
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return "unknown"
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
// generateCaddyProviderCode returns the Caddy DNS provider Go source.
|
|
// This is the same code used by the VPS-side caddy installer.
|
|
func generateCaddyProviderCode() string {
|
|
return `// Package orama implements a DNS provider for Caddy that uses the Orama Network
|
|
// gateway's internal ACME API for DNS-01 challenge validation.
|
|
package orama
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
"github.com/libdns/libdns"
|
|
)
|
|
|
|
func init() {
|
|
caddy.RegisterModule(Provider{})
|
|
}
|
|
|
|
// Provider wraps the Orama DNS provider for Caddy.
|
|
type Provider struct {
|
|
// Endpoint is the URL of the Orama gateway's ACME API
|
|
// Default: http://localhost:6001/v1/internal/acme
|
|
Endpoint string ` + "`json:\"endpoint,omitempty\"`" + `
|
|
}
|
|
|
|
// CaddyModule returns the Caddy module information.
|
|
func (Provider) CaddyModule() caddy.ModuleInfo {
|
|
return caddy.ModuleInfo{
|
|
ID: "dns.providers.orama",
|
|
New: func() caddy.Module { return new(Provider) },
|
|
}
|
|
}
|
|
|
|
// Provision sets up the module.
|
|
func (p *Provider) Provision(ctx caddy.Context) error {
|
|
if p.Endpoint == "" {
|
|
p.Endpoint = "http://localhost:6001/v1/internal/acme"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalCaddyfile parses the Caddyfile configuration.
|
|
func (p *Provider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|
for d.Next() {
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "endpoint":
|
|
if !d.NextArg() {
|
|
return d.ArgErr()
|
|
}
|
|
p.Endpoint = d.Val()
|
|
default:
|
|
return d.Errf("unrecognized option: %s", d.Val())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AppendRecords adds records to the zone.
|
|
func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
|
var added []libdns.Record
|
|
for _, rec := range records {
|
|
rr := rec.RR()
|
|
if rr.Type != "TXT" {
|
|
continue
|
|
}
|
|
fqdn := rr.Name + "." + zone
|
|
payload := map[string]string{"fqdn": fqdn, "value": rr.Data}
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return added, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, "POST", p.Endpoint+"/present", bytes.NewReader(body))
|
|
if err != nil {
|
|
return added, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return added, fmt.Errorf("failed to present challenge: %w", err)
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return added, fmt.Errorf("present failed with status %d", resp.StatusCode)
|
|
}
|
|
added = append(added, rec)
|
|
}
|
|
return added, nil
|
|
}
|
|
|
|
// DeleteRecords removes records from the zone.
|
|
func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
|
var deleted []libdns.Record
|
|
for _, rec := range records {
|
|
rr := rec.RR()
|
|
if rr.Type != "TXT" {
|
|
continue
|
|
}
|
|
fqdn := rr.Name + "." + zone
|
|
payload := map[string]string{"fqdn": fqdn, "value": rr.Data}
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return deleted, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, "POST", p.Endpoint+"/cleanup", bytes.NewReader(body))
|
|
if err != nil {
|
|
return deleted, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return deleted, fmt.Errorf("failed to cleanup challenge: %w", err)
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return deleted, fmt.Errorf("cleanup failed with status %d", resp.StatusCode)
|
|
}
|
|
deleted = append(deleted, rec)
|
|
}
|
|
return deleted, nil
|
|
}
|
|
|
|
// GetRecords returns the records in the zone. Not used for ACME.
|
|
func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// SetRecords sets the records in the zone. Not used for ACME.
|
|
func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Interface guards
|
|
var (
|
|
_ caddy.Module = (*Provider)(nil)
|
|
_ caddy.Provisioner = (*Provider)(nil)
|
|
_ caddyfile.Unmarshaler = (*Provider)(nil)
|
|
_ libdns.RecordAppender = (*Provider)(nil)
|
|
_ libdns.RecordDeleter = (*Provider)(nil)
|
|
_ libdns.RecordGetter = (*Provider)(nil)
|
|
_ libdns.RecordSetter = (*Provider)(nil)
|
|
)
|
|
`
|
|
}
|