prod deployment
This commit is contained in:
@@ -23,6 +23,7 @@ usage() {
|
||||
echo " relay-config - ZMQ relay configuration"
|
||||
echo " ingestor-config - CCXT ingestor configuration"
|
||||
echo " flink-config - Flink job configuration"
|
||||
echo " gateway-config - Gateway configuration (prod only; uses op inject)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Update all dev configs"
|
||||
@@ -49,16 +50,18 @@ if [ ! -d "$CONFIG_DIR" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get kubectl context
|
||||
# Set kubectl command and warn for prod
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
CONTEXT=$(kubectl config current-context)
|
||||
KUBECTL="kubectl --context=prod"
|
||||
echo -e "${YELLOW}⚠️ WARNING: Updating PRODUCTION configs!${NC}"
|
||||
echo -e "${YELLOW}Current kubectl context: $CONTEXT${NC}"
|
||||
echo -e "${YELLOW}kubectl context: prod${NC}"
|
||||
read -p "Are you sure you want to continue? (yes/no): " confirm
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
KUBECTL="kubectl"
|
||||
fi
|
||||
|
||||
apply_config() {
|
||||
@@ -71,9 +74,15 @@ apply_config() {
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}→${NC} Creating/updating ConfigMap $config_name..."
|
||||
kubectl create configmap "$config_name" \
|
||||
--from-file=config.yaml="$config_file" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
if [[ "$ENV" == "prod" && "$config_name" == "gateway-config" ]]; then
|
||||
# gateway-config contains op:// references — resolve via op inject
|
||||
op inject -i "$config_file" | $KUBECTL apply -f -
|
||||
else
|
||||
$KUBECTL create configmap "$config_name" \
|
||||
--from-file=config.yaml="$config_file" \
|
||||
--dry-run=client -o yaml | $KUBECTL apply -f -
|
||||
fi
|
||||
echo -e "${GREEN}✓${NC} $config_name updated"
|
||||
|
||||
# Optionally restart pods that use this config
|
||||
@@ -88,11 +97,14 @@ apply_config() {
|
||||
flink-config)
|
||||
restart_pods="deployment/flink-jobmanager deployment/flink-taskmanager"
|
||||
;;
|
||||
gateway-config)
|
||||
restart_pods="deployment/gateway"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "$restart_pods" ]; then
|
||||
echo -e "${YELLOW} Restarting pods...${NC}"
|
||||
kubectl rollout restart $restart_pods 2>/dev/null || echo -e "${YELLOW} (No pods found to restart)${NC}"
|
||||
$KUBECTL rollout restart $restart_pods 2>/dev/null || echo -e "${YELLOW} (No pods found to restart)${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -105,11 +117,20 @@ else
|
||||
echo -e "${GREEN}Updating all $ENV configs...${NC}"
|
||||
echo ""
|
||||
|
||||
CONFIGS=(
|
||||
"relay-config"
|
||||
"ingestor-config"
|
||||
"flink-config"
|
||||
)
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
CONFIGS=(
|
||||
"relay-config"
|
||||
"ingestor-config"
|
||||
"flink-config"
|
||||
"gateway-config"
|
||||
)
|
||||
else
|
||||
CONFIGS=(
|
||||
"relay-config"
|
||||
"ingestor-config"
|
||||
"flink-config"
|
||||
)
|
||||
fi
|
||||
|
||||
FAILED=0
|
||||
for config in "${CONFIGS[@]}"; do
|
||||
|
||||
36
bin/deploy
36
bin/deploy
@@ -28,6 +28,15 @@ if [ "$1" == "dev" ]; then
|
||||
DEV=1
|
||||
fi
|
||||
|
||||
if [ "$1" == "prod" ]; then
|
||||
shift
|
||||
ENV=prod
|
||||
KUBECTL="kubectl --context=prod"
|
||||
else
|
||||
ENV=dev
|
||||
KUBECTL="kubectl"
|
||||
fi
|
||||
|
||||
if [ "$PROJECT" == "dev" ]; then
|
||||
DEV=1
|
||||
# NO_CACHE=--no-cache
|
||||
@@ -183,7 +192,28 @@ echo "$TAG" >&2
|
||||
|
||||
if [ "$DEPLOY" == "1" ]; then
|
||||
docker push $REMOTE/ai-$PROJECT:$TAG
|
||||
YAML=$(sed "s#image: dexorder/ai-$PROJECT*#image: $REMOTE/ai-$PROJECT:$TAG#" deploy/k8s/$KUBERNETES.yaml)
|
||||
echo "$YAML" | kubectl apply -f - || echo "$YAML" "\nkubectl apply failed" && exit 1
|
||||
echo deployed $KUBERNETES.yaml $REMOTE/ai-$PROJECT:$TAG
|
||||
docker push $REMOTE/ai-$PROJECT:latest
|
||||
|
||||
if [ "$ENV" == "prod" ]; then
|
||||
case "$PROJECT" in
|
||||
flink)
|
||||
$KUBECTL set image deployment/flink-jobmanager flink-jobmanager=$REMOTE/ai-flink:$TAG
|
||||
$KUBECTL set image deployment/flink-taskmanager flink-taskmanager=$REMOTE/ai-flink:$TAG
|
||||
;;
|
||||
web)
|
||||
$KUBECTL set image deployment/ai-web ai-web=$REMOTE/ai-web:$TAG
|
||||
;;
|
||||
sandbox|lifecycle-sidecar)
|
||||
echo "Image pushed. New sandboxes will use $REMOTE/ai-$PROJECT:$TAG"
|
||||
;;
|
||||
*)
|
||||
$KUBECTL set image deployment/$PROJECT $PROJECT=$REMOTE/ai-$PROJECT:$TAG
|
||||
;;
|
||||
esac
|
||||
echo "deployed $PROJECT $REMOTE/ai-$PROJECT:$TAG"
|
||||
else
|
||||
YAML=$(sed "s#image: dexorder/ai-$PROJECT*#image: $REMOTE/ai-$PROJECT:$TAG#" deploy/k8s/$KUBERNETES.yaml)
|
||||
echo "$YAML" | kubectl apply -f - || { echo "$YAML"; echo "kubectl apply failed"; exit 1; }
|
||||
echo deployed $KUBERNETES.yaml $REMOTE/ai-$PROJECT:$TAG
|
||||
fi
|
||||
fi
|
||||
|
||||
63
bin/dev
63
bin/dev
@@ -249,7 +249,7 @@ EOF
|
||||
echo -e "${GREEN}→${NC} Applying Kubernetes manifests..."
|
||||
kubectl apply -k .
|
||||
|
||||
# Apply sandbox-namespace secrets (must be after kustomize creates the dexorder-sandboxes namespace)
|
||||
# Apply sandbox-namespace secrets (must be after kustomize creates the sandbox namespace)
|
||||
echo -e "${GREEN}→${NC} Applying sandbox secrets..."
|
||||
if [ -f "$ROOT_DIR/deploy/k8s/dev/secrets/sandbox-secrets.yaml" ]; then
|
||||
kubectl apply -f "$ROOT_DIR/deploy/k8s/dev/secrets/sandbox-secrets.yaml"
|
||||
@@ -274,53 +274,8 @@ EOF
|
||||
deployment/flink-taskmanager \
|
||||
2>/dev/null || echo -e "${YELLOW}(Some deployments not ready yet)${NC}"
|
||||
|
||||
# Initialize gateway database schema
|
||||
echo -e "${BLUE}Initializing gateway database schema...${NC}"
|
||||
echo -e "${GREEN}→${NC} Waiting for postgres..."
|
||||
kubectl wait --for=condition=ready --timeout=120s pod -l app=postgres 2>/dev/null || {
|
||||
echo -e "${YELLOW}⚠️ Postgres not ready yet${NC}"
|
||||
}
|
||||
|
||||
pg_pod=$(kubectl get pods -l app=postgres -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||
if [ -n "$pg_pod" ]; then
|
||||
# Wait for postgres to actually be ready to accept connections
|
||||
echo -e "${GREEN}→${NC} Verifying postgres is ready to accept connections..."
|
||||
for i in {1..30}; do
|
||||
if kubectl exec "$pg_pod" -- psql -U postgres -d iceberg -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ Postgres ready${NC}"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 30 ]; then
|
||||
echo -e "${RED}✗ Postgres not ready after 30 seconds${NC}"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
table_count=$(kubectl exec "$pg_pod" -- psql -U postgres -d iceberg -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'user';" 2>/dev/null | tr -d ' ')
|
||||
if [ "$table_count" = "1" ]; then
|
||||
echo -e "${GREEN}✓ Gateway schema already exists${NC}"
|
||||
else
|
||||
echo -e "${GREEN}→${NC} Applying gateway schema..."
|
||||
if kubectl exec -i "$pg_pod" -- psql -U postgres -d iceberg < "$ROOT_DIR/gateway/schema.sql" > /dev/null 2>&1; then
|
||||
# Verify schema was actually created
|
||||
sleep 1
|
||||
table_count=$(kubectl exec "$pg_pod" -- psql -U postgres -d iceberg -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'user';" 2>/dev/null | tr -d ' ')
|
||||
if [ "$table_count" = "1" ]; then
|
||||
echo -e "${GREEN}✓ Gateway schema initialized${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Failed to verify schema creation${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Failed to initialize gateway schema${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create dev user (refactored into reusable function)
|
||||
create_dev_user
|
||||
fi
|
||||
# Initialize schema and dev user
|
||||
"$SCRIPT_DIR/init" dev
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Dev environment ready!${NC}"
|
||||
@@ -555,7 +510,7 @@ deep_restart() {
|
||||
kubectl delete pod -l app=iceberg-catalog 2>/dev/null || true
|
||||
# Remove all sandbox deployments and services to free quota
|
||||
echo -e "${GREEN}→${NC} Removing all sandbox deployments and services..."
|
||||
kubectl delete deployments,services --all -n dexorder-sandboxes 2>/dev/null || true
|
||||
kubectl delete deployments,services --all -n sandbox 2>/dev/null || true
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Error: Unknown service '$service'${NC}"
|
||||
@@ -699,9 +654,9 @@ case "$COMMAND" in
|
||||
kubectl wait --for=delete pod -l app=qdrant --timeout=60s 2>/dev/null || true
|
||||
# Now delete PVCs
|
||||
delete_pvcs all
|
||||
# Delete dexorder-sandboxes namespace
|
||||
echo -e "${GREEN}→${NC} Deleting dexorder-sandboxes namespace..."
|
||||
kubectl delete namespace dexorder-sandboxes 2>/dev/null || true
|
||||
# Delete sandbox namespace
|
||||
echo -e "${GREEN}→${NC} Deleting sandbox namespace..."
|
||||
kubectl delete namespace sandbox 2>/dev/null || true
|
||||
minikube stop
|
||||
echo -e "${GREEN}✓ Minikube stopped and PVCs deleted${NC}"
|
||||
echo -e "${YELLOW}Tip: Use 'bin/dev stop --keep-data' to preserve PVCs${NC}"
|
||||
@@ -779,8 +734,8 @@ case "$COMMAND" in
|
||||
|
||||
# Handle sandbox separately
|
||||
if [ "$sandbox_requested" == "1" ]; then
|
||||
echo -e "${GREEN}→${NC} Deleting user container deployments in dexorder-sandboxes namespace..."
|
||||
kubectl delete deployments --all -n dexorder-sandboxes 2>/dev/null || true
|
||||
echo -e "${GREEN}→${NC} Deleting user container deployments in sandbox namespace..."
|
||||
kubectl delete deployments --all -n sandbox 2>/dev/null || true
|
||||
echo -e "${GREEN}✓ User containers will be recreated by gateway on next login${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
194
bin/init
Executable file
194
bin/init
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [dev|prod]"
|
||||
echo ""
|
||||
echo "Initialize database schema and create admin user."
|
||||
echo ""
|
||||
echo " dev - Initialize dev environment (minikube, default namespace)"
|
||||
echo " prod - Initialize prod environment (requires confirmation)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ENV="${1:-dev}"
|
||||
|
||||
if [[ "$ENV" != "dev" && "$ENV" != "prod" ]]; then
|
||||
echo -e "${RED}Error: Environment must be 'dev' or 'prod'${NC}"
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
KUBECTL="kubectl --context=prod"
|
||||
BASE_URL="https://dexorder.ai"
|
||||
MCP_URL="https://dexorder.ai/mcp"
|
||||
echo -e "${YELLOW}⚠️ WARNING: Initializing PRODUCTION environment!${NC}"
|
||||
echo -e "${YELLOW}kubectl context: prod${NC}"
|
||||
read -p "Are you sure you want to continue? (yes/no): " confirm
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
KUBECTL="kubectl"
|
||||
BASE_URL="http://dexorder.local"
|
||||
MCP_URL="http://localhost:8080/mcp"
|
||||
fi
|
||||
|
||||
# ---------- Schema Initialization ----------
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Schema Initialization ===${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Waiting for postgres pod...${NC}"
|
||||
$KUBECTL wait --for=condition=ready --timeout=180s pod -l app=postgres 2>/dev/null || {
|
||||
echo -e "${RED}Postgres not ready after 180s${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
PG_POD=$($KUBECTL get pods -l app=postgres -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||
if [ -z "$PG_POD" ]; then
|
||||
echo -e "${RED}No postgres pod found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}Found postgres pod: $PG_POD${NC}"
|
||||
|
||||
# Wait for postgres to accept connections
|
||||
echo -e "${BLUE}Waiting for postgres to accept connections...${NC}"
|
||||
for i in $(seq 1 30); do
|
||||
if $KUBECTL exec "$PG_POD" -- psql -U postgres -d iceberg -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}Postgres ready${NC}"
|
||||
break
|
||||
fi
|
||||
if [[ $i -eq 30 ]]; then
|
||||
echo -e "${RED}Postgres not accepting connections after 60s${NC}"
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Check if schema exists
|
||||
TABLE_COUNT=$($KUBECTL exec "$PG_POD" -- psql -U postgres -d iceberg -t \
|
||||
-c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'user';" \
|
||||
2>/dev/null | tr -d ' \n')
|
||||
|
||||
if [[ "$TABLE_COUNT" == "1" ]]; then
|
||||
echo -e "${GREEN}✓ Schema already initialized${NC}"
|
||||
else
|
||||
echo -e "${BLUE}Applying gateway schema...${NC}"
|
||||
$KUBECTL exec -i "$PG_POD" -- psql -U postgres -d iceberg < "$ROOT_DIR/gateway/schema.sql"
|
||||
echo -e "${GREEN}✓ Schema applied${NC}"
|
||||
fi
|
||||
|
||||
# ---------- Admin User Creation ----------
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Admin User Setup ===${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ "$ENV" == "dev" ]]; then
|
||||
# Dev: use fixed credentials
|
||||
USER_EMAIL="tim@dexorder.ai"
|
||||
USER_PASSWORD="test1234"
|
||||
USER_NAME="Tim"
|
||||
LICENSE_TYPE="pro"
|
||||
echo -e "${BLUE}Using dev defaults: $USER_EMAIL / $USER_PASSWORD ($LICENSE_TYPE)${NC}"
|
||||
else
|
||||
# Prod: prompt for credentials
|
||||
read -p "Admin email: " USER_EMAIL
|
||||
read -s -p "Admin password (min 8 chars): " USER_PASSWORD
|
||||
echo ""
|
||||
read -p "Admin display name: " USER_NAME
|
||||
read -p "License type [free|pro|enterprise] (default: pro): " LICENSE_TYPE
|
||||
LICENSE_TYPE="${LICENSE_TYPE:-pro}"
|
||||
fi
|
||||
|
||||
# Check if user already exists
|
||||
EXISTING_ID=$($KUBECTL exec "$PG_POD" -- psql -U postgres -d iceberg -t \
|
||||
-c "SELECT id FROM \"user\" WHERE email = '$USER_EMAIL';" \
|
||||
2>/dev/null | tr -d ' \n')
|
||||
|
||||
if [ -n "$EXISTING_ID" ]; then
|
||||
echo -e "${GREEN}✓ User already exists in database ($USER_EMAIL)${NC}"
|
||||
USER_ID="$EXISTING_ID"
|
||||
else
|
||||
# Register via API
|
||||
echo -e "${BLUE}Waiting for gateway...${NC}"
|
||||
$KUBECTL wait --for=condition=available --timeout=120s deployment/gateway 2>/dev/null || {
|
||||
echo -e "${YELLOW}⚠️ Gateway not ready after 120s, trying anyway${NC}"
|
||||
}
|
||||
sleep 3
|
||||
|
||||
echo -e "${GREEN}→${NC} Registering user via API..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/dexorder-init-response.json -w "%{http_code}" \
|
||||
-X POST "$BASE_URL/api/auth/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASSWORD\",\"name\":\"$USER_NAME\"}")
|
||||
|
||||
if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "201" ]]; then
|
||||
echo -e "${GREEN}✓ User registered via API${NC}"
|
||||
elif [[ "$HTTP_CODE" == "400" ]]; then
|
||||
echo -e "${YELLOW}⚠️ API returned 400 (user may already exist), continuing...${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ API returned HTTP $HTTP_CODE${NC}"
|
||||
cat /tmp/dexorder-init-response.json 2>/dev/null || true
|
||||
fi
|
||||
rm -f /tmp/dexorder-init-response.json
|
||||
|
||||
sleep 2
|
||||
|
||||
USER_ID=$($KUBECTL exec "$PG_POD" -- psql -U postgres -d iceberg -t \
|
||||
-c "SELECT id FROM \"user\" WHERE email = '$USER_EMAIL';" \
|
||||
2>/dev/null | tr -d ' \n')
|
||||
fi
|
||||
|
||||
if [ -z "$USER_ID" ]; then
|
||||
echo -e "${RED}User not found in database after registration. Is the gateway running?${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}User ID: $USER_ID${NC}"
|
||||
|
||||
# Build license JSON based on type
|
||||
case "$LICENSE_TYPE" in
|
||||
enterprise)
|
||||
LICENSE_JSON='{"licenseType":"enterprise","features":{"maxIndicators":200,"maxStrategies":100,"maxBacktestDays":1825,"realtimeData":true,"customExecutors":true,"apiAccess":true},"resourceLimits":{"maxConcurrentSessions":20,"maxMessagesPerDay":10000,"maxTokensPerMessage":32768,"rateLimitPerMinute":300},"k8sResources":{"memoryRequest":"1Gi","memoryLimit":"4Gi","cpuRequest":"500m","cpuLimit":"4000m","storage":"50Gi","tmpSizeLimit":"1Gi","enableIdleShutdown":true,"idleTimeoutMinutes":120},"preferredModel":{"provider":"anthropic","model":"claude-opus-4-6","temperature":0.7}}'
|
||||
;;
|
||||
free)
|
||||
LICENSE_JSON='{"licenseType":"free","features":{"maxIndicators":10,"maxStrategies":3,"maxBacktestDays":30,"realtimeData":false,"customExecutors":false,"apiAccess":false},"resourceLimits":{"maxConcurrentSessions":1,"maxMessagesPerDay":100,"maxTokensPerMessage":4096,"rateLimitPerMinute":20},"k8sResources":{"memoryRequest":"256Mi","memoryLimit":"512Mi","cpuRequest":"100m","cpuLimit":"500m","storage":"2Gi","tmpSizeLimit":"128Mi","enableIdleShutdown":true,"idleTimeoutMinutes":30},"preferredModel":{"provider":"anthropic","model":"claude-haiku-4-5-20251001","temperature":0.7}}'
|
||||
;;
|
||||
pro|*)
|
||||
LICENSE_JSON='{"licenseType":"pro","features":{"maxIndicators":50,"maxStrategies":20,"maxBacktestDays":365,"realtimeData":true,"customExecutors":true,"apiAccess":true},"resourceLimits":{"maxConcurrentSessions":5,"maxMessagesPerDay":1000,"maxTokensPerMessage":8192,"rateLimitPerMinute":60},"k8sResources":{"memoryRequest":"512Mi","memoryLimit":"2Gi","cpuRequest":"250m","cpuLimit":"2000m","storage":"10Gi","tmpSizeLimit":"256Mi","enableIdleShutdown":true,"idleTimeoutMinutes":60},"preferredModel":{"provider":"anthropic","model":"claude-sonnet-4-6","temperature":0.7}}'
|
||||
;;
|
||||
esac
|
||||
|
||||
echo -e "${GREEN}→${NC} Creating $LICENSE_TYPE license..."
|
||||
$KUBECTL exec "$PG_POD" -- psql -U postgres -d iceberg -c "
|
||||
INSERT INTO user_licenses (user_id, email, license, mcp_server_url)
|
||||
VALUES (
|
||||
'$USER_ID',
|
||||
'$USER_EMAIL',
|
||||
'$LICENSE_JSON',
|
||||
'$MCP_URL'
|
||||
)
|
||||
ON CONFLICT (user_id) DO UPDATE SET
|
||||
license = EXCLUDED.license,
|
||||
updated_at = NOW();
|
||||
" > /dev/null
|
||||
|
||||
echo -e "${GREEN}✓ User ready: $USER_EMAIL ($LICENSE_TYPE)${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Initialization complete.${NC}"
|
||||
if [[ "$ENV" == "dev" ]]; then
|
||||
echo -e "${BLUE}Login at http://dexorder.local with $USER_EMAIL / $USER_PASSWORD${NC}"
|
||||
fi
|
||||
153
bin/op-setup
Executable file
153
bin/op-setup
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create the "AI Prod" 1Password vault and all required items with placeholder values.
|
||||
# Run this once on a fresh setup, then edit each item in 1Password with real values.
|
||||
#
|
||||
# Usage:
|
||||
# bin/op-setup # Create vault and all items
|
||||
# bin/op-setup --dry-run # Print what would be created without doing it
|
||||
|
||||
set -e
|
||||
|
||||
VAULT="AI Prod"
|
||||
DRY_RUN=false
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||
DRY_RUN=true
|
||||
echo -e "${YELLOW}Dry run mode — no changes will be made${NC}"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
run() {
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${BLUE}[dry-run]${NC} $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
item_exists() {
|
||||
local title="$1"
|
||||
op item get "$title" --vault "$VAULT" &>/dev/null
|
||||
}
|
||||
|
||||
create_item() {
|
||||
local title="$1"
|
||||
shift
|
||||
if item_exists "$title"; then
|
||||
echo -e " ${YELLOW}↩${NC} $title — already exists, skipping"
|
||||
else
|
||||
echo -e " ${GREEN}+${NC} Creating: $title"
|
||||
run op item create \
|
||||
--vault "$VAULT" \
|
||||
--category "Login" \
|
||||
--title "$title" \
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 1: Ensure vault exists
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== 1Password Vault ===${NC}"
|
||||
echo ""
|
||||
|
||||
if op vault get "$VAULT" &>/dev/null; then
|
||||
echo -e "${GREEN}✓${NC} Vault '$VAULT' already exists"
|
||||
else
|
||||
echo -e "${GREEN}+${NC} Creating vault: $VAULT"
|
||||
run op vault create "$VAULT"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 2: Create items
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Creating Items in '$VAULT' ===${NC}"
|
||||
echo ""
|
||||
|
||||
# --- PostgreSQL ---
|
||||
# Used by: gateway (DB connection), minio-init job (postgres metadata)
|
||||
create_item "PostgreSQL" \
|
||||
"password[password]=REPLACE_WITH_STRONG_PASSWORD"
|
||||
|
||||
# --- MinIO ---
|
||||
# Used by: minio StatefulSet, flink-secrets, gateway-secrets (iceberg S3), sandbox-secrets
|
||||
# access_key = MinIO root user (equivalent to AWS_ACCESS_KEY_ID)
|
||||
# secret_key = MinIO root password (equivalent to AWS_SECRET_ACCESS_KEY)
|
||||
create_item "MinIO" \
|
||||
"access_key[text]=minio-admin" \
|
||||
"secret_key[password]=REPLACE_WITH_STRONG_SECRET_KEY"
|
||||
|
||||
# --- Gateway ---
|
||||
# Used by: ai-secrets (anthropic_api_key), gateway-secrets (all LLM keys + jwt_secret)
|
||||
# jwt_secret: used to sign user sessions — generate with: openssl rand -base64 48
|
||||
# anthropic_api_key: Anthropic Console → API Keys (https://console.anthropic.com)
|
||||
# openai_api_key: OpenAI Platform → API Keys (https://platform.openai.com)
|
||||
# google_api_key: Google AI Studio (https://aistudio.google.com)
|
||||
# openrouter_api_key: OpenRouter (https://openrouter.ai)
|
||||
create_item "Gateway" \
|
||||
"anthropic_api_key[password]=sk-ant-REPLACE_ME" \
|
||||
"jwt_secret[password]=REPLACE_WITH_RANDOM_64_CHAR_SECRET" \
|
||||
"openai_api_key[password]=sk-REPLACE_ME" \
|
||||
"google_api_key[password]=REPLACE_ME" \
|
||||
"openrouter_api_key[password]=sk-or-REPLACE_ME"
|
||||
|
||||
# --- Telegram ---
|
||||
# Used by: gateway-secrets (optional Telegram bot integration)
|
||||
# bot_token: BotFather → /newbot (https://t.me/BotFather)
|
||||
# Leave as placeholder if Telegram integration is not needed.
|
||||
create_item "Telegram" \
|
||||
"bot_token[password]=REPLACE_ME_OR_LEAVE_EMPTY"
|
||||
|
||||
# --- Ingestor ---
|
||||
# Used by: ingestor-secrets (exchange API keys for CCXT market data)
|
||||
# Keys with empty/placeholder values will cause the ingestor to skip that exchange.
|
||||
# Binance: https://www.binance.com/en/my/settings/api-management
|
||||
# Coinbase: https://portal.cdp.coinbase.com/
|
||||
# Kraken: https://www.kraken.com/u/security/api
|
||||
create_item "Ingestor" \
|
||||
"binance_api_key[text]=REPLACE_ME" \
|
||||
"binance_api_secret[password]=REPLACE_ME" \
|
||||
"coinbase_api_key[text]=REPLACE_ME" \
|
||||
"coinbase_api_secret[password]=REPLACE_ME" \
|
||||
"kraken_api_key[text]=REPLACE_ME" \
|
||||
"kraken_api_secret[password]=REPLACE_ME"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Done
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
if $DRY_RUN; then
|
||||
echo -e "${YELLOW}Dry run complete — no items were created.${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Setup complete.${NC}"
|
||||
echo ""
|
||||
echo -e "Next steps:"
|
||||
echo -e " 1. Open 1Password and update each item in the '${VAULT}' vault with real values:"
|
||||
echo -e " • PostgreSQL → set a strong random password"
|
||||
echo -e " • MinIO → set a strong secret_key (access_key can stay as-is)"
|
||||
echo -e " • Gateway → add real API keys and a random jwt_secret"
|
||||
echo -e " • Ingestor → add real exchange API keys"
|
||||
echo -e " • Telegram → add bot token (or leave placeholder if unused)"
|
||||
echo ""
|
||||
echo -e " 2. Verify op:// references resolve correctly:"
|
||||
echo -e " op inject -i deploy/k8s/prod/secrets/gateway-secrets.tpl.yaml | head -20"
|
||||
echo ""
|
||||
echo -e " 3. Continue with cluster setup:"
|
||||
echo -e " bin/secret-update prod"
|
||||
fi
|
||||
echo ""
|
||||
@@ -50,19 +50,21 @@ if [ ! -d "$SECRETS_DIR" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get kubectl context
|
||||
# Set kubectl command and warn for prod
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
CONTEXT=$(kubectl config current-context)
|
||||
KUBECTL="kubectl --context=prod"
|
||||
echo -e "${YELLOW}⚠️ WARNING: Updating PRODUCTION secrets!${NC}"
|
||||
echo -e "${YELLOW}Current kubectl context: $CONTEXT${NC}"
|
||||
echo -e "${YELLOW}kubectl context: prod${NC}"
|
||||
read -p "Are you sure you want to continue? (yes/no): " confirm
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
KUBECTL="kubectl"
|
||||
fi
|
||||
|
||||
apply_secret() {
|
||||
apply_secret_dev() {
|
||||
local secret_file="$1"
|
||||
local secret_basename=$(basename "$secret_file" .yaml)
|
||||
|
||||
@@ -73,45 +75,71 @@ apply_secret() {
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}→${NC} Applying $secret_basename..."
|
||||
kubectl apply -f "$secret_file"
|
||||
$KUBECTL apply -f "$secret_file"
|
||||
echo -e "${GREEN}✓${NC} $secret_basename updated"
|
||||
}
|
||||
|
||||
apply_secret_prod() {
|
||||
local tpl_file="$1"
|
||||
local secret_basename=$(basename "$tpl_file" .tpl.yaml)
|
||||
|
||||
if [ ! -f "$tpl_file" ]; then
|
||||
echo -e "${RED}✗ Template file not found: $tpl_file${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}→${NC} Applying $secret_basename (via op inject)..."
|
||||
op inject -i "$tpl_file" | $KUBECTL apply -f -
|
||||
echo -e "${GREEN}✓${NC} $secret_basename updated"
|
||||
}
|
||||
|
||||
SECRETS=(
|
||||
"ai-secrets"
|
||||
"postgres-secret"
|
||||
"minio-secret"
|
||||
"ingestor-secrets"
|
||||
"flink-secrets"
|
||||
"gateway-secrets"
|
||||
"sandbox-secrets"
|
||||
)
|
||||
|
||||
# Update specific secret or all secrets
|
||||
if [ -n "$SECRET_NAME" ]; then
|
||||
# Update single secret
|
||||
SECRET_FILE="$SECRETS_DIR/$SECRET_NAME.yaml"
|
||||
apply_secret "$SECRET_FILE"
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
apply_secret_prod "$SECRETS_DIR/$SECRET_NAME.tpl.yaml"
|
||||
else
|
||||
apply_secret_dev "$SECRETS_DIR/$SECRET_NAME.yaml"
|
||||
fi
|
||||
else
|
||||
# Update all secrets
|
||||
echo -e "${GREEN}Updating all $ENV secrets...${NC}"
|
||||
echo ""
|
||||
|
||||
SECRETS=(
|
||||
"ai-secrets"
|
||||
"postgres-secret"
|
||||
"minio-secret"
|
||||
"ingestor-secrets"
|
||||
"flink-secrets"
|
||||
"gateway-secrets"
|
||||
"sandbox-secrets"
|
||||
)
|
||||
|
||||
FAILED=0
|
||||
for secret in "${SECRETS[@]}"; do
|
||||
SECRET_FILE="$SECRETS_DIR/$secret.yaml"
|
||||
if ! apply_secret "$SECRET_FILE"; then
|
||||
FAILED=$((FAILED + 1))
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
if ! apply_secret_prod "$SECRETS_DIR/$secret.tpl.yaml"; then
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
else
|
||||
if ! apply_secret_dev "$SECRETS_DIR/$secret.yaml"; then
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ $FAILED secret(s) failed to apply${NC}"
|
||||
echo -e "${YELLOW}Create missing secret files by copying from .example templates:${NC}"
|
||||
echo -e "${YELLOW} cd $SECRETS_DIR${NC}"
|
||||
echo -e "${YELLOW} cp SECRET_NAME.yaml.example SECRET_NAME.yaml${NC}"
|
||||
echo -e "${YELLOW} # Edit SECRET_NAME.yaml with actual values${NC}"
|
||||
if [[ "$ENV" == "prod" ]]; then
|
||||
echo -e "${YELLOW}⚠️ $FAILED secret(s) failed to apply${NC}"
|
||||
echo -e "${YELLOW}Ensure 1Password CLI is authenticated: op signin${NC}"
|
||||
echo -e "${YELLOW}Ensure 'AI Prod' vault items exist (see deploy/k8s/prod/secrets/*.tpl.yaml)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ $FAILED secret(s) failed to apply${NC}"
|
||||
echo -e "${YELLOW}Create missing secret files by copying from .example templates:${NC}"
|
||||
echo -e "${YELLOW} cd $SECRETS_DIR${NC}"
|
||||
echo -e "${YELLOW} cp SECRET_NAME.yaml.example SECRET_NAME.yaml${NC}"
|
||||
echo -e "${YELLOW} # Edit SECRET_NAME.yaml with actual values${NC}"
|
||||
fi
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}✓ All secrets updated successfully${NC}"
|
||||
|
||||
Reference in New Issue
Block a user