mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 09:16:57 +00:00
Refactored cli to make things more clear and easy to understand for developers
This commit is contained in:
parent
7163aad850
commit
865a4f3434
4
Makefile
4
Makefile
@ -75,7 +75,7 @@ build: deps
|
||||
@mkdir -p bin
|
||||
go build -ldflags "$(LDFLAGS)" -o bin/identity ./cmd/identity
|
||||
go build -ldflags "$(LDFLAGS)" -o bin/orama-node ./cmd/node
|
||||
go build -ldflags "$(LDFLAGS)" -o bin/orama cmd/cli/main.go
|
||||
go build -ldflags "$(LDFLAGS)" -o bin/orama ./cmd/cli/
|
||||
# Inject gateway build metadata via pkg path variables
|
||||
go build -ldflags "$(LDFLAGS) -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildVersion=$(VERSION)' -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildCommit=$(COMMIT)' -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildTime=$(DATE)'" -o bin/gateway ./cmd/gateway
|
||||
@echo "Build complete! Run ./bin/orama version"
|
||||
@ -84,7 +84,7 @@ build: deps
|
||||
build-linux: deps
|
||||
@echo "Cross-compiling CLI for linux/amd64 (version=$(VERSION))..."
|
||||
@mkdir -p bin-linux
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS_LINUX)" -trimpath -o bin-linux/orama cmd/cli/main.go
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS_LINUX)" -trimpath -o bin-linux/orama ./cmd/cli/
|
||||
@echo "✓ CLI built at bin-linux/orama"
|
||||
@echo ""
|
||||
@echo "Next steps:"
|
||||
|
||||
220
cmd/cli/main.go
220
cmd/cli/main.go
@ -1,223 +1,5 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
timeout = 30 * time.Second
|
||||
format = "table"
|
||||
)
|
||||
|
||||
// version metadata populated via -ldflags at build time
|
||||
var (
|
||||
version = "dev"
|
||||
commit = ""
|
||||
date = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
showHelp()
|
||||
return
|
||||
}
|
||||
|
||||
command := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
// Parse global flags
|
||||
parseGlobalFlags(args)
|
||||
|
||||
switch command {
|
||||
case "version":
|
||||
fmt.Printf("orama %s", version)
|
||||
if commit != "" {
|
||||
fmt.Printf(" (commit %s)", commit)
|
||||
}
|
||||
if date != "" {
|
||||
fmt.Printf(" built %s", date)
|
||||
}
|
||||
fmt.Println()
|
||||
return
|
||||
|
||||
// Production environment commands (legacy with 'prod' prefix)
|
||||
case "prod":
|
||||
cli.HandleProdCommand(args)
|
||||
|
||||
// Direct production commands (new simplified interface)
|
||||
case "invite":
|
||||
cli.HandleProdCommand(append([]string{"invite"}, args...))
|
||||
case "install":
|
||||
cli.HandleProdCommand(append([]string{"install"}, args...))
|
||||
case "upgrade":
|
||||
cli.HandleProdCommand(append([]string{"upgrade"}, args...))
|
||||
case "migrate":
|
||||
cli.HandleProdCommand(append([]string{"migrate"}, args...))
|
||||
case "status":
|
||||
cli.HandleProdCommand(append([]string{"status"}, args...))
|
||||
case "start":
|
||||
cli.HandleProdCommand(append([]string{"start"}, args...))
|
||||
case "stop":
|
||||
cli.HandleProdCommand(append([]string{"stop"}, args...))
|
||||
case "restart":
|
||||
cli.HandleProdCommand(append([]string{"restart"}, args...))
|
||||
case "logs":
|
||||
cli.HandleProdCommand(append([]string{"logs"}, args...))
|
||||
case "uninstall":
|
||||
cli.HandleProdCommand(append([]string{"uninstall"}, args...))
|
||||
|
||||
// Authentication commands
|
||||
case "auth":
|
||||
cli.HandleAuthCommand(args)
|
||||
|
||||
// Deployment commands
|
||||
case "deploy":
|
||||
cli.HandleDeployCommand(args)
|
||||
case "deployments":
|
||||
cli.HandleDeploymentsCommand(args)
|
||||
|
||||
// Database commands
|
||||
case "db":
|
||||
cli.HandleDBCommand(args)
|
||||
|
||||
// Cluster management
|
||||
case "cluster":
|
||||
cli.HandleClusterCommand(args)
|
||||
|
||||
// Cluster inspection
|
||||
case "inspect":
|
||||
cli.HandleInspectCommand(args)
|
||||
|
||||
// Namespace management
|
||||
case "namespace":
|
||||
cli.HandleNamespaceCommand(args)
|
||||
|
||||
// Environment management
|
||||
case "env":
|
||||
cli.HandleEnvCommand(args)
|
||||
|
||||
// Help
|
||||
case "help", "--help", "-h":
|
||||
showHelp()
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
|
||||
showHelp()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func parseGlobalFlags(args []string) {
|
||||
for i, arg := range args {
|
||||
switch arg {
|
||||
case "-f", "--format":
|
||||
if i+1 < len(args) {
|
||||
format = args[i+1]
|
||||
}
|
||||
case "-t", "--timeout":
|
||||
if i+1 < len(args) {
|
||||
if d, err := time.ParseDuration(args[i+1]); err == nil {
|
||||
timeout = d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showHelp() {
|
||||
fmt.Printf("Orama CLI - Distributed P2P Network Management Tool\n\n")
|
||||
fmt.Printf("Usage: orama <command> [args...]\n\n")
|
||||
|
||||
fmt.Printf("🚀 Production Deployment:\n")
|
||||
fmt.Printf(" install - Install production node (requires root/sudo)\n")
|
||||
fmt.Printf(" upgrade - Upgrade existing installation\n")
|
||||
fmt.Printf(" status - Show production service status\n")
|
||||
fmt.Printf(" start - Start all production services (requires root/sudo)\n")
|
||||
fmt.Printf(" stop - Stop all production services (requires root/sudo)\n")
|
||||
fmt.Printf(" restart - Restart all production services (requires root/sudo)\n")
|
||||
fmt.Printf(" logs <service> - View production service logs\n")
|
||||
fmt.Printf(" uninstall - Remove production services (requires root/sudo)\n\n")
|
||||
|
||||
fmt.Printf("🔐 Authentication:\n")
|
||||
fmt.Printf(" auth login - Authenticate with wallet\n")
|
||||
fmt.Printf(" auth logout - Clear stored credentials\n")
|
||||
fmt.Printf(" auth whoami - Show current authentication\n")
|
||||
fmt.Printf(" auth status - Show detailed auth info\n")
|
||||
fmt.Printf(" auth help - Show auth command help\n\n")
|
||||
|
||||
fmt.Printf("📦 Deployments:\n")
|
||||
fmt.Printf(" deploy static <path> - Deploy a static site (React, Vue, etc.)\n")
|
||||
fmt.Printf(" deploy nextjs <path> - Deploy a Next.js application\n")
|
||||
fmt.Printf(" deploy go <path> - Deploy a Go backend\n")
|
||||
fmt.Printf(" deploy nodejs <path> - Deploy a Node.js backend\n")
|
||||
fmt.Printf(" deployments list - List all deployments\n")
|
||||
fmt.Printf(" deployments get <name> - Get deployment details\n")
|
||||
fmt.Printf(" deployments logs <name> - View deployment logs\n")
|
||||
fmt.Printf(" deployments delete <name> - Delete a deployment\n")
|
||||
fmt.Printf(" deployments rollback <name> - Rollback to previous version\n\n")
|
||||
|
||||
fmt.Printf("🗄️ Databases:\n")
|
||||
fmt.Printf(" db create <name> - Create a SQLite database\n")
|
||||
fmt.Printf(" db query <name> \"<sql>\" - Execute SQL query\n")
|
||||
fmt.Printf(" db list - List all databases\n")
|
||||
fmt.Printf(" db backup <name> - Backup database to IPFS\n")
|
||||
fmt.Printf(" db backups <name> - List database backups\n\n")
|
||||
|
||||
fmt.Printf("🏢 Namespaces:\n")
|
||||
fmt.Printf(" namespace delete - Delete current namespace and all resources\n")
|
||||
fmt.Printf(" namespace repair <name> - Repair under-provisioned cluster (add missing nodes)\n\n")
|
||||
|
||||
fmt.Printf("🔧 Cluster Management:\n")
|
||||
fmt.Printf(" cluster status - Show cluster node status\n")
|
||||
fmt.Printf(" cluster health - Run cluster health checks\n")
|
||||
fmt.Printf(" cluster rqlite status - Show detailed Raft state\n")
|
||||
fmt.Printf(" cluster rqlite voters - Show voter list\n")
|
||||
fmt.Printf(" cluster rqlite backup - Trigger manual backup\n")
|
||||
fmt.Printf(" cluster watch - Live cluster status monitor\n\n")
|
||||
|
||||
fmt.Printf("🔍 Cluster Inspection:\n")
|
||||
fmt.Printf(" inspect - Inspect cluster health via SSH\n")
|
||||
fmt.Printf(" inspect --env devnet - Inspect devnet nodes\n")
|
||||
fmt.Printf(" inspect --subsystem rqlite - Inspect only RQLite subsystem\n")
|
||||
fmt.Printf(" inspect --format json - Output as JSON\n\n")
|
||||
|
||||
fmt.Printf("🌍 Environments:\n")
|
||||
fmt.Printf(" env list - List all environments\n")
|
||||
fmt.Printf(" env current - Show current environment\n")
|
||||
fmt.Printf(" env switch <name> - Switch to environment\n\n")
|
||||
|
||||
fmt.Printf("Global Flags:\n")
|
||||
fmt.Printf(" -f, --format <format> - Output format: table, json (default: table)\n")
|
||||
fmt.Printf(" -t, --timeout <duration> - Operation timeout (default: 30s)\n")
|
||||
fmt.Printf(" --help, -h - Show this help message\n\n")
|
||||
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" # Deploy a React app\n")
|
||||
fmt.Printf(" cd my-react-app && npm run build\n")
|
||||
fmt.Printf(" orama deploy static ./dist --name my-app\n\n")
|
||||
|
||||
fmt.Printf(" # Deploy a Next.js app with SSR\n")
|
||||
fmt.Printf(" cd my-nextjs-app && npm run build\n")
|
||||
fmt.Printf(" orama deploy nextjs . --name my-nextjs --ssr\n\n")
|
||||
|
||||
fmt.Printf(" # Create and use a database\n")
|
||||
fmt.Printf(" orama db create my-db\n")
|
||||
fmt.Printf(" orama db query my-db \"CREATE TABLE users (id INT, name TEXT)\"\n")
|
||||
fmt.Printf(" orama db query my-db \"INSERT INTO users VALUES (1, 'Alice')\"\n\n")
|
||||
|
||||
fmt.Printf(" # Manage deployments\n")
|
||||
fmt.Printf(" orama deployments list\n")
|
||||
fmt.Printf(" orama deployments get my-app\n")
|
||||
fmt.Printf(" orama deployments logs my-app --follow\n\n")
|
||||
|
||||
fmt.Printf(" # First node (creates new cluster)\n")
|
||||
fmt.Printf(" sudo orama install --vps-ip 203.0.113.1 --domain node-1.orama.network\n\n")
|
||||
|
||||
fmt.Printf(" # Service management\n")
|
||||
fmt.Printf(" orama status\n")
|
||||
fmt.Printf(" orama logs node --follow\n")
|
||||
runCLI()
|
||||
}
|
||||
|
||||
87
cmd/cli/root.go
Normal file
87
cmd/cli/root.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
// Command groups
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/app"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/authcmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/dbcmd"
|
||||
deploycmd "github.com/DeBrosOfficial/network/pkg/cli/cmd/deploy"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/envcmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/inspectcmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/namespacecmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/node"
|
||||
)
|
||||
|
||||
// version metadata populated via -ldflags at build time
|
||||
// Must match Makefile: -X 'main.version=...' -X 'main.commit=...' -X 'main.date=...'
|
||||
var (
|
||||
version = "dev"
|
||||
commit = ""
|
||||
date = ""
|
||||
)
|
||||
|
||||
func newRootCmd() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "orama",
|
||||
Short: "Orama CLI - Distributed P2P Network Management Tool",
|
||||
Long: `Orama CLI is a tool for managing nodes, deploying applications,
|
||||
and interacting with the Orama distributed network.`,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
||||
// Version command
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show version information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("orama %s", version)
|
||||
if commit != "" {
|
||||
fmt.Printf(" (commit %s)", commit)
|
||||
}
|
||||
if date != "" {
|
||||
fmt.Printf(" built %s", date)
|
||||
}
|
||||
fmt.Println()
|
||||
},
|
||||
})
|
||||
|
||||
// Node operator commands (was "prod")
|
||||
rootCmd.AddCommand(node.Cmd)
|
||||
|
||||
// Deploy command (top-level, upsert)
|
||||
rootCmd.AddCommand(deploycmd.Cmd)
|
||||
|
||||
// App management (was "deployments")
|
||||
rootCmd.AddCommand(app.Cmd)
|
||||
|
||||
// Database commands
|
||||
rootCmd.AddCommand(dbcmd.Cmd)
|
||||
|
||||
// Namespace commands
|
||||
rootCmd.AddCommand(namespacecmd.Cmd)
|
||||
|
||||
// Environment commands
|
||||
rootCmd.AddCommand(envcmd.Cmd)
|
||||
|
||||
// Auth commands
|
||||
rootCmd.AddCommand(authcmd.Cmd)
|
||||
|
||||
// Inspect command
|
||||
rootCmd.AddCommand(inspectcmd.Cmd)
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func runCLI() {
|
||||
rootCmd := newRootCmd()
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@ -163,7 +163,7 @@ orama deploy nextjs ./nextjs.tar.gz --name my-nextjs --ssr
|
||||
# URLs:
|
||||
# • https://my-nextjs.orama.network
|
||||
#
|
||||
# ⚠️ Note: SSR deployment may take a minute to start. Check status with: orama deployments get my-nextjs
|
||||
# ⚠️ Note: SSR deployment may take a minute to start. Check status with: orama app get my-nextjs
|
||||
```
|
||||
|
||||
### What Happens Behind the Scenes
|
||||
@ -795,7 +795,7 @@ Open your browser to:
|
||||
### List All Deployments
|
||||
|
||||
```bash
|
||||
orama deployments list
|
||||
orama app list
|
||||
|
||||
# Output:
|
||||
# NAME TYPE STATUS VERSION CREATED
|
||||
@ -809,7 +809,7 @@ orama deployments list
|
||||
### Get Deployment Details
|
||||
|
||||
```bash
|
||||
orama deployments get my-react-app
|
||||
orama app get my-react-app
|
||||
|
||||
# Output:
|
||||
# Deployment: my-react-app
|
||||
@ -835,17 +835,17 @@ orama deployments get my-react-app
|
||||
|
||||
```bash
|
||||
# View last 100 lines
|
||||
orama deployments logs my-nextjs
|
||||
orama app logs my-nextjs
|
||||
|
||||
# Follow logs in real-time
|
||||
orama deployments logs my-nextjs --follow
|
||||
orama app logs my-nextjs --follow
|
||||
```
|
||||
|
||||
### Rollback to Previous Version
|
||||
|
||||
```bash
|
||||
# Rollback to version 1
|
||||
orama deployments rollback my-nextjs --version 1
|
||||
orama app rollback my-nextjs --version 1
|
||||
|
||||
# Output:
|
||||
# ⚠️ Rolling back 'my-nextjs' to version 1. Continue? (y/N): y
|
||||
@ -862,7 +862,7 @@ orama deployments rollback my-nextjs --version 1
|
||||
### Delete Deployment
|
||||
|
||||
```bash
|
||||
orama deployments delete my-old-app
|
||||
orama app delete my-old-app
|
||||
|
||||
# Output:
|
||||
# ⚠️ Are you sure you want to delete deployment 'my-old-app'? (y/N): y
|
||||
@ -880,10 +880,10 @@ orama deployments delete my-old-app
|
||||
|
||||
```bash
|
||||
# Check deployment details
|
||||
orama deployments get my-app
|
||||
orama app get my-app
|
||||
|
||||
# View logs for errors
|
||||
orama deployments logs my-app
|
||||
orama app logs my-app
|
||||
|
||||
# Common issues:
|
||||
# - Binary not compiled for Linux (GOOS=linux GOARCH=amd64)
|
||||
@ -896,7 +896,7 @@ orama deployments logs my-app
|
||||
|
||||
```bash
|
||||
# 1. Check deployment status
|
||||
orama deployments get my-app
|
||||
orama app get my-app
|
||||
|
||||
# 2. Verify DNS (may take up to 10 seconds to propagate)
|
||||
dig my-app.orama.network
|
||||
@ -980,7 +980,7 @@ orama auth status
|
||||
|
||||
- **Explore the API**: See `/docs/GATEWAY_API.md` for HTTP API details
|
||||
- **Advanced Features**: Custom domains, load balancing, autoscaling (coming soon)
|
||||
- **Production Deployment**: Install nodes with `orama install` for production clusters
|
||||
- **Production Deployment**: Install nodes with `orama node install` for production clusters
|
||||
- **Client SDK**: Use the Go/JS SDK for programmatic deployments
|
||||
|
||||
---
|
||||
|
||||
@ -40,16 +40,16 @@ make build-linux
|
||||
# Creates: /tmp/network-source.tar.gz
|
||||
|
||||
# 3. Install on a new VPS (handles SCP, extract, and remote install automatically)
|
||||
./bin/orama install --vps-ip <ip> --nameserver --domain <domain> --base-domain <domain>
|
||||
./bin/orama node install --vps-ip <ip> --nameserver --domain <domain> --base-domain <domain>
|
||||
|
||||
# Or upgrade an existing VPS
|
||||
./bin/orama upgrade --restart
|
||||
./bin/orama node upgrade --restart
|
||||
```
|
||||
|
||||
The `orama install` command automatically:
|
||||
The `orama node install` command automatically:
|
||||
1. Uploads the source archive via SCP
|
||||
2. Extracts source to `/opt/orama/src` and installs the CLI to `/usr/local/bin/orama`
|
||||
3. Runs `orama install` on the VPS which builds all binaries from source (Go, CoreDNS, Caddy, Olric, etc.)
|
||||
3. Runs `orama node install` on the VPS which builds all binaries from source (Go, CoreDNS, Caddy, Olric, etc.)
|
||||
|
||||
### Upgrading a Multi-Node Cluster (CRITICAL)
|
||||
|
||||
@ -84,7 +84,7 @@ done
|
||||
ssh ubuntu@<any-node> 'curl -s http://localhost:5001/status | jq -r .store.raft.state'
|
||||
|
||||
# 6. Upgrade FOLLOWER nodes one at a time
|
||||
ssh ubuntu@<follower-ip> 'sudo orama prod stop && sudo orama upgrade --restart'
|
||||
ssh ubuntu@<follower-ip> 'sudo orama node stop && sudo orama node upgrade --restart'
|
||||
|
||||
# Wait for rejoin before proceeding to next node
|
||||
ssh ubuntu@<leader-ip> 'curl -s http://localhost:5001/status | jq -r .store.raft.num_peers'
|
||||
@ -93,13 +93,13 @@ ssh ubuntu@<leader-ip> 'curl -s http://localhost:5001/status | jq -r .store.raft
|
||||
# Repeat for each follower...
|
||||
|
||||
# 7. Upgrade the LEADER node last
|
||||
ssh ubuntu@<leader-ip> 'sudo orama prod stop && sudo orama upgrade --restart'
|
||||
ssh ubuntu@<leader-ip> 'sudo orama node stop && sudo orama node upgrade --restart'
|
||||
```
|
||||
|
||||
#### What NOT to Do
|
||||
|
||||
- **DON'T** stop all nodes, replace binaries, then start all nodes
|
||||
- **DON'T** run `orama upgrade --restart` on multiple nodes in parallel
|
||||
- **DON'T** run `orama node upgrade --restart` on multiple nodes in parallel
|
||||
- **DON'T** clear RQLite data directories unless doing a full cluster rebuild
|
||||
- **DON'T** use `systemctl stop orama-node` on multiple nodes simultaneously
|
||||
|
||||
@ -111,7 +111,7 @@ If nodes get stuck in "Candidate" state or show "leader not found" errors:
|
||||
2. Keep that node running as the new leader
|
||||
3. On each other node, clear RQLite data and restart:
|
||||
```bash
|
||||
sudo orama prod stop
|
||||
sudo orama node stop
|
||||
sudo rm -rf /opt/orama/.orama/data/rqlite
|
||||
sudo systemctl start orama-node
|
||||
```
|
||||
@ -135,7 +135,7 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS
|
||||
|
||||
### CLI Flags Reference
|
||||
|
||||
#### `orama install`
|
||||
#### `orama node install`
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
@ -144,7 +144,7 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS
|
||||
| `--base-domain <domain>` | Base domain for deployment routing (e.g., example.com) |
|
||||
| `--nameserver` | Configure this node as a nameserver (CoreDNS + Caddy) |
|
||||
| `--join <url>` | Join existing cluster via HTTPS URL (e.g., `https://node1.example.com`) |
|
||||
| `--token <token>` | Invite token for joining (from `orama invite` on existing node) |
|
||||
| `--token <token>` | Invite token for joining (from `orama node invite` on existing node) |
|
||||
| `--force` | Force reconfiguration even if already installed |
|
||||
| `--skip-firewall` | Skip UFW firewall setup |
|
||||
| `--skip-checks` | Skip minimum resource checks (RAM/CPU) |
|
||||
@ -159,7 +159,7 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS
|
||||
| `--anyone-bandwidth <pct>` | Limit relay to N% of VPS bandwidth (default: 30, 0=unlimited). Runs a speedtest during install to measure available bandwidth |
|
||||
| `--anyone-accounting <GB>` | Monthly data cap for relay in GB (0=unlimited) |
|
||||
|
||||
#### `orama invite`
|
||||
#### `orama node invite`
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
@ -171,7 +171,7 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS
|
||||
- **Expiry is checked in UTC.** RQLite uses `datetime('now')` which is always UTC. If your local timezone differs, account for the offset when choosing expiry durations.
|
||||
- **Use longer expiry for multi-node deployments.** When deploying multiple nodes, use `--expiry 24h` to avoid tokens expiring mid-deployment.
|
||||
|
||||
#### `orama upgrade`
|
||||
#### `orama node upgrade`
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
@ -180,41 +180,44 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS
|
||||
| `--anyone-bandwidth <pct>` | Limit relay to N% of VPS bandwidth (default: 30, 0=unlimited) |
|
||||
| `--anyone-accounting <GB>` | Monthly data cap for relay in GB (0=unlimited) |
|
||||
|
||||
#### `orama prod` (Service Management)
|
||||
#### `orama node` (Service Management)
|
||||
|
||||
Use these commands to manage services on production nodes:
|
||||
|
||||
```bash
|
||||
# Stop all services (orama-node, coredns, caddy)
|
||||
sudo orama prod stop
|
||||
sudo orama node stop
|
||||
|
||||
# Start all services
|
||||
sudo orama prod start
|
||||
sudo orama node start
|
||||
|
||||
# Restart all services
|
||||
sudo orama prod restart
|
||||
sudo orama node restart
|
||||
|
||||
# Check service status
|
||||
sudo orama prod status
|
||||
sudo orama node status
|
||||
|
||||
# Diagnose common issues
|
||||
sudo orama node doctor
|
||||
```
|
||||
|
||||
**Note:** Always use `orama prod stop` instead of manually running `systemctl stop`. The CLI ensures all related services (including CoreDNS and Caddy on nameserver nodes) are handled correctly.
|
||||
**Note:** Always use `orama node stop` instead of manually running `systemctl stop`. The CLI ensures all related services (including CoreDNS and Caddy on nameserver nodes) are handled correctly.
|
||||
|
||||
### Node Join Flow
|
||||
|
||||
```bash
|
||||
# 1. Genesis node (first node, creates cluster)
|
||||
# Nameserver nodes use the base domain as --domain
|
||||
sudo orama install --vps-ip 1.2.3.4 --domain example.com \
|
||||
sudo orama node install --vps-ip 1.2.3.4 --domain example.com \
|
||||
--base-domain example.com --nameserver
|
||||
|
||||
# 2. On genesis node, generate an invite
|
||||
orama invite
|
||||
# Output: sudo orama install --join https://example.com --token <TOKEN> --vps-ip <IP>
|
||||
orama node invite
|
||||
# Output: sudo orama node install --join https://example.com --token <TOKEN> --vps-ip <IP>
|
||||
|
||||
# 3. On the new node, run the printed command
|
||||
# Nameserver nodes use the base domain; non-nameserver nodes use subdomains (e.g., node-4.example.com)
|
||||
sudo orama install --join https://example.com --token abc123... \
|
||||
sudo orama node install --join https://example.com --token abc123... \
|
||||
--vps-ip 5.6.7.8 --domain example.com --base-domain example.com --nameserver
|
||||
```
|
||||
|
||||
@ -231,7 +234,7 @@ node's IP so that `node1.example.com` resolves publicly.
|
||||
**If DNS is not yet configured**, you can use the genesis node's public IP with HTTP as a fallback:
|
||||
|
||||
```bash
|
||||
sudo orama install --join http://1.2.3.4 --vps-ip 5.6.7.8 --token abc123... --nameserver
|
||||
sudo orama node install --join http://1.2.3.4 --vps-ip 5.6.7.8 --token abc123... --nameserver
|
||||
```
|
||||
|
||||
This works because Caddy's `:80` block proxies all HTTP traffic to the gateway. However, once DNS
|
||||
@ -243,7 +246,7 @@ which proxies to the gateway internally.
|
||||
|
||||
## Pre-Install Checklist
|
||||
|
||||
Before running `orama install` on a VPS, ensure:
|
||||
Before running `orama node install` on a VPS, ensure:
|
||||
|
||||
1. **Stop Docker if running.** Docker commonly binds ports 4001 and 8080 which conflict with IPFS. The installer checks for port conflicts and shows which process is using each port, but it's easier to stop Docker first:
|
||||
```bash
|
||||
|
||||
@ -1,423 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/auth"
|
||||
"github.com/DeBrosOfficial/network/pkg/client"
|
||||
)
|
||||
|
||||
// HandleHealthCommand handles the health command
|
||||
func HandleHealthCommand(format string, timeout time.Duration) {
|
||||
cli, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer cli.Disconnect()
|
||||
|
||||
health, err := cli.Health()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get health: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
printJSON(health)
|
||||
} else {
|
||||
printHealth(health)
|
||||
}
|
||||
}
|
||||
|
||||
// HandlePeersCommand handles the peers command
|
||||
func HandlePeersCommand(format string, timeout time.Duration) {
|
||||
cli, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer cli.Disconnect()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
peers, err := cli.Network().GetPeers(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get peers: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
printJSON(peers)
|
||||
} else {
|
||||
printPeers(peers)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleStatusCommand handles the status command
|
||||
func HandleStatusCommand(format string, timeout time.Duration) {
|
||||
cli, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer cli.Disconnect()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
status, err := cli.Network().GetStatus(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get status: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
printJSON(status)
|
||||
} else {
|
||||
printStatus(status)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleQueryCommand handles the query command
|
||||
func HandleQueryCommand(sql, format string, timeout time.Duration) {
|
||||
// Ensure user is authenticated
|
||||
_ = ensureAuthenticated()
|
||||
|
||||
cli, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer cli.Disconnect()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
result, err := cli.Database().Query(ctx, sql)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to execute query: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
printJSON(result)
|
||||
} else {
|
||||
printQueryResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleConnectCommand handles the connect command
|
||||
func HandleConnectCommand(peerAddr string, timeout time.Duration) {
|
||||
cli, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer cli.Disconnect()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
err = cli.Network().ConnectToPeer(ctx, peerAddr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to connect to peer: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Connected to peer: %s\n", peerAddr)
|
||||
}
|
||||
|
||||
// HandlePeerIDCommand handles the peer-id command
|
||||
func HandlePeerIDCommand(format string, timeout time.Duration) {
|
||||
cli, err := createClient()
|
||||
if err == nil {
|
||||
defer cli.Disconnect()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if status, err := cli.Network().GetStatus(ctx); err == nil {
|
||||
if format == "json" {
|
||||
printJSON(map[string]string{"peer_id": status.NodeID})
|
||||
} else {
|
||||
fmt.Printf("🆔 Peer ID: %s\n", status.NodeID)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "❌ Could not find peer ID. Make sure the node is running or identity files exist.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// HandlePubSubCommand handles pubsub commands
|
||||
func HandlePubSubCommand(args []string, format string, timeout time.Duration) {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: orama pubsub <publish|subscribe|topics> [args...]\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Ensure user is authenticated
|
||||
_ = ensureAuthenticated()
|
||||
|
||||
cli, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer cli.Disconnect()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
subcommand := args[0]
|
||||
switch subcommand {
|
||||
case "publish":
|
||||
if len(args) < 3 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: orama pubsub publish <topic> <message>\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
err := cli.PubSub().Publish(ctx, args[1], []byte(args[2]))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to publish message: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✅ Published message to topic: %s\n", args[1])
|
||||
|
||||
case "subscribe":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: orama pubsub subscribe <topic> [duration]\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
duration := 30 * time.Second
|
||||
if len(args) > 2 {
|
||||
if d, err := time.ParseDuration(args[2]); err == nil {
|
||||
duration = d
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), duration)
|
||||
defer cancel()
|
||||
|
||||
fmt.Printf("🔔 Subscribing to topic '%s' for %v...\n", args[1], duration)
|
||||
|
||||
messageHandler := func(topic string, data []byte) error {
|
||||
fmt.Printf("📨 [%s] %s: %s\n", time.Now().Format("15:04:05"), topic, string(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
err := cli.PubSub().Subscribe(ctx, args[1], messageHandler)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to subscribe: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
fmt.Printf("✅ Subscription ended\n")
|
||||
|
||||
case "topics":
|
||||
topics, err := cli.PubSub().ListTopics(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to list topics: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if format == "json" {
|
||||
printJSON(topics)
|
||||
} else {
|
||||
for _, topic := range topics {
|
||||
fmt.Println(topic)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown pubsub command: %s\n", subcommand)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func createClient() (client.NetworkClient, error) {
|
||||
config := client.DefaultClientConfig("orama")
|
||||
|
||||
// Use active environment's gateway URL
|
||||
gatewayURL := getGatewayURL()
|
||||
config.GatewayURL = gatewayURL
|
||||
|
||||
// Try to get peer configuration from active environment
|
||||
env, err := GetActiveEnvironment()
|
||||
if err == nil && env != nil {
|
||||
// Environment loaded successfully - gateway URL already set above
|
||||
_ = env // Reserve for future peer configuration
|
||||
}
|
||||
|
||||
// Check for existing credentials using enhanced authentication
|
||||
creds, err := auth.GetValidEnhancedCredentials()
|
||||
if err != nil {
|
||||
// No valid credentials found, use the enhanced authentication flow
|
||||
newCreds, authErr := auth.GetOrPromptForCredentials(gatewayURL)
|
||||
if authErr != nil {
|
||||
return nil, fmt.Errorf("authentication failed: %w", authErr)
|
||||
}
|
||||
|
||||
creds = newCreds
|
||||
}
|
||||
|
||||
// Configure client with API key
|
||||
config.APIKey = creds.APIKey
|
||||
|
||||
// Update last used time - the enhanced store handles saving automatically
|
||||
creds.UpdateLastUsed()
|
||||
|
||||
networkClient, err := client.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := networkClient.Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkClient, nil
|
||||
}
|
||||
|
||||
func ensureAuthenticated() *auth.Credentials {
|
||||
gatewayURL := getGatewayURL()
|
||||
|
||||
credentials, err := auth.GetOrPromptForCredentials(gatewayURL)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Authentication failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return credentials
|
||||
}
|
||||
|
||||
func printHealth(health *client.HealthStatus) {
|
||||
fmt.Printf("🏥 Network Health\n")
|
||||
fmt.Printf("Status: %s\n", getStatusEmoji(health.Status)+health.Status)
|
||||
fmt.Printf("Last Updated: %s\n", health.LastUpdated.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("Response Time: %v\n", health.ResponseTime)
|
||||
fmt.Printf("\nChecks:\n")
|
||||
for check, status := range health.Checks {
|
||||
emoji := "✅"
|
||||
if status != "ok" {
|
||||
emoji = "❌"
|
||||
}
|
||||
fmt.Printf(" %s %s: %s\n", emoji, check, status)
|
||||
}
|
||||
}
|
||||
|
||||
func printPeers(peers []client.PeerInfo) {
|
||||
fmt.Printf("👥 Connected Peers (%d)\n\n", len(peers))
|
||||
if len(peers) == 0 {
|
||||
fmt.Printf("No peers connected\n")
|
||||
return
|
||||
}
|
||||
|
||||
for i, peer := range peers {
|
||||
connEmoji := "🔴"
|
||||
if peer.Connected {
|
||||
connEmoji = "🟢"
|
||||
}
|
||||
fmt.Printf("%d. %s %s\n", i+1, connEmoji, peer.ID)
|
||||
fmt.Printf(" Addresses: %v\n", peer.Addresses)
|
||||
fmt.Printf(" Last Seen: %s\n", peer.LastSeen.Format("2006-01-02 15:04:05"))
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func printStatus(status *client.NetworkStatus) {
|
||||
fmt.Printf("🌐 Network Status\n")
|
||||
fmt.Printf("Node ID: %s\n", status.NodeID)
|
||||
fmt.Printf("Connected: %s\n", getBoolEmoji(status.Connected)+strconv.FormatBool(status.Connected))
|
||||
fmt.Printf("Peer Count: %d\n", status.PeerCount)
|
||||
fmt.Printf("Database Size: %s\n", formatBytes(status.DatabaseSize))
|
||||
fmt.Printf("Uptime: %v\n", status.Uptime.Round(time.Second))
|
||||
}
|
||||
|
||||
func printQueryResult(result *client.QueryResult) {
|
||||
fmt.Printf("📊 Query Result\n")
|
||||
fmt.Printf("Rows: %d\n\n", result.Count)
|
||||
|
||||
if len(result.Rows) == 0 {
|
||||
fmt.Printf("No data returned\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Print header
|
||||
for i, col := range result.Columns {
|
||||
if i > 0 {
|
||||
fmt.Printf(" | ")
|
||||
}
|
||||
fmt.Printf("%-15s", col)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Print separator
|
||||
for i := range result.Columns {
|
||||
if i > 0 {
|
||||
fmt.Printf("-+-")
|
||||
}
|
||||
fmt.Printf("%-15s", "---------------")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Print rows
|
||||
for _, row := range result.Rows {
|
||||
for i, cell := range row {
|
||||
if i > 0 {
|
||||
fmt.Printf(" | ")
|
||||
}
|
||||
fmt.Printf("%-15v", cell)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func printJSON(data interface{}) {
|
||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to marshal JSON: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(jsonData))
|
||||
}
|
||||
|
||||
func getStatusEmoji(status string) string {
|
||||
switch status {
|
||||
case "healthy":
|
||||
return "🟢 "
|
||||
case "degraded":
|
||||
return "🟡 "
|
||||
case "unhealthy":
|
||||
return "🔴 "
|
||||
default:
|
||||
return "⚪ "
|
||||
}
|
||||
}
|
||||
|
||||
func getBoolEmoji(b bool) string {
|
||||
if b {
|
||||
return "✅ "
|
||||
}
|
||||
return "❌ "
|
||||
}
|
||||
|
||||
func formatBytes(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cluster"
|
||||
)
|
||||
|
||||
// HandleClusterCommand handles cluster management commands.
|
||||
func HandleClusterCommand(args []string) {
|
||||
cluster.HandleCommand(args)
|
||||
}
|
||||
23
pkg/cli/cmd/app/app.go
Normal file
23
pkg/cli/cmd/app/app.go
Normal file
@ -0,0 +1,23 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/deployments"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for managing deployed applications (was "deployments").
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "app",
|
||||
Aliases: []string{"apps"},
|
||||
Short: "Manage deployed applications",
|
||||
Long: `List, get, delete, rollback, and view logs/stats for your deployed applications.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(deployments.ListCmd)
|
||||
Cmd.AddCommand(deployments.GetCmd)
|
||||
Cmd.AddCommand(deployments.DeleteCmd)
|
||||
Cmd.AddCommand(deployments.RollbackCmd)
|
||||
Cmd.AddCommand(deployments.LogsCmd)
|
||||
Cmd.AddCommand(deployments.StatsCmd)
|
||||
}
|
||||
72
pkg/cli/cmd/authcmd/auth.go
Normal file
72
pkg/cli/cmd/authcmd/auth.go
Normal file
@ -0,0 +1,72 @@
|
||||
package authcmd
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for authentication.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authentication management",
|
||||
Long: `Manage authentication with the Orama network.
|
||||
Supports RootWallet (EVM) and Phantom (Solana) authentication methods.`,
|
||||
}
|
||||
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Authenticate with wallet",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleAuthCommand(append([]string{"login"}, args...))
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
|
||||
var logoutCmd = &cobra.Command{
|
||||
Use: "logout",
|
||||
Short: "Clear stored credentials",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleAuthCommand([]string{"logout"})
|
||||
},
|
||||
}
|
||||
|
||||
var whoamiCmd = &cobra.Command{
|
||||
Use: "whoami",
|
||||
Short: "Show current authentication status",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleAuthCommand([]string{"whoami"})
|
||||
},
|
||||
}
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show detailed authentication info",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleAuthCommand([]string{"status"})
|
||||
},
|
||||
}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all stored credentials",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleAuthCommand([]string{"list"})
|
||||
},
|
||||
}
|
||||
|
||||
var switchCmd = &cobra.Command{
|
||||
Use: "switch",
|
||||
Short: "Switch between stored credentials",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleAuthCommand([]string{"switch"})
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(loginCmd)
|
||||
Cmd.AddCommand(logoutCmd)
|
||||
Cmd.AddCommand(whoamiCmd)
|
||||
Cmd.AddCommand(statusCmd)
|
||||
Cmd.AddCommand(listCmd)
|
||||
Cmd.AddCommand(switchCmd)
|
||||
}
|
||||
74
pkg/cli/cmd/cluster/cluster.go
Normal file
74
pkg/cli/cmd/cluster/cluster.go
Normal file
@ -0,0 +1,74 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
origCluster "github.com/DeBrosOfficial/network/pkg/cli/cluster"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for cluster operations (flattened from cluster rqlite).
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "cluster",
|
||||
Short: "Cluster management and diagnostics",
|
||||
Long: `View cluster status, run health checks, manage RQLite Raft state,
|
||||
and monitor the cluster in real-time.`,
|
||||
}
|
||||
|
||||
var statusSubCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show cluster node status (RQLite + Olric)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
origCluster.HandleStatus(args)
|
||||
},
|
||||
}
|
||||
|
||||
var healthSubCmd = &cobra.Command{
|
||||
Use: "health",
|
||||
Short: "Run cluster health checks",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
origCluster.HandleHealth(args)
|
||||
},
|
||||
}
|
||||
|
||||
var watchSubCmd = &cobra.Command{
|
||||
Use: "watch",
|
||||
Short: "Live cluster status monitor",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
origCluster.HandleWatch(args)
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
|
||||
// Flattened rqlite commands (was cluster rqlite <cmd>)
|
||||
var raftStatusCmd = &cobra.Command{
|
||||
Use: "raft-status",
|
||||
Short: "Show detailed Raft state for local node",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
origCluster.HandleRQLite([]string{"status"})
|
||||
},
|
||||
}
|
||||
|
||||
var votersCmd = &cobra.Command{
|
||||
Use: "voters",
|
||||
Short: "Show current voter list",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
origCluster.HandleRQLite([]string{"voters"})
|
||||
},
|
||||
}
|
||||
|
||||
var backupCmd = &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Trigger manual RQLite backup",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
origCluster.HandleRQLite(append([]string{"backup"}, args...))
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(statusSubCmd)
|
||||
Cmd.AddCommand(healthSubCmd)
|
||||
Cmd.AddCommand(watchSubCmd)
|
||||
Cmd.AddCommand(raftStatusCmd)
|
||||
Cmd.AddCommand(votersCmd)
|
||||
Cmd.AddCommand(backupCmd)
|
||||
}
|
||||
21
pkg/cli/cmd/dbcmd/db.go
Normal file
21
pkg/cli/cmd/dbcmd/db.go
Normal file
@ -0,0 +1,21 @@
|
||||
package dbcmd
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/db"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for database operations.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "db",
|
||||
Short: "Manage SQLite databases",
|
||||
Long: `Create and manage per-namespace SQLite databases.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(db.CreateCmd)
|
||||
Cmd.AddCommand(db.QueryCmd)
|
||||
Cmd.AddCommand(db.ListCmd)
|
||||
Cmd.AddCommand(db.BackupCmd)
|
||||
Cmd.AddCommand(db.BackupsCmd)
|
||||
}
|
||||
21
pkg/cli/cmd/deploy/deploy.go
Normal file
21
pkg/cli/cmd/deploy/deploy.go
Normal file
@ -0,0 +1,21 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/deployments"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the top-level deploy command (upsert: create or update).
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy applications to the Orama network",
|
||||
Long: `Deploy static sites, Next.js apps, Go backends, and Node.js backends.
|
||||
If a deployment with the same name exists, it will be updated.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(deployments.DeployStaticCmd)
|
||||
Cmd.AddCommand(deployments.DeployNextJSCmd)
|
||||
Cmd.AddCommand(deployments.DeployGoCmd)
|
||||
Cmd.AddCommand(deployments.DeployNodeJSCmd)
|
||||
}
|
||||
66
pkg/cli/cmd/envcmd/env.go
Normal file
66
pkg/cli/cmd/envcmd/env.go
Normal file
@ -0,0 +1,66 @@
|
||||
package envcmd
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for environment management.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "env",
|
||||
Short: "Manage environments",
|
||||
Long: `List, switch, add, and remove Orama network environments.
|
||||
Available default environments: production, devnet, testnet.`,
|
||||
}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all available environments",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleEnvCommand([]string{"list"})
|
||||
},
|
||||
}
|
||||
|
||||
var currentCmd = &cobra.Command{
|
||||
Use: "current",
|
||||
Short: "Show current active environment",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleEnvCommand([]string{"current"})
|
||||
},
|
||||
}
|
||||
|
||||
var useCmd = &cobra.Command{
|
||||
Use: "use <name>",
|
||||
Aliases: []string{"switch"},
|
||||
Short: "Switch to a different environment",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleEnvCommand(append([]string{"switch"}, args...))
|
||||
},
|
||||
}
|
||||
|
||||
var addCmd = &cobra.Command{
|
||||
Use: "add <name> <gateway_url> [description]",
|
||||
Short: "Add a custom environment",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleEnvCommand(append([]string{"add"}, args...))
|
||||
},
|
||||
}
|
||||
|
||||
var removeCmd = &cobra.Command{
|
||||
Use: "remove <name>",
|
||||
Short: "Remove an environment",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleEnvCommand(append([]string{"remove"}, args...))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(listCmd)
|
||||
Cmd.AddCommand(currentCmd)
|
||||
Cmd.AddCommand(useCmd)
|
||||
Cmd.AddCommand(addCmd)
|
||||
Cmd.AddCommand(removeCmd)
|
||||
}
|
||||
18
pkg/cli/cmd/inspectcmd/inspect.go
Normal file
18
pkg/cli/cmd/inspectcmd/inspect.go
Normal file
@ -0,0 +1,18 @@
|
||||
package inspectcmd
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the inspect command for SSH-based cluster inspection.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "inspect",
|
||||
Short: "Inspect cluster health via SSH",
|
||||
Long: `SSH into cluster nodes and run health checks.
|
||||
Supports AI-powered failure analysis and result export.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleInspectCommand(args)
|
||||
},
|
||||
DisableFlagParsing: true, // Pass all flags through to existing handler
|
||||
}
|
||||
44
pkg/cli/cmd/namespacecmd/namespace.go
Normal file
44
pkg/cli/cmd/namespacecmd/namespace.go
Normal file
@ -0,0 +1,44 @@
|
||||
package namespacecmd
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for namespace management.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "namespace",
|
||||
Aliases: []string{"ns"},
|
||||
Short: "Manage namespaces",
|
||||
Long: `List, delete, and repair namespaces on the Orama network.`,
|
||||
}
|
||||
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete the current namespace and all its resources",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
forceFlag, _ := cmd.Flags().GetBool("force")
|
||||
var cliArgs []string
|
||||
cliArgs = append(cliArgs, "delete")
|
||||
if forceFlag {
|
||||
cliArgs = append(cliArgs, "--force")
|
||||
}
|
||||
cli.HandleNamespaceCommand(cliArgs)
|
||||
},
|
||||
}
|
||||
|
||||
var repairCmd = &cobra.Command{
|
||||
Use: "repair <namespace>",
|
||||
Short: "Repair an under-provisioned namespace cluster",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cli.HandleNamespaceCommand(append([]string{"repair"}, args...))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
deleteCmd.Flags().Bool("force", false, "Skip confirmation prompt")
|
||||
|
||||
Cmd.AddCommand(deleteCmd)
|
||||
Cmd.AddCommand(repairCmd)
|
||||
}
|
||||
177
pkg/cli/cmd/node/doctor.go
Normal file
177
pkg/cli/cmd/node/doctor.go
Normal file
@ -0,0 +1,177 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var doctorCmd = &cobra.Command{
|
||||
Use: "doctor",
|
||||
Short: "Diagnose common node issues",
|
||||
Long: `Run a series of diagnostic checks on this node to identify
|
||||
common issues with services, connectivity, disk space, and more.`,
|
||||
RunE: runDoctor,
|
||||
}
|
||||
|
||||
type check struct {
|
||||
Name string
|
||||
Status string // PASS, FAIL, WARN
|
||||
Detail string
|
||||
}
|
||||
|
||||
func runDoctor(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println("Node Doctor")
|
||||
fmt.Println("===========")
|
||||
fmt.Println()
|
||||
|
||||
var checks []check
|
||||
|
||||
// 1. Check if services exist
|
||||
services := utils.GetProductionServices()
|
||||
if len(services) == 0 {
|
||||
checks = append(checks, check{"Services installed", "FAIL", "No Orama services found. Run 'orama node install' first."})
|
||||
} else {
|
||||
checks = append(checks, check{"Services installed", "PASS", fmt.Sprintf("%d services found", len(services))})
|
||||
}
|
||||
|
||||
// 2. Check each service status
|
||||
running := 0
|
||||
stopped := 0
|
||||
for _, svc := range services {
|
||||
active, _ := utils.IsServiceActive(svc)
|
||||
if active {
|
||||
running++
|
||||
} else {
|
||||
stopped++
|
||||
}
|
||||
}
|
||||
if stopped > 0 {
|
||||
checks = append(checks, check{"Services running", "WARN", fmt.Sprintf("%d running, %d stopped", running, stopped)})
|
||||
} else if running > 0 {
|
||||
checks = append(checks, check{"Services running", "PASS", fmt.Sprintf("All %d services running", running)})
|
||||
}
|
||||
|
||||
// 3. Check RQLite health
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := client.Get("http://localhost:5001/status")
|
||||
if err != nil {
|
||||
checks = append(checks, check{"RQLite reachable", "FAIL", fmt.Sprintf("Cannot connect: %v", err)})
|
||||
} else {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
checks = append(checks, check{"RQLite reachable", "PASS", "HTTP API responding on :5001"})
|
||||
} else {
|
||||
checks = append(checks, check{"RQLite reachable", "WARN", fmt.Sprintf("HTTP %d", resp.StatusCode)})
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check Olric health
|
||||
resp, err = client.Get("http://localhost:3320/")
|
||||
if err != nil {
|
||||
checks = append(checks, check{"Olric reachable", "FAIL", fmt.Sprintf("Cannot connect: %v", err)})
|
||||
} else {
|
||||
resp.Body.Close()
|
||||
checks = append(checks, check{"Olric reachable", "PASS", "Responding on :3320"})
|
||||
}
|
||||
|
||||
// 5. Check Gateway health
|
||||
resp, err = client.Get("http://localhost:8443/health")
|
||||
if err != nil {
|
||||
checks = append(checks, check{"Gateway reachable", "FAIL", fmt.Sprintf("Cannot connect: %v", err)})
|
||||
} else {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
var health map[string]interface{}
|
||||
if json.Unmarshal(body, &health) == nil {
|
||||
if s, ok := health["status"].(string); ok {
|
||||
checks = append(checks, check{"Gateway reachable", "PASS", fmt.Sprintf("Status: %s", s)})
|
||||
} else {
|
||||
checks = append(checks, check{"Gateway reachable", "PASS", "Responding"})
|
||||
}
|
||||
} else {
|
||||
checks = append(checks, check{"Gateway reachable", "PASS", "Responding"})
|
||||
}
|
||||
} else {
|
||||
checks = append(checks, check{"Gateway reachable", "WARN", fmt.Sprintf("HTTP %d", resp.StatusCode)})
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Check disk space
|
||||
out, err := exec.Command("df", "-h", "/opt/orama").Output()
|
||||
if err == nil {
|
||||
lines := strings.Split(string(out), "\n")
|
||||
if len(lines) > 1 {
|
||||
fields := strings.Fields(lines[1])
|
||||
if len(fields) >= 5 {
|
||||
usePercent := fields[4]
|
||||
checks = append(checks, check{"Disk space (/opt/orama)", "PASS", fmt.Sprintf("Usage: %s (available: %s)", usePercent, fields[3])})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Check DNS resolution (basic)
|
||||
_, err = net.LookupHost("orama-devnet.network")
|
||||
if err != nil {
|
||||
checks = append(checks, check{"DNS resolution", "WARN", fmt.Sprintf("Cannot resolve orama-devnet.network: %v", err)})
|
||||
} else {
|
||||
checks = append(checks, check{"DNS resolution", "PASS", "orama-devnet.network resolves"})
|
||||
}
|
||||
|
||||
// 8. Check if ports are conflicting (only for stopped services)
|
||||
ports, err := utils.CollectPortsForServices(services, true)
|
||||
if err == nil && len(ports) > 0 {
|
||||
var conflicts []string
|
||||
for _, spec := range ports {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", spec.Port))
|
||||
if err != nil {
|
||||
conflicts = append(conflicts, fmt.Sprintf("%s (:%d)", spec.Name, spec.Port))
|
||||
} else {
|
||||
ln.Close()
|
||||
}
|
||||
}
|
||||
if len(conflicts) > 0 {
|
||||
checks = append(checks, check{"Port conflicts", "WARN", fmt.Sprintf("Ports in use: %s", strings.Join(conflicts, ", "))})
|
||||
} else {
|
||||
checks = append(checks, check{"Port conflicts", "PASS", "No conflicts detected"})
|
||||
}
|
||||
}
|
||||
|
||||
// Print results
|
||||
maxName := 0
|
||||
for _, c := range checks {
|
||||
if len(c.Name) > maxName {
|
||||
maxName = len(c.Name)
|
||||
}
|
||||
}
|
||||
|
||||
pass, fail, warn := 0, 0, 0
|
||||
for _, c := range checks {
|
||||
fmt.Printf(" [%s] %-*s %s\n", c.Status, maxName, c.Name, c.Detail)
|
||||
switch c.Status {
|
||||
case "PASS":
|
||||
pass++
|
||||
case "FAIL":
|
||||
fail++
|
||||
case "WARN":
|
||||
warn++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nSummary: %d passed, %d failed, %d warnings\n", pass, fail, warn)
|
||||
|
||||
if fail > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
pkg/cli/cmd/node/install.go
Normal file
18
pkg/cli/cmd/node/install.go
Normal file
@ -0,0 +1,18 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/install"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install production node (requires sudo)",
|
||||
Long: `Install and configure an Orama production node on this machine.
|
||||
For the first node, this creates a new cluster. For subsequent nodes,
|
||||
use --join and --token to join an existing cluster.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
install.Handle(args)
|
||||
},
|
||||
DisableFlagParsing: true, // Pass flags through to existing handler
|
||||
}
|
||||
18
pkg/cli/cmd/node/invite.go
Normal file
18
pkg/cli/cmd/node/invite.go
Normal file
@ -0,0 +1,18 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/invite"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var inviteCmd = &cobra.Command{
|
||||
Use: "invite",
|
||||
Short: "Manage invite tokens for joining the cluster",
|
||||
Long: `Generate invite tokens that allow new nodes to join the cluster.
|
||||
Running without a subcommand creates a new token (same as 'invite create').`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Default behavior: create a new invite token
|
||||
invite.Handle(args)
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
45
pkg/cli/cmd/node/lifecycle.go
Normal file
45
pkg/cli/cmd/node/lifecycle.go
Normal file
@ -0,0 +1,45 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/lifecycle"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var forceFlag bool
|
||||
|
||||
var startCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start all production services (requires sudo)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
lifecycle.HandleStart()
|
||||
},
|
||||
}
|
||||
|
||||
var stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "Stop all production services (requires sudo)",
|
||||
Long: `Stop all Orama services in dependency order and disable auto-start.
|
||||
Includes namespace services, global services, and supporting services.
|
||||
Use --force to bypass quorum safety check.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
lifecycle.HandleStopWithFlags(force)
|
||||
},
|
||||
}
|
||||
|
||||
var restartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "Restart all production services (requires sudo)",
|
||||
Long: `Restart all Orama services. Stops in dependency order then restarts.
|
||||
Includes explicit namespace service restart.
|
||||
Use --force to bypass quorum safety check.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
lifecycle.HandleRestartWithFlags(force)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
stopCmd.Flags().Bool("force", false, "Bypass quorum safety check")
|
||||
restartCmd.Flags().Bool("force", false, "Bypass quorum safety check")
|
||||
}
|
||||
17
pkg/cli/cmd/node/logs.go
Normal file
17
pkg/cli/cmd/node/logs.go
Normal file
@ -0,0 +1,17 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/logs"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var logsCmd = &cobra.Command{
|
||||
Use: "logs <service>",
|
||||
Short: "View production service logs",
|
||||
Long: `Stream logs for a specific Orama production service.
|
||||
Service aliases: node, ipfs, cluster, gateway, olric`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logs.Handle(args)
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
15
pkg/cli/cmd/node/migrate.go
Normal file
15
pkg/cli/cmd/node/migrate.go
Normal file
@ -0,0 +1,15 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/migrate"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrate from old unified setup (requires sudo)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migrate.Handle(args)
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
28
pkg/cli/cmd/node/node.go
Normal file
28
pkg/cli/cmd/node/node.go
Normal file
@ -0,0 +1,28 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd is the root command for node operator commands (was "prod").
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Node operator commands (requires sudo for most operations)",
|
||||
Long: `Manage the Orama node running on this machine.
|
||||
Includes install, upgrade, start/stop/restart, status, logs, and more.
|
||||
Most commands require root privileges (sudo).`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(installCmd)
|
||||
Cmd.AddCommand(uninstallCmd)
|
||||
Cmd.AddCommand(upgradeCmd)
|
||||
Cmd.AddCommand(startCmd)
|
||||
Cmd.AddCommand(stopCmd)
|
||||
Cmd.AddCommand(restartCmd)
|
||||
Cmd.AddCommand(statusCmd)
|
||||
Cmd.AddCommand(logsCmd)
|
||||
Cmd.AddCommand(inviteCmd)
|
||||
Cmd.AddCommand(migrateCmd)
|
||||
Cmd.AddCommand(doctorCmd)
|
||||
}
|
||||
14
pkg/cli/cmd/node/status.go
Normal file
14
pkg/cli/cmd/node/status.go
Normal file
@ -0,0 +1,14 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/status"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show production service status",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
status.Handle()
|
||||
},
|
||||
}
|
||||
14
pkg/cli/cmd/node/uninstall.go
Normal file
14
pkg/cli/cmd/node/uninstall.go
Normal file
@ -0,0 +1,14 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/uninstall"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var uninstallCmd = &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "Remove production services (requires sudo)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
uninstall.Handle()
|
||||
},
|
||||
}
|
||||
17
pkg/cli/cmd/node/upgrade.go
Normal file
17
pkg/cli/cmd/node/upgrade.go
Normal file
@ -0,0 +1,17 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production/upgrade"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var upgradeCmd = &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "Upgrade existing installation (requires sudo)",
|
||||
Long: `Upgrade the Orama node binary and optionally restart services.
|
||||
Uses rolling restart with quorum safety to ensure zero downtime.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
upgrade.Handle(args)
|
||||
},
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/db"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/deployments"
|
||||
)
|
||||
|
||||
// HandleDeployCommand handles deploy commands
|
||||
func HandleDeployCommand(args []string) {
|
||||
deployCmd := deployments.DeployCmd
|
||||
deployCmd.SetArgs(args)
|
||||
|
||||
if err := deployCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleDeploymentsCommand handles deployments management commands
|
||||
func HandleDeploymentsCommand(args []string) {
|
||||
// Create root command for deployments management
|
||||
deploymentsCmd := deployments.DeployCmd
|
||||
deploymentsCmd.Use = "deployments"
|
||||
deploymentsCmd.Short = "Manage deployments"
|
||||
deploymentsCmd.Long = "List, get, delete, rollback, and view logs for deployments"
|
||||
|
||||
// Add management subcommands
|
||||
deploymentsCmd.AddCommand(deployments.ListCmd)
|
||||
deploymentsCmd.AddCommand(deployments.GetCmd)
|
||||
deploymentsCmd.AddCommand(deployments.DeleteCmd)
|
||||
deploymentsCmd.AddCommand(deployments.RollbackCmd)
|
||||
deploymentsCmd.AddCommand(deployments.LogsCmd)
|
||||
deploymentsCmd.AddCommand(deployments.StatsCmd)
|
||||
|
||||
deploymentsCmd.SetArgs(args)
|
||||
|
||||
if err := deploymentsCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleDBCommand handles database commands
|
||||
func HandleDBCommand(args []string) {
|
||||
dbCmd := db.DBCmd
|
||||
dbCmd.SetArgs(args)
|
||||
|
||||
if err := dbCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@ -218,7 +218,7 @@ func deployNextJS(cmd *cobra.Command, args []string) error {
|
||||
printDeploymentInfo(resp)
|
||||
|
||||
if deploySSR {
|
||||
fmt.Printf("⚠️ Note: SSR deployment may take a minute to start. Check status with: orama deployments get %s\n", deployName)
|
||||
fmt.Printf("⚠️ Note: SSR deployment may take a minute to start. Check status with: orama app get %s\n", deployName)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -47,7 +47,7 @@ func showEnvHelp() {
|
||||
fmt.Printf(" enable - Alias for 'switch' (e.g., 'devnet enable')\n\n")
|
||||
fmt.Printf("Available Environments:\n")
|
||||
fmt.Printf(" devnet - Development network (https://orama-devnet.network)\n")
|
||||
fmt.Printf(" testnet - Test network (https://orama-tesetnet.network)\n\n")
|
||||
fmt.Printf(" testnet - Test network (https://orama-testnet.network)\n\n")
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" orama env list\n")
|
||||
fmt.Printf(" orama env current\n")
|
||||
|
||||
@ -39,7 +39,7 @@ var DefaultEnvironments = []Environment{
|
||||
},
|
||||
{
|
||||
Name: "testnet",
|
||||
GatewayURL: "https://orama-tesetnet.network",
|
||||
GatewayURL: "https://orama-testnet.network",
|
||||
Description: "Test network (staging)",
|
||||
IsActive: false,
|
||||
},
|
||||
|
||||
@ -30,7 +30,7 @@ func NewValidator(flags *Flags, oramaDir string) *Validator {
|
||||
// ValidateFlags validates required flags
|
||||
func (v *Validator) ValidateFlags() error {
|
||||
if v.flags.VpsIP == "" && !v.flags.DryRun {
|
||||
return fmt.Errorf("--vps-ip is required for installation\nExample: orama prod install --vps-ip 1.2.3.4")
|
||||
return fmt.Errorf("--vps-ip is required for installation\nExample: orama node install --vps-ip 1.2.3.4")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ func HandleRestartWithFlags(force bool) {
|
||||
if !force {
|
||||
if warning := checkQuorumSafety(); warning != "" {
|
||||
fmt.Fprintf(os.Stderr, "\nWARNING: %s\n", warning)
|
||||
fmt.Fprintf(os.Stderr, "Use 'orama prod restart --force' to proceed anyway.\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Use 'orama node restart --force' to proceed anyway.\n\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,10 @@ func HandleRestartWithFlags(force bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Stop namespace services first (same as stop command)
|
||||
fmt.Printf("\n Stopping namespace services...\n")
|
||||
stopAllNamespaceServices()
|
||||
|
||||
// Ordered stop: gateway first, then node (RQLite), then supporting services
|
||||
fmt.Printf("\n Stopping services (ordered)...\n")
|
||||
shutdownOrder := [][]string{
|
||||
|
||||
@ -51,7 +51,7 @@ func HandleStart() {
|
||||
}
|
||||
if active {
|
||||
fmt.Printf(" ℹ️ %s already running\n", svc)
|
||||
// Re-enable if disabled (in case it was stopped with 'orama prod stop')
|
||||
// Re-enable if disabled (in case it was stopped with 'orama node stop')
|
||||
enabled, err := utils.IsServiceEnabled(svc)
|
||||
if err == nil && !enabled {
|
||||
if err := exec.Command("systemctl", "enable", svc).Run(); err != nil {
|
||||
@ -81,7 +81,7 @@ func HandleStart() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Re-enable inactive services first (in case they were disabled by 'orama prod stop')
|
||||
// Re-enable inactive services first (in case they were disabled by 'orama node stop')
|
||||
for _, svc := range inactive {
|
||||
enabled, err := utils.IsServiceEnabled(svc)
|
||||
if err == nil && !enabled {
|
||||
|
||||
@ -31,7 +31,7 @@ func HandleStopWithFlags(force bool) {
|
||||
if !force {
|
||||
if warning := checkQuorumSafety(); warning != "" {
|
||||
fmt.Fprintf(os.Stderr, "\nWARNING: %s\n", warning)
|
||||
fmt.Fprintf(os.Stderr, "Use 'orama prod stop --force' to proceed anyway.\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Use 'orama node stop --force' to proceed anyway.\n\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@ -161,7 +161,7 @@ func HandleStopWithFlags(force bool) {
|
||||
fmt.Fprintf(os.Stderr, " If services are still restarting, they may need manual intervention\n")
|
||||
} else {
|
||||
fmt.Printf("\n✅ All services stopped and disabled (will not auto-start on boot)\n")
|
||||
fmt.Printf(" Use 'orama prod start' to start and re-enable services\n")
|
||||
fmt.Printf(" Use 'orama node start' to start and re-enable services\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ func Handle(args []string) {
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: orama prod logs <service> [--follow]\n")
|
||||
fmt.Fprintf(os.Stderr, "Usage: orama node logs <service> [--follow]\n")
|
||||
fmt.Fprintf(os.Stderr, "\nService aliases:\n")
|
||||
fmt.Fprintf(os.Stderr, " node, ipfs, cluster, gateway, olric\n")
|
||||
fmt.Fprintf(os.Stderr, "\nOr use full service name:\n")
|
||||
|
||||
@ -54,5 +54,5 @@ func Handle() {
|
||||
fmt.Printf(" ❌ %s not found\n", oramaDir)
|
||||
}
|
||||
|
||||
fmt.Printf("\nView logs with: orama prod logs <service>\n")
|
||||
fmt.Printf("\nView logs with: orama node logs <service>\n")
|
||||
}
|
||||
|
||||
@ -29,6 +29,11 @@ func Handle() {
|
||||
return
|
||||
}
|
||||
|
||||
// Stop and remove namespace services first
|
||||
fmt.Printf("Stopping namespace services...\n")
|
||||
stopNamespaceServices()
|
||||
|
||||
// All global services (was missing: orama-anyone-relay, coredns, caddy)
|
||||
services := []string{
|
||||
"orama-gateway",
|
||||
"orama-node",
|
||||
@ -36,9 +41,12 @@ func Handle() {
|
||||
"orama-ipfs-cluster",
|
||||
"orama-ipfs",
|
||||
"orama-anyone-client",
|
||||
"orama-anyone-relay",
|
||||
"coredns",
|
||||
"caddy",
|
||||
}
|
||||
|
||||
fmt.Printf("Stopping services...\n")
|
||||
fmt.Printf("Stopping global services...\n")
|
||||
for _, svc := range services {
|
||||
exec.Command("systemctl", "stop", svc).Run()
|
||||
exec.Command("systemctl", "disable", svc).Run()
|
||||
@ -46,8 +54,46 @@ func Handle() {
|
||||
os.Remove(unitPath)
|
||||
}
|
||||
|
||||
// Remove namespace template unit files
|
||||
removeNamespaceTemplates()
|
||||
|
||||
exec.Command("systemctl", "daemon-reload").Run()
|
||||
fmt.Printf("✅ Services uninstalled\n")
|
||||
fmt.Printf(" Configuration and data preserved in /opt/orama/.orama\n")
|
||||
fmt.Printf(" To remove all data: rm -rf /opt/orama/.orama\n\n")
|
||||
}
|
||||
|
||||
// stopNamespaceServices discovers and stops all running namespace services
|
||||
func stopNamespaceServices() {
|
||||
cmd := exec.Command("systemctl", "list-units", "--type=service", "--all", "--no-pager", "--no-legend", "orama-namespace-*@*.service")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 0 && strings.HasPrefix(fields[0], "orama-namespace-") {
|
||||
svc := fields[0]
|
||||
exec.Command("systemctl", "stop", svc).Run()
|
||||
exec.Command("systemctl", "disable", svc).Run()
|
||||
fmt.Printf(" Stopped %s\n", svc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeNamespaceTemplates removes namespace template unit files
|
||||
func removeNamespaceTemplates() {
|
||||
templatePatterns := []string{
|
||||
"orama-namespace-rqlite@.service",
|
||||
"orama-namespace-olric@.service",
|
||||
"orama-namespace-gateway@.service",
|
||||
}
|
||||
for _, pattern := range templatePatterns {
|
||||
unitPath := filepath.Join("/etc/systemd/system", pattern)
|
||||
if _, err := os.Stat(unitPath); err == nil {
|
||||
os.Remove(unitPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,7 +636,7 @@ func (o *Orchestrator) restartServices() error {
|
||||
services := utils.GetProductionServices()
|
||||
|
||||
// Re-enable all services BEFORE restarting them.
|
||||
// orama prod stop disables services, and orama-node's PartOf= dependency
|
||||
// orama node stop disables services, and orama-node's PartOf= dependency
|
||||
// won't propagate restart to disabled services. We must re-enable first
|
||||
// so that all services restart with the updated binary.
|
||||
for _, svc := range services {
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/production"
|
||||
)
|
||||
|
||||
// HandleProdCommand handles production environment commands
|
||||
func HandleProdCommand(args []string) {
|
||||
production.HandleCommand(args)
|
||||
}
|
||||
40
pkg/cli/shared/api.go
Normal file
40
pkg/cli/shared/api.go
Normal file
@ -0,0 +1,40 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/auth"
|
||||
)
|
||||
|
||||
// GetAPIURL returns the gateway/API URL from env var or active environment config.
|
||||
func GetAPIURL() string {
|
||||
if url := os.Getenv("ORAMA_API_URL"); url != "" {
|
||||
return url
|
||||
}
|
||||
return auth.GetDefaultGatewayURL()
|
||||
}
|
||||
|
||||
// GetAuthToken returns an auth token from env var or the credentials store.
|
||||
func GetAuthToken() (string, error) {
|
||||
if token := os.Getenv("ORAMA_TOKEN"); token != "" {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
store, err := auth.LoadEnhancedCredentials()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load credentials: %w", err)
|
||||
}
|
||||
|
||||
gatewayURL := auth.GetDefaultGatewayURL()
|
||||
creds := store.GetDefaultCredential(gatewayURL)
|
||||
if creds == nil {
|
||||
return "", fmt.Errorf("no credentials found for %s. Run 'orama auth login' to authenticate", gatewayURL)
|
||||
}
|
||||
|
||||
if !creds.IsValid() {
|
||||
return "", fmt.Errorf("credentials expired for %s. Run 'orama auth login' to re-authenticate", gatewayURL)
|
||||
}
|
||||
|
||||
return creds.APIKey, nil
|
||||
}
|
||||
33
pkg/cli/shared/confirm.go
Normal file
33
pkg/cli/shared/confirm.go
Normal file
@ -0,0 +1,33 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Confirm prompts the user for yes/no confirmation. Returns true if user confirms.
|
||||
func Confirm(prompt string) bool {
|
||||
fmt.Printf("%s (y/N): ", prompt)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
return response == "y" || response == "yes"
|
||||
}
|
||||
|
||||
// ConfirmExact prompts the user to type an exact string to confirm. Returns true if matched.
|
||||
func ConfirmExact(prompt, expected string) bool {
|
||||
fmt.Printf("%s: ", prompt)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
return strings.TrimSpace(scanner.Text()) == expected
|
||||
}
|
||||
|
||||
// RequireRoot exits with an error if the current user is not root.
|
||||
func RequireRoot() {
|
||||
if os.Geteuid() != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Error: This command must be run as root (use sudo)\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
44
pkg/cli/shared/format.go
Normal file
44
pkg/cli/shared/format.go
Normal file
@ -0,0 +1,44 @@
|
||||
package shared
|
||||
|
||||
import "fmt"
|
||||
|
||||
// FormatBytes formats a byte count into a human-readable string (KB, MB, GB, etc.)
|
||||
func FormatBytes(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
// FormatUptime formats seconds into a human-readable uptime string.
|
||||
func FormatUptime(seconds float64) string {
|
||||
s := int(seconds)
|
||||
days := s / 86400
|
||||
hours := (s % 86400) / 3600
|
||||
mins := (s % 3600) / 60
|
||||
|
||||
if days > 0 {
|
||||
return fmt.Sprintf("%dd %dh %dm", days, hours, mins)
|
||||
}
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%dh %dm", hours, mins)
|
||||
}
|
||||
return fmt.Sprintf("%dm", mins)
|
||||
}
|
||||
|
||||
// FormatSize formats a megabyte value into a human-readable string.
|
||||
func FormatSize(mb float64) string {
|
||||
if mb < 0.1 {
|
||||
return fmt.Sprintf("%.1f KB", mb*1024)
|
||||
}
|
||||
if mb >= 1024 {
|
||||
return fmt.Sprintf("%.1f GB", mb/1024)
|
||||
}
|
||||
return fmt.Sprintf("%.1f MB", mb)
|
||||
}
|
||||
17
pkg/cli/shared/output.go
Normal file
17
pkg/cli/shared/output.go
Normal file
@ -0,0 +1,17 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// PrintJSON pretty-prints data as indented JSON to stdout.
|
||||
func PrintJSON(data interface{}) {
|
||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to marshal JSON: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(jsonData))
|
||||
}
|
||||
@ -32,7 +32,7 @@ const systemPrompt = `You are a distributed systems expert analyzing health chec
|
||||
- High TCP retransmission (>2%) indicates packet loss, often due to WireGuard MTU issues.
|
||||
|
||||
## Service Management
|
||||
- ALWAYS use the CLI for service operations: ` + "`sudo orama prod restart`" + `, ` + "`sudo orama prod stop`" + `, ` + "`sudo orama prod start`" + `
|
||||
- ALWAYS use the CLI for service operations: ` + "`sudo orama node restart`" + `, ` + "`sudo orama node stop`" + `, ` + "`sudo orama node start`" + `
|
||||
- NEVER use raw systemctl commands (they skip important lifecycle hooks).
|
||||
- For rolling restarts: upgrade followers first, leader LAST, one node at a time.
|
||||
- Check RQLite leader: ` + "`curl -s localhost:4001/status | python3 -c \"import sys,json; print(json.load(sys.stdin)['store']['raft']['state'])\"`" + `
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user