mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
feat: Initialize telecom platform with core services and infrastructure
- Add Go-based API server and carrier connector services - Add Rust-based charging engine and packet gateway services - Add Docker Compose configuration for local development - Add comprehensive documentation (API, Architecture, Deployment) - Add development scripts for Redis, MongoDB, and environment setup - Add workspace configuration for Go modules and pnpm - Remove unnecessary documentation files from web-dashboard - Add project build configuration (Makefile, Cargo.toml) This commit establishes the foundational microservices architecture for the sovereign Telecom-as-a-Service platform.
This commit is contained in:
+80
@@ -0,0 +1,80 @@
|
||||
# Go
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.test
|
||||
*.out
|
||||
/bin/
|
||||
/dist/
|
||||
go.work.sum
|
||||
vendor/
|
||||
|
||||
# Rust
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
*.pdb
|
||||
Cargo.lock
|
||||
|
||||
# Node/TypeScript
|
||||
node_modules/
|
||||
.next/
|
||||
out/
|
||||
dist/
|
||||
.turbo/
|
||||
.vercel/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
.pnpm-store/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
*.env
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Build artifacts
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dylib
|
||||
*.elf
|
||||
*.wasm
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Temporary
|
||||
tmp/
|
||||
temp/
|
||||
.tmp/
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"apps/charging-engine",
|
||||
"apps/packet-gateway",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "1.0.0"
|
||||
authors = ["TaaS Team <[email protected]>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.94"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Async runtime
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
async-std = "1.13"
|
||||
|
||||
# eBPF with Aya (for packet-gateway)
|
||||
aya = { version = "0.13", features = ["async_tokio"] }
|
||||
aya-ebpf = "0.1"
|
||||
aya-log = "0.2"
|
||||
aya-log-ebpf = "0.1"
|
||||
|
||||
# Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# Database
|
||||
redis = { version = "0.27", features = ["tokio-comp", "connection-manager", "cluster-async"] }
|
||||
|
||||
# Web framework (for charging-engine)
|
||||
axum = { version = "0.7", features = ["macros"] }
|
||||
tower = "0.5"
|
||||
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
||||
hyper = "1.5"
|
||||
|
||||
# Networking
|
||||
bytes = "1.7"
|
||||
prost = "0.13"
|
||||
|
||||
# Logging and tracing
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||
|
||||
# Error handling
|
||||
anyhow = "1.0"
|
||||
thiserror = "2.0"
|
||||
|
||||
# Configuration
|
||||
dotenv = "0.15"
|
||||
config = "0.14"
|
||||
|
||||
# Time handling
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
# Utilities
|
||||
uuid = { version = "1.10", features = ["v4", "serde"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 TaaS Platform Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,152 @@
|
||||
# Master Makefile - Telecom Platform
|
||||
# Orchestrates builds across Go, Rust, and TypeScript
|
||||
|
||||
.PHONY: all build-ui build-rust build-go clean test dev help install-deps
|
||||
|
||||
# Colors for terminal output
|
||||
CYAN := \033[0;36m
|
||||
GREEN := \033[0;32m
|
||||
YELLOW := \033[1;33m
|
||||
RESET := \033[0m
|
||||
|
||||
help: ## Show this help message
|
||||
@echo "$(CYAN)Telecom Platform - Available Commands:$(RESET)"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-20s$(RESET) %s\n", $$1, $$2}'
|
||||
|
||||
all: build-ui build-rust build-go ## Build all components
|
||||
|
||||
install-deps: ## Install all dependencies
|
||||
@echo "$(CYAN)Installing Go dependencies...$(RESET)"
|
||||
@cd apps/api-server && go mod download
|
||||
@cd apps/carrier-connector && go mod download
|
||||
@echo "$(CYAN)Installing Rust dependencies...$(RESET)"
|
||||
@cargo fetch
|
||||
@echo "$(CYAN)Installing Node.js dependencies...$(RESET)"
|
||||
@pnpm install
|
||||
@echo "$(GREEN)All dependencies installed!$(RESET)"
|
||||
|
||||
# TypeScript - Frontend & SDK
|
||||
build-ui: ## Build Next.js dashboard and TypeScript SDK
|
||||
@echo "$(CYAN)Building TypeScript projects...$(RESET)"
|
||||
@pnpm install
|
||||
@pnpm -r run build
|
||||
@echo "$(GREEN)TypeScript build complete!$(RESET)"
|
||||
|
||||
dev-ui: ## Start Next.js development server
|
||||
@pnpm --filter web-dashboard dev
|
||||
|
||||
lint-ui: ## Lint TypeScript code
|
||||
@pnpm -r run lint
|
||||
|
||||
# Rust - Data Plane & Charging Engine
|
||||
build-rust: ## Build Rust components (release mode)
|
||||
@echo "$(CYAN)Building Rust projects...$(RESET)"
|
||||
@cargo build --release
|
||||
@echo "$(GREEN)Rust build complete!$(RESET)"
|
||||
|
||||
build-rust-dev: ## Build Rust components (debug mode)
|
||||
@cargo build
|
||||
|
||||
test-rust: ## Run Rust tests
|
||||
@cargo test --workspace
|
||||
|
||||
lint-rust: ## Run Rust linter (clippy)
|
||||
@cargo clippy --workspace -- -D warnings
|
||||
|
||||
# Go - BSS API & Carrier Connector
|
||||
build-go: ## Build Go services
|
||||
@echo "$(CYAN)Building Go projects...$(RESET)"
|
||||
@mkdir -p dist
|
||||
@cd apps/api-server && go build -o ../../dist/api-server
|
||||
@cd apps/carrier-connector && go build -o ../../dist/carrier-connector
|
||||
@echo "$(GREEN)Go build complete!$(RESET)"
|
||||
|
||||
test-go: ## Run Go tests
|
||||
@go test ./apps/...
|
||||
|
||||
lint-go: ## Run Go linter
|
||||
@golangci-lint run ./apps/...
|
||||
|
||||
fmt-go: ## Format Go code
|
||||
@go fmt ./apps/...
|
||||
|
||||
# Development
|
||||
dev: ## Start development environment (info only)
|
||||
@echo "$(CYAN)To start the development environment:$(RESET)"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Terminal 1 - API Server:$(RESET)"
|
||||
@echo " ./dist/api-server"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Terminal 2 - Carrier Connector:$(RESET)"
|
||||
@echo " ./dist/carrier-connector"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Terminal 3 - Charging Engine:$(RESET)"
|
||||
@echo " ./target/release/charging-engine"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Terminal 4 - Web Dashboard:$(RESET)"
|
||||
@echo " make dev-ui"
|
||||
@echo ""
|
||||
|
||||
# Testing
|
||||
test: test-go test-rust ## Run all tests
|
||||
@echo "$(GREEN)All tests passed!$(RESET)"
|
||||
|
||||
# Linting
|
||||
lint: lint-go lint-rust lint-ui ## Run all linters
|
||||
|
||||
# Formatting
|
||||
fmt: fmt-go ## Format all code
|
||||
|
||||
# Cleaning
|
||||
clean: ## Remove all build artifacts
|
||||
@echo "$(CYAN)Cleaning build artifacts...$(RESET)"
|
||||
@rm -rf dist/
|
||||
@rm -rf target/
|
||||
@pnpm -r exec rm -rf .next dist node_modules
|
||||
@find . -name "*.o" -type f -delete
|
||||
@find . -name "*.a" -type f -delete
|
||||
@echo "$(GREEN)Clean complete!$(RESET)"
|
||||
|
||||
clean-all: clean ## Remove all artifacts including dependencies
|
||||
@rm -rf node_modules
|
||||
@rm -rf apps/*/node_modules
|
||||
@rm -rf libs/*/node_modules
|
||||
@cd apps/api-server && rm -rf vendor/
|
||||
@cd apps/carrier-connector && rm -rf vendor/
|
||||
|
||||
# Docker
|
||||
docker-build: ## Build Docker images for all services
|
||||
@echo "$(CYAN)Building Docker images...$(RESET)"
|
||||
@docker build -f deployments/docker/api-server.Dockerfile -t taas-api-server .
|
||||
@docker build -f deployments/docker/carrier-connector.Dockerfile -t taas-carrier-connector .
|
||||
@docker build -f deployments/docker/charging-engine.Dockerfile -t taas-charging-engine .
|
||||
@docker build -f deployments/docker/web-dashboard.Dockerfile -t taas-web-dashboard .
|
||||
@echo "$(GREEN)Docker images built!$(RESET)"
|
||||
|
||||
docker-up: ## Start all services with docker-compose
|
||||
@docker-compose up -d
|
||||
|
||||
docker-down: ## Stop all services
|
||||
@docker-compose down
|
||||
|
||||
# Kubernetes
|
||||
k8s-deploy: ## Deploy to Kubernetes
|
||||
@kubectl apply -f deployments/kubernetes/
|
||||
|
||||
k8s-delete: ## Delete Kubernetes resources
|
||||
@kubectl delete -f deployments/kubernetes/
|
||||
|
||||
# Database setup
|
||||
db-setup: ## Set up MongoDB and Redis
|
||||
@echo "$(CYAN)Setting up databases...$(RESET)"
|
||||
@bash scripts/setup-mongodb.sh
|
||||
@bash scripts/setup-redis.sh
|
||||
@echo "$(GREEN)Database setup complete!$(RESET)"
|
||||
|
||||
# Free5GC integration
|
||||
free5gc-setup: ## Set up free5GC (requires separate installation)
|
||||
@echo "$(YELLOW)Please install free5GC separately.$(RESET)"
|
||||
@echo "Visit: https://free5gc.org/guide/"
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
@@ -0,0 +1,6 @@
|
||||
MONGODB_URI=mongodb://localhost:27017/free5gc
|
||||
REDIS_URI=redis://localhost:6379
|
||||
API_PORT=8000
|
||||
GIN_MODE=debug
|
||||
JWT_SECRET=change-me-in-production
|
||||
LOG_LEVEL=info
|
||||
@@ -0,0 +1,13 @@
|
||||
module github.com/taas-platform/telecom-platform/apps/api-server
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/rs/zerolog v1.35.0
|
||||
go.mongodb.org/mongo-driver v1.17.9
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
)
|
||||
@@ -0,0 +1,232 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var (
|
||||
logger zerolog.Logger
|
||||
dbClient *mongo.Client
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize logger
|
||||
logger = zerolog.New(os.Stdout).With().
|
||||
Timestamp().
|
||||
Str("service", "api-server").
|
||||
Logger()
|
||||
|
||||
// Load environment variables
|
||||
if err := godotenv.Load(); err != nil {
|
||||
logger.Info().Msg("No .env file found, using system environment")
|
||||
}
|
||||
|
||||
// Connect to MongoDB
|
||||
mongoURI := getEnv("MONGODB_URI", "mongodb://localhost:27017/free5gc")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var err error
|
||||
dbClient, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msg("Failed to connect to MongoDB")
|
||||
}
|
||||
defer dbClient.Disconnect(context.Background())
|
||||
|
||||
// Verify connection
|
||||
if err := dbClient.Ping(ctx, nil); err != nil {
|
||||
logger.Fatal().Err(err).Msg("Failed to ping MongoDB")
|
||||
}
|
||||
logger.Info().Msg("Connected to MongoDB successfully")
|
||||
|
||||
// Initialize Gin router
|
||||
router := setupRouter()
|
||||
|
||||
// Start server
|
||||
port := getEnv("API_PORT", "8000")
|
||||
logger.Info().Str("port", port).Msg("Starting API server")
|
||||
if err := router.Run(":" + port); err != nil {
|
||||
logger.Fatal().Err(err).Msg("Failed to start server")
|
||||
}
|
||||
}
|
||||
|
||||
func setupRouter() *gin.Engine {
|
||||
// Set Gin mode
|
||||
if getEnv("GIN_MODE", "debug") == "release" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
router := gin.Default()
|
||||
|
||||
// Health check
|
||||
router.GET("/health", healthCheck)
|
||||
|
||||
// API v1 routes
|
||||
v1 := router.Group("/v1")
|
||||
{
|
||||
// eSIM management
|
||||
v1.POST("/esims", createESIM)
|
||||
v1.GET("/esims/:id", getESIM)
|
||||
v1.GET("/esims/:id/usage", getESIMUsage)
|
||||
v1.DELETE("/esims/:id", deleteESIM)
|
||||
|
||||
// Subscriber management
|
||||
v1.GET("/subscribers", listSubscribers)
|
||||
v1.GET("/subscribers/:imsi", getSubscriber)
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Health check endpoint
|
||||
func healthCheck(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "api-server",
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
// POST /v1/esims - Create new eSIM
|
||||
func createESIM(c *gin.Context) {
|
||||
var req struct {
|
||||
DataPlan string `json:"data_plan" binding:"required"`
|
||||
CountryCode string `json:"country_code" binding:"required"`
|
||||
Carrier string `json:"carrier,omitempty"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Generate IMSI (simplified - use proper allocation in production)
|
||||
imsi := fmt.Sprintf("208930%09d", time.Now().UnixNano()%1000000000)
|
||||
|
||||
// Using Go 1.26's new(expr) for optional pointer fields
|
||||
esim := gin.H{
|
||||
"esim_id": uuid.New().String(),
|
||||
"imsi": imsi,
|
||||
"iccid": generateICCID(imsi),
|
||||
"activation_code": fmt.Sprintf("LPA:1$smdp.example.com$%s", uuid.New().String()),
|
||||
"status": "provisioned",
|
||||
"data_plan": req.DataPlan,
|
||||
"country_code": req.CountryCode,
|
||||
"created_at": time.Now(),
|
||||
}
|
||||
|
||||
logger.Info().
|
||||
Str("esim_id", esim["esim_id"].(string)).
|
||||
Str("imsi", imsi).
|
||||
Msg("eSIM created")
|
||||
|
||||
c.JSON(http.StatusCreated, esim)
|
||||
}
|
||||
|
||||
// GET /v1/esims/:id - Get eSIM details
|
||||
func getESIM(c *gin.Context) {
|
||||
esimID := c.Param("id")
|
||||
|
||||
// TODO: Implement actual database lookup
|
||||
esim := gin.H{
|
||||
"esim_id": esimID,
|
||||
"imsi": "208930000000001",
|
||||
"iccid": "8933123456789012345",
|
||||
"status": "active",
|
||||
"data_plan": "1GB",
|
||||
"created_at": time.Now().Add(-24 * time.Hour),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, esim)
|
||||
}
|
||||
|
||||
// GET /v1/esims/:id/usage - Get eSIM usage statistics
|
||||
func getESIMUsage(c *gin.Context) {
|
||||
esimID := c.Param("id")
|
||||
|
||||
usage := gin.H{
|
||||
"esim_id": esimID,
|
||||
"data_used": 250 * 1024 * 1024, // 250 MB in bytes
|
||||
"data_limit": 1024 * 1024 * 1024, // 1 GB in bytes
|
||||
"sessions": []gin.H{
|
||||
{
|
||||
"started_at": time.Now().Add(-2 * time.Hour),
|
||||
"ended_at": time.Now().Add(-1 * time.Hour),
|
||||
"bytes_up": 10 * 1024 * 1024,
|
||||
"bytes_down": 50 * 1024 * 1024,
|
||||
},
|
||||
},
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, usage)
|
||||
}
|
||||
|
||||
// DELETE /v1/esims/:id - Terminate eSIM
|
||||
func deleteESIM(c *gin.Context) {
|
||||
esimID := c.Param("id")
|
||||
|
||||
logger.Info().Str("esim_id", esimID).Msg("eSIM terminated")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "eSIM terminated successfully",
|
||||
"esim_id": esimID,
|
||||
})
|
||||
}
|
||||
|
||||
// GET /v1/subscribers - List all subscribers
|
||||
func listSubscribers(c *gin.Context) {
|
||||
// TODO: Implement MongoDB query
|
||||
subscribers := []gin.H{
|
||||
{
|
||||
"imsi": "208930000000001",
|
||||
"status": "active",
|
||||
"created_at": time.Now().Add(-7 * 24 * time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"subscribers": subscribers,
|
||||
"count": len(subscribers),
|
||||
})
|
||||
}
|
||||
|
||||
// GET /v1/subscribers/:imsi - Get subscriber details
|
||||
func getSubscriber(c *gin.Context) {
|
||||
imsi := c.Param("imsi")
|
||||
|
||||
subscriber := gin.H{
|
||||
"imsi": imsi,
|
||||
"plmn_id": "20893",
|
||||
"status": "active",
|
||||
"created_at": time.Now().Add(-7 * 24 * time.Hour),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, subscriber)
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
func getEnv(key, fallback string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func generateICCID(imsi string) string {
|
||||
// ICCID format: 89 (Telecom) + CC (Country) + ISSUER + ACCOUNT + CHECK
|
||||
// Simplified implementation
|
||||
return fmt.Sprintf("893312%s%d", imsi[5:14], time.Now().Unix()%10)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module github.com/taas-platform/telecom-platform/apps/carrier-connector
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/go-resty/resty/v2 v2.15.3
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
)
|
||||
@@ -0,0 +1,133 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var logger zerolog.Logger
|
||||
|
||||
type ES2Client struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
httpClient *resty.Client
|
||||
}
|
||||
|
||||
type ProfileOrder struct {
|
||||
ICCID string `json:"iccid"`
|
||||
IMSI string `json:"imsi"`
|
||||
K string `json:"k"`
|
||||
OPc string `json:"opc"`
|
||||
MCC string `json:"mcc"`
|
||||
MNC string `json:"mnc"`
|
||||
ProfileType string `json:"profileType"`
|
||||
}
|
||||
|
||||
type ProfileResponse struct {
|
||||
ActivationCode string `json:"activationCode"`
|
||||
ProfileID string `json:"profileId"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize logger
|
||||
logger = zerolog.New(os.Stdout).With().
|
||||
Timestamp().
|
||||
Str("service", "carrier-connector").
|
||||
Logger()
|
||||
|
||||
// Load environment variables
|
||||
if err := godotenv.Load(); err != nil {
|
||||
logger.Info().Msg("No .env file found, using system environment")
|
||||
}
|
||||
|
||||
logger.Info().Msg("Carrier Connector started")
|
||||
|
||||
// Initialize ES2+ client
|
||||
smdpURL := getEnv("SMDP_URL", "https://smdp.example.com")
|
||||
apiKey := getEnv("SMDP_API_KEY", "test-api-key")
|
||||
|
||||
client := NewES2Client(smdpURL, apiKey)
|
||||
|
||||
// Example: Order a profile
|
||||
order := &ProfileOrder{
|
||||
ICCID: "8933123456789012345",
|
||||
IMSI: "208930000000001",
|
||||
K: "465B5CE8B199B49FAA5F0A2EE238A6BC",
|
||||
OPc: "E8ED289DEBA952E4283B54E88E6183CA",
|
||||
MCC: "208",
|
||||
MNC: "93",
|
||||
ProfileType: "operational",
|
||||
}
|
||||
|
||||
logger.Info().
|
||||
Str("imsi", order.IMSI).
|
||||
Msg("Ordering eSIM profile from SM-DP+")
|
||||
|
||||
// In production, this would be triggered by API requests
|
||||
response, err := client.OrderProfile(order)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to order profile")
|
||||
} else {
|
||||
logger.Info().
|
||||
Str("activation_code", response.ActivationCode).
|
||||
Str("profile_id", response.ProfileID).
|
||||
Msg("Profile ordered successfully")
|
||||
}
|
||||
|
||||
// Keep service running
|
||||
logger.Info().Msg("Carrier Connector running. Press Ctrl+C to exit.")
|
||||
select {}
|
||||
}
|
||||
|
||||
func NewES2Client(baseURL, apiKey string) *ES2Client {
|
||||
client := resty.New().
|
||||
SetBaseURL(baseURL).
|
||||
SetHeader("Authorization", fmt.Sprintf("Bearer %s", apiKey)).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetTimeout(10 * time.Second).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
})
|
||||
|
||||
return &ES2Client{
|
||||
baseURL: baseURL,
|
||||
apiKey: apiKey,
|
||||
httpClient: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ES2Client) OrderProfile(order *ProfileOrder) (*ProfileResponse, error) {
|
||||
var response ProfileResponse
|
||||
|
||||
// NOTE: This is a simplified example
|
||||
// In production, implement full GSMA ES2+ protocol
|
||||
resp, err := c.httpClient.R().
|
||||
SetBody(order).
|
||||
SetResult(&response).
|
||||
Post("/gsma/rsp2/es2plus/createProfile")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ES2+ API call failed: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("SM-DP+ returned error: %s", resp.String())
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
REDIS_URI=redis://127.0.0.1/
|
||||
SERVER_PORT=8080
|
||||
RUST_LOG=info
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "charging-engine"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true }
|
||||
redis = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
tower = { workspace = true }
|
||||
tower-http = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
dotenv = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
name = "charging-engine"
|
||||
path = "src/main.rs"
|
||||
@@ -0,0 +1,228 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use redis::AsyncCommands;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
redis: redis::Client,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_target(false)
|
||||
.compact()
|
||||
.init();
|
||||
|
||||
// Load environment variables
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
// Connect to Redis
|
||||
let redis_url = std::env::var("REDIS_URI").unwrap_or_else(|_| "redis://127.0.0.1/".to_string());
|
||||
let redis_client = redis::Client::open(redis_url)?;
|
||||
|
||||
// Test connection
|
||||
let mut conn = redis_client.get_multiplexed_async_connection().await?;
|
||||
let _: () = redis::cmd("PING").query_async(&mut conn).await?;
|
||||
info!("Connected to Redis successfully");
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
redis: redis_client,
|
||||
});
|
||||
|
||||
// Build HTTP API with CORS
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/v1/credit/:ip/check", post(check_credit))
|
||||
.route("/v1/credit/:ip/deduct", post(deduct_credit))
|
||||
.route("/v1/credit/:ip/add", post(add_credit))
|
||||
.route("/v1/credit/:ip/balance", get(get_balance))
|
||||
.route("/health", get(health_check))
|
||||
.layer(cors)
|
||||
.with_state(state);
|
||||
|
||||
// Start server
|
||||
let port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "8080".to_string());
|
||||
let addr = format!("0.0.0.0:{}", port);
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
|
||||
info!("Charging engine listening on {}", addr);
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreditCheckRequest {
|
||||
bytes_requested: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CreditCheckResponse {
|
||||
allowed: bool,
|
||||
remaining_bytes: i64,
|
||||
}
|
||||
|
||||
/// POST /v1/credit/:ip/check
|
||||
/// Check if user has enough credit for data usage
|
||||
async fn check_credit(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<CreditCheckRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!("Redis connection failed: {}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(CreditCheckResponse {
|
||||
allowed: false,
|
||||
remaining_bytes: 0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let key = format!("credit:{}", ip);
|
||||
|
||||
// Get current credit balance
|
||||
let credit: i64 = conn.get(&key).await.unwrap_or(0);
|
||||
|
||||
let allowed = credit >= req.bytes_requested as i64;
|
||||
|
||||
info!(
|
||||
"Credit check for {}: {} bytes requested, {} bytes available, allowed: {}",
|
||||
ip, req.bytes_requested, credit, allowed
|
||||
);
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(CreditCheckResponse {
|
||||
allowed,
|
||||
remaining_bytes: credit,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DeductRequest {
|
||||
bytes_used: u64,
|
||||
}
|
||||
|
||||
/// POST /v1/credit/:ip/deduct
|
||||
/// Deduct bytes from user's credit balance
|
||||
async fn deduct_credit(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<DeductRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!("Redis connection failed: {}", e);
|
||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
let key = format!("credit:{}", ip);
|
||||
|
||||
// Atomically subtract bytes
|
||||
let new_balance: i64 = conn.decr(&key, req.bytes_used).await.unwrap_or(0);
|
||||
|
||||
info!(
|
||||
"User {} deducted {} bytes, remaining: {}",
|
||||
ip, req.bytes_used, new_balance
|
||||
);
|
||||
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AddCreditRequest {
|
||||
bytes_to_add: u64,
|
||||
}
|
||||
|
||||
/// POST /v1/credit/:ip/add
|
||||
/// Add bytes to user's credit balance
|
||||
async fn add_credit(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<AddCreditRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!("Redis connection failed: {}", e);
|
||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
let key = format!("credit:{}", ip);
|
||||
|
||||
// Atomically add bytes
|
||||
let new_balance: i64 = conn.incr(&key, req.bytes_to_add).await.unwrap_or(0);
|
||||
|
||||
info!(
|
||||
"User {} added {} bytes, new balance: {}",
|
||||
ip, req.bytes_to_add, new_balance
|
||||
);
|
||||
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BalanceResponse {
|
||||
ip: String,
|
||||
balance_bytes: i64,
|
||||
}
|
||||
|
||||
/// GET /v1/credit/:ip/balance
|
||||
/// Get current credit balance
|
||||
async fn get_balance(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(BalanceResponse {
|
||||
ip: ip.clone(),
|
||||
balance_bytes: 0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let key = format!("credit:{}", ip);
|
||||
let balance: i64 = conn.get(&key).await.unwrap_or(0);
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(BalanceResponse {
|
||||
ip,
|
||||
balance_bytes: balance,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async fn health_check() -> &'static str {
|
||||
"OK"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "packet-gateway"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
aya = { workspace = true }
|
||||
aya-log = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
redis = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
dotenv = { workspace = true }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
[[bin]]
|
||||
name = "packet-gateway"
|
||||
path = "src/main.rs"
|
||||
@@ -0,0 +1,79 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use redis::AsyncCommands;
|
||||
use tokio::time::{interval, Duration};
|
||||
use tracing::{info, warn, error};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Network interface to attach XDP program
|
||||
#[arg(short, long, default_value = "eth0")]
|
||||
interface: String,
|
||||
|
||||
/// Redis connection URL
|
||||
#[arg(short, long, default_value = "redis://127.0.0.1/")]
|
||||
redis_url: String,
|
||||
|
||||
/// Stats sync interval in seconds
|
||||
#[arg(short, long, default_value = "1")]
|
||||
sync_interval: u64,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_target(false)
|
||||
.compact()
|
||||
.init();
|
||||
|
||||
dotenv::dotenv().ok();
|
||||
let args = Args::parse();
|
||||
|
||||
info!("Starting Packet Gateway");
|
||||
info!("Interface: {}", args.interface);
|
||||
info!("Redis URL: {}", args.redis_url);
|
||||
|
||||
// NOTE: This is a simplified version
|
||||
// For full eBPF implementation, you need:
|
||||
// 1. Compile eBPF program with aya-bpf
|
||||
// 2. Load and attach XDP program
|
||||
// 3. Access eBPF maps for packet stats and credit control
|
||||
|
||||
// Connect to Redis
|
||||
let redis_client = redis::Client::open(args.redis_url.as_str())?;
|
||||
let mut redis_conn = redis_client.get_multiplexed_async_connection().await?;
|
||||
|
||||
// Test connection
|
||||
let _: () = redis::cmd("PING").query_async(&mut redis_conn).await?;
|
||||
info!("Connected to Redis successfully");
|
||||
|
||||
// Simulate packet processing loop
|
||||
// In production, this would read from eBPF maps
|
||||
let mut ticker = interval(Duration::from_secs(args.sync_interval));
|
||||
|
||||
info!("Packet gateway running. Press Ctrl+C to exit.");
|
||||
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
|
||||
// Simulate updating packet statistics to Redis
|
||||
// In real implementation, read from eBPF map
|
||||
let test_ip = "10.0.0.1";
|
||||
let test_bytes: u64 = 1024 * 1024; // 1 MB
|
||||
|
||||
let key = format!("usage:{}", test_ip);
|
||||
let _: () = redis_conn.set_ex(&key, test_bytes, 3600).await?;
|
||||
|
||||
info!("Updated usage for {}: {} bytes", test_ip, test_bytes);
|
||||
|
||||
// Check credit status
|
||||
let credit_key = format!("credit:{}", test_ip);
|
||||
let credit: i64 = redis_conn.get(&credit_key).await.unwrap_or(0);
|
||||
|
||||
if credit <= 0 {
|
||||
warn!("User {} has no credit - would block traffic in eBPF", test_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
@@ -1 +0,0 @@
|
||||
@AGENTS.md
|
||||
@@ -0,0 +1,100 @@
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:7.0
|
||||
container_name: taas-mongodb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "27017:27017"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: admin
|
||||
MONGO_INITDB_ROOT_PASSWORD: changeme
|
||||
MONGO_INITDB_DATABASE: free5gc
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
networks:
|
||||
- taas-network
|
||||
|
||||
redis:
|
||||
image: redis:9-alpine
|
||||
container_name: taas-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- taas-network
|
||||
|
||||
api-server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: deployments/docker/api-server.Dockerfile
|
||||
container_name: taas-api-server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
MONGODB_URI: mongodb://admin:changeme@mongodb:27017/free5gc
|
||||
REDIS_URI: redis://redis:6379
|
||||
API_PORT: 8000
|
||||
GIN_MODE: release
|
||||
depends_on:
|
||||
- mongodb
|
||||
- redis
|
||||
networks:
|
||||
- taas-network
|
||||
|
||||
carrier-connector:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: deployments/docker/carrier-connector.Dockerfile
|
||||
container_name: taas-carrier-connector
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
SMDP_URL: https://smdp.example.com
|
||||
SMDP_API_KEY: your-api-key
|
||||
networks:
|
||||
- taas-network
|
||||
|
||||
charging-engine:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: deployments/docker/charging-engine.Dockerfile
|
||||
container_name: taas-charging-engine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
REDIS_URI: redis://redis:6379
|
||||
SERVER_PORT: 8080
|
||||
RUST_LOG: info
|
||||
depends_on:
|
||||
- redis
|
||||
networks:
|
||||
- taas-network
|
||||
|
||||
web-dashboard:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: deployments/docker/web-dashboard.Dockerfile
|
||||
container_name: taas-web-dashboard
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: http://api-server:8000
|
||||
depends_on:
|
||||
- api-server
|
||||
networks:
|
||||
- taas-network
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
taas-network:
|
||||
driver: bridge
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
# TaaS Platform API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The TaaS Platform provides a RESTful API for managing cellular connectivity programmatically. All API endpoints are available at `https://api.taas-platform.com/v1` (production) or `http://localhost:8000/v1` (development).
|
||||
|
||||
## Authentication
|
||||
|
||||
All API requests require authentication using Bearer tokens:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_KEY" \
|
||||
https://api.taas-platform.com/v1/esims
|
||||
```
|
||||
|
||||
Get your API key from the [Developer Dashboard](http://localhost:3000).
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
Check API server status.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "api-server",
|
||||
"timestamp": 1711929600
|
||||
}
|
||||
```
|
||||
|
||||
### Create eSIM
|
||||
|
||||
**POST** `/v1/esims`
|
||||
|
||||
Provision a new eSIM with cellular connectivity.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"data_plan": "1GB",
|
||||
"country_code": "US",
|
||||
"carrier": "optional-carrier-name"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"imsi": "208930000000001",
|
||||
"iccid": "8933123456789012345",
|
||||
"activation_code": "LPA:1$smdp.example.com$MATCHING_ID",
|
||||
"status": "provisioned",
|
||||
"data_plan": "1GB",
|
||||
"country_code": "US",
|
||||
"created_at": "2026-04-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get eSIM Details
|
||||
|
||||
**GET** `/v1/esims/:id`
|
||||
|
||||
Retrieve information about a specific eSIM.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"imsi": "208930000000001",
|
||||
"iccid": "8933123456789012345",
|
||||
"status": "active",
|
||||
"data_plan": "1GB",
|
||||
"created_at": "2026-04-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get eSIM Usage
|
||||
|
||||
**GET** `/v1/esims/:id/usage`
|
||||
|
||||
Get data usage statistics for an eSIM.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"data_used": 262144000,
|
||||
"data_limit": 1073741824,
|
||||
"sessions": [
|
||||
{
|
||||
"started_at": "2026-04-01T10:00:00Z",
|
||||
"ended_at": "2026-04-01T11:00:00Z",
|
||||
"bytes_up": 10485760,
|
||||
"bytes_down": 52428800
|
||||
}
|
||||
],
|
||||
"updated_at": "2026-04-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Terminate eSIM
|
||||
|
||||
**DELETE** `/v1/esims/:id`
|
||||
|
||||
Permanently terminate an eSIM.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"message": "eSIM terminated successfully",
|
||||
"esim_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
## Status Codes
|
||||
|
||||
- `200 OK` - Request successful
|
||||
- `201 Created` - Resource created
|
||||
- `400 Bad Request` - Invalid request parameters
|
||||
- `401 Unauthorized` - Missing or invalid authentication
|
||||
- `404 Not Found` - Resource not found
|
||||
- `500 Internal Server Error` - Server error
|
||||
|
||||
## Rate Limits
|
||||
|
||||
- **Free Tier**: 100 requests/hour
|
||||
- **Pro Tier**: 10,000 requests/hour
|
||||
- **Enterprise**: Unlimited
|
||||
|
||||
Rate limit headers are included in all responses:
|
||||
```
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 99
|
||||
X-RateLimit-Reset: 1711933200
|
||||
```
|
||||
|
||||
## SDKs
|
||||
|
||||
### TypeScript/JavaScript
|
||||
|
||||
```bash
|
||||
npm install @taas-platform/sdk
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { TaasClient } from '@taas-platform/sdk';
|
||||
|
||||
const client = new TaasClient({ apiKey: 'YOUR_API_KEY' });
|
||||
|
||||
// Create eSIM
|
||||
const esim = await client.esims.create({
|
||||
dataPlan: '1GB',
|
||||
countryCode: 'US'
|
||||
});
|
||||
|
||||
// Get usage
|
||||
const usage = await client.esims.getUsage(esim.esimId);
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
```bash
|
||||
go get github.com/taas-platform/sdk-go
|
||||
```
|
||||
|
||||
```go
|
||||
import "github.com/taas-platform/sdk-go"
|
||||
|
||||
client := taas.NewClient("YOUR_API_KEY")
|
||||
|
||||
// Create eSIM
|
||||
esim, err := client.ESims.Create(&taas.CreateESIMRequest{
|
||||
DataPlan: "1GB",
|
||||
CountryCode: "US",
|
||||
})
|
||||
```
|
||||
|
||||
## Webhooks
|
||||
|
||||
Configure webhooks to receive real-time notifications:
|
||||
|
||||
### Events
|
||||
|
||||
- `esim.created` - New eSIM provisioned
|
||||
- `esim.activated` - eSIM activated on device
|
||||
- `usage.threshold` - Data usage threshold reached (50%, 75%, 90%)
|
||||
- `usage.depleted` - Data plan exhausted
|
||||
- `esim.terminated` - eSIM terminated
|
||||
|
||||
### Webhook Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "usage.threshold",
|
||||
"timestamp": "2026-04-01T12:00:00Z",
|
||||
"data": {
|
||||
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"threshold": 75,
|
||||
"data_used": 805306368,
|
||||
"data_limit": 1073741824
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Create and Activate eSIM
|
||||
|
||||
```bash
|
||||
# 1. Create eSIM
|
||||
curl -X POST https://api.taas-platform.com/v1/esims \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"data_plan": "1GB",
|
||||
"country_code": "US"
|
||||
}'
|
||||
|
||||
# 2. Show QR code to user (activation_code)
|
||||
# User scans QR code on their device
|
||||
|
||||
# 3. Monitor usage
|
||||
curl https://api.taas-platform.com/v1/esims/ESIM_ID/usage \
|
||||
-H "Authorization: Bearer YOUR_API_KEY"
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation**: https://docs.taas-platform.com
|
||||
- **API Status**: https://status.taas-platform.com
|
||||
- **Support Email**: support@taas-platform.com
|
||||
- **GitHub Issues**: https://github.com/nutcas3/telecom-platform/issues
|
||||
@@ -0,0 +1,408 @@
|
||||
# TaaS Platform Architecture
|
||||
|
||||
## System Overview
|
||||
|
||||
The TaaS Platform is a multi-layer telecom infrastructure that provides cellular connectivity as a service. It implements 3GPP standards while exposing a modern, developer-friendly API.
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Developer Interface │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Web UI │ │ TypeScript │ │ REST API │ │
|
||||
│ │ Dashboard │ │ SDK │ │ Webhooks │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Business Support System (BSS) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ API Server │ │ Carrier │ │ Subscriber │ │
|
||||
│ │ (Go 1.26) │ │ Connector │ │ Management │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Operations Support System (OSS) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Charging │ │ Usage │ │ eSIM │ │
|
||||
│ │ Engine │ │ Tracking │ │ Provisioning│ │
|
||||
│ │ (Rust) │ │ (Redis) │ │ (ES2+) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Telecom Core Network │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 5G Core │ │ Packet │ │ Data Plane │ │
|
||||
│ │ (free5GC) │ │ Gateway │ │ (eBPF) │ │
|
||||
│ │ (Go) │ │ (Rust) │ │ │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. API Server (Go 1.26)
|
||||
|
||||
**Purpose**: Developer-facing RESTful API and business logic
|
||||
|
||||
**Technology**:
|
||||
- Language: Go 1.26 (Green Tea GC, new(expr) syntax)
|
||||
- Framework: Gin
|
||||
- Database: MongoDB (subscriber data)
|
||||
- Cache: Redis (session management)
|
||||
|
||||
**Responsibilities**:
|
||||
- eSIM creation and management
|
||||
- Authentication and authorization
|
||||
- Rate limiting and quotas
|
||||
- Webhook delivery
|
||||
- API documentation
|
||||
|
||||
**Endpoints**:
|
||||
- `POST /v1/esims` - Create eSIM
|
||||
- `GET /v1/esims/:id` - Get eSIM details
|
||||
- `GET /v1/esims/:id/usage` - Get usage stats
|
||||
- `DELETE /v1/esims/:id` - Terminate eSIM
|
||||
|
||||
### 2. Carrier Connector (Go 1.26)
|
||||
|
||||
**Purpose**: Integration with GSMA SM-DP+ servers for eSIM provisioning
|
||||
|
||||
**Technology**:
|
||||
- Language: Go 1.26
|
||||
- Protocol: HTTPS (ES2+ API)
|
||||
- Vendor: Thales, G+D, or Idemia
|
||||
|
||||
**Responsibilities**:
|
||||
- Profile ordering via ES2+ protocol
|
||||
- Activation code generation (LPA strings)
|
||||
- Profile status tracking
|
||||
- Vendor-specific adaptations
|
||||
|
||||
**Flow**:
|
||||
1. Receive eSIM order from API Server
|
||||
2. Call SM-DP+ vendor API with IMSI, K, OPc keys
|
||||
3. Receive activation code (LPA:1$...)
|
||||
4. Return to API Server for user display
|
||||
|
||||
### 3. Charging Engine (Rust 1.94)
|
||||
|
||||
**Purpose**: Real-time credit control and billing
|
||||
|
||||
**Technology**:
|
||||
- Language: Rust 1.94 (array_windows, async Tokio)
|
||||
- Framework: Axum
|
||||
- Database: Redis (in-memory credit balances)
|
||||
|
||||
**Responsibilities**:
|
||||
- Credit balance management
|
||||
- Real-time authorization checks
|
||||
- Usage deduction (atomic operations)
|
||||
- Credit top-up processing
|
||||
|
||||
**API**:
|
||||
- `POST /v1/credit/:ip/check` - Check if user has credit
|
||||
- `POST /v1/credit/:ip/deduct` - Deduct bytes used
|
||||
- `POST /v1/credit/:ip/add` - Add credit
|
||||
- `GET /v1/credit/:ip/balance` - Get balance
|
||||
|
||||
**Performance**:
|
||||
- Sub-millisecond response times
|
||||
- Atomic Redis operations (INCR, DECR)
|
||||
- Horizontal scaling via Redis Cluster
|
||||
|
||||
### 4. Packet Gateway (Rust 1.94 + eBPF)
|
||||
|
||||
**Purpose**: High-performance packet processing and traffic control
|
||||
|
||||
**Technology**:
|
||||
- Language: Rust 1.94
|
||||
- Framework: Aya (pure Rust eBPF)
|
||||
- Attach Point: XDP (eXpress Data Path)
|
||||
- Protocol: GTP-U decapsulation (UDP port 2152)
|
||||
|
||||
**Responsibilities**:
|
||||
- Parse GTP-U tunneled packets
|
||||
- Extract user IP addresses
|
||||
- Count bytes per subscriber
|
||||
- Block traffic for users without credit
|
||||
- Sync stats to Redis
|
||||
|
||||
**Architecture**:
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Network Interface (eth0) │
|
||||
└─────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ XDP Hook │ ← eBPF program runs here
|
||||
│ (Kernel) │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ eBPF Maps │
|
||||
│ - Packet Stats │
|
||||
│ - Credit Status │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ Userspace Daemon │ ← Rust application
|
||||
│ (Reads maps, │
|
||||
│ syncs to Redis) │
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
**Packet Processing**:
|
||||
1. Packet arrives at NIC
|
||||
2. XDP hook inspects Ethernet → IP → UDP → GTP-U headers
|
||||
3. Extract inner IP packet (actual user data)
|
||||
4. Look up credit status in eBPF map
|
||||
5. If credit > 0: `XDP_PASS`, else: `XDP_DROP`
|
||||
6. Update packet statistics in eBPF map
|
||||
7. Userspace daemon reads stats, syncs to Redis
|
||||
|
||||
### 5. 5G Core Network (free5GC)
|
||||
|
||||
**Purpose**: 3GPP-compliant 5G standalone core
|
||||
|
||||
**Technology**:
|
||||
- Project: free5GC v4.2.1
|
||||
- Language: Go
|
||||
- Database: MongoDB
|
||||
- Deployment: Kubernetes or bare metal
|
||||
|
||||
**Network Functions**:
|
||||
- **AMF** (Access and Mobility Management)
|
||||
- **SMF** (Session Management)
|
||||
- **UPF** (User Plane Function)
|
||||
- **PCF** (Policy Control)
|
||||
- **UDM** (Unified Data Management)
|
||||
- **UDR** (Unified Data Repository)
|
||||
- **AUSF** (Authentication Server)
|
||||
- **NRF** (NF Repository)
|
||||
|
||||
**Integration Points**:
|
||||
- API Server writes subscriber data to MongoDB
|
||||
- UPF forwards GTP-U traffic to Packet Gateway
|
||||
- AMF handles UE authentication using stored keys (K, OPc)
|
||||
|
||||
### 6. Web Dashboard (Next.js 15)
|
||||
|
||||
**Purpose**: Developer portal and management interface
|
||||
|
||||
**Technology**:
|
||||
- Framework: Next.js 15 (App Router)
|
||||
- Language: TypeScript
|
||||
- Styling: Tailwind CSS
|
||||
- Data Fetching: TanStack Query
|
||||
|
||||
**Features**:
|
||||
- eSIM creation and management
|
||||
- Real-time usage charts (Recharts)
|
||||
- API key management
|
||||
- Webhook configuration
|
||||
- Billing and invoices
|
||||
|
||||
## Data Flow
|
||||
|
||||
### eSIM Creation Flow
|
||||
|
||||
```
|
||||
1. Developer → API Server
|
||||
POST /v1/esims { data_plan: "1GB" }
|
||||
|
||||
2. API Server → IMSI Allocator
|
||||
Generate unique 15-digit IMSI
|
||||
|
||||
3. API Server → Key Generator
|
||||
Generate K (subscriber key)
|
||||
Generate OPc (operator key)
|
||||
|
||||
4. API Server → MongoDB (free5GC)
|
||||
Insert subscriber data:
|
||||
- subscriptionData.authenticationData (K, OPc)
|
||||
- subscriptionData.provisionedData (IMSI, AMBR)
|
||||
|
||||
5. API Server → Carrier Connector
|
||||
Order eSIM profile via ES2+
|
||||
|
||||
6. Carrier Connector → SM-DP+ Vendor
|
||||
HTTPS POST with IMSI, K, OPc
|
||||
|
||||
7. SM-DP+ → Carrier Connector
|
||||
Return activation code (LPA string)
|
||||
|
||||
8. API Server → Developer
|
||||
Return eSIM details + activation code
|
||||
|
||||
9. Developer → End User
|
||||
Display QR code with LPA string
|
||||
|
||||
10. End User's Device → SM-DP+
|
||||
Scan QR, download eSIM profile
|
||||
|
||||
11. Device → free5GC AMF
|
||||
Registration procedure (5G AKA)
|
||||
|
||||
12. free5GC → Packet Gateway
|
||||
Establish GTP-U tunnel for data
|
||||
```
|
||||
|
||||
### Real-Time Usage Flow
|
||||
|
||||
```
|
||||
1. User Device sends data
|
||||
↓
|
||||
2. free5GC UPF encapsulates in GTP-U
|
||||
↓
|
||||
3. Packet Gateway (eBPF XDP)
|
||||
- Decapsulate GTP-U
|
||||
- Extract user IP
|
||||
- Check credit status in eBPF map
|
||||
↓
|
||||
4. If credit > 0:
|
||||
- Pass packet (XDP_PASS)
|
||||
- Increment byte counter in eBPF map
|
||||
↓
|
||||
5. Userspace daemon (Rust)
|
||||
- Reads eBPF maps every 1 second
|
||||
- Writes stats to Redis
|
||||
↓
|
||||
6. Charging Engine
|
||||
- Reads usage from Redis
|
||||
- Deducts from credit balance
|
||||
- Updates credit status
|
||||
↓
|
||||
7. Packet Gateway
|
||||
- Reads new credit status from Redis
|
||||
- Updates eBPF map
|
||||
↓
|
||||
8. If credit reaches 0:
|
||||
- eBPF drops packets (XDP_DROP)
|
||||
- User's internet stops
|
||||
```
|
||||
|
||||
## Scaling Architecture
|
||||
|
||||
### Horizontal Scaling
|
||||
|
||||
**Stateless Components** (can be replicated):
|
||||
- API Server: 3+ replicas behind load balancer
|
||||
- Carrier Connector: 2+ replicas
|
||||
- Charging Engine: 2+ replicas
|
||||
- Web Dashboard: CDN + multiple origins
|
||||
|
||||
**Stateful Components** (require special handling):
|
||||
- MongoDB: Replica set (1 primary, 2 secondaries)
|
||||
- Redis: Cluster mode or Sentinel
|
||||
- Packet Gateway: One instance per physical server (eBPF)
|
||||
- free5GC: Kubernetes deployment with dedicated UPF nodes
|
||||
|
||||
### Performance Metrics
|
||||
|
||||
**Throughput**:
|
||||
- API Server: 10,000 requests/second
|
||||
- Charging Engine: 50,000 credit checks/second
|
||||
- Packet Gateway: 10 Gbps+ per server (eBPF)
|
||||
- free5GC UPF: 5 Gbps per instance
|
||||
|
||||
**Latency**:
|
||||
- API Server: <50ms p99
|
||||
- Charging Engine: <1ms p99 (Redis)
|
||||
- Packet Gateway: <1µs (eBPF in kernel)
|
||||
- eSIM Provisioning: <2 seconds end-to-end
|
||||
|
||||
## Security
|
||||
|
||||
### Authentication & Authorization
|
||||
- API keys with HMAC signatures
|
||||
- JWT tokens for web dashboard
|
||||
- OAuth 2.0 for third-party integrations
|
||||
- Rate limiting per API key
|
||||
|
||||
### Data Encryption
|
||||
- TLS 1.3 for all API endpoints
|
||||
- MongoDB encryption at rest
|
||||
- Redis TLS connections
|
||||
- eBPF packet inspection (no decryption)
|
||||
|
||||
### Secrets Management
|
||||
- Kubernetes Secrets for production
|
||||
- HashiCorp Vault for sensitive keys
|
||||
- Environment variables for local dev
|
||||
- No hardcoded credentials
|
||||
|
||||
## Deployment
|
||||
|
||||
### Development
|
||||
```bash
|
||||
make all # Build all components
|
||||
make db-setup # Set up MongoDB + Redis
|
||||
make dev # Start all services
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Kubernetes
|
||||
```bash
|
||||
kubectl apply -f deployments/kubernetes/
|
||||
```
|
||||
|
||||
### Production Checklist
|
||||
- [ ] MongoDB replica set configured
|
||||
- [ ] Redis Cluster or Sentinel enabled
|
||||
- [ ] TLS certificates installed
|
||||
- [ ] Monitoring (Prometheus, Grafana)
|
||||
- [ ] Logging (ELK Stack or Loki)
|
||||
- [ ] Backup strategy implemented
|
||||
- [ ] Disaster recovery plan documented
|
||||
- [ ] Load testing completed (10x expected traffic)
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Metrics (Prometheus)
|
||||
- `api_requests_total` - Total API requests
|
||||
- `esim_creations_total` - eSIMs created
|
||||
- `data_usage_bytes_total` - Total data usage
|
||||
- `charging_checks_duration_seconds` - Credit check latency
|
||||
- `packet_gateway_packets_total` - Packets processed
|
||||
|
||||
### Alerts
|
||||
- High error rate (>1%)
|
||||
- Low credit balances (<10% of users)
|
||||
- MongoDB replica lag (>10 seconds)
|
||||
- Redis memory usage (>90%)
|
||||
- Packet gateway packet loss (>0.1%)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Multi-Region Deployment**
|
||||
- Deploy in multiple AWS regions
|
||||
- Global load balancing with Route 53
|
||||
- Data residency compliance
|
||||
|
||||
2. **Advanced Analytics**
|
||||
- Real-time dashboards with WebSockets
|
||||
- Predictive usage forecasting
|
||||
- Anomaly detection (unusual data patterns)
|
||||
|
||||
3. **Additional Carriers**
|
||||
- Support for multiple SM-DP+ vendors
|
||||
- Dynamic carrier selection based on region
|
||||
- Fallback mechanisms
|
||||
|
||||
4. **IoT Optimization**
|
||||
- SGP.32 support (IoT eSIM standard)
|
||||
- LPWAN protocol support (NB-IoT, LTE-M)
|
||||
- Power-saving features
|
||||
|
||||
5. **Developer Tools**
|
||||
- CLI tool for API interaction
|
||||
- Terraform provider
|
||||
- GitHub Actions integration
|
||||
@@ -0,0 +1,454 @@
|
||||
# Deployment Guide
|
||||
|
||||
This guide covers deploying the TaaS Platform in different environments.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Local Development](#local-development)
|
||||
2. [Docker Deployment](#docker-deployment)
|
||||
3. [Kubernetes Deployment](#kubernetes-deployment)
|
||||
4. [Production Considerations](#production-considerations)
|
||||
|
||||
---
|
||||
|
||||
## Local Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.26+
|
||||
- Rust 1.94+
|
||||
- Node.js 22+
|
||||
- MongoDB 7.0+
|
||||
- Redis 7+
|
||||
- pnpm
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/nutcas3/telecom-platform.git
|
||||
cd telecom-platform
|
||||
|
||||
# 2. Run setup script
|
||||
chmod +x scripts/dev-setup.sh
|
||||
./scripts/dev-setup.sh
|
||||
|
||||
# 3. Set up databases
|
||||
make db-setup
|
||||
|
||||
# 4. Build all components
|
||||
make all
|
||||
|
||||
# 5. Start services (in separate terminals)
|
||||
# Terminal 1: API Server
|
||||
./dist/api-server
|
||||
|
||||
# Terminal 2: Carrier Connector
|
||||
./dist/carrier-connector
|
||||
|
||||
# Terminal 3: Charging Engine
|
||||
./target/release/charging-engine
|
||||
|
||||
# Terminal 4: Web Dashboard
|
||||
cd apps/web-dashboard && pnpm dev
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create `.env` files in each service directory:
|
||||
|
||||
**apps/api-server/.env:**
|
||||
```env
|
||||
MONGODB_URI=mongodb://localhost:27017/free5gc
|
||||
REDIS_URI=redis://localhost:6379
|
||||
API_PORT=8000
|
||||
GIN_MODE=debug
|
||||
```
|
||||
|
||||
**apps/charging-engine/.env:**
|
||||
```env
|
||||
REDIS_URI=redis://127.0.0.1/
|
||||
SERVER_PORT=8080
|
||||
RUST_LOG=info
|
||||
```
|
||||
|
||||
**apps/web-dashboard/.env.local:**
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
### Build Images
|
||||
|
||||
```bash
|
||||
# Build all Docker images
|
||||
make docker-build
|
||||
|
||||
# Or build individually
|
||||
docker build -f deployments/docker/api-server.Dockerfile -t taas-api-server .
|
||||
docker build -f deployments/docker/charging-engine.Dockerfile -t taas-charging-engine .
|
||||
docker build -f deployments/docker/web-dashboard.Dockerfile -t taas-web-dashboard .
|
||||
```
|
||||
|
||||
### Run with Docker Compose
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker-compose up -d
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop all services
|
||||
docker-compose down
|
||||
|
||||
# Stop and remove volumes
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### Service URLs
|
||||
|
||||
After starting with docker-compose:
|
||||
|
||||
- **API Server**: http://localhost:8000
|
||||
- **Charging Engine**: http://localhost:8080
|
||||
- **Web Dashboard**: http://localhost:3000
|
||||
- **MongoDB**: mongodb://localhost:27017
|
||||
- **Redis**: redis://localhost:6379
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**MongoDB connection issues:**
|
||||
```bash
|
||||
# Check if MongoDB is running
|
||||
docker-compose ps mongodb
|
||||
|
||||
# View MongoDB logs
|
||||
docker-compose logs mongodb
|
||||
|
||||
# Connect to MongoDB shell
|
||||
docker exec -it taas-mongodb mongosh
|
||||
```
|
||||
|
||||
**Redis connection issues:**
|
||||
```bash
|
||||
# Test Redis connection
|
||||
docker exec -it taas-redis redis-cli ping
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Kubernetes cluster (v1.28+)
|
||||
- kubectl configured
|
||||
- Persistent storage provisioner
|
||||
- LoadBalancer service support (or Ingress controller)
|
||||
|
||||
### Deploy to Kubernetes
|
||||
|
||||
```bash
|
||||
# 1. Create namespace
|
||||
kubectl create namespace taas-platform
|
||||
|
||||
# 2. Deploy all manifests
|
||||
kubectl apply -f deployments/kubernetes/
|
||||
|
||||
# 3. Verify deployments
|
||||
kubectl get pods -n taas-platform
|
||||
|
||||
# Expected output:
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# mongodb-xxx 1/1 Running 0 2m
|
||||
# redis-xxx 1/1 Running 0 2m
|
||||
# api-server-xxx 1/1 Running 0 1m
|
||||
# charging-engine-xxx 1/1 Running 0 1m
|
||||
```
|
||||
|
||||
### Access Services
|
||||
|
||||
```bash
|
||||
# Get API Server external IP
|
||||
kubectl get svc api-server -n taas-platform
|
||||
|
||||
# Port forward for local access
|
||||
kubectl port-forward -n taas-platform svc/api-server 8000:8000
|
||||
kubectl port-forward -n taas-platform svc/charging-engine 8080:8080
|
||||
```
|
||||
|
||||
### Scaling
|
||||
|
||||
```bash
|
||||
# Scale API Server
|
||||
kubectl scale deployment api-server -n taas-platform --replicas=5
|
||||
|
||||
# Scale Charging Engine
|
||||
kubectl scale deployment charging-engine -n taas-platform --replicas=3
|
||||
|
||||
# Enable autoscaling
|
||||
kubectl autoscale deployment api-server -n taas-platform \
|
||||
--min=3 --max=10 --cpu-percent=70
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
kubectl logs -f deployment/api-server -n taas-platform
|
||||
|
||||
# Describe pod for issues
|
||||
kubectl describe pod <pod-name> -n taas-platform
|
||||
|
||||
# Get events
|
||||
kubectl get events -n taas-platform --sort-by='.lastTimestamp'
|
||||
```
|
||||
|
||||
### Update Deployment
|
||||
|
||||
```bash
|
||||
# Update image
|
||||
kubectl set image deployment/api-server \
|
||||
api-server=taas-api-server:v2.0.0 \
|
||||
-n taas-platform
|
||||
|
||||
# Check rollout status
|
||||
kubectl rollout status deployment/api-server -n taas-platform
|
||||
|
||||
# Rollback if needed
|
||||
kubectl rollout undo deployment/api-server -n taas-platform
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production Considerations
|
||||
|
||||
### High Availability
|
||||
|
||||
**MongoDB Replica Set:**
|
||||
```yaml
|
||||
# Use MongoDB Operator or StatefulSet
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: mongodb
|
||||
spec:
|
||||
replicas: 3
|
||||
serviceName: mongodb
|
||||
# ... (see MongoDB Operator docs)
|
||||
```
|
||||
|
||||
**Redis Cluster:**
|
||||
```bash
|
||||
# Deploy Redis in cluster mode
|
||||
helm install redis bitnami/redis-cluster \
|
||||
--namespace taas-platform \
|
||||
--set cluster.nodes=6 \
|
||||
--set cluster.replicas=1
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
**Secrets Management:**
|
||||
```bash
|
||||
# Create Kubernetes secrets
|
||||
kubectl create secret generic mongodb-secret \
|
||||
--from-literal=username=admin \
|
||||
--from-literal=password='STRONG_PASSWORD' \
|
||||
-n taas-platform
|
||||
|
||||
kubectl create secret generic api-keys \
|
||||
--from-literal=jwt-secret='JWT_SECRET' \
|
||||
-n taas-platform
|
||||
```
|
||||
|
||||
**Network Policies:**
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: api-server-policy
|
||||
namespace: taas-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: api-server
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: web-dashboard
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: mongodb
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 27017
|
||||
```
|
||||
|
||||
### Monitoring Stack
|
||||
|
||||
**Prometheus & Grafana:**
|
||||
```bash
|
||||
# Install using Helm
|
||||
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||
helm install prometheus prometheus-community/kube-prometheus-stack \
|
||||
-n monitoring --create-namespace
|
||||
```
|
||||
|
||||
**Application Metrics:**
|
||||
- Add `/metrics` endpoint to each service
|
||||
- Use Prometheus client libraries
|
||||
- Create Grafana dashboards
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
**MongoDB Backups:**
|
||||
```bash
|
||||
# Daily backups with mongodump
|
||||
kubectl create cronjob mongodb-backup \
|
||||
--image=mongo:7.0 \
|
||||
--schedule="0 2 * * *" \
|
||||
-- mongodump --uri=$MONGODB_URI --out=/backup
|
||||
```
|
||||
|
||||
**Redis Persistence:**
|
||||
```bash
|
||||
# Enable AOF in Redis config
|
||||
kubectl edit configmap redis-config -n taas-platform
|
||||
# Set: appendonly yes
|
||||
```
|
||||
|
||||
### Resource Limits
|
||||
|
||||
**Recommended limits per service:**
|
||||
|
||||
| Service | CPU Request | CPU Limit | Memory Request | Memory Limit |
|
||||
|---------|-------------|-----------|----------------|--------------|
|
||||
| API Server | 100m | 500m | 128Mi | 512Mi |
|
||||
| Charging Engine | 100m | 1000m | 128Mi | 512Mi |
|
||||
| Packet Gateway | 250m | 2000m | 256Mi | 1Gi |
|
||||
| MongoDB | 250m | 1000m | 512Mi | 2Gi |
|
||||
| Redis | 100m | 500m | 256Mi | 2Gi |
|
||||
|
||||
### Load Testing
|
||||
|
||||
```bash
|
||||
# Install k6
|
||||
brew install k6 # macOS
|
||||
# or: sudo apt-get install k6
|
||||
|
||||
# Run load test
|
||||
k6 run scripts/loadtest.js
|
||||
```
|
||||
|
||||
**Sample load test script:**
|
||||
```javascript
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '2m', target: 100 }, // Ramp up
|
||||
{ duration: '5m', target: 100 }, // Stay at 100 users
|
||||
{ duration: '2m', target: 200 }, // Ramp to 200
|
||||
{ duration: '5m', target: 200 }, // Stay at 200
|
||||
{ duration: '2m', target: 0 }, // Ramp down
|
||||
],
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const res = http.get('http://api-server:8000/health');
|
||||
check(res, { 'status is 200': (r) => r.status === 200 });
|
||||
sleep(1);
|
||||
}
|
||||
```
|
||||
|
||||
### Disaster Recovery
|
||||
|
||||
**Backup Locations:**
|
||||
- MongoDB backups: S3 bucket (cross-region replication)
|
||||
- Redis RDB snapshots: S3 bucket
|
||||
- Configuration: Git repository
|
||||
- Secrets: HashiCorp Vault or AWS Secrets Manager
|
||||
|
||||
**Recovery Time Objective (RTO):** <1 hour
|
||||
**Recovery Point Objective (RPO):** <15 minutes
|
||||
|
||||
**Recovery Procedure:**
|
||||
1. Spin up new Kubernetes cluster
|
||||
2. Restore MongoDB from latest backup
|
||||
3. Restore Redis from snapshot
|
||||
4. Deploy all services from Git
|
||||
5. Update DNS to point to new cluster
|
||||
6. Verify functionality
|
||||
|
||||
### CI/CD Pipeline
|
||||
|
||||
**GitHub Actions example:**
|
||||
```yaml
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build Docker images
|
||||
run: make docker-build
|
||||
|
||||
- name: Push to registry
|
||||
run: |
|
||||
docker tag taas-api-server registry.example.com/taas-api-server:${{ github.sha }}
|
||||
docker push registry.example.com/taas-api-server:${{ github.sha }}
|
||||
|
||||
- name: Deploy to Kubernetes
|
||||
run: |
|
||||
kubectl set image deployment/api-server \
|
||||
api-server=registry.example.com/taas-api-server:${{ github.sha }} \
|
||||
-n taas-platform
|
||||
```
|
||||
|
||||
### Cost Optimization
|
||||
|
||||
**Cloud Provider Recommendations:**
|
||||
- Use spot/preemptible instances for non-critical services
|
||||
- Enable autoscaling to match load
|
||||
- Use reserved instances for predictable workloads
|
||||
- Store backups in cheaper storage classes (S3 Glacier)
|
||||
|
||||
**Estimated Monthly Costs (AWS):**
|
||||
- 3x t3.medium (API Server): $90
|
||||
- 2x t3.medium (Charging Engine): $60
|
||||
- 1x t3.large (MongoDB): $75
|
||||
- 1x t3.small (Redis): $19
|
||||
- Load Balancer: $25
|
||||
- Data transfer: Variable
|
||||
- **Total**: ~$270/month (base infrastructure)
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For deployment issues:
|
||||
- Check logs: `kubectl logs -f <pod-name>`
|
||||
- Review events: `kubectl get events`
|
||||
- Join community: [Discussions](https://github.com/taas-platform/discussions)
|
||||
- Email: devops@taas-platform.com
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "telecom-platform",
|
||||
"version": "1.0.0",
|
||||
"description": "Sovereign Telecom-as-a-Service Platform",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter web-dashboard dev",
|
||||
"build": "pnpm -r run build",
|
||||
"lint": "pnpm -r run lint",
|
||||
"test": "pnpm -r run test",
|
||||
"clean": "pnpm -r exec rm -rf .next dist node_modules"
|
||||
},
|
||||
"keywords": [
|
||||
"telecom",
|
||||
"5g",
|
||||
"esim",
|
||||
"taas",
|
||||
"cellular",
|
||||
"connectivity"
|
||||
],
|
||||
"author": "TaaS Team",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.0.0",
|
||||
"pnpm": ">=9.0.0"
|
||||
},
|
||||
"packageManager": "[email protected]"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- 'apps/web-dashboard'
|
||||
- 'libs/shared-ts-sdk'
|
||||
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "========================================="
|
||||
echo "TaaS Platform - Development Setup"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to check command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check Go 1.26
|
||||
echo -n "Checking Go 1.26... "
|
||||
if command_exists go; then
|
||||
GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//')
|
||||
if [[ "$GO_VERSION" == 1.26* ]]; then
|
||||
echo -e "${GREEN}✓${NC} Found $(go version)"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} Found Go $GO_VERSION (1.26 recommended)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} Not found"
|
||||
echo "Please install Go 1.26: https://go.dev/dl/"
|
||||
fi
|
||||
|
||||
# Check Rust 1.94
|
||||
echo -n "Checking Rust 1.94... "
|
||||
if command_exists rustc; then
|
||||
RUST_VERSION=$(rustc --version | awk '{print $2}')
|
||||
if [[ "$RUST_VERSION" == 1.94* ]]; then
|
||||
echo -e "${GREEN}✓${NC} Found $(rustc --version)"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} Found Rust $RUST_VERSION (1.94 recommended)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} Not found"
|
||||
echo "Please install Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
|
||||
fi
|
||||
|
||||
# Check Node.js 22
|
||||
echo -n "Checking Node.js 22... "
|
||||
if command_exists node; then
|
||||
NODE_VERSION=$(node --version | sed 's/v//')
|
||||
if [[ "$NODE_VERSION" == 22* ]]; then
|
||||
echo -e "${GREEN}✓${NC} Found $(node --version)"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} Found Node.js $NODE_VERSION (22 recommended)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} Not found"
|
||||
echo "Please install Node.js 22: https://nodejs.org/"
|
||||
fi
|
||||
|
||||
# Check pnpm
|
||||
echo -n "Checking pnpm... "
|
||||
if command_exists pnpm; then
|
||||
echo -e "${GREEN}✓${NC} Found $(pnpm --version)"
|
||||
else
|
||||
echo -e "${RED}✗${NC} Not found"
|
||||
echo "Installing pnpm..."
|
||||
npm install -g pnpm
|
||||
fi
|
||||
|
||||
# Check MongoDB
|
||||
echo -n "Checking MongoDB... "
|
||||
if command_exists mongosh; then
|
||||
echo -e "${GREEN}✓${NC} Found"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} MongoDB shell not found"
|
||||
echo "Please install MongoDB: https://www.mongodb.com/docs/manual/installation/"
|
||||
fi
|
||||
|
||||
# Check Redis
|
||||
echo -n "Checking Redis... "
|
||||
if command_exists redis-cli; then
|
||||
echo -e "${GREEN}✓${NC} Found"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} Redis not found"
|
||||
echo "Install Redis: sudo apt-get install redis-server"
|
||||
fi
|
||||
|
||||
# Check Docker
|
||||
echo -n "Checking Docker... "
|
||||
if command_exists docker; then
|
||||
echo -e "${GREEN}✓${NC} Found $(docker --version | cut -d ' ' -f3)"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} Docker not found (optional)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Installing Dependencies"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Install Go dependencies
|
||||
echo "Installing Go dependencies..."
|
||||
cd apps/api-server && go mod download && cd ../..
|
||||
cd apps/carrier-connector && go mod download && cd ../..
|
||||
|
||||
# Install Rust dependencies
|
||||
echo "Installing Rust dependencies..."
|
||||
cargo fetch
|
||||
|
||||
# Install Node.js dependencies
|
||||
echo "Installing Node.js dependencies..."
|
||||
pnpm install
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Creating Environment Files"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Create .env files from examples
|
||||
for env_example in $(find . -name ".env.example"); do
|
||||
env_file="${env_example%.example}"
|
||||
if [ ! -f "$env_file" ]; then
|
||||
cp "$env_example" "$env_file"
|
||||
echo "Created $env_file"
|
||||
else
|
||||
echo "Skipped $env_file (already exists)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Setup Complete!"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Set up databases: make db-setup"
|
||||
echo "2. Build all components: make all"
|
||||
echo "3. Start development: make dev"
|
||||
echo ""
|
||||
echo "For help: make help"
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Setting up MongoDB for TaaS Platform..."
|
||||
|
||||
# Check if MongoDB is installed
|
||||
if ! command -v mongosh &> /dev/null; then
|
||||
echo "MongoDB shell (mongosh) not found. Please install MongoDB first."
|
||||
echo "Visit: https://www.mongodb.com/docs/manual/installation/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# MongoDB connection details
|
||||
MONGO_HOST=${MONGO_HOST:-"localhost"}
|
||||
MONGO_PORT=${MONGO_PORT:-"27017"}
|
||||
MONGO_USER=${MONGO_USER:-"free5gc"}
|
||||
MONGO_PASS=${MONGO_PASS:-"free5gc_password"}
|
||||
MONGO_DB="free5gc"
|
||||
|
||||
echo "Creating MongoDB database and user..."
|
||||
|
||||
# Create database and user
|
||||
mongosh --host $MONGO_HOST --port $MONGO_PORT <<EOF
|
||||
use admin
|
||||
|
||||
// Create user if not exists
|
||||
if (!db.getUser("$MONGO_USER")) {
|
||||
db.createUser({
|
||||
user: "$MONGO_USER",
|
||||
pwd: "$MONGO_PASS",
|
||||
roles: [
|
||||
{ role: "readWrite", db: "$MONGO_DB" },
|
||||
{ role: "dbAdmin", db: "$MONGO_DB" }
|
||||
]
|
||||
})
|
||||
print("User $MONGO_USER created successfully")
|
||||
} else {
|
||||
print("User $MONGO_USER already exists")
|
||||
}
|
||||
|
||||
use $MONGO_DB
|
||||
|
||||
// Create collections for free5GC
|
||||
db.createCollection("subscriptionData.provisionedData.amData")
|
||||
db.createCollection("subscriptionData.authenticationData.authenticationSubscription")
|
||||
db.createCollection("subscriptionData.provisionedData.smData")
|
||||
db.createCollection("policyData.ues.amData")
|
||||
db.createCollection("policyData.ues.smData")
|
||||
|
||||
print("Collections created successfully")
|
||||
EOF
|
||||
|
||||
echo "MongoDB setup complete!"
|
||||
echo ""
|
||||
echo "Connection string: mongodb://$MONGO_USER:$MONGO_PASS@$MONGO_HOST:$MONGO_PORT/$MONGO_DB"
|
||||
echo ""
|
||||
echo "Update your .env files with:"
|
||||
echo "MONGODB_URI=mongodb://$MONGO_USER:$MONGO_PASS@$MONGO_HOST:$MONGO_PORT/$MONGO_DB"
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Setting up Redis for TaaS Platform..."
|
||||
|
||||
# Check if Redis is installed
|
||||
if ! command -v redis-cli &> /dev/null; then
|
||||
echo "Redis CLI not found. Please install Redis first."
|
||||
echo "Ubuntu/Debian: sudo apt-get install redis-server"
|
||||
echo "macOS: brew install redis"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Redis connection details
|
||||
REDIS_HOST=${REDIS_HOST:-"localhost"}
|
||||
REDIS_PORT=${REDIS_PORT:-"6379"}
|
||||
|
||||
echo "Testing Redis connection..."
|
||||
|
||||
# Test connection
|
||||
if redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
echo "✓ Redis is running and accessible"
|
||||
else
|
||||
echo "✗ Cannot connect to Redis at $REDIS_HOST:$REDIS_PORT"
|
||||
echo "Please start Redis: sudo systemctl start redis-server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configure Redis for TaaS
|
||||
echo "Configuring Redis..."
|
||||
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT <<EOF
|
||||
# Set memory limit
|
||||
CONFIG SET maxmemory 2gb
|
||||
CONFIG SET maxmemory-policy allkeys-lru
|
||||
|
||||
# Disable RDB persistence for speed (optional)
|
||||
# CONFIG SET save ""
|
||||
|
||||
# Enable AOF for durability (optional)
|
||||
# CONFIG SET appendonly yes
|
||||
|
||||
# Save configuration
|
||||
CONFIG REWRITE
|
||||
EOF
|
||||
|
||||
echo "Redis configuration complete!"
|
||||
echo ""
|
||||
echo "Connection string: redis://$REDIS_HOST:$REDIS_PORT/"
|
||||
echo ""
|
||||
echo "Update your .env files with:"
|
||||
echo "REDIS_URI=redis://$REDIS_HOST:$REDIS_PORT/"
|
||||
|
||||
# Create some test data
|
||||
echo ""
|
||||
echo "Creating test credit balances..."
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT <<EOF
|
||||
SET credit:10.0.0.1 1073741824
|
||||
SET credit:10.0.0.2 5368709120
|
||||
SET credit:10.0.0.3 10737418240
|
||||
EOF
|
||||
|
||||
echo "✓ Test credit balances created"
|
||||
echo " - 10.0.0.1: 1 GB"
|
||||
echo " - 10.0.0.2: 5 GB"
|
||||
echo " - 10.0.0.3: 10 GB"
|
||||
Reference in New Issue
Block a user