refactor: remove unused deployment infrastructure

- Remove scripts/ directory (21 deployment/helper scripts)
- Remove k8s/ and k8s-kustomize/ directories (Kubernetes configs)
- Remove docker/ directory (docker-compose and configs)
- Remove docs/deployment/ directory and related documentation
- Remove CI-specific docker compose and bake files
- App is not production-ready yet, focusing on development
- Can be re-added when needed for production deployment
This commit is contained in:
William Valentin
2025-09-08 21:23:23 -07:00
parent 8aaeb1fe85
commit 11a0066b67
83 changed files with 0 additions and 11685 deletions
-453
View File
@@ -1,453 +0,0 @@
#!/bin/bash
# Docker Buildx Helper Script
# Provides multi-platform Docker image building and pushing capabilities
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
BUILDER_NAME="rxminder-builder"
PLATFORMS="linux/amd64,linux/arm64"
DOCKERFILE_PATH="docker/Dockerfile"
DOCKER_CONTEXT="."
IMAGE_NAME="${DOCKER_IMAGE_NAME:-rxminder}"
REGISTRY="${DOCKER_REGISTRY:-}"
TAG="${DOCKER_TAG:-latest}"
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to show usage
show_usage() {
echo "Docker Buildx Helper Script"
echo ""
echo "Usage: $0 <command> [options]"
echo ""
echo "Commands:"
echo " setup Setup buildx builder instance"
echo " build-local Build image for local platform only"
echo " build-multi Build multi-platform images (production)"
echo " build-multi-dev Build multi-platform images (development)"
echo " build-push Build and push multi-platform images"
echo " push Push existing images to registry"
echo " inspect Inspect builder instance"
echo " bake Build using docker-bake.hcl"
echo " cleanup Remove builder instance and cleanup"
echo " list List available builders"
echo ""
echo "Environment Variables:"
echo " DOCKER_IMAGE_NAME Image name (default: rxminder)"
echo " DOCKER_REGISTRY Registry URL (e.g., ghcr.io/username)"
echo " DOCKER_TAG Image tag (default: latest)"
echo " APP_NAME Application name for build args"
echo " NODE_ENV Build environment (development/production)"
echo ""
echo "Examples:"
echo " $0 setup"
echo " $0 build-local"
echo " DOCKER_REGISTRY=ghcr.io/myuser $0 build-push"
echo " DOCKER_TAG=v1.0.0 $0 build-multi"
}
# Function to setup buildx builder
setup_builder() {
print_status "Setting up Docker Buildx builder..."
# Check if buildx is available
if ! docker buildx version >/dev/null 2>&1; then
print_error "Docker Buildx is not available. Please update Docker."
exit 1
fi
# Remove existing builder if it exists
if docker buildx ls | grep -q "$BUILDER_NAME"; then
print_status "Removing existing builder instance..."
docker buildx rm "$BUILDER_NAME" 2>/dev/null || true
fi
# Create new builder instance
print_status "Creating new buildx builder instance: $BUILDER_NAME"
docker buildx create \
--name "$BUILDER_NAME" \
--driver docker-container \
--platform "$PLATFORMS" \
--bootstrap
# Use the builder
docker buildx use "$BUILDER_NAME"
# Inspect the builder
docker buildx inspect --bootstrap
print_success "Buildx builder setup completed!"
}
# Function to get build arguments
get_build_args() {
local build_env="${1:-${NODE_ENV:-production}}"
echo "--build-arg APP_NAME=${APP_NAME:-RxMinder}"
echo "--build-arg NODE_ENV=${build_env}"
echo "--build-arg JWT_SECRET=${JWT_SECRET:-demo_jwt_secret_for_frontend_only}"
echo "--build-arg SESSION_SECRET=${SESSION_SECRET:-demo_session_secret_for_frontend_only}"
echo "--build-arg VITE_COUCHDB_URL=${VITE_COUCHDB_URL:-http://couchdb:5984}"
echo "--build-arg VITE_COUCHDB_USER=${VITE_COUCHDB_USER:-admin}"
echo "--build-arg VITE_COUCHDB_PASSWORD=${VITE_COUCHDB_PASSWORD:-change-this-secure-password}"
echo "--build-arg APP_BASE_URL=${APP_BASE_URL:-http://localhost:8080}"
echo "--build-arg VITE_GOOGLE_CLIENT_ID=${VITE_GOOGLE_CLIENT_ID:-}"
echo "--build-arg VITE_GITHUB_CLIENT_ID=${VITE_GITHUB_CLIENT_ID:-}"
echo "--build-arg MAILGUN_API_KEY=${MAILGUN_API_KEY:-}"
echo "--build-arg MAILGUN_DOMAIN=${MAILGUN_DOMAIN:-}"
echo "--build-arg MAILGUN_FROM_EMAIL=${MAILGUN_FROM_EMAIL:-}"
}
# Function to get image tags
get_image_tags() {
local base_name="$1"
local tags=""
# Always include the specified tag
tags="$tags -t $base_name:$TAG"
# Add latest tag if not already latest
if [ "$TAG" != "latest" ]; then
tags="$tags -t $base_name:latest"
fi
# Add git-based tags if in git repo
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
local git_hash=$(git rev-parse --short HEAD)
local git_branch=$(git rev-parse --abbrev-ref HEAD | sed 's/[^a-zA-Z0-9.-]/-/g')
tags="$tags -t $base_name:$git_hash"
if [ "$git_branch" != "HEAD" ] && [ "$git_branch" != "main" ] && [ "$git_branch" != "master" ]; then
tags="$tags -t $base_name:$git_branch"
fi
fi
echo "$tags"
}
# Function to build for local platform only
build_local() {
print_status "Building Docker image for local platform..."
# Ensure builder is available
if ! docker buildx ls | grep -q "$BUILDER_NAME"; then
print_warning "Builder not found, setting up..."
setup_builder
fi
# Use the builder
docker buildx use "$BUILDER_NAME"
# Get the current platform
local platform=$(docker version --format '{{.Server.Os}}/{{.Server.Arch}}')
print_status "Building for platform: $platform"
# Determine image name
local image_name="$IMAGE_NAME"
if [ -n "$REGISTRY" ]; then
image_name="$REGISTRY/$IMAGE_NAME"
fi
# Get build arguments and tags (use development for local builds)
local build_args=$(get_build_args "development")
local tags=$(get_image_tags "$image_name")
print_status "Building image: $image_name:$TAG (development mode)"
# Build the image
docker buildx build \
--platform "$platform" \
$build_args \
$tags \
--load \
-f "$DOCKERFILE_PATH" \
"$DOCKER_CONTEXT"
print_success "Local build completed successfully!"
# Show image info
docker images "$image_name" | head -2
}
# Function to build multi-platform images
build_multi() {
print_status "Building multi-platform Docker images..."
# Ensure builder is available
if ! docker buildx ls | grep -q "$BUILDER_NAME"; then
print_warning "Builder not found, setting up..."
setup_builder
fi
# Use the builder
docker buildx use "$BUILDER_NAME"
# Determine image name
local image_name="$IMAGE_NAME"
if [ -n "$REGISTRY" ]; then
image_name="$REGISTRY/$IMAGE_NAME"
fi
# Get build arguments and tags (use production for multi-platform)
local build_args=$(get_build_args "production")
local tags=$(get_image_tags "$image_name")
print_status "Building for platforms: $PLATFORMS"
print_status "Image: $image_name:$TAG (production mode)"
# Build the images
docker buildx build \
--platform "$PLATFORMS" \
$build_args \
$tags \
-f "$DOCKERFILE_PATH" \
"$DOCKER_CONTEXT"
print_success "Multi-platform build completed successfully!"
}
# Function to build multi-platform images for development
build_multi_dev() {
print_status "Building multi-platform Docker images for development..."
# Ensure builder is available
if ! docker buildx ls | grep -q "$BUILDER_NAME"; then
print_warning "Builder not found, setting up..."
setup_builder
fi
# Use the builder
docker buildx use "$BUILDER_NAME"
# Determine image name
local image_name="$IMAGE_NAME"
if [ -n "$REGISTRY" ]; then
image_name="$REGISTRY/$IMAGE_NAME"
fi
# Get build arguments and tags (use development for multi-platform dev)
local build_args=$(get_build_args "development")
local tags=$(get_image_tags "$image_name")
print_status "Building for platforms: $PLATFORMS"
print_status "Image: $image_name:$TAG (development mode)"
# Build the images
docker buildx build \
--platform "$PLATFORMS" \
$build_args \
$tags \
-f "$DOCKERFILE_PATH" \
"$DOCKER_CONTEXT"
print_success "Multi-platform development build completed successfully!"
}
# Function to build and push multi-platform images
build_push() {
print_status "Building and pushing multi-platform Docker images..."
if [ -z "$REGISTRY" ]; then
print_error "DOCKER_REGISTRY environment variable must be set for pushing"
print_status "Example: DOCKER_REGISTRY=ghcr.io/username $0 build-push"
exit 1
fi
# Ensure builder is available
if ! docker buildx ls | grep -q "$BUILDER_NAME"; then
print_warning "Builder not found, setting up..."
setup_builder
fi
# Use the builder
docker buildx use "$BUILDER_NAME"
# Determine image name
local image_name="$REGISTRY/$IMAGE_NAME"
# Get build arguments and tags (use production for build-push)
local build_args=$(get_build_args "production")
local tags=$(get_image_tags "$image_name")
print_status "Building and pushing for platforms: $PLATFORMS"
print_status "Registry: $REGISTRY"
print_status "Image: $image_name:$TAG (production mode)"
# Build and push the images
docker buildx build \
--platform "$PLATFORMS" \
$build_args \
$tags \
--push \
-f "$DOCKERFILE_PATH" \
"$DOCKER_CONTEXT"
print_success "Multi-platform build and push completed successfully!"
# Show pushed images
echo ""
print_status "Pushed images:"
echo "$image_name:$TAG"
if [ "$TAG" != "latest" ]; then
echo "$image_name:latest"
fi
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
local git_hash=$(git rev-parse --short HEAD)
echo "$image_name:$git_hash"
fi
}
# Function to push existing images
push_images() {
print_status "Pushing existing images to registry..."
if [ -z "$REGISTRY" ]; then
print_error "DOCKER_REGISTRY environment variable must be set for pushing"
exit 1
fi
local image_name="$REGISTRY/$IMAGE_NAME"
print_status "Pushing $image_name:$TAG"
docker push "$image_name:$TAG"
if [ "$TAG" != "latest" ]; then
print_status "Pushing $image_name:latest"
docker push "$image_name:latest"
fi
print_success "Push completed successfully!"
}
# Function to inspect builder
inspect_builder() {
print_status "Inspecting buildx builder..."
if docker buildx ls | grep -q "$BUILDER_NAME"; then
docker buildx inspect "$BUILDER_NAME"
else
print_warning "Builder '$BUILDER_NAME' not found"
print_status "Available builders:"
docker buildx ls
fi
}
# Function to build using docker-bake.hcl
build_bake() {
print_status "Building using docker-bake.hcl..."
if [ ! -f "docker-bake.hcl" ]; then
print_error "docker-bake.hcl not found"
exit 1
fi
# Ensure builder is available
if ! docker buildx ls | grep -q "$BUILDER_NAME"; then
print_warning "Builder not found, setting up..."
setup_builder
fi
# Use the builder
docker buildx use "$BUILDER_NAME"
# Build using bake
docker buildx bake -f docker-bake.hcl
print_success "Bake build completed successfully!"
}
# Function to cleanup builder
cleanup_builder() {
print_status "Cleaning up buildx builder..."
if docker buildx ls | grep -q "$BUILDER_NAME"; then
docker buildx rm "$BUILDER_NAME"
print_success "Builder '$BUILDER_NAME' removed"
else
print_warning "Builder '$BUILDER_NAME' not found"
fi
# Cleanup unused build cache
print_status "Cleaning up build cache..."
docker buildx prune -f
print_success "Cleanup completed!"
}
# Function to list builders
list_builders() {
print_status "Available buildx builders:"
docker buildx ls
}
# Main script logic
case "${1:-}" in
"setup")
setup_builder
;;
"build-local")
build_local
;;
"build-multi")
build_multi
;;
"build-multi-dev")
build_multi_dev
;;
"build-push")
build_push
;;
"push")
push_images
;;
"inspect")
inspect_builder
;;
"bake")
build_bake
;;
"cleanup")
cleanup_builder
;;
"list")
list_builders
;;
"help"|"--help"|"-h")
show_usage
;;
"")
print_error "No command specified"
show_usage
exit 1
;;
*)
print_error "Unknown command: $1"
show_usage
exit 1
;;
esac
-402
View File
@@ -1,402 +0,0 @@
#!/bin/bash
# Test Cleanup Script
# Removes redundant test files and reorganizes test structure
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
TESTS_DIR="$PROJECT_ROOT/tests"
# Colors for output
BLUE='\033[34m'
GREEN='\033[32m'
YELLOW='\033[33m'
RED='\033[31m'
RESET='\033[0m'
echo -e "${BLUE}🧪 Cleaning up test files and structure...${RESET}"
# Function to backup files before deletion
backup_file() {
local file="$1"
local backup_dir="$TESTS_DIR/.backup"
if [ -f "$file" ]; then
mkdir -p "$backup_dir"
cp "$file" "$backup_dir/$(basename "$file").$(date +%Y%m%d-%H%M%S)"
echo -e "${YELLOW} Backed up: $(basename "$file")${RESET}"
fi
}
# Function to remove redundant files
remove_redundant_files() {
echo -e "${BLUE}🗑️ Removing redundant manual test files...${RESET}"
# Manual test files that can be replaced by E2E tests
local manual_files=(
"$TESTS_DIR/manual/admin-login-debug.js"
"$TESTS_DIR/manual/auth-db-debug.js"
"$TESTS_DIR/manual/debug-email-validation.js"
)
for file in "${manual_files[@]}"; do
if [ -f "$file" ]; then
backup_file "$file"
rm "$file"
echo -e "${RED} Removed: $(basename "$file") (replaced by E2E tests)${RESET}"
fi
done
# Remove manual directory if empty
if [ -d "$TESTS_DIR/manual" ] && [ -z "$(ls -A "$TESTS_DIR/manual")" ]; then
rmdir "$TESTS_DIR/manual"
echo -e "${RED} Removed empty manual directory${RESET}"
fi
}
# Function to consolidate mock files
consolidate_mocks() {
echo -e "${BLUE}🔧 Consolidating mock files...${RESET}"
# Check if mocks can be simplified
local mock_files=(
"$TESTS_DIR/__mocks__/node-fetch.js"
"$TESTS_DIR/__mocks__/uuid.js"
)
# Create a consolidated mock index
cat > "$TESTS_DIR/__mocks__/index.js" << 'EOF'
// Consolidated mock exports
module.exports = {
fetch: require('./node-fetch'),
uuid: require('./uuid'),
};
EOF
echo -e "${GREEN} Created consolidated mock index${RESET}"
}
# Function to clean up redundant E2E test patterns
clean_e2e_tests() {
echo -e "${BLUE}🎭 Optimizing E2E test structure...${RESET}"
# Check for duplicate test patterns in E2E files
local e2e_dir="$TESTS_DIR/e2e"
if [ -d "$e2e_dir" ]; then
# Remove redundant beforeEach blocks that are identical
echo -e "${YELLOW} Analyzing E2E test patterns...${RESET}"
# Create optimized test utilities
cat > "$e2e_dir/test-utils.ts" << 'EOF'
// Optimized test utilities to reduce duplication
import { Page } from '@playwright/test';
export class TestUtils {
static async loginAsAdmin(page: Page): Promise<void> {
await page.goto('/');
await page.fill('input[type="email"]', 'admin@localhost');
await page.fill('input[type="password"]', 'admin123!');
await page.click('button[type="submit"]');
await page.waitForSelector('h1:has-text("Medication Reminder")');
}
static async loginAsUser(page: Page, email: string = 'testuser@example.com', password: string = 'TestPassword123!'): Promise<void> {
await page.goto('/');
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', password);
await page.click('button[type="submit"]');
await page.waitForSelector('h1:has-text("Medication Reminder")');
}
static async waitForApp(page: Page): Promise<void> {
await page.waitForSelector('h1:has-text("Medication Reminder")');
}
static async openModal(page: Page, buttonText: string): Promise<void> {
await page.click(`button:has-text("${buttonText}")`);
}
static async closeModal(page: Page): Promise<void> {
await page.click('button:has-text("Close")');
}
}
EOF
echo -e "${GREEN} Created optimized test utilities${RESET}"
fi
}
# Function to clean up integration tests
clean_integration_tests() {
echo -e "${BLUE}🔗 Reviewing integration tests...${RESET}"
local integration_dir="$TESTS_DIR/integration"
if [ -d "$integration_dir" ]; then
# Keep production.test.js as it's useful for deployment validation
echo -e "${GREEN} Integration tests are properly structured${RESET}"
# Add a test runner script for integration tests
cat > "$integration_dir/run-integration.sh" << 'EOF'
#!/bin/bash
# Integration Test Runner
# Ensures services are running before running integration tests
echo "🔍 Checking service availability..."
# Check CouchDB
if ! curl -s http://localhost:5984/ > /dev/null 2>&1; then
echo "❌ CouchDB not available at localhost:5984"
exit 1
fi
# Check Frontend
if ! curl -s http://localhost:8080/ > /dev/null 2>&1; then
echo "⚠️ Frontend not available at localhost:8080"
echo " Starting services..."
# Could add auto-start logic here
fi
echo "✅ Services are available"
echo "🧪 Running integration tests..."
bun run test:integration
EOF
chmod +x "$integration_dir/run-integration.sh"
echo -e "${GREEN} Created integration test runner${RESET}"
fi
}
# Function to update test documentation
update_test_docs() {
echo -e "${BLUE}📚 Updating test documentation...${RESET}"
# Create an updated README with cleanup information
cat > "$TESTS_DIR/README-CLEANUP.md" << 'EOF'
# 🧹 Test Structure Cleanup
## Changes Made
### ❌ Removed Files
- `manual/admin-login-debug.js` → Replaced by `e2e/auth-debug.spec.ts`
- `manual/auth-db-debug.js` → Replaced by automated E2E tests
- `manual/debug-email-validation.js` → Integrated into auth E2E tests
### ✅ Optimizations
- Consolidated mock files with index.js
- Created shared test utilities in `e2e/test-utils.ts`
- Added integration test runner script
- Removed duplicate test patterns
### 📁 Current Structure
```
tests/
├── __mocks__/ # Consolidated mocks
│ ├── index.js # ✨ New: Mock aggregator
│ ├── node-fetch.js # HTTP mocking
│ └── uuid.js # UUID mocking
├── integration/ # Service integration tests
│ ├── production.test.js # Production readiness
│ └── run-integration.sh # ✨ New: Test runner
├── e2e/ # End-to-end tests
│ ├── auth-debug.spec.ts # ✨ New: Replaces manual auth tests
│ ├── test-utils.ts # ✨ New: Shared utilities
│ ├── auth.spec.ts # Authentication flows
│ ├── medication.spec.ts # Medication management
│ ├── admin.spec.ts # Admin interface
│ ├── ui-navigation.spec.ts # UI and navigation
│ ├── reminders.spec.ts # Reminder system
│ ├── fixtures.ts # Test fixtures
│ └── helpers.ts # Test helpers
└── setup.ts # Global test setup
```
## Running Tests After Cleanup
### All Tests
```bash
make test-all
```
### Specific Test Types
```bash
# Unit tests
make test
# Integration tests
./tests/integration/run-integration.sh
# E2E tests
make test-e2e
```
### Debugging
Instead of manual browser scripts, use:
```bash
# Interactive E2E debugging
make test-e2e-ui
# Debug specific auth issues
bunx playwright test auth-debug.spec.ts --debug
```
## Migration Guide
### Manual Tests → E2E Tests
| Old Manual Script | New E2E Test | Purpose |
|---|---|---|
| `admin-login-debug.js` | `auth-debug.spec.ts` | Admin authentication validation |
| `auth-db-debug.js` | `auth-debug.spec.ts` | Database auth testing |
| `debug-email-validation.js` | `auth-debug.spec.ts` | Email format validation |
### Benefits
- ✅ Automated instead of manual
- ✅ Cross-browser testing
- ✅ CI/CD integration
- ✅ Better error reporting
- ✅ Reproducible results
EOF
echo -e "${GREEN} Created cleanup documentation${RESET}"
}
# Function to create test optimization report
create_optimization_report() {
echo -e "${BLUE}📊 Creating optimization report...${RESET}"
local report_file="$TESTS_DIR/cleanup-report.json"
cat > "$report_file" << EOF
{
"cleanup_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"removed_files": [
"manual/admin-login-debug.js",
"manual/auth-db-debug.js",
"manual/debug-email-validation.js"
],
"added_files": [
"__mocks__/index.js",
"e2e/auth-debug.spec.ts",
"e2e/test-utils.ts",
"integration/run-integration.sh",
"README-CLEANUP.md"
],
"optimizations": {
"removed_manual_tests": 3,
"added_automated_tests": 1,
"consolidated_mocks": true,
"added_test_utilities": true,
"improved_documentation": true
},
"test_count_before": {
"manual": 3,
"e2e": 5,
"integration": 1,
"unit": "variable"
},
"test_count_after": {
"manual": 0,
"e2e": 6,
"integration": 1,
"unit": "variable"
}
}
EOF
echo -e "${GREEN} Created optimization report: $report_file${RESET}"
}
# Function to validate test structure
validate_test_structure() {
echo -e "${BLUE}✅ Validating test structure...${RESET}"
local errors=0
# Check required directories exist
local required_dirs=(
"$TESTS_DIR"
"$TESTS_DIR/__mocks__"
"$TESTS_DIR/e2e"
"$TESTS_DIR/integration"
)
for dir in "${required_dirs[@]}"; do
if [ ! -d "$dir" ]; then
echo -e "${RED} ❌ Missing directory: $dir${RESET}"
((errors++))
else
echo -e "${GREEN} ✅ Directory exists: $(basename "$dir")${RESET}"
fi
done
# Check critical test files exist
local required_files=(
"$TESTS_DIR/setup.ts"
"$TESTS_DIR/e2e/auth.spec.ts"
"$TESTS_DIR/integration/production.test.js"
)
for file in "${required_files[@]}"; do
if [ ! -f "$file" ]; then
echo -e "${RED} ❌ Missing file: $(basename "$file")${RESET}"
((errors++))
else
echo -e "${GREEN} ✅ File exists: $(basename "$file")${RESET}"
fi
done
if [ $errors -eq 0 ]; then
echo -e "${GREEN}🎉 Test structure validation passed!${RESET}"
return 0
else
echo -e "${RED}💥 Test structure validation failed with $errors errors${RESET}"
return 1
fi
}
# Main execution
main() {
echo -e "${BLUE}Starting test cleanup process...${RESET}"
# Ensure we're in the right directory
if [ ! -d "$TESTS_DIR" ]; then
echo -e "${RED}❌ Tests directory not found: $TESTS_DIR${RESET}"
exit 1
fi
# Run cleanup steps
remove_redundant_files
consolidate_mocks
clean_e2e_tests
clean_integration_tests
update_test_docs
create_optimization_report
# Validate the result
if validate_test_structure; then
echo -e "${GREEN}🎉 Test cleanup completed successfully!${RESET}"
echo -e "${BLUE}📋 Summary:${RESET}"
echo -e " • Removed redundant manual test files"
echo -e " • Consolidated mock utilities"
echo -e " • Created optimized E2E test structure"
echo -e " • Added automated test runners"
echo -e " • Updated documentation"
echo -e ""
echo -e "${YELLOW}📖 Next steps:${RESET}"
echo -e " 1. Review tests/README-CLEANUP.md"
echo -e " 2. Run 'make test-all' to verify tests work"
echo -e " 3. Update CI/CD to use new test structure"
echo -e " 4. Train team on new E2E debugging workflow"
else
echo -e "${RED}💥 Test cleanup completed with errors${RESET}"
exit 1
fi
}
# Run the script
main "$@"
-393
View File
@@ -1,393 +0,0 @@
#!/bin/bash
# Kubernetes deployment script with environment variable substitution
# This script processes template files and applies them to Kubernetes
#
# Registry Authentication Setup:
# To pull images from a private registry, set these environment variables:
# REGISTRY_USERNAME - Username for the container registry
# REGISTRY_PASSWORD - Password/token for the container registry
# REGISTRY_HOST - Registry hostname (default: gitea-http.taildb3494.ts.net)
#
# Example in .env file:
# REGISTRY_USERNAME=your-username
# REGISTRY_PASSWORD=your-password-or-token
# REGISTRY_HOST=gitea-http.taildb3494.ts.net
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
K8S_DIR="$PROJECT_ROOT/k8s"
TEMP_DIR="/tmp/meds-k8s-deploy"
# Function to print colored output
print_info() {
echo -e "${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Function to load environment variables
load_env() {
local env_file="$1"
if [[ -f "$env_file" ]]; then
print_info "Loading environment from $env_file"
# Export variables from .env file
set -a
source "$env_file"
set +a
else
print_warning "Environment file $env_file not found"
fi
}
# Function to substitute environment variables in template files
substitute_templates() {
print_info "Processing template files..."
# Create temporary directory
mkdir -p "$TEMP_DIR"
# Process each template file
for template_file in "$K8S_DIR"/*.template; do
if [[ -f "$template_file" ]]; then
local filename=$(basename "$template_file" .template)
local output_file="$TEMP_DIR/$filename"
print_info "Processing template: $filename"
# Substitute environment variables
envsubst < "$template_file" > "$output_file"
print_success "Generated: $output_file"
fi
done
}
# Function to create registry authentication
create_registry_auth() {
if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then
local registry_host="${REGISTRY_HOST:-gitea-http.taildb3494.ts.net}"
local auth_string=$(echo -n "${REGISTRY_USERNAME}:${REGISTRY_PASSWORD}" | base64 -w 0)
local docker_config="{\"auths\":{\"${registry_host}\":{\"auth\":\"${auth_string}\"}}}"
export REGISTRY_AUTH_BASE64=$(echo -n "$docker_config" | base64 -w 0)
print_info "Registry authentication configured for $registry_host"
else
print_warning "Registry credentials not provided - skipping registry secret creation"
export REGISTRY_AUTH_BASE64=""
fi
}
# Function to validate required environment variables
validate_env() {
local required_vars=("INGRESS_HOST")
local missing_vars=()
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
missing_vars+=("$var")
fi
done
if [[ ${#missing_vars[@]} -gt 0 ]]; then
print_error "Missing required environment variables:"
for var in "${missing_vars[@]}"; do
echo " - $var"
done
echo ""
echo "Please set these variables in your .env file or environment."
exit 1
fi
}
# Function to ensure namespace exists
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
print_info "Creating namespace: $NAMESPACE"
kubectl create namespace "$NAMESPACE"
print_success "Created namespace: $NAMESPACE"
else
print_info "Using existing namespace: $NAMESPACE"
fi
}
# Function to convert Kubernetes storage units to bytes
storage_to_bytes() {
local storage="$1"
local number=$(echo "$storage" | sed 's/[^0-9]*//g')
local unit=$(echo "$storage" | sed 's/[0-9]*//g')
case "$unit" in
"Ki"|"K") echo $((number * 1024)) ;;
"Mi"|"M") echo $((number * 1024 * 1024)) ;;
"Gi"|"G") echo $((number * 1024 * 1024 * 1024)) ;;
"Ti"|"T") echo $((number * 1024 * 1024 * 1024 * 1024)) ;;
"") echo "$number" ;;
*) echo "0" ;;
esac
}
# Function to check if PVC storage can be updated
can_update_pvc_storage() {
local pvc_file="$1"
local pvc_name=$(grep "name:" "$pvc_file" | head -1 | awk '{print $2}')
local new_storage=$(grep "storage:" "$pvc_file" | awk '{print $2}')
# Check if PVC exists
if kubectl get pvc "$pvc_name" -n "$NAMESPACE" &> /dev/null; then
local current_storage=$(kubectl get pvc "$pvc_name" -n "$NAMESPACE" -o jsonpath='{.status.capacity.storage}')
# Convert storage sizes to bytes for comparison
local current_bytes=$(storage_to_bytes "$current_storage")
local new_bytes=$(storage_to_bytes "$new_storage")
if [[ "$new_bytes" -lt "$current_bytes" ]]; then
print_warning "Skipping PVC $pvc_name: cannot reduce storage from $current_storage to $new_storage"
return 1
fi
fi
return 0
}
# Function to apply Kubernetes manifests
apply_manifests() {
local manifest_dir="$1"
print_info "Applying Kubernetes manifests from $manifest_dir"
# Apply static files that don't have template counterparts
for manifest_file in "$K8S_DIR"/*.yaml; do
if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then
local basename_file=$(basename "$manifest_file")
local template_file="$K8S_DIR/${basename_file}.template"
# Only apply if there's no corresponding template file
if [[ ! -f "$template_file" ]]; then
print_info "Applying static file: $basename_file"
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
fi
fi
done
# Apply processed template files
if [[ -d "$TEMP_DIR" ]]; then
for manifest_file in "$TEMP_DIR"/*.yaml; do
if [[ -f "$manifest_file" ]]; then
local basename_file=$(basename "$manifest_file")
# Special handling for PVC files
if [[ "$basename_file" == *"pvc.yaml" ]] && grep -q "kind: PersistentVolumeClaim" "$manifest_file"; then
if can_update_pvc_storage "$manifest_file"; then
print_info "Applying template: $basename_file"
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
fi
# Special handling for registry secret - skip if no auth provided
elif [[ "$basename_file" == *"registry-secret.yaml" ]] && [[ -z "${REGISTRY_AUTH_BASE64:-}" ]]; then
print_info "Skipping registry secret: no registry credentials provided"
else
print_info "Applying template: $basename_file"
if ! kubectl apply -f "$manifest_file" -n "$NAMESPACE" 2>/dev/null; then
# Handle StatefulSet update failures gracefully
if [[ "$basename_file" == *"statefulset.yaml" ]] && grep -q "kind: StatefulSet" "$manifest_file"; then
print_warning "StatefulSet update failed (likely due to immutable fields) - continuing deployment"
else
# Re-run the command to show the actual error for non-StatefulSet resources
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
fi
fi
fi
fi
done
fi
}
# Function to cleanup temporary files
cleanup() {
if [[ -d "$TEMP_DIR" ]]; then
print_info "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
fi
}
# Function to show deployment status
show_status() {
print_info "Deployment Status:"
echo ""
print_info "Pods:"
kubectl get pods -l app=rxminder -n "$NAMESPACE"
echo ""
print_info "Services:"
kubectl get services -l app=rxminder -n "$NAMESPACE"
echo ""
print_info "Ingress:"
kubectl get ingress -l app=rxminder -n "$NAMESPACE"
echo ""
if [[ -n "${INGRESS_HOST:-}" ]]; then
print_success "Application should be available at: http://$INGRESS_HOST"
fi
}
# Function to show usage
usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -e, --env FILE Environment file to load (default: .env)"
echo " -d, --dry-run Show what would be applied without applying"
echo " -s, --status Show deployment status only"
echo " -c, --cleanup Cleanup temporary files and exit"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 Deploy with default .env file"
echo " $0 -e .env.prod Deploy with production environment"
echo " $0 --dry-run Preview what would be deployed"
echo " $0 --status Check deployment status"
}
# Main function
main() {
local env_file=".env"
local dry_run=false
local status_only=false
local cleanup_only=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-e|--env)
env_file="$2"
shift 2
;;
-d|--dry-run)
dry_run=true
shift
;;
-s|--status)
status_only=true
shift
;;
-c|--cleanup)
cleanup_only=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
print_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Handle cleanup only
if [[ "$cleanup_only" == true ]]; then
cleanup
exit 0
fi
# Handle status only
if [[ "$status_only" == true ]]; then
load_env "$env_file"
NAMESPACE="${NAMESPACE:-rxminder}"
show_status
exit 0
fi
# Check if kubectl is available
if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed or not in PATH"
exit 1
fi
# Check if we can connect to Kubernetes cluster
if ! kubectl cluster-info &> /dev/null; then
print_error "Cannot connect to Kubernetes cluster"
print_info "Make sure your kubectl is configured correctly"
exit 1
fi
print_info "🚀 Deploying Medication Reminder App to Kubernetes"
echo ""
# Load environment variables
load_env "$env_file"
# Set default values for required variables
export APP_NAME="${APP_NAME:-rxminder}"
export STORAGE_CLASS="${STORAGE_CLASS:-longhorn}"
export STORAGE_SIZE="${STORAGE_SIZE:-5Gi}"
export DOCKER_IMAGE="${DOCKER_IMAGE:-gitea-http.taildb3494.ts.net/will/rxminder:latest}"
# Set default namespace if not provided in environment
NAMESPACE="${NAMESPACE:-rxminder}"
# Create registry authentication if credentials are provided
create_registry_auth
# Validate required environment variables
validate_env
# Ensure namespace exists
ensure_namespace
# Process templates
substitute_templates
if [[ "$dry_run" == true ]]; then
print_info "Dry run mode - showing generated manifests:"
echo ""
for manifest_file in "$TEMP_DIR"/*.yaml; do
if [[ -f "$manifest_file" ]]; then
echo "=== $(basename "$manifest_file") ==="
cat "$manifest_file"
echo ""
fi
done
else
# Apply manifests
apply_manifests "$K8S_DIR"
print_success "Deployment completed!"
echo ""
# Show status
show_status
fi
# Cleanup
cleanup
}
# Trap to ensure cleanup happens
trap cleanup EXIT
# Run main function
main "$@"
-426
View File
@@ -1,426 +0,0 @@
#!/bin/bash
# deploy-with-env.sh
# Deploy Kustomize configurations with environment variable substitution
# Usage: ./scripts/deploy-with-env.sh [environment] [action]
# Example: ./scripts/deploy-with-env.sh dev apply
# Example: ./scripts/deploy-with-env.sh prod diff
set -euo pipefail
# Default values
ENVIRONMENT=${1:-dev}
ACTION=${2:-apply}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Load environment variables from multiple sources
load_environment() {
local env_files=(
"$HOME/.env"
"$PROJECT_ROOT/.env"
"$PROJECT_ROOT/.env.$ENVIRONMENT"
"$PROJECT_ROOT/.env.local"
)
print_status "Loading environment variables for: $ENVIRONMENT"
for env_file in "${env_files[@]}"; do
if [[ -f "$env_file" ]]; then
print_status "Loading: $env_file"
set -a
source "$env_file"
set +a
fi
done
# Set defaults if not provided
export APP_NAME="${APP_NAME:-rxminder}"
export NODE_ENV="${NODE_ENV:-$ENVIRONMENT}"
export IMAGE_TAG="${IMAGE_TAG:-latest}"
export NAMESPACE="${NAMESPACE:-${APP_NAME}-${ENVIRONMENT}}"
# Environment-specific defaults
case "$ENVIRONMENT" in
"dev"|"development")
export NODE_ENV="development"
export LOG_LEVEL="${LOG_LEVEL:-debug}"
export DEBUG="${DEBUG:-true}"
export IMAGE_TAG="${IMAGE_TAG:-dev}"
export INGRESS_HOST="${INGRESS_HOST:-${APP_NAME}-dev.local}"
;;
"prod"|"production")
export NODE_ENV="production"
export LOG_LEVEL="${LOG_LEVEL:-warn}"
export DEBUG="${DEBUG:-false}"
export IMAGE_TAG="${IMAGE_TAG:-v1.0.0}"
export INGRESS_HOST="${INGRESS_HOST:-${APP_NAME}.yourdomain.com}"
;;
"staging")
export NODE_ENV="staging"
export LOG_LEVEL="${LOG_LEVEL:-info}"
export DEBUG="${DEBUG:-false}"
export IMAGE_TAG="${IMAGE_TAG:-staging}"
export INGRESS_HOST="${INGRESS_HOST:-staging.${APP_NAME}.yourdomain.com}"
;;
esac
print_success "Environment loaded: $ENVIRONMENT"
print_status "Key variables:"
echo " APP_NAME: $APP_NAME"
echo " NODE_ENV: $NODE_ENV"
echo " IMAGE_TAG: $IMAGE_TAG"
echo " NAMESPACE: $NAMESPACE"
echo " INGRESS_HOST: $INGRESS_HOST"
}
# Substitute environment variables in kustomization files
substitute_variables() {
local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT"
local temp_dir=$(mktemp -d)
local kustomization_file="$overlay_dir/kustomization.yaml"
print_status "Creating temporary overlay with variable substitution..."
# Copy overlay directory to temp location
cp -r "$overlay_dir" "$temp_dir/"
local temp_overlay="$temp_dir/$(basename "$overlay_dir")"
# Substitute variables in kustomization.yaml
if [[ -f "$temp_overlay/kustomization.yaml" ]]; then
envsubst < "$kustomization_file" > "$temp_overlay/kustomization.yaml.tmp"
mv "$temp_overlay/kustomization.yaml.tmp" "$temp_overlay/kustomization.yaml"
fi
# Substitute variables in any other YAML files in the overlay
find "$temp_overlay" -name "*.yaml" -not -name "kustomization.yaml" | while read -r file; do
envsubst < "$file" > "$file.tmp"
mv "$file.tmp" "$file"
done
echo "$temp_overlay"
}
# Generate dynamic ConfigMap with current environment variables
generate_dynamic_config() {
local temp_dir="$1"
local config_file="$temp_dir/dynamic-config.env"
print_status "Generating dynamic configuration..."
cat > "$config_file" << EOF
# Dynamic configuration generated at deploy time
# Generated on: $(date)
# Environment: $ENVIRONMENT
# Application Configuration
APP_NAME=$APP_NAME
NODE_ENV=$NODE_ENV
LOG_LEVEL=$LOG_LEVEL
DEBUG=$DEBUG
# Image Configuration
IMAGE_TAG=$IMAGE_TAG
REGISTRY_URL=${REGISTRY_URL:-gitea-http.taildb3494.ts.net}
IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-will/rxminder}
# Network Configuration
INGRESS_HOST=$INGRESS_HOST
NAMESPACE=$NAMESPACE
# API Configuration
REACT_APP_API_URL=${REACT_APP_API_URL:-http://${APP_NAME}-couchdb-service:5984}
# Database Configuration
DB_HOST=${DB_HOST:-${APP_NAME}-couchdb-service}
DB_PORT=${DB_PORT:-5984}
COUCHDB_DATABASE_NAME=${COUCHDB_DATABASE_NAME:-meds_app}
# Feature Flags
ENABLE_MONITORING=${ENABLE_MONITORING:-false}
ENABLE_METRICS=${ENABLE_METRICS:-false}
ENABLE_TRACING=${ENABLE_TRACING:-false}
# Performance Configuration
CACHE_TTL=${CACHE_TTL:-1800}
REQUEST_TIMEOUT=${REQUEST_TIMEOUT:-30000}
MAX_CONNECTIONS=${MAX_CONNECTIONS:-100}
# Security Configuration
ENABLE_CORS=${ENABLE_CORS:-true}
CORS_ORIGIN=${CORS_ORIGIN:-*}
# Build Information
BUILD_VERSION=${BUILD_VERSION:-unknown}
BUILD_COMMIT=${BUILD_COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")}
BUILD_DATE=${BUILD_DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}
EOF
# Update kustomization.yaml to include dynamic config
local kustomization_file="$temp_dir/kustomization.yaml"
# Add configMapGenerator for dynamic config if not present
if ! grep -q "dynamic-config.env" "$kustomization_file" 2>/dev/null; then
cat >> "$kustomization_file" << EOF
# Dynamic configuration added at deploy time
configMapGenerator:
- name: ${APP_NAME}-dynamic-config
envs:
- dynamic-config.env
behavior: create
EOF
fi
print_success "Dynamic configuration generated"
}
# Validate deployment prerequisites
validate_prerequisites() {
print_status "Validating prerequisites..."
# Check kubectl
if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed or not in PATH"
return 1
fi
# Check kubectl connectivity
if ! kubectl cluster-info &> /dev/null; then
print_error "Cannot connect to Kubernetes cluster"
return 1
fi
# Check if overlay exists
local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT"
if [[ ! -d "$overlay_dir" ]]; then
print_error "Overlay directory not found: $overlay_dir"
return 1
fi
# Check if kustomization.yaml exists
if [[ ! -f "$overlay_dir/kustomization.yaml" ]]; then
print_error "kustomization.yaml not found in: $overlay_dir"
return 1
fi
print_success "Prerequisites validated"
}
# Execute kubectl command with the prepared overlay
execute_kubectl() {
local temp_overlay="$1"
local action="$2"
print_status "Executing kubectl $action with prepared overlay..."
case "$action" in
"apply")
kubectl apply -k "$temp_overlay"
;;
"delete")
kubectl delete -k "$temp_overlay" --ignore-not-found=true
;;
"diff")
kubectl diff -k "$temp_overlay" || true
;;
"dry-run")
kubectl apply -k "$temp_overlay" --dry-run=client -o yaml
;;
"validate")
kubectl kustomize "$temp_overlay" | kubectl apply --dry-run=client --validate=true -f -
;;
"build")
kubectl kustomize "$temp_overlay"
;;
*)
print_error "Unknown action: $action"
print_status "Supported actions: apply, delete, diff, dry-run, validate, build"
return 1
;;
esac
}
# Show deployment status
show_status() {
local namespace="${NAMESPACE:-${APP_NAME}-${ENVIRONMENT}}"
print_status "Deployment status for $ENVIRONMENT environment:"
echo
echo "Namespace: $namespace"
kubectl get namespace "$namespace" 2>/dev/null || echo "Namespace not found"
echo
echo "Deployments:"
kubectl get deployments -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No deployments found"
echo
echo "Services:"
kubectl get services -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No services found"
echo
echo "Ingress:"
kubectl get ingress -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No ingress found"
echo
echo "ConfigMaps:"
kubectl get configmaps -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No configmaps found"
}
# Cleanup function
cleanup() {
if [[ -n "${TEMP_DIR:-}" ]] && [[ -d "$TEMP_DIR" ]]; then
print_status "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
fi
}
# Set up cleanup trap
trap cleanup EXIT
# Show usage information
show_usage() {
cat << EOF
Usage: $0 [environment] [action] [options]
ENVIRONMENTS:
dev, development Deploy to development environment
prod, production Deploy to production environment
staging Deploy to staging environment
ACTIONS:
apply Apply the configuration (default)
delete Delete the deployment
diff Show differences
dry-run Show what would be applied
validate Validate configuration
build Build and show manifests
status Show deployment status
OPTIONS:
-h, --help Show this help message
--no-env Skip loading environment files
--verbose Enable verbose output
EXAMPLES:
$0 dev apply Deploy to development
$0 prod diff Show production differences
$0 staging delete Delete staging deployment
$0 dev status Show development status
ENVIRONMENT FILES:
The script loads variables from (in order):
- ~/.env
- ./.env
- ./.env.\$ENVIRONMENT
- ./.env.local
EOF
}
# Main execution function
main() {
local skip_env=false
local verbose=false
# Parse options
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
--no-env)
skip_env=true
shift
;;
--verbose)
verbose=true
set -x
shift
;;
-*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
*)
if [[ -z "${ENVIRONMENT_SET:-}" ]]; then
ENVIRONMENT="$1"
ENVIRONMENT_SET=true
elif [[ -z "${ACTION_SET:-}" ]]; then
ACTION="$1"
ACTION_SET=true
else
print_error "Too many arguments: $1"
show_usage
exit 1
fi
shift
;;
esac
done
print_status "Kustomize Deployment with Environment Variables"
print_status "Environment: $ENVIRONMENT"
print_status "Action: $ACTION"
# Special case for status action
if [[ "$ACTION" == "status" ]]; then
if [[ "$skip_env" != "true" ]]; then
load_environment
fi
show_status
exit 0
fi
# Validate prerequisites
validate_prerequisites
# Load environment variables
if [[ "$skip_env" != "true" ]]; then
load_environment
fi
# Create temporary overlay with substituted variables
TEMP_DIR=$(substitute_variables)
generate_dynamic_config "$TEMP_DIR"
# Execute the kubectl command
execute_kubectl "$TEMP_DIR" "$ACTION"
if [[ "$ACTION" == "apply" ]]; then
print_success "Deployment completed successfully!"
show_status
elif [[ "$ACTION" == "delete" ]]; then
print_success "Resources deleted successfully!"
fi
}
# Run main function with all arguments
main "$@"
-258
View File
@@ -1,258 +0,0 @@
#!/bin/bash
# 🧪 Deployment Validation Script
# Validates complete deployment with all environment variables and health checks
set -e
echo "🚀 Starting deployment validation..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function
cleanup() {
print_status "Cleaning up test containers..."
APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]')
docker stop ${APP_NAME_LOWER}-validation-test 2>/dev/null || true
docker rm ${APP_NAME_LOWER}-validation-test 2>/dev/null || true
docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation down 2>/dev/null || true
}
# Set trap for cleanup
trap cleanup EXIT
print_status "1. Validating environment files..."
# Check if required environment files exist
if [[ ! -f .env ]]; then
print_error ".env file not found. Run 'cp .env.example .env' and configure it."
exit 1
fi
if [[ ! -f .env.example ]]; then
print_error ".env.example file not found."
exit 1
fi
print_success "Environment files exist"
# Validate environment consistency
print_status "2. Checking environment variable consistency..."
./scripts/validate-env.sh
print_status "3. Setting up Docker Buildx..."
# Ensure buildx is available
if ! docker buildx version >/dev/null 2>&1; then
print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx."
exit 1
fi
# Create a new builder instance if it doesn't exist
if ! docker buildx ls | grep -q "meds-builder"; then
print_status "Creating new buildx builder instance..."
docker buildx create --name meds-builder --driver docker-container --bootstrap
fi
# Use the builder
docker buildx use meds-builder
print_status "4. Building multi-platform Docker image with buildx..."
# Convert APP_NAME to lowercase for Docker compatibility
APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]')
# Determine host platform
HOST_ARCH="$(uname -m)"
case "$HOST_ARCH" in
x86_64) HOST_PLATFORM="linux/amd64" ;;
aarch64|arm64) HOST_PLATFORM="linux/arm64" ;;
*) HOST_PLATFORM="linux/amd64" ;;
esac
# Decide build strategy:
# Enable multi-platform (push) if MULTI_PLATFORM=1 or CONTAINER_REGISTRY is set
if [[ "${MULTI_PLATFORM:-0}" == "1" || -n "${CONTAINER_REGISTRY:-}" ]]; then
BUILD_PLATFORMS="linux/amd64,linux/arm64"
EXPORT_MODE="--push"
# If CONTAINER_REGISTRY provided, ensure trailing slash
if [[ -n "${CONTAINER_REGISTRY:-}" && "${CONTAINER_REGISTRY}" != */ ]]; then
CONTAINER_REGISTRY="${CONTAINER_REGISTRY}/"
fi
IMAGE_TAG="${IMAGE_TAG:-${CONTAINER_REGISTRY:-}${APP_NAME_LOWER}-validation:latest}"
print_status "Multi-platform build enabled (platforms: $BUILD_PLATFORMS) -> push $IMAGE_TAG"
else
BUILD_PLATFORMS="$HOST_PLATFORM"
EXPORT_MODE="--load"
IMAGE_TAG="${IMAGE_TAG:-${APP_NAME_LOWER}-validation}"
print_status "Single-platform build ($BUILD_PLATFORMS) -> load locally as $IMAGE_TAG"
fi
# Perform build
docker buildx build --no-cache \
--platform "$BUILD_PLATFORMS" \
--build-arg APP_NAME="${APP_NAME:-RxMinder}" \
--build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \
--build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg VITE_COUCHDB_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \
--build-arg VITE_COUCHDB_USER="${VITE_COUCHDB_USER:-admin}" \
--build-arg VITE_COUCHDB_PASSWORD="${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg APP_BASE_URL="${APP_BASE_URL:-http://localhost:8080}" \
--build-arg VITE_GOOGLE_CLIENT_ID="${VITE_GOOGLE_CLIENT_ID:-}" \
--build-arg VITE_GITHUB_CLIENT_ID="${VITE_GITHUB_CLIENT_ID:-}" \
--build-arg MAILGUN_API_KEY="${MAILGUN_API_KEY:-}" \
--build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \
--build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \
--build-arg NODE_ENV="${NODE_ENV:-production}" \
-t "$IMAGE_TAG" \
$EXPORT_MODE \
.
# If we pushed (multi-platform), pull the host-specific image for local tests
if [[ "$EXPORT_MODE" == "--push" ]]; then
print_status "Pulling image $IMAGE_TAG for local validation..."
docker pull "$IMAGE_TAG"
fi
print_success "Docker image built successfully"
print_status "5. Testing container startup and health..."
# Run container in background
docker run --rm -d \
-p 8083:80 \
--name ${APP_NAME_LOWER}-validation-test \
"$IMAGE_TAG"
# Wait for container to start
sleep 5
# Check if container is running
if ! docker ps | grep -q ${APP_NAME_LOWER}-validation-test; then
print_error "Container failed to start"
docker logs ${APP_NAME_LOWER}-validation-test
exit 1
fi
print_success "Container started successfully"
# Test health endpoint
print_status "5. Testing health endpoint..."
for i in {1..10}; do
if curl -s -f http://localhost:8083/health > /dev/null; then
print_success "Health endpoint responding"
break
elif [[ $i -eq 10 ]]; then
print_error "Health endpoint not responding after 10 attempts"
exit 1
else
print_warning "Health endpoint not ready, retrying... ($i/10)"
sleep 2
fi
done
# Test main application
print_status "6. Testing main application..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083)
if [[ $HTTP_CODE -eq 200 ]]; then
print_success "Main application responding (HTTP $HTTP_CODE)"
else
print_error "Main application not responding properly (HTTP $HTTP_CODE)"
exit 1
fi
# Test docker-compose build
print_status "7. Testing Docker Compose build..."
docker compose -f docker/docker-compose.yaml build frontend --no-cache
print_success "Docker Compose build successful"
# Test docker-compose with validation project name
print_status "8. Testing Docker Compose deployment..."
docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation up -d --build
# Wait for services to start
sleep 10
# Check service health
if docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation ps | grep -q "Up"; then
print_success "Docker Compose services started successfully"
else
print_error "Docker Compose services failed to start"
docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation logs
exit 1
fi
# Test health of compose deployment
if curl -s -f http://localhost:8080/health > /dev/null; then
print_success "Docker Compose health endpoint responding"
else
print_warning "Docker Compose health endpoint not responding (may need CouchDB)"
fi
print_status "9. Checking image size..."
IMAGE_SIZE=$(docker image inspect "$IMAGE_TAG" --format='{{.Size}}' | numfmt --to=iec)
print_success "Image size: $IMAGE_SIZE"
print_status "10. Validating security configuration..."
# Check if image runs as non-root
USER_INFO=$(docker run --rm "$IMAGE_TAG" whoami)
if [[ "$USER_INFO" != "root" ]]; then
print_success "Container runs as non-root user: $USER_INFO"
else
print_warning "Container runs as root user (security consideration)"
fi
# Check nginx configuration
if docker run --rm "$IMAGE_TAG" nginx -t 2>/dev/null; then
print_success "Nginx configuration is valid"
else
print_error "Nginx configuration has issues"
exit 1
fi
print_status "11. Final validation complete!"
echo
echo "🎉 Deployment validation successful!"
echo
echo "Summary:"
echo "✅ Environment files validated"
echo "✅ Docker image builds successfully"
echo "✅ Container starts and runs healthy"
echo "✅ Health endpoints respond correctly"
echo "✅ Docker Compose deployment works"
echo "✅ Security configuration validated"
echo "✅ Image size optimized ($IMAGE_SIZE)"
echo
echo "Your deployment is ready for production! 🚀"
echo
echo "Next steps:"
echo "1. Configure production environment variables in .env"
echo "2. Run './deploy.sh production' for production deployment"
echo "3. Set up monitoring and backups"
echo "4. Configure SSL/TLS certificates"
echo
-474
View File
@@ -1,474 +0,0 @@
#!/bin/bash
# generate-config.sh
# Generates config.env files for Kustomize from environment variables
# Usage: ./scripts/generate-config.sh [environment]
# Example: ./scripts/generate-config.sh dev
set -euo pipefail
# Default environment
ENVIRONMENT=${1:-dev}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Load environment variables from various sources
load_env_files() {
local env_files=(
"$HOME/.env"
"$PROJECT_ROOT/.env"
"$PROJECT_ROOT/.env.$ENVIRONMENT"
"$PROJECT_ROOT/.env.local"
)
print_status "Loading environment variables..."
for env_file in "${env_files[@]}"; do
if [[ -f "$env_file" ]]; then
print_status "Loading: $env_file"
# Source the file in a subshell to avoid polluting current environment
set -a
source "$env_file"
set +a
else
print_warning "File not found: $env_file"
fi
done
}
# Generate config.env for base configuration
generate_base_config() {
local config_file="$PROJECT_ROOT/k8s-kustomize/base/config.env"
local temp_file=$(mktemp)
print_status "Generating base config.env..."
cat > "$temp_file" << EOF
# Base configuration for rxminder application
# Generated automatically from environment variables
# Generated on: $(date)
# Environment: $ENVIRONMENT
# Application Environment
NODE_ENV=${NODE_ENV:-production}
LOG_LEVEL=${LOG_LEVEL:-info}
# API Configuration
REACT_APP_API_URL=${REACT_APP_API_URL:-http://rxminder-couchdb-service:5984}
# Feature Flags
ENABLE_MONITORING=${ENABLE_MONITORING:-false}
DEBUG=${DEBUG:-false}
# Cache Configuration
CACHE_TTL=${CACHE_TTL:-1800}
# Database Configuration
DB_HOST=${DB_HOST:-rxminder-couchdb-service}
DB_PORT=${DB_PORT:-5984}
COUCHDB_DATABASE_NAME=${COUCHDB_DATABASE_NAME:-meds_app}
# Security Configuration
ENABLE_CORS=${ENABLE_CORS:-true}
CORS_ORIGIN=${CORS_ORIGIN:-*}
# Performance Configuration
REQUEST_TIMEOUT=${REQUEST_TIMEOUT:-30000}
MAX_CONNECTIONS=${MAX_CONNECTIONS:-100}
# Logging Configuration
LOG_FORMAT=${LOG_FORMAT:-json}
LOG_TIMESTAMP=${LOG_TIMESTAMP:-true}
# Health Check Configuration
HEALTH_CHECK_INTERVAL=${HEALTH_CHECK_INTERVAL:-30}
READINESS_CHECK_TIMEOUT=${READINESS_CHECK_TIMEOUT:-5}
# Application Metadata
APP_NAME=${APP_NAME:-rxminder}
APP_VERSION=${APP_VERSION:-1.0.0}
# Container Registry
REGISTRY_URL=${REGISTRY_URL:-gitea-http.taildb3494.ts.net}
IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-will/rxminder}
# Ingress Configuration
INGRESS_CLASS=${INGRESS_CLASS:-nginx}
CERT_MANAGER_ISSUER=${CERT_MANAGER_ISSUER:-letsencrypt-prod}
# Monitoring and Observability
ENABLE_METRICS=${ENABLE_METRICS:-false}
METRICS_PORT=${METRICS_PORT:-9090}
ENABLE_TRACING=${ENABLE_TRACING:-false}
# Development specific (will be overridden in overlays)
DEV_MODE=${DEV_MODE:-false}
HOT_RELOAD=${HOT_RELOAD:-false}
EOF
# Move temp file to final location
mv "$temp_file" "$config_file"
print_success "Generated: $config_file"
}
# Generate environment-specific config
generate_environment_config() {
local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT"
local env_config_file="$overlay_dir/config.env"
if [[ ! -d "$overlay_dir" ]]; then
print_error "Environment overlay directory not found: $overlay_dir"
return 1
fi
print_status "Generating environment-specific config for: $ENVIRONMENT"
case "$ENVIRONMENT" in
"dev"|"development")
generate_dev_config "$env_config_file"
;;
"prod"|"production")
generate_prod_config "$env_config_file"
;;
"staging")
generate_staging_config "$env_config_file"
;;
*)
print_warning "Unknown environment: $ENVIRONMENT. Generating generic config."
generate_generic_config "$env_config_file"
;;
esac
}
# Generate development-specific configuration
generate_dev_config() {
local config_file="$1"
local temp_file=$(mktemp)
cat > "$temp_file" << EOF
# Development environment configuration
# Generated on: $(date)
NODE_ENV=development
LOG_LEVEL=debug
DEBUG=true
ENABLE_MONITORING=false
DEV_MODE=true
HOT_RELOAD=true
# Development URLs (override if needed)
REACT_APP_API_URL=${DEV_API_URL:-http://rxminder-couchdb-service:5984}
CORS_ORIGIN=${DEV_CORS_ORIGIN:-*}
# Development domain
INGRESS_HOST=${DEV_INGRESS_HOST:-rxminder-dev.local}
# Relaxed timeouts for debugging
REQUEST_TIMEOUT=60000
HEALTH_CHECK_INTERVAL=60
# Development image tag
IMAGE_TAG=${DEV_IMAGE_TAG:-dev}
EOF
mv "$temp_file" "$config_file"
print_success "Generated development config: $config_file"
}
# Generate production-specific configuration
generate_prod_config() {
local config_file="$1"
local temp_file=$(mktemp)
cat > "$temp_file" << EOF
# Production environment configuration
# Generated on: $(date)
NODE_ENV=production
LOG_LEVEL=${PROD_LOG_LEVEL:-warn}
DEBUG=false
ENABLE_MONITORING=true
DEV_MODE=false
# Production URLs
REACT_APP_API_URL=${PROD_API_URL:-http://rxminder-couchdb-service:5984}
CORS_ORIGIN=${PROD_CORS_ORIGIN:-https://rxminder.yourdomain.com}
# Production domain
INGRESS_HOST=${PROD_INGRESS_HOST:-rxminder.yourdomain.com}
# Production performance settings
CACHE_TTL=3600
REQUEST_TIMEOUT=30000
MAX_CONNECTIONS=200
# Production monitoring
ENABLE_METRICS=true
ENABLE_TRACING=true
# Production image tag
IMAGE_TAG=${PROD_IMAGE_TAG:-v1.0.0}
# Security settings
ENABLE_SECURITY_HEADERS=true
ENABLE_RATE_LIMITING=true
EOF
mv "$temp_file" "$config_file"
print_success "Generated production config: $config_file"
}
# Generate staging-specific configuration
generate_staging_config() {
local config_file="$1"
local temp_file=$(mktemp)
cat > "$temp_file" << EOF
# Staging environment configuration
# Generated on: $(date)
NODE_ENV=staging
LOG_LEVEL=${STAGING_LOG_LEVEL:-info}
DEBUG=false
ENABLE_MONITORING=true
DEV_MODE=false
# Staging URLs
REACT_APP_API_URL=${STAGING_API_URL:-http://rxminder-couchdb-service:5984}
CORS_ORIGIN=${STAGING_CORS_ORIGIN:-https://staging.rxminder.yourdomain.com}
# Staging domain
INGRESS_HOST=${STAGING_INGRESS_HOST:-staging.rxminder.yourdomain.com}
# Staging image tag
IMAGE_TAG=${STAGING_IMAGE_TAG:-staging}
# Enable monitoring but with relaxed settings
ENABLE_METRICS=true
ENABLE_TRACING=false
EOF
mv "$temp_file" "$config_file"
print_success "Generated staging config: $config_file"
}
# Generate generic environment configuration
generate_generic_config() {
local config_file="$1"
local temp_file=$(mktemp)
cat > "$temp_file" << EOF
# Generic environment configuration for: $ENVIRONMENT
# Generated on: $(date)
NODE_ENV=${ENVIRONMENT}
LOG_LEVEL=${LOG_LEVEL:-info}
DEBUG=${DEBUG:-false}
# Image tag for this environment
IMAGE_TAG=${ENVIRONMENT}
EOF
mv "$temp_file" "$config_file"
print_success "Generated generic config: $config_file"
}
# Generate secrets template (not actual secrets)
generate_secrets_template() {
local secrets_file="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT/secrets.env.template"
local temp_file=$(mktemp)
print_status "Generating secrets template..."
cat > "$temp_file" << EOF
# Secrets template for $ENVIRONMENT environment
# Copy this to secrets.env and fill in actual values
# DO NOT commit secrets.env to version control
# Database credentials
COUCHDB_USERNAME=${COUCHDB_USERNAME:-admin}
COUCHDB_PASSWORD=CHANGE_ME_IN_${ENVIRONMENT^^}
# Registry credentials (if using private registry)
REGISTRY_USERNAME=${REGISTRY_USERNAME:-}
REGISTRY_PASSWORD=CHANGE_ME
REGISTRY_EMAIL=${REGISTRY_EMAIL:-}
# TLS/SSL certificates (base64 encoded)
TLS_CERT=CHANGE_ME
TLS_KEY=CHANGE_ME
# API keys and tokens
API_SECRET_KEY=CHANGE_ME
JWT_SECRET=CHANGE_ME
# External service credentials
MONITORING_API_KEY=CHANGE_ME
SMTP_PASSWORD=CHANGE_ME
EOF
mv "$temp_file" "$secrets_file"
print_success "Generated secrets template: $secrets_file"
print_warning "Remember to create actual secrets.env file and add it to .gitignore!"
}
# Validate generated configuration
validate_config() {
local config_file="$PROJECT_ROOT/k8s-kustomize/base/config.env"
print_status "Validating generated configuration..."
if [[ ! -f "$config_file" ]]; then
print_error "Config file not found: $config_file"
return 1
fi
# Check for required variables
local required_vars=("APP_NAME" "NODE_ENV" "REACT_APP_API_URL")
local missing_vars=()
for var in "${required_vars[@]}"; do
if ! grep -q "^${var}=" "$config_file"; then
missing_vars+=("$var")
fi
done
if [[ ${#missing_vars[@]} -gt 0 ]]; then
print_error "Missing required variables: ${missing_vars[*]}"
return 1
fi
print_success "Configuration validation passed!"
}
# Display usage information
show_usage() {
cat << EOF
Usage: $0 [environment] [options]
ENVIRONMENTS:
dev, development Generate development configuration
prod, production Generate production configuration
staging Generate staging configuration
<custom> Generate configuration for custom environment
OPTIONS:
-h, --help Show this help message
-v, --validate Only validate existing configuration
--secrets Generate secrets template
--dry-run Show what would be generated without writing files
EXAMPLES:
$0 dev Generate development configuration
$0 prod --secrets Generate production config and secrets template
$0 --validate Validate existing configuration
ENVIRONMENT VARIABLES:
The script will load variables from:
- ~/.env (global user environment)
- ./.env (project environment)
- ./.env.\$ENVIRONMENT (environment-specific)
- ./.env.local (local overrides)
EOF
}
# Main execution
main() {
local validate_only=false
local generate_secrets=false
local dry_run=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-v|--validate)
validate_only=true
shift
;;
--secrets)
generate_secrets=true
shift
;;
--dry-run)
dry_run=true
shift
;;
-*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
*)
ENVIRONMENT="$1"
shift
;;
esac
done
print_status "Kustomize Config Generator"
print_status "Environment: $ENVIRONMENT"
if [[ "$validate_only" == "true" ]]; then
validate_config
exit $?
fi
if [[ "$dry_run" == "true" ]]; then
print_status "DRY RUN MODE - No files will be written"
# Set dry run flag for other functions to check
export DRY_RUN=true
fi
# Load environment variables
load_env_files
# Generate configurations
generate_base_config
generate_environment_config
if [[ "$generate_secrets" == "true" ]]; then
generate_secrets_template
fi
# Validate generated configuration
validate_config
print_success "Configuration generation completed!"
print_status "Next steps:"
echo " 1. Review generated files in k8s-kustomize/"
echo " 2. Update any environment-specific values"
echo " 3. Create secrets.env files for sensitive data"
echo " 4. Test with: make kustomize-dry-run-$ENVIRONMENT"
}
# Run main function with all arguments
main "$@"
-612
View File
@@ -1,612 +0,0 @@
#!/usr/bin/env bun
/**
* Generate Configuration Files from Unified Config
*
* This script reads the unified configuration and generates all necessary
* config files for different environments and deployment targets.
*
* Usage:
* bun scripts/generate-unified-config.ts [environment]
* bun scripts/generate-unified-config.ts production
* bun scripts/generate-unified-config.ts --all
*/
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import {
createUnifiedConfigForEnvironment,
exportAsEnvVars,
type Environment,
type UnifiedConfig,
} from '../config/unified.config';
interface GeneratorOptions {
environment?: Environment;
outputDir?: string;
generateAll?: boolean;
dryRun?: boolean;
verbose?: boolean;
}
class ConfigGenerator {
private options: GeneratorOptions;
private projectRoot: string;
constructor(options: GeneratorOptions = {}) {
this.options = options;
this.projectRoot = join(__dirname, '..');
}
/**
* Generate all configuration files
*/
async generate(): Promise<void> {
const environments: Environment[] = this.options.generateAll
? ['development', 'staging', 'production']
: [this.options.environment || this.getCurrentEnvironment()];
console.warn('🔧 Generating unified configuration files...');
console.warn(`📁 Project root: ${this.projectRoot}`);
console.warn(`🌍 Environments: ${environments.join(', ')}`);
for (const env of environments) {
await this.generateForEnvironment(env);
}
console.warn('✅ Configuration generation completed!');
}
/**
* Generate configuration files for a specific environment
*/
private async generateForEnvironment(
environment: Environment
): Promise<void> {
console.warn(`\n🔄 Generating configuration for: ${environment}`);
// Create config instance for specific environment
const config = createUnifiedConfigForEnvironment(environment);
const envVars = exportAsEnvVars(config);
// Generate different config file formats
await Promise.all([
this.generateDotEnv(environment, envVars),
this.generateKubernetesConfig(environment, config, envVars),
this.generateDockerEnv(environment, envVars),
this.generateViteEnv(environment, envVars),
this.generateTypeScriptConfig(environment, config),
]);
console.warn(`✅ Generated configuration for ${environment}`);
}
/**
* Generate .env file
*/
private async generateDotEnv(
environment: Environment,
envVars: Record<string, string>
): Promise<void> {
const content = this.createEnvFileContent(environment, envVars);
const filePath = join(this.projectRoot, `.env.${environment}`);
this.writeFile(filePath, content, `📄 .env.${environment}`);
}
/**
* Generate Kubernetes config.env file
*/
private async generateKubernetesConfig(
environment: Environment,
config: UnifiedConfig,
envVars: Record<string, string>
): Promise<void> {
const overlayDir = join(
this.projectRoot,
'k8s-kustomize',
'overlays',
environment
);
this.ensureDirectoryExists(overlayDir);
// Generate config.env for kustomize
const configContent = this.createKubernetesConfigContent(
environment,
envVars
);
const configPath = join(overlayDir, 'config.env');
this.writeFile(
configPath,
configContent,
`🚢 Kubernetes config.env (${environment})`
);
// Generate kustomization.yaml updates
await this.updateKustomizationFile(environment, config);
}
/**
* Generate Docker environment file
*/
private async generateDockerEnv(
environment: Environment,
envVars: Record<string, string>
): Promise<void> {
const dockerDir = join(this.projectRoot, 'docker');
this.ensureDirectoryExists(dockerDir);
const content = this.createDockerEnvContent(environment, envVars);
const filePath = join(dockerDir, `.env.${environment}`);
this.writeFile(filePath, content, `🐳 Docker .env.${environment}`);
}
/**
* Generate Vite-specific environment file
*/
private async generateViteEnv(
environment: Environment,
envVars: Record<string, string>
): Promise<void> {
const viteVars = Object.entries(envVars)
.filter(([key]) => key.startsWith('VITE_') || this.isViteRelevant(key))
.reduce(
(acc, [key, value]) => {
acc[key.startsWith('VITE_') ? key : `VITE_${key}`] = value;
return acc;
},
{} as Record<string, string>
);
const content = this.createViteEnvContent(environment, viteVars);
const filePath = join(this.projectRoot, `.env.vite.${environment}`);
this.writeFile(filePath, content, `⚡ Vite .env.vite.${environment}`);
}
/**
* Generate TypeScript config export
*/
private async generateTypeScriptConfig(
environment: Environment,
config: UnifiedConfig
): Promise<void> {
const configDir = join(this.projectRoot, 'config', 'generated');
this.ensureDirectoryExists(configDir);
const content = this.createTypeScriptConfigContent(environment, config);
const filePath = join(configDir, `${environment}.config.ts`);
this.writeFile(filePath, content, `📝 TypeScript ${environment}.config.ts`);
}
/**
* Create .env file content
*/
private createEnvFileContent(
environment: Environment,
envVars: Record<string, string>
): string {
const timestamp = new Date().toISOString();
return `# ${environment.toUpperCase()} Environment Configuration
# Generated automatically from unified configuration
# Generated on: ${timestamp}
#
# This file was auto-generated. Do not edit manually.
# To make changes, update config/unified.config.ts and regenerate.
${Object.entries(envVars)
.map(([key, value]) => `${key}=${value}`)
.join('\n')}
# Additional environment-specific variables can be added below
# (These will not be overwritten during regeneration)
`;
}
/**
* Create Kubernetes config.env content
*/
private createKubernetesConfigContent(
environment: Environment,
envVars: Record<string, string>
): string {
const timestamp = new Date().toISOString();
// Filter to Kubernetes-relevant variables
const k8sVars = Object.entries(envVars)
.filter(
([key]) => !key.startsWith('VITE_') && this.isKubernetesRelevant(key)
)
.reduce(
(acc, [key, value]) => {
acc[key] = value;
return acc;
},
{} as Record<string, string>
);
return `# Kubernetes configuration for ${environment}
# Generated automatically from unified configuration
# Generated on: ${timestamp}
${Object.entries(k8sVars)
.map(([key, value]) => `${key}=${value}`)
.join('\n')}
`;
}
/**
* Create Docker environment content
*/
private createDockerEnvContent(
environment: Environment,
envVars: Record<string, string>
): string {
const timestamp = new Date().toISOString();
return `# Docker environment for ${environment}
# Generated on: ${timestamp}
# Container Configuration
DOCKER_IMAGE=${envVars.DOCKER_IMAGE}
CONTAINER_REGISTRY=${envVars.CONTAINER_REGISTRY}
CONTAINER_REPOSITORY=${envVars.CONTAINER_REPOSITORY}
CONTAINER_TAG=${envVars.CONTAINER_TAG}
# Application Configuration
APP_NAME=${envVars.APP_NAME}
NODE_ENV=${envVars.NODE_ENV}
PORT=${envVars.PORT}
# Database Configuration
COUCHDB_URL=${envVars.COUCHDB_URL}
COUCHDB_USER=${envVars.COUCHDB_USER}
COUCHDB_PASSWORD=${envVars.COUCHDB_PASSWORD}
`;
}
/**
* Create Vite environment content
*/
private createViteEnvContent(
environment: Environment,
viteVars: Record<string, string>
): string {
const timestamp = new Date().toISOString();
return `# Vite environment variables for ${environment}
# Generated on: ${timestamp}
#
# These variables are available in the frontend build process
${Object.entries(viteVars)
.map(([key, value]) => `${key}=${value}`)
.join('\n')}
`;
}
/**
* Create TypeScript config content
*/
private createTypeScriptConfigContent(
environment: Environment,
config: UnifiedConfig
): string {
const timestamp = new Date().toISOString();
return `/**
* Generated configuration for ${environment}
* Generated on: ${timestamp}
*
* This file exports the resolved configuration for the ${environment} environment.
* It can be imported by other TypeScript files for type-safe configuration access.
*/
import type { UnifiedConfig } from '../unified.config';
export const ${environment}Config: UnifiedConfig = ${JSON.stringify(config, null, 2)} as const;
export default ${environment}Config;
`;
}
/**
* Update kustomization.yaml file with current configuration
*/
private async updateKustomizationFile(
environment: Environment,
config: UnifiedConfig
): Promise<void> {
const overlayDir = join(
this.projectRoot,
'k8s-kustomize',
'overlays',
environment
);
const kustomizationPath = join(overlayDir, 'kustomization.yaml');
if (!existsSync(kustomizationPath)) {
// Create new kustomization.yaml
const content = this.createKustomizationContent(environment, config);
this.writeFile(
kustomizationPath,
content,
`🎛️ kustomization.yaml (${environment})`
);
} else {
console.warn(
` ️ Kustomization file exists: ${kustomizationPath} (not overwriting)`
);
}
}
/**
* Create kustomization.yaml content
*/
private createKustomizationContent(
environment: Environment,
config: UnifiedConfig
): string {
return `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: ${config.app.name}-${environment}
# Reference the base configuration
resources:
- ../../base
- namespace.yaml
# Override namespace for ${environment}
namespace: ${config.kubernetes.namespace}
# ${environment.charAt(0).toUpperCase() + environment.slice(1)}-specific labels
labels:
- pairs:
environment: ${environment}
tier: ${environment === 'production' ? 'prod' : environment}
# ${environment.charAt(0).toUpperCase() + environment.slice(1)} image tags and configurations
images:
- name: frontend-image
newName: ${config.container.registry}/${config.container.repository}
newTag: ${config.container.tag}
- name: couchdb-image
newName: couchdb
newTag: 3.3.2
# ${environment.charAt(0).toUpperCase() + environment.slice(1)} replicas
replicas:
- name: ${config.app.name}-frontend
count: ${config.kubernetes.replicas.frontend}
- name: ${config.app.name}-couchdb
count: ${config.kubernetes.replicas.database}
# Environment-specific patches
patches:
# Resource limits
- target:
kind: Deployment
name: ${config.app.name}-frontend
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources
value:
requests:
memory: "${config.kubernetes.resources.frontend.requests.memory}"
cpu: "${config.kubernetes.resources.frontend.requests.cpu}"
limits:
memory: "${config.kubernetes.resources.frontend.limits.memory}"
cpu: "${config.kubernetes.resources.frontend.limits.cpu}"
- target:
kind: StatefulSet
name: ${config.app.name}-couchdb
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources
value:
requests:
memory: "${config.kubernetes.resources.database.requests.memory}"
cpu: "${config.kubernetes.resources.database.requests.cpu}"
limits:
memory: "${config.kubernetes.resources.database.limits.memory}"
cpu: "${config.kubernetes.resources.database.limits.cpu}"
# ConfigMap generation
configMapGenerator:
- name: ${config.app.name}-config
envs:
- config.env
behavior: create
`;
}
/**
* Check if a variable is relevant for Vite
*/
private isViteRelevant(key: string): boolean {
const viteRelevantKeys = [
'APP_NAME',
'APP_VERSION',
'APP_BASE_URL',
'COUCHDB_URL',
'COUCHDB_USER',
'COUCHDB_PASSWORD',
'MAILGUN_API_KEY',
'MAILGUN_DOMAIN',
'MAILGUN_FROM_NAME',
'MAILGUN_FROM_EMAIL',
'GOOGLE_CLIENT_ID',
'GITHUB_CLIENT_ID',
'ENABLE_EMAIL_VERIFICATION',
'ENABLE_OAUTH',
'ENABLE_ADMIN_INTERFACE',
'DEBUG_MODE',
];
return viteRelevantKeys.includes(key);
}
/**
* Check if a variable is relevant for Kubernetes
*/
private isKubernetesRelevant(key: string): boolean {
const k8sIrrelevantKeys = ['VITE_', 'CONTAINER_', 'DOCKER_'];
return !k8sIrrelevantKeys.some(prefix => key.startsWith(prefix));
}
/**
* Get current environment from NODE_ENV
*/
private getCurrentEnvironment(): Environment {
const env = process.env.NODE_ENV || 'development';
return ['development', 'staging', 'production', 'test'].includes(env)
? (env as Environment)
: 'development';
}
/**
* Ensure directory exists
*/
private ensureDirectoryExists(dirPath: string): void {
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
if (this.options.verbose) {
console.warn(` 📁 Created directory: ${dirPath}`);
}
}
}
/**
* Write file with logging
*/
private writeFile(
filePath: string,
content: string,
description: string
): void {
if (this.options.dryRun) {
console.warn(` 🔍 [DRY RUN] Would write: ${description}`);
console.warn(` Path: ${filePath}`);
return;
}
// Ensure directory exists
this.ensureDirectoryExists(dirname(filePath));
writeFileSync(filePath, content, 'utf8');
console.warn(` ✅ Generated: ${description}`);
if (this.options.verbose) {
console.warn(` Path: ${filePath}`);
console.warn(` Size: ${content.length} bytes`);
}
}
}
/**
* CLI Interface
*/
async function main() {
const args = process.argv.slice(2);
const options: GeneratorOptions = {};
// Parse command line arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case '--all':
options.generateAll = true;
break;
case '--dry-run':
options.dryRun = true;
break;
case '--verbose':
case '-v':
options.verbose = true;
break;
case '--help':
case '-h':
showHelp();
process.exit(0);
break;
default:
if (!arg.startsWith('--') && !options.environment) {
options.environment = arg as Environment;
}
break;
}
}
// Validate environment
if (
options.environment &&
!['development', 'staging', 'production', 'test'].includes(
options.environment
)
) {
console.error(`❌ Invalid environment: ${options.environment}`);
console.error(
' Valid environments: development, staging, production, test'
);
process.exit(1);
}
try {
const generator = new ConfigGenerator(options);
await generator.generate();
} catch (error) {
console.error('❌ Configuration generation failed:', error);
process.exit(1);
}
}
/**
* Show help message
*/
function showHelp() {
console.warn(`
🔧 Unified Configuration Generator
USAGE:
bun scripts/generate-unified-config.ts [environment] [options]
ARGUMENTS:
environment Target environment (development, staging, production, test)
OPTIONS:
--all Generate configuration for all environments
--dry-run Show what would be generated without writing files
--verbose, -v Show detailed output
--help, -h Show this help message
EXAMPLES:
bun scripts/generate-unified-config.ts development
bun scripts/generate-unified-config.ts production --verbose
bun scripts/generate-unified-config.ts --all
bun scripts/generate-unified-config.ts --dry-run
GENERATED FILES:
📄 .env.[environment] - Environment variables
🚢 k8s-kustomize/overlays/[env]/config.env - Kubernetes configuration
🐳 docker/.env.[environment] - Docker environment
⚡ .env.vite.[environment] - Vite-specific variables
📝 config/generated/[env].config.ts - TypeScript config export
The generator reads from config/unified.config.ts and creates all necessary
configuration files for the specified environment(s).
`);
}
// Run CLI if this file is executed directly
if (import.meta.main) {
main().catch(console.error);
}
export { ConfigGenerator, type GeneratorOptions };
-382
View File
@@ -1,382 +0,0 @@
#!/bin/bash
# 🧪 Deployment Validation Script
# Validates complete deployment with all environment variables and health checks
set -e
echo "🚀 Starting deploymif docker compose -f docker/docker-compose.yaml -p rxminder-validation ps | grep -q "Up"; then
print_success "Docker Compose setup completed successfully!"
else
print_error "Docker Compose services failed to start"
docker compose -f docker/docker-compose.yaml -p rxminder-validation logsalidation..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function
cleanup() {
print_status "Cleaning up test containers..."
docker stop rxminder-validation-test 2>/dev/null || true
docker rm rxminder-validation-test 2>/dev/null || true
docker compose -f docker/docker-compose.yaml -p rxminder-validation down 2>/dev/null || true
}
# Set trap for cleanup
trap cleanup EXIT
print_status "1. Validating environment files..."
# Check if required environment files exist
if [[ ! -f .env ]]; then
print_error ".env file not found. Run 'cp .env.example .env' and configure it."
exit 1
fi
if [[ ! -f .env.example ]]; then
print_error ".env.example file not found."
exit 1
fi
print_success "Environment files exist"
# Validate environment consistency
print_status "2. Checking environment variable consistency..."
./validate-env.sh
print_status "3. Setting up Docker Buildx..."
# Ensure buildx is available
if ! docker buildx version >/dev/null 2>&1; then
print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx."
exit 1
fi
# Create a new builder instance if it doesn't exist
if ! docker buildx ls | grep -q "rxminder-builder"; then
print_status "Creating new buildx builder instance..."
docker buildx create --name rxminder-builder --driver docker-container --bootstrap
fi
# Use the builder
docker buildx use rxminder-builder
print_status "4. Building multi-platform Docker image with buildx..."
# Build the image with buildx for multiple platforms
docker buildx build --no-cache \
--platform linux/amd64,linux/arm64 \
--build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \
--build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg VITE_COUCHDB_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \
--build-arg VITE_COUCHDB_USER="${VITE_COUCHDB_USER:-admin}" \
--build-arg VITE_COUCHDB_PASSWORD="${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg APP_BASE_URL="${APP_BASE_URL:-http://localhost:8080}" \
--build-arg VITE_GOOGLE_CLIENT_ID="${VITE_GOOGLE_CLIENT_ID:-}" \
--build-arg VITE_GITHUB_CLIENT_ID="${VITE_GITHUB_CLIENT_ID:-}" \
--build-arg MAILGUN_API_KEY="${MAILGUN_API_KEY:-}" \
--build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \
--build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \
--build-arg NODE_ENV="${NODE_ENV:-production}" \
-t rxminder-validation \
--load \
.
print_success "Docker image built successfully"
print_status "5. Testing container startup and health..."
# Run container in background
docker run --rm -d \
-p 8083:80 \
--name rxminder-validation-test \
rxminder-validation
# Wait for container to start
sleep 5
# Check if container is running
if ! docker ps | grep -q rxminder-validation-test; then
print_error "Container failed to start"
docker logs rxminder-validation-test
exit 1
fi
print_success "Container started successfully"
# Test health endpoint
print_status "5. Testing health endpoint..."
for i in {1..10}; do
if curl -s -f http://localhost:8083/health > /dev/null; then
print_success "Health endpoint responding"
break
elif [[ $i -eq 10 ]]; then
print_error "Health endpoint not responding after 10 attempts"
exit 1
else
print_warning "Health endpoint not ready, retrying... ($i/10)"
sleep 2
fi
done
# Test main application
print_status "6. Testing main application..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083)
if [[ $HTTP_CODE -eq 200 ]]; then
print_success "Main application responding (HTTP $HTTP_CODE)"
else
print_error "Main application not responding properly (HTTP $HTTP_CODE)"
exit 1
fi
# Test docker-compose build
print_status "7. Testing Docker Compose build..."
docker compose -f docker/docker-compose.yaml build frontend --no-cache
print_success "Docker Compose build successful"
# Test docker-compose with validation project name
print_status "8. Testing Docker Compose deployment..."
docker compose -f docker/docker-compose.yaml -p rxminder-validation up -d --build
# Wait for services to start
sleep 10
# Check service health
if docker compose -f docker/docker-compose.yaml -p meds-validation ps | grep -q "Up"; then
print_success "Docker Compose services started successfully"
else
print_error "Docker Compose services failed to start"
docker compose -f docker/docker-compose.yaml -p meds-validation logs
exit 1
fi
# Test health of compose deployment
if curl -s -f http://localhost:8080/health > /dev/null; then
print_success "Docker Compose health endpoint responding"
else
print_warning "Docker Compose health endpoint not responding (may need CouchDB)"
fi
print_status "9. Checking image size..."
IMAGE_SIZE=$(docker image inspect rxminder-validation --format='{{.Size}}' | numfmt --to=iec)
print_success "Image size: $IMAGE_SIZE"
print_status "10. Validating security configuration..."
# Check if image runs as non-root
USER_INFO=$(docker run --rm rxminder-validation whoami)
if [[ "$USER_INFO" != "root" ]]; then
print_success "Container runs as non-root user: $USER_INFO"
else
print_warning "Container runs as root user (security consideration)"
fi
# Check nginx configuration
if docker run --rm rxminder-validation nginx -t 2>/dev/null; then
print_success "Nginx configuration is valid"
else
print_error "Nginx configuration has issues"
exit 1
fi
print_status "11. Final validation complete!"
echo
echo "🎉 Deployment validation successful!"
echo
echo "Summary:"
echo "✅ Environment files validated"
echo "✅ Docker image builds successfully"
echo "✅ Container starts and runs healthy"
echo "✅ Health endpoints respond correctly"
echo "✅ Docker Compose deployment works"
echo "✅ Security configuration validated"
echo "✅ Image size optimized ($IMAGE_SIZE)"
echo
echo "Your deployment is ready for production! 🚀"
echo
echo "Next steps:"
echo "1. Configure production environment variables in .env"
echo "2. Run './deploy.sh production' for production deployment"
echo "3. Set up monitoring and backups"
echo "4. Configure SSL/TLS certificates"
echo
# Load Gitea-specific environment variables
REGISTRY=${GITEA_SERVER_URL#https://}
IMAGE_NAME=${GITEA_REPOSITORY}
IMAGE_TAG=${GITEA_SHA:0:8}
print_status "Registry: $REGISTRY"
print_status "Image: $IMAGE_NAME:$IMAGE_TAG"
fi
# Check if .env file exists
if [ ! -f ".env" ]; then
print_warning ".env file not found, using defaults"
# Create minimal .env for Gitea deployment
cat > .env << EOF
COUCHDB_USER=admin
COUCHDB_PASSWORD=change-this-secure-password
VITE_COUCHDB_URL=http://couchdb:5984
VITE_COUCHDB_USER=admin
VITE_COUCHDB_PASSWORD=change-this-secure-password
APP_BASE_URL=http://localhost:8080
NODE_ENV=production
EOF
print_warning "Created default .env file - please update with your credentials"
fi
# Load environment variables
print_status "Loading environment variables from .env file..."
export $(cat .env | grep -v '^#' | xargs)
# Validate required environment variables
REQUIRED_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "VITE_COUCHDB_URL")
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
print_error "Required environment variable $var is not set"
exit 1
fi
done
print_success "Environment variables validated"
# Function to deploy via Docker Compose
deploy_compose() {
print_status "Deploying with Docker Compose..."
# Export image variables for compose
export IMAGE_TAG
export REGISTRY
export IMAGE_NAME
# Use the built image from registry if available
if [ "$GITEA_ACTIONS" = "true" ]; then
# Override the image in docker-compose
export FRONTEND_IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
print_status "Using Gitea Actions built image: $FRONTEND_IMAGE"
fi
# Pull the latest images
print_status "Pulling latest images..."
docker-compose -f docker/docker-compose.yaml pull || print_warning "Failed to pull some images"
# Start services
print_status "Starting services..."
docker-compose -f docker/docker-compose.yaml up -d
# Wait for services
print_status "Waiting for services to be ready..."
sleep 10
# Health check
print_status "Checking service health..."
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
print_success "Frontend service is healthy"
else
print_warning "Frontend health check failed, checking logs..."
docker-compose -f docker/docker-compose.yaml logs frontend
fi
if curl -f http://localhost:5984/_up > /dev/null 2>&1; then
print_success "CouchDB service is healthy"
else
print_warning "CouchDB health check failed"
fi
}
# Function to deploy via Kubernetes
deploy_k8s() {
print_status "Deploying to Kubernetes..."
if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed"
exit 1
fi
# Update image in k8s manifests
if [ "$GITEA_ACTIONS" = "true" ]; then
print_status "Updating Kubernetes manifests with new image..."
sed -i "s|image:.*rxminder.*|image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG|g" k8s/frontend-deployment.yaml
fi
# Apply manifests
print_status "Applying Kubernetes manifests..."
kubectl apply -f k8s/
# Wait for rollout
print_status "Waiting for deployment rollout..."
kubectl rollout status deployment/frontend-deployment
print_success "Kubernetes deployment completed"
}
# Main deployment logic
case "$ENVIRONMENT" in
"production"|"prod")
print_status "Deploying to production environment"
deploy_compose
;;
"kubernetes"|"k8s")
print_status "Deploying to Kubernetes environment"
deploy_k8s
;;
"staging")
print_status "Deploying to staging environment"
# Use staging-specific configurations
export APP_BASE_URL="http://staging.localhost:8080"
deploy_compose
;;
*)
print_error "Unknown environment: $ENVIRONMENT"
echo "Available environments: production, kubernetes, staging"
exit 1
;;
esac
print_success "Deployment to $ENVIRONMENT completed successfully! 🎉"
# Post-deployment tasks
print_status "Running post-deployment tasks..."
# Cleanup old images (optional)
if [ "$CLEANUP_OLD_IMAGES" = "true" ]; then
print_status "Cleaning up old Docker images..."
docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed"
fi
# Send notification (if configured)
if [ -n "$DEPLOYMENT_WEBHOOK_URL" ]; then
print_status "Sending deployment notification..."
curl -X POST "$DEPLOYMENT_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"text\":\"✅ RxMinder deployed to $ENVIRONMENT\", \"environment\":\"$ENVIRONMENT\", \"image\":\"$REGISTRY/$IMAGE_NAME:$IMAGE_TAG\"}" \
|| print_warning "Failed to send notification"
fi
print_success "All tasks completed! 🚀"
-518
View File
@@ -1,518 +0,0 @@
#!/bin/bash
# gitea-deploy.sh - Gitea-specific deployment script
# Usage: ./gitea-deploy.sh [environment] [image-tag]
set -e # Exit on any error
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# Load environment variables from .env file if it exists
if [ -f "$PROJECT_DIR/.env" ]; then
export $(cat "$PROJECT_DIR/.env" | grep -v '^#' | grep -E '^[A-Z_]+=.*' | xargs)
fi
ENVIRONMENT=${1:-production}
IMAGE_TAG=${2:-latest}
REGISTRY=${GITEA_REGISTRY:-${CONTAINER_REGISTRY:-"ghcr.io"}}
IMAGE_NAME=${GITEA_REPOSITORY:-${CONTAINER_REPOSITORY:-"rxminder"}}
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_status() {
echo -e "${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
echo "🚀 Deploying RxMinder from Gitea to $ENVIRONMENT environment..."
# Check if running in Gitea Actions
if [ "$GITEA_ACTIONS" = "true" ]; then
print_status "Running in Gitea Actions environment"
# Load Gitea-specific environment variables
REGISTRY=${GITEA_SERVER_URL#https://}
IMAGE_NAME=${GITEA_REPOSITORY}
IMAGE_TAG=${GITEA_SHA:0:8}
print_status "Registry: $REGISTRY"
print_status "Image: $IMAGE_NAME:$IMAGE_TAG"
fi
# Check if .env file exists
if [ ! -f ".env" ]; then
print_warning ".env file not found, using defaults"
# Create minimal .env for Gitea deployment
cat > .env << EOF
COUCHDB_USER=admin
COUCHDB_PASSWORD=change-this-secure-password
VITE_COUCHDB_URL=http://couchdb:5984
VITE_COUCHDB_USER=admin
VITE_COUCHDB_PASSWORD=change-this-secure-password
APP_BASE_URL=http://localhost:8080
NODE_ENV=production
EOF
print_warning "Created default .env file - please update with your credentials"
fi
# Load environment variables
print_status "Loading environment variables from .env file..."
export $(cat .env | grep -v '^#' | xargs)
# Validate required environment variables
REQUIRED_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "VITE_COUCHDB_URL")
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
print_error "Required environment variable $var is not set"
exit 1
fi
done
print_success "Environment variables validated"
# Function to deploy via Docker Compose
deploy_compose() {
print_status "Deploying with Docker Compose..."
# Export image variables for compose
export IMAGE_TAG
export REGISTRY
export IMAGE_NAME
# Use the built image from registry if available
if [ "$GITEA_ACTIONS" = "true" ]; then
# Override the image in docker-compose
export FRONTEND_IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
print_status "Using Gitea Actions built image: $FRONTEND_IMAGE"
fi
# Pull the latest images
print_status "Pulling latest images..."
docker-compose -f docker/docker-compose.yaml pull || print_warning "Failed to pull some images"
# Start services
print_status "Starting services..."
docker-compose -f docker/docker-compose.yaml up -d
# Wait for services
print_status "Waiting for services to be ready..."
sleep 10
# Health check
print_status "Checking service health..."
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
print_success "Frontend service is healthy"
else
print_warning "Frontend health check failed, checking logs..."
docker-compose -f docker/docker-compose.yaml logs frontend
fi
if curl -f http://localhost:5984/_up > /dev/null 2>&1; then
print_success "CouchDB service is healthy"
else
print_warning "CouchDB health check failed"
fi
}
# Function to deploy via Kubernetes
deploy_k8s() {
print_status "Deploying to Kubernetes..."
if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed"
exit 1
fi
# Update image in k8s manifests
if [ "$GITEA_ACTIONS" = "true" ]; then
print_status "Updating Kubernetes manifests with new image..."
sed -i "s|image:.*rxminder.*|image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG|g" k8s/frontend-deployment.yaml
fi
# Apply manifests
print_status "Applying Kubernetes manifests..."
kubectl apply -f k8s/
# Wait for rollout
print_status "Waiting for deployment rollout..."
kubectl rollout status deployment/frontend-deployment
print_success "Kubernetes deployment completed"
}
# Main deployment logic
case "$ENVIRONMENT" in
"production"|"prod")
print_status "Deploying to production environment"
deploy_compose
;;
"kubernetes"|"k8s")
print_status "Deploying to Kubernetes environment"
deploy_k8s
;;
"staging")
print_status "Deploying to staging environment"
# Use staging-specific configurations
export APP_BASE_URL="http://staging.localhost:8080"
deploy_compose
;;
*)
print_error "Unknown environment: $ENVIRONMENT"
echo "Available environments: production, kubernetes, staging"
exit 1
;;
esac
print_success "Deployment to $ENVIRONMENT completed successfully! 🎉"
# Post-deployment tasks
print_status "Running post-deployment tasks..."
# Cleanup old images (optional)
if [ "$CLEANUP_OLD_IMAGES" = "true" ]; then
print_status "Cleaning up old Docker images..."
docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed"
fi
# Send notification (if configured)
if [ -n "$DEPLOYMENT_WEBHOOK_URL" ]; then
print_status "Sending deployment notification..."
curl -X POST "$DEPLOYMENT_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"text\":\"✅ RxMinder deployed to $ENVIRONMENT\", \"environment\":\"$ENVIRONMENT\", \"image\":\"$REGISTRY/$IMAGE_NAME:$IMAGE_TAG\"}" \
|| print_warning "Failed to send notification"
fi
print_success "All tasks completed! 🚀"
print_error "Docker is not installed"
exit 1
fi
# Check Docker Buildx
if ! docker buildx version >/dev/null 2>&1; then
print_error "Docker Buildx is not available"
exit 1
fi
# Check if in Gitea Actions environment
if [ "$GITEA_ACTIONS" = "true" ]; then
print_status "Running in Gitea Actions environment"
GITEA_REGISTRY=${GITEA_SERVER_URL#https://}
GITEA_REPOSITORY=${GITEA_REPOSITORY}
fi
print_success "All requirements met"
}
setup_buildx() {
print_status "Setting up Docker Buildx for Gitea..."
# Create builder if it doesn't exist
if ! docker buildx ls | grep -q "gitea-builder"; then
print_status "Creating Gitea buildx builder..."
docker buildx create \
--name gitea-builder \
--driver docker-container \
--bootstrap \
--use
print_success "Gitea builder created"
else
docker buildx use gitea-builder
print_success "Using existing Gitea builder"
fi
}
login_registry() {
print_status "Logging into Gitea registry..."
if [ -z "$GITEA_TOKEN" ]; then
print_error "GITEA_TOKEN environment variable is required"
print_status "Set it with: export GITEA_TOKEN=your_token"
exit 1
fi
# Login to Gitea registry
echo "$GITEA_TOKEN" | docker login "$GITEA_REGISTRY" -u "$GITEA_ACTOR" --password-stdin
print_success "Logged into Gitea registry"
}
build_local() {
print_status "Building for local development..."
cd "$PROJECT_DIR"
# Load environment variables
if [ -f ".env" ]; then
export $(cat .env | grep -v '^#' | xargs)
fi
# Build with Gitea bake file
docker buildx bake \
-f .gitea/gitea-bake.hcl \
--set="*.platform=linux/amd64" \
--load \
dev
print_success "Local build completed"
}
build_multiplatform() {
local tag=${1:-$DEFAULT_TAG}
print_status "Building multi-platform image with tag: $tag..."
cd "$PROJECT_DIR"
# Load environment variables
if [ -f ".env" ]; then
export $(cat .env | grep -v '^#' | xargs)
fi
# Export variables for bake
export TAG="$tag"
export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "dev")}
# Build with Gitea bake file
docker buildx bake \
-f .gitea/gitea-bake.hcl \
app-ci
print_success "Multi-platform build completed"
}
build_staging() {
print_status "Building staging image..."
cd "$PROJECT_DIR"
# Load environment variables
if [ -f ".env.staging" ]; then
export $(cat .env.staging | grep -v '^#' | xargs)
elif [ -f ".env" ]; then
export $(cat .env | grep -v '^#' | xargs)
fi
# Export variables for bake
export TAG="staging-$(date +%Y%m%d-%H%M%S)"
export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "staging")}
# Build staging target
docker buildx bake \
-f .gitea/gitea-bake.hcl \
staging
print_success "Staging build completed"
}
build_production() {
local tag=${1:-$DEFAULT_TAG}
print_status "Building production image with tag: $tag..."
cd "$PROJECT_DIR"
# Load production environment variables
if [ -f ".env.production" ]; then
export $(cat .env.production | grep -v '^#' | xargs)
elif [ -f ".env" ]; then
export $(cat .env | grep -v '^#' | xargs)
fi
# Export variables for bake
export TAG="$tag"
export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "prod")}
# Build production target with full attestations
docker buildx bake \
-f .gitea/gitea-bake.hcl \
prod
print_success "Production build completed"
}
test_local() {
print_status "Running tests locally..."
cd "$PROJECT_DIR"
# Install dependencies if needed
if [ ! -d "node_modules" ]; then
print_status "Installing dependencies..."
bun install --frozen-lockfile
fi
# Run linting
print_status "Running linter..."
bun run lint
# Run type checking
print_status "Running type checker..."
bun run type-check
# Run tests
print_status "Running tests..."
bun run test
print_success "All tests passed"
}
deploy() {
local environment=${1:-production}
local tag=${2:-latest}
print_status "Deploying to $environment with tag $tag..."
# Use the gitea-deploy script
"$SCRIPT_DIR/gitea-deploy.sh" "$environment" "$tag"
}
cleanup() {
print_status "Cleaning up Gitea builder and images..."
# Remove builder
if docker buildx ls | grep -q "gitea-builder"; then
docker buildx rm gitea-builder
print_success "Gitea builder removed"
fi
# Clean up old images (keep last 3 tags)
print_status "Cleaning up old images..."
docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed"
print_success "Cleanup completed"
}
show_status() {
print_status "Gitea CI/CD Status"
echo
# Check environment
if [ "$GITEA_ACTIONS" = "true" ]; then
echo "🏃 Running in Gitea Actions"
echo "📦 Registry: $GITEA_REGISTRY"
echo "📋 Repository: $GITEA_REPOSITORY"
echo "🏷️ SHA: ${GITEA_SHA:-unknown}"
echo "🌿 Branch: ${GITEA_REF_NAME:-unknown}"
else
echo "💻 Running locally"
echo "📦 Registry: $GITEA_REGISTRY"
echo "📋 Repository: $GITEA_REPOSITORY"
fi
echo
# Check Docker and buildx
echo "🐳 Docker version: $(docker --version)"
echo "🔧 Buildx version: $(docker buildx version)"
# Check builders
echo
echo "🏗️ Available builders:"
docker buildx ls
}
show_help() {
cat << EOF
Gitea CI/CD Helper for RxMinder
Usage: $0 [command] [options]
Commands:
setup - Setup buildx builder for Gitea
login - Login to Gitea registry
build-local - Build for local development
build-multi [tag] - Build multi-platform image
build-staging - Build staging image
build-prod [tag] - Build production image
test - Run tests locally
deploy [env] [tag] - Deploy to environment
cleanup - Cleanup builders and images
status - Show CI/CD status
help - Show this help
Examples:
$0 setup
$0 build-local
$0 build-multi v1.2.3
$0 build-staging
$0 build-prod v1.2.3
$0 test
$0 deploy production v1.2.3
$0 deploy staging
$0 status
Environment Variables:
GITEA_REGISTRY - Gitea registry URL (default: gitea.example.com)
GITEA_REPOSITORY - Repository name (default: user/rxminder)
GITEA_TOKEN - Gitea access token (required for registry)
GITEA_ACTOR - Gitea username (for registry login)
EOF
}
# Main command handling
case "${1:-help}" in
"setup")
check_requirements
setup_buildx
;;
"login")
check_requirements
login_registry
;;
"build-local")
check_requirements
setup_buildx
build_local
;;
"build-multi")
check_requirements
setup_buildx
login_registry
build_multiplatform "$2"
;;
"build-staging")
check_requirements
setup_buildx
login_registry
build_staging
;;
"build-prod")
check_requirements
setup_buildx
login_registry
build_production "$2"
;;
"test")
test_local
;;
"deploy")
deploy "$2" "$3"
;;
"cleanup")
cleanup
;;
"status")
show_status
;;
"help"|*)
show_help
;;
esac
-330
View File
@@ -1,330 +0,0 @@
#!/usr/bin/env bash
# 🚀 RxMinder Template-based Kubernetes Deployment Script
# This script processes template files and applies them to Kubernetes
set -euo pipefail
# Script configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
K8S_DIR="$(dirname "$SCRIPT_DIR")/k8s"
ENV_FILE="$(dirname "$SCRIPT_DIR")/.env"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_status() {
echo -e "${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Function to load environment variables
load_env() {
if [[ -f "$ENV_FILE" ]]; then
print_status "Loading environment variables from .env..."
# Export variables from .env file
set -a
source "$ENV_FILE"
set +a
print_success "Environment variables loaded"
else
print_warning ".env file not found at $ENV_FILE"
print_warning "Using default values. Copy .env.example to .env and customize."
fi
}
# Function to substitute environment variables in templates
substitute_template() {
local template_file="$1"
local output_file="$2"
print_status "Processing template: $template_file"
# Use envsubst to substitute environment variables
if command -v envsubst >/dev/null 2>&1; then
envsubst < "$template_file" > "$output_file"
else
print_error "envsubst not found. Please install gettext package."
exit 1
fi
print_success "Generated: $output_file"
}
# Function to apply Kubernetes resources
apply_k8s_resource() {
local resource_file="$1"
if [[ -f "$resource_file" ]]; then
print_status "Applying Kubernetes resource: $resource_file"
if kubectl apply -f "$resource_file"; then
print_success "Applied: $resource_file"
else
print_error "Failed to apply: $resource_file"
return 1
fi
else
print_warning "Resource file not found: $resource_file"
fi
}
# Function to validate required environment variables
validate_env() {
local required_vars=(
"APP_NAME"
"DOCKER_IMAGE"
"COUCHDB_USER"
"COUCHDB_PASSWORD"
"INGRESS_HOST"
"STORAGE_CLASS"
"STORAGE_SIZE"
)
local missing_vars=()
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
missing_vars+=("$var")
fi
done
if [[ ${#missing_vars[@]} -gt 0 ]]; then
print_error "Missing required environment variables:"
for var in "${missing_vars[@]}"; do
echo -e " ${RED}- $var${NC}"
done
print_warning "Please update your .env file with these variables."
exit 1
fi
print_success "All required environment variables are set"
}
# Function to process all templates
process_templates() {
local temp_dir="/tmp/rxminder-k8s-$$"
mkdir -p "$temp_dir"
print_status "Processing Kubernetes templates..."
# Find all template files
local template_files=(
"$K8S_DIR/couchdb-secret.yaml.template"
"$K8S_DIR/ingress.yaml.template"
)
# Add any additional template files
for template_file in "$K8S_DIR"/*.template; do
if [[ -f "$template_file" ]]; then
template_files+=("$template_file")
fi
done
# Process each template
for template_file in "${template_files[@]}"; do
if [[ -f "$template_file" ]]; then
local base_name
base_name="$(basename "$template_file" .template)"
local output_file="$temp_dir/$base_name"
substitute_template "$template_file" "$output_file"
fi
done
echo "$temp_dir"
}
# Function to deploy resources in correct order
deploy_resources() {
local resource_dir="$1"
print_status "Deploying Kubernetes resources..."
# Deploy in specific order for dependencies
local deployment_order=(
"couchdb-secret.yaml"
"couchdb-pvc.yaml"
"couchdb-service.yaml"
"couchdb-statefulset.yaml"
"configmap.yaml"
"frontend-deployment.yaml"
"frontend-service.yaml"
"ingress.yaml"
"$K8S_DIR/network-policy.yaml"
"$K8S_DIR/hpa.yaml"
)
for resource in "${deployment_order[@]}"; do
if [[ "$resource" == *.yaml ]]; then
# Check if it's a template-generated file
if [[ -f "$resource_dir/$(basename "$resource")" ]]; then
apply_k8s_resource "$resource_dir/$(basename "$resource")"
else
# Apply directly from k8s directory
apply_k8s_resource "$resource"
fi
fi
done
}
# Function to run database seeding job
run_db_seed() {
print_status "Running database seed job..."
# Apply the db-seed-job (which uses environment variables from secret)
if kubectl apply -f "$K8S_DIR/db-seed-job.yaml"; then
print_success "Database seed job submitted"
# Wait for job completion
print_status "Waiting for database seed job to complete..."
if kubectl wait --for=condition=complete --timeout=300s job/db-seed-job; then
print_success "Database seeding completed successfully"
else
print_warning "Database seed job may have failed. Check logs:"
echo "kubectl logs job/db-seed-job"
fi
else
print_error "Failed to apply database seed job"
return 1
fi
}
# Function to display deployment status
show_status() {
print_status "Deployment Status:"
echo
print_status "Pods:"
kubectl get pods -l app="${APP_NAME:-rxminder}"
echo
print_status "Services:"
kubectl get services -l app="${APP_NAME:-rxminder}"
echo
print_status "Ingress:"
kubectl get ingress
echo
if [[ -n "${INGRESS_HOST:-}" ]]; then
print_success "Application should be available at: http://${INGRESS_HOST}"
fi
}
# Function to cleanup temporary files
cleanup() {
if [[ -n "${temp_dir:-}" && -d "$temp_dir" ]]; then
rm -rf "$temp_dir"
fi
}
# Main deployment function
main() {
local command="${1:-deploy}"
case "$command" in
"deploy"|"apply")
print_status "🚀 Starting RxMinder Kubernetes deployment..."
echo
# Set default values for required variables
export APP_NAME="${APP_NAME:-rxminder}"
export DOCKER_IMAGE="${DOCKER_IMAGE:-gitea-http.taildb3494.ts.net/will/meds:latest}"
export COUCHDB_USER="${COUCHDB_USER:-admin}"
export COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}"
export INGRESS_HOST="${INGRESS_HOST:-rxminder.local}"
export STORAGE_CLASS="${STORAGE_CLASS:-longhorn}"
export STORAGE_SIZE="${STORAGE_SIZE:-5Gi}"
load_env
validate_env
# Process templates
temp_dir=$(process_templates)
trap cleanup EXIT
# Deploy resources
deploy_resources "$temp_dir"
# Run database seeding
run_db_seed
# Show status
echo
show_status
print_success "🎉 RxMinder deployment completed!"
;;
"status")
load_env
show_status
;;
"delete"|"cleanup")
print_status "🗑️ Cleaning up RxMinder deployment..."
kubectl delete all,pvc,secret,configmap,ingress -l app="${APP_NAME:-rxminder}" || true
kubectl delete job db-seed-job || true
print_success "Cleanup completed"
;;
"help"|"-h"|"--help")
echo "RxMinder Kubernetes Deployment Script"
echo
echo "Usage: $0 [command]"
echo
echo "Commands:"
echo " deploy Deploy RxMinder to Kubernetes (default)"
echo " status Show deployment status"
echo " delete Delete all RxMinder resources"
echo " help Show this help message"
echo
echo "Environment variables (set in .env):"
echo " APP_NAME Application name (default: rxminder)"
echo " DOCKER_IMAGE Container image to deploy"
echo " COUCHDB_USER Database username (default: admin)"
echo " COUCHDB_PASSWORD Database password (required)"
echo " INGRESS_HOST Ingress hostname (required)"
echo " STORAGE_CLASS Storage class for PVCs (default: longhorn)"
echo " STORAGE_SIZE Storage size for database (default: 5Gi)"
;;
*)
print_error "Unknown command: $command"
echo "Use '$0 help' for usage information"
exit 1
;;
esac
}
# Check if kubectl is available
if ! command -v kubectl >/dev/null 2>&1; then
print_error "kubectl not found. Please install kubectl and configure it to connect to your cluster."
exit 1
fi
# Check if envsubst is available
if ! command -v envsubst >/dev/null 2>&1; then
print_error "envsubst not found. Please install the gettext package:"
echo " Ubuntu/Debian: sudo apt-get install gettext"
echo " macOS: brew install gettext"
echo " RHEL/CentOS: sudo yum install gettext"
exit 1
fi
# Run main function
main "$@"
-180
View File
@@ -1,180 +0,0 @@
#!/bin/bash
# Enhanced pre-commit checks with parallel execution for speed
# This script runs multiple checks efficiently on staged files only
set -e
echo "🚀 Running pre-commit checks..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[PRE-COMMIT]${NC} $1"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}⚠️${NC} $1"
}
# Check if there are any staged files
STAGED_FILES=$(git diff --cached --name-only)
if [ -z "$STAGED_FILES" ]; then
print_warning "No staged files found"
exit 0
fi
print_status "Found $(echo "$STAGED_FILES" | wc -l) staged files"
# Create temporary directory for parallel job management
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# Function to run a command and capture its output
run_check() {
local name="$1"
local command="$2"
local output_file="$TEMP_DIR/$name.out"
local error_file="$TEMP_DIR/$name.err"
{
echo "Running: $command"
eval "$command" > "$output_file" 2> "$error_file"
echo $? > "$TEMP_DIR/$name.exit"
} &
echo $! > "$TEMP_DIR/$name.pid"
}
# Start parallel checks
print_status "Starting parallel checks..."
# 1. Prettier formatting (fast, runs on all relevant files)
run_check "prettier" "bun run pre-commit"
# 2. Fast unit tests (utils, types, services) - very quick
run_check "fast-tests" "bun run test:fast"
# 3. ESLint on staged JS/TS files only
STAGED_JS_TS_FILES=$(echo "$STAGED_FILES" | grep -E '\.(js|jsx|ts|tsx)$' || true)
if [ -n "$STAGED_JS_TS_FILES" ]; then
# Convert newlines to spaces for proper argument passing
ESLINT_FILES=$(echo "$STAGED_JS_TS_FILES" | tr '\n' ' ')
run_check "eslint" "bunx eslint --fix --max-warnings 0 $ESLINT_FILES"
else
echo "0" > "$TEMP_DIR/eslint.exit"
echo "No JS/TS files to lint" > "$TEMP_DIR/eslint.out"
fi
# 4. TypeScript type checking on staged files
STAGED_TS_FILES=$(echo "$STAGED_FILES" | grep -E '\.(ts|tsx)$' || true)
if [ -n "$STAGED_TS_FILES" ]; then
run_check "typecheck" "./scripts/type-check-staged.sh"
else
echo "0" > "$TEMP_DIR/typecheck.exit"
echo "No TypeScript files to check" > "$TEMP_DIR/typecheck.out"
fi
# 5. Markdown linting on staged markdown files
STAGED_MD_FILES=$(echo "$STAGED_FILES" | grep -E '\.md$' || true)
if [ -n "$STAGED_MD_FILES" ]; then
# Convert newlines to spaces for proper argument passing
MARKDOWN_FILES=$(echo "$STAGED_MD_FILES" | tr '\n' ' ')
run_check "markdown" "bunx markdownlint-cli2 --fix $MARKDOWN_FILES || echo 'Markdown linting failed but continuing...'"
else
echo "0" > "$TEMP_DIR/markdown.exit"
echo "No markdown files to lint" > "$TEMP_DIR/markdown.out"
fi
# 6. Secret scanning on staged files (optional check)
if command -v secretlint > /dev/null; then
# Convert newlines to spaces for proper argument passing
SECRET_FILES=$(echo "$STAGED_FILES" | tr '\n' ' ')
run_check "secrets" "bunx secretlint $SECRET_FILES || echo 'Secret scanning failed but continuing...'"
else
echo "0" > "$TEMP_DIR/secrets.exit"
echo "secretlint not available, skipping secret scanning" > "$TEMP_DIR/secrets.out"
fi
# Wait for all jobs to complete and collect results
print_status "Waiting for checks to complete..."
FAILED_CHECKS=()
ALL_CHECKS=("prettier" "fast-tests" "eslint" "typecheck" "markdown" "secrets")
for check in "${ALL_CHECKS[@]}"; do
if [ -f "$TEMP_DIR/$check.pid" ]; then
wait $(cat "$TEMP_DIR/$check.pid") 2>/dev/null || true
fi
exit_code=$(cat "$TEMP_DIR/$check.exit" 2>/dev/null || echo "1")
if [ "$exit_code" = "0" ]; then
print_success "$check passed"
else
# For some checks, failure is not critical
if [ "$check" = "secrets" ] || [ "$check" = "markdown" ] || [ "$check" = "fast-tests" ]; then
print_warning "$check had issues (non-critical)"
if [ -f "$TEMP_DIR/$check.out" ] && [ -s "$TEMP_DIR/$check.out" ]; then
echo -e "${YELLOW}$check output:${NC}"
cat "$TEMP_DIR/$check.out"
fi
else
print_error "$check failed"
FAILED_CHECKS+=("$check")
# Show error output
if [ -f "$TEMP_DIR/$check.err" ] && [ -s "$TEMP_DIR/$check.err" ]; then
echo -e "${RED}$check errors:${NC}"
cat "$TEMP_DIR/$check.err"
fi
if [ -f "$TEMP_DIR/$check.out" ] && [ -s "$TEMP_DIR/$check.out" ]; then
echo -e "${YELLOW}$check output:${NC}"
cat "$TEMP_DIR/$check.out"
fi
fi
fi
done
# Re-stage any files that were modified by formatters
git add $STAGED_FILES 2>/dev/null || true
# Final result
if [ ${#FAILED_CHECKS[@]} -eq 0 ]; then
print_success "All critical pre-commit checks passed! 🎉"
echo ""
echo "Summary of checks:"
echo " ✅ Code formatting (Prettier)"
echo " ✅ Fast unit tests (Utils/Types/Services)"
echo " ✅ Code linting (ESLint)"
echo " ✅ Type checking (TypeScript)"
echo " ⚠️ Markdown linting (non-critical)"
echo " ⚠️ Secret scanning (optional)"
echo ""
exit 0
else
print_error "The following critical checks failed: ${FAILED_CHECKS[*]}"
echo ""
echo "💡 Tips to fix:"
echo " - Run 'bun run lint:fix' to auto-fix linting issues"
echo " - Run 'bun run format' to fix formatting issues"
echo " - Run 'bun run type-check' to see detailed type errors"
echo ""
echo "🚨 Critical failures prevent commit. Fix these issues first."
echo ""
exit 1
fi
-24
View File
@@ -1,24 +0,0 @@
#!/bin/bash
# Process HTML template with environment variables
# This script substitutes environment variables in index.html.template
set -e
# Default values
APP_NAME="${APP_NAME:-RxMinder}"
# Input and output files
TEMPLATE_FILE="index.html.template"
OUTPUT_FILE="index.html"
# Check if template exists
if [[ ! -f "$TEMPLATE_FILE" ]]; then
echo "Error: Template file $TEMPLATE_FILE not found"
exit 1
fi
# Process template with environment variable substitution
envsubst '$APP_NAME' < "$TEMPLATE_FILE" > "$OUTPUT_FILE"
echo "✅ Processed $TEMPLATE_FILE -> $OUTPUT_FILE with APP_NAME=$APP_NAME"
-98
View File
@@ -1,98 +0,0 @@
#!/usr/bin/env node
// Production database seeder script
// This script seeds the production CouchDB with default admin user
import { readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectDir = resolve(__dirname, '..');
console.warn('🌱 Starting production database seeding...');
// Load environment variables from .env file if it exists
try {
const envFile = resolve(projectDir, '.env');
const envContent = readFileSync(envFile, 'utf8');
console.warn('📄 Loading environment variables from .env file...');
envContent.split('\n').forEach(line => {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
const [key, ...valueParts] = trimmed.split('=');
const value = valueParts.join('=');
if (!process.env[key]) {
process.env[key] = value;
}
}
});
} catch (_error) {
console.warn(
'️ No .env file found, using environment variables or defaults'
);
}
// Use environment variables with fallbacks
const COUCHDB_URL =
process.env.VITE_COUCHDB_URL ||
process.env.COUCHDB_URL ||
'http://localhost:5984';
const COUCHDB_USER =
process.env.VITE_COUCHDB_USER || process.env.COUCHDB_USER || 'admin';
const COUCHDB_PASSWORD =
process.env.VITE_COUCHDB_PASSWORD ||
process.env.COUCHDB_PASSWORD ||
'change-this-secure-password';
// Set environment variables for the seeder to use
process.env.VITE_COUCHDB_URL = COUCHDB_URL;
process.env.VITE_COUCHDB_USER = COUCHDB_USER;
process.env.VITE_COUCHDB_PASSWORD = COUCHDB_PASSWORD;
console.warn('🔗 CouchDB Configuration:');
console.warn(` URL: ${COUCHDB_URL}`);
console.warn(` User: ${COUCHDB_USER}`);
console.warn(` Password: ${'*'.repeat(COUCHDB_PASSWORD.length)}`);
// Validate required environment variables
if (!COUCHDB_URL || !COUCHDB_USER || !COUCHDB_PASSWORD) {
console.error('❌ Missing required environment variables:');
console.error(' VITE_COUCHDB_URL or COUCHDB_URL');
console.error(' VITE_COUCHDB_USER or COUCHDB_USER');
console.error(' VITE_COUCHDB_PASSWORD or COUCHDB_PASSWORD');
console.error('');
console.error('💡 Set these in your .env file or as environment variables');
process.exit(1);
}
async function seedDatabase() {
try {
// Import the seeder (this will use the production CouchDB due to env vars)
const { DatabaseSeeder } = await import('../services/database.seeder.ts');
// Wait a bit for databases to be initialized
console.warn('⏳ Waiting for databases to initialize...');
await new Promise(resolve => setTimeout(resolve, 2000));
const seeder = new DatabaseSeeder();
console.warn('📊 Seeding admin user...');
await seeder.seedDefaultAdmin();
console.warn('🎉 Production database seeding completed successfully!');
console.warn('🔐 You can now login with:');
console.warn(' Email: admin@localhost');
console.warn(' Password: change-this-secure-password');
process.exit(0);
} catch (error) {
console.error('❌ Seeding failed:', error);
process.exit(1);
}
}
seedDatabase();
-55
View File
@@ -1,55 +0,0 @@
#!/bin/bash
# 🎭 Playwright E2E Test Setup Script
echo "🎭 Setting up Playwright for E2E testing..."
# Check if we're in the right directory
if [[ ! -f "package.json" ]]; then
echo "❌ Error: Must be run from project root directory"
exit 1
fi
# Install Playwright if not already installed
echo "📦 Installing Playwright..."
if command -v bun &> /dev/null; then
bun add -D @playwright/test
else
npm install -D @playwright/test
fi
# Install browser binaries
echo "🌐 Installing browser binaries..."
bunx playwright install
# Install system dependencies (Linux)
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo "🐧 Installing system dependencies for Linux..."
bunx playwright install-deps
fi
# Create .gitignore entries for Playwright
echo "📝 Updating .gitignore for Playwright artifacts..."
if ! grep -q "test-results" .gitignore; then
echo "" >> .gitignore
echo "# Playwright artifacts" >> .gitignore
echo "test-results/" >> .gitignore
echo "playwright-report/" >> .gitignore
echo "playwright/.cache/" >> .gitignore
fi
# Verify installation
echo "✅ Verifying Playwright installation..."
bunx playwright --version
echo ""
echo "🎉 Playwright setup complete!"
echo ""
echo "📋 Quick start commands:"
echo " bun run test:e2e # Run all E2E tests"
echo " bun run test:e2e:ui # Run tests in UI mode"
echo " bun run test:e2e:debug # Debug tests"
echo " bun run test:e2e:report # View test report"
echo ""
echo "📚 Documentation: tests/e2e/README.md"
echo "⚙️ Configuration: playwright.config.ts"
-133
View File
@@ -1,133 +0,0 @@
# Run lint-staged for file-specific checks
bunx lint-staged
# Run type checking (doesn't need file filtering)
bun run type-check
# Check for large files (similar to pre-commit check-added-large-files)
git diff --cached --name-only | while IFS= read -r file; do
if [ -f "$file" ]; then
size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
if [ "$size" -gt 500000 ]; then # 500KB limit
echo "Error: Large file detected: $file ($(echo $size | awk '{print int($1/1024)}')KB)"
echo "Consider using Git LFS for large files."
exit 1
fi
fi
done
# Check for merge conflict markers
if git diff --cached | grep -E "^[<>=]{7}" >/dev/null; then
echo "Error: Merge conflict markers detected"
exit 1
fi
# Check for private keys (basic check)
if git diff --cached --name-only | xargs grep -l "BEGIN.*PRIVATE KEY" 2>/dev/null; then
echo "Error: Private key detected in staged files"
exit 1
fi
echo "✅ Pre-commit checks passed!"
NC='\033[0m' # No Color
echo -e "${GREEN}Setting up NodeJS-native pre-commit hooks and code formatters...${NC}"
# Check if we're in a git repository
if [ ! -d ".git" ]; then
echo -e "${RED}Error: Not a git repository. Please run this script from the project root.${NC}"
exit 1
fi
# Install dependencies
echo -e "${YELLOW}Installing dependencies...${NC}"
if command -v bun &> /dev/null; then
bun install
elif command -v npm &> /dev/null; then
npm install
else
echo -e "${RED}Error: Neither bun nor npm found. Please install one of them first.${NC}"
exit 1
fi
# Initialize Husky (NodeJS-native git hooks)
echo -e "${YELLOW}Setting up Husky git hooks...${NC}"
if command -v bun &> /dev/null; then
bunx husky init
elif command -v npm &> /dev/null; then
npx husky init
fi
# Make pre-commit hook executable
chmod +x .husky/pre-commit
# Run initial formatting
echo -e "${YELLOW}Running initial code formatting...${NC}"
if command -v bun &> /dev/null; then
bun run format
elif command -v npm &> /dev/null; then
bun run format
fi
# Run initial linting
echo -e "${YELLOW}Running initial linting...${NC}"
if command -v bun &> /dev/null; then
bun run lint:fix
elif command -v npm &> /dev/null; then
bun run lint:fix
fi
# Run initial markdown linting
echo -e "${YELLOW}Running initial markdown linting...${NC}"
if command -v bun &> /dev/null; then
bun run lint:markdown:fix || echo "Markdown linting completed with warnings"
elif command -v npm &> /dev/null; then
bun run lint:markdown:fix || echo "Markdown linting completed with warnings"
fi
# Fix EditorConfig issues
echo -e "${YELLOW}Fixing EditorConfig issues...${NC}"
if command -v bun &> /dev/null; then
bun run fix:editorconfig || echo "EditorConfig fixes completed"
elif command -v npm &> /dev/null; then
bun run fix:editorconfig || echo "EditorConfig fixes completed"
fi
echo -e "${GREEN}✅ NodeJS-native pre-commit hooks and code formatters have been set up successfully!${NC}"
echo ""
echo -e "${YELLOW}What was configured:${NC}"
echo " ✓ Husky git hooks (.husky/pre-commit)"
echo " ✓ Prettier code formatter (.prettierrc)"
echo " ✓ ESLint configuration (eslint.config.cjs)"
echo " ✓ EditorConfig (.editorconfig)"
echo " ✓ Markdownlint configuration (.markdownlint.json)"
echo " ✓ Secretlint configuration (.secretlintrc.json)"
echo " ✓ Lint-staged for efficient pre-commit formatting"
echo " ✓ Dockerfilelint for Docker file validation"
echo ""
echo -e "${YELLOW}Available commands:${NC}"
echo " • bun run format - Format all files with Prettier"
echo " • bun run lint - Run ESLint on TypeScript/JavaScript files"
echo " • bun run lint:fix - Run ESLint with auto-fix"
echo " • bun run lint:markdown - Check Markdown files"
echo " • bun run lint:markdown:fix - Fix Markdown files"
echo " • bun run lint:docker - Check Dockerfile"
echo " • bun run check:secrets - Check for secrets in files"
echo " • bun run check:editorconfig - Check EditorConfig compliance"
echo " • bun run fix:editorconfig - Fix EditorConfig issues"
echo " • bun run type-check - Run TypeScript type checking"
echo ""
echo -e "${YELLOW}What happens on commit:${NC}"
echo " 1. Lint-staged runs on changed files:"
echo " • ESLint auto-fix + Prettier formatting for JS/TS files"
echo " • Prettier formatting for JSON/YAML/MD/CSS files"
echo " • Markdownlint auto-fix for Markdown files"
echo " • Dockerfilelint for Dockerfile"
echo " • EditorConfig fixes for all files"
echo " 2. TypeScript type checking on entire project"
echo " 3. Large file detection (>500KB)"
echo " 4. Merge conflict marker detection"
echo " 5. Basic private key detection"
echo ""
echo -e "${GREEN}All commits will now be automatically checked and formatted! 🎉${NC}"
echo -e "${GREEN}This setup is 100% NodeJS-native - no Python dependencies required! 🚀${NC}"
-225
View File
@@ -1,225 +0,0 @@
#!/bin/bash
# 🧪 Deployment Validation Script
# Validates complete deployment with all environment variables and health checks
set -e
echo "🚀 Starting deploymif docker compose -f docker/docker-compose.yaml -p rxminder-validation ps | grep -q "Up"; then
print_success "Docker Compose setup completed successfully!"
else
print_error "Docker Compose services failed to start"
docker compose -f docker/docker-compose.yaml -p rxminder-validation logsalidation..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function
cleanup() {
print_status "Cleaning up test containers..."
docker stop rxminder-validation-test 2>/dev/null || true
docker rm rxminder-validation-test 2>/dev/null || true
docker compose -f docker/docker-compose.yaml -p rxminder-validation down 2>/dev/null || true
}
# Set trap for cleanup
trap cleanup EXIT
print_status "1. Validating environment files..."
# Check if required environment files exist
if [[ ! -f .env ]]; then
print_error ".env file not found. Run 'cp .env.example .env' and configure it."
exit 1
fi
if [[ ! -f .env.example ]]; then
print_error ".env.example file not found."
exit 1
fi
print_success "Environment files exist"
# Validate environment consistency
print_status "2. Checking environment variable consistency..."
./validate-env.sh
print_status "3. Setting up Docker Buildx..."
# Ensure buildx is available
if ! docker buildx version >/dev/null 2>&1; then
print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx."
exit 1
fi
# Create a new builder instance if it doesn't exist
if ! docker buildx ls | grep -q "rxminder-builder"; then
print_status "Creating new buildx builder instance..."
docker buildx create --name rxminder-builder --driver docker-container --bootstrap
fi
# Use the builder
docker buildx use rxminder-builder
print_status "4. Building multi-platform Docker image with buildx..."
# Build the image with buildx for multiple platforms
docker buildx build --no-cache \
--platform linux/amd64,linux/arm64 \
--build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \
--build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg VITE_COUCHDB_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \
--build-arg VITE_COUCHDB_USER="${VITE_COUCHDB_USER:-admin}" \
--build-arg VITE_COUCHDB_PASSWORD="${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg APP_BASE_URL="${APP_BASE_URL:-http://localhost:8080}" \
--build-arg VITE_GOOGLE_CLIENT_ID="${VITE_GOOGLE_CLIENT_ID:-}" \
--build-arg VITE_GITHUB_CLIENT_ID="${VITE_GITHUB_CLIENT_ID:-}" \
--build-arg MAILGUN_API_KEY="${MAILGUN_API_KEY:-}" \
--build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \
--build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \
--build-arg NODE_ENV="${NODE_ENV:-production}" \
-t rxminder-validation \
--load \
.
print_success "Docker image built successfully"
print_status "5. Testing container startup and health..."
# Run container in background
docker run --rm -d \
-p 8083:80 \
--name rxminder-validation-test \
rxminder-validation
# Wait for container to start
sleep 5
# Check if container is running
if ! docker ps | grep -q rxminder-validation-test; then
print_error "Container failed to start"
docker logs rxminder-validation-test
exit 1
fi
print_success "Container started successfully"
# Test health endpoint
print_status "5. Testing health endpoint..."
for i in {1..10}; do
if curl -s -f http://localhost:8083/health > /dev/null; then
print_success "Health endpoint responding"
break
elif [[ $i -eq 10 ]]; then
print_error "Health endpoint not responding after 10 attempts"
exit 1
else
print_warning "Health endpoint not ready, retrying... ($i/10)"
sleep 2
fi
done
# Test main application
print_status "6. Testing main application..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083)
if [[ $HTTP_CODE -eq 200 ]]; then
print_success "Main application responding (HTTP $HTTP_CODE)"
else
print_error "Main application not responding properly (HTTP $HTTP_CODE)"
exit 1
fi
# Test docker-compose build
print_status "7. Testing Docker Compose build..."
docker compose -f docker/docker-compose.yaml build frontend --no-cache
print_success "Docker Compose build successful"
# Test docker-compose with validation project name
print_status "8. Testing Docker Compose deployment..."
docker compose -f docker/docker-compose.yaml -p rxminder-validation up -d --build
# Wait for services to start
sleep 10
# Check service health
if docker compose -f docker/docker-compose.yaml -p meds-validation ps | grep -q "Up"; then
print_success "Docker Compose services started successfully"
else
print_error "Docker Compose services failed to start"
docker compose -f docker/docker-compose.yaml -p meds-validation logs
exit 1
fi
# Test health of compose deployment
if curl -s -f http://localhost:8080/health > /dev/null; then
print_success "Docker Compose health endpoint responding"
else
print_warning "Docker Compose health endpoint not responding (may need CouchDB)"
fi
print_status "9. Checking image size..."
IMAGE_SIZE=$(docker image inspect rxminder-validation --format='{{.Size}}' | numfmt --to=iec)
print_success "Image size: $IMAGE_SIZE"
print_status "10. Validating security configuration..."
# Check if image runs as non-root
USER_INFO=$(docker run --rm rxminder-validation whoami)
if [[ "$USER_INFO" != "root" ]]; then
print_success "Container runs as non-root user: $USER_INFO"
else
print_warning "Container runs as root user (security consideration)"
fi
# Check nginx configuration
if docker run --rm rxminder-validation nginx -t 2>/dev/null; then
print_success "Nginx configuration is valid"
else
print_error "Nginx configuration has issues"
exit 1
fi
print_status "11. Final validation complete!"
echo
echo "🎉 Deployment validation successful!"
echo
echo "Summary:"
echo "✅ Environment files validated"
echo "✅ Docker image builds successfully"
echo "✅ Container starts and runs healthy"
echo "✅ Health endpoints respond correctly"
echo "✅ Docker Compose deployment works"
echo "✅ Security configuration validated"
echo "✅ Image size optimized ($IMAGE_SIZE)"
echo
echo "Your deployment is ready for production! 🚀"
echo
echo "Next steps:"
echo "1. Configure production environment variables in .env"
echo "2. Run './deploy.sh production' for production deployment"
echo "3. Set up monitoring and backups"
echo "4. Configure SSL/TLS certificates"
echo
-52
View File
@@ -1,52 +0,0 @@
#!/bin/bash
# Fast TypeScript type checking for staged files only
# This script checks only the staged TypeScript files for type errors
set -e
# Get list of staged TypeScript files
STAGED_TS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx)$' || true)
if [ -z "$STAGED_TS_FILES" ]; then
echo "📝 No TypeScript files staged, skipping type check"
exit 0
fi
echo "🔍 Type checking staged TypeScript files..."
# Check if we have a tsconfig.json
if [ ! -f "tsconfig.json" ]; then
echo "⚠️ No tsconfig.json found, skipping type check"
exit 0
fi
# Try using the project's existing TypeScript setup
# First, try a quick check with noEmit
if bunx tsc --noEmit --skipLibCheck --incremental false > /dev/null 2>&1; then
echo "✅ TypeScript type check passed"
exit 0
else
echo "⚠️ Full type check failed, checking individual staged files..."
# Fallback: check each staged file individually with minimal config
FAILED_FILES=()
for file in $STAGED_TS_FILES; do
if [ -f "$file" ]; then
# Basic syntax check without full type checking
if ! bunx tsc --noEmit --skipLibCheck --allowJs --jsx preserve "$file" > /dev/null 2>&1; then
FAILED_FILES+=("$file")
fi
fi
done
if [ ${#FAILED_FILES[@]} -eq 0 ]; then
echo "✅ TypeScript syntax check passed for staged files"
exit 0
else
echo "❌ TypeScript errors found in: ${FAILED_FILES[*]}"
echo "💡 Run 'bun run type-check' for detailed errors"
exit 1
fi
fi
-298
View File
@@ -1,298 +0,0 @@
#!/bin/bash
# Kubernetes deployment script with environment variable substitution
# This script processes template files and applies them to Kubernetes
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
K8S_DIR="$PROJECT_ROOT/k8s"
TEMP_DIR="/tmp/meds-k8s-deploy"
# Function to print colored output
print_info() {
echo -e "${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Function to load environment variables
load_env() {
local env_file="$1"
if [[ -f "$env_file" ]]; then
print_info "Loading environment from $env_file"
# Export variables from .env file
set -a
source "$env_file"
set +a
else
print_warning "Environment file $env_file not found"
fi
}
# Function to substitute environment variables in template files
substitute_templates() {
print_info "Processing template files..."
# Create temporary directory
mkdir -p "$TEMP_DIR"
# Process each template file
for template_file in "$K8S_DIR"/*.template; do
if [[ -f "$template_file" ]]; then
local filename=$(basename "$template_file" .template)
local output_file="$TEMP_DIR/$filename"
print_info "Processing template: $filename"
# Substitute environment variables
envsubst < "$template_file" > "$output_file"
print_success "Generated: $output_file"
fi
done
}
# Function to validate required environment variables
validate_env() {
local required_vars=("INGRESS_HOST")
local missing_vars=()
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
missing_vars+=("$var")
fi
done
if [[ ${#missing_vars[@]} -gt 0 ]]; then
print_error "Missing required environment variables:"
for var in "${missing_vars[@]}"; do
echo " - $var"
done
echo ""
echo "Please set these variables in your .env file or environment."
exit 1
fi
}
# Function to ensure namespace exists
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
print_info "Creating namespace: $NAMESPACE"
kubectl create namespace "$NAMESPACE"
print_success "Created namespace: $NAMESPACE"
else
print_info "Using existing namespace: $NAMESPACE"
fi
}
# Function to apply Kubernetes manifests
apply_manifests() {
local manifest_dir="$1"
print_info "Applying Kubernetes manifests from $manifest_dir"
# Apply static files that don't have template counterparts
for manifest_file in "$K8S_DIR"/*.yaml; do
if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then
local basename_file=$(basename "$manifest_file")
local template_file="$K8S_DIR/${basename_file}.template"
# Only apply if there's no corresponding template file
if [[ ! -f "$template_file" ]]; then
print_info "Applying static file: $basename_file"
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
fi
fi
done
# Apply processed template files
if [[ -d "$TEMP_DIR" ]]; then
for manifest_file in "$TEMP_DIR"/*.yaml; do
if [[ -f "$manifest_file" ]]; then
print_info "Applying template: $(basename "$manifest_file")"
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
fi
done
fi
}
# Function to cleanup temporary files
cleanup() {
if [[ -d "$TEMP_DIR" ]]; then
print_info "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
fi
}
# Function to show deployment status
show_status() {
print_info "Deployment Status:"
echo ""
print_info "Pods:"
kubectl get pods -l app=rxminder -n "$NAMESPACE"
echo ""
print_info "Services:"
kubectl get services -l app=rxminder -n "$NAMESPACE"
echo ""
print_info "Ingress:"
kubectl get ingress -l app=rxminder -n "$NAMESPACE"
echo ""
if [[ -n "${INGRESS_HOST:-}" ]]; then
print_success "Application should be available at: http://$INGRESS_HOST"
fi
}
# Function to show usage
usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -e, --env FILE Environment file to load (default: .env)"
echo " -d, --dry-run Show what would be applied without applying"
echo " -s, --status Show deployment status only"
echo " -c, --cleanup Cleanup temporary files and exit"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 Deploy with default .env file"
echo " $0 -e .env.prod Deploy with production environment"
echo " $0 --dry-run Preview what would be deployed"
echo " $0 --status Check deployment status"
}
# Main function
main() {
local env_file=".env"
local dry_run=false
local status_only=false
local cleanup_only=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-e|--env)
env_file="$2"
shift 2
;;
-d|--dry-run)
dry_run=true
shift
;;
-s|--status)
status_only=true
shift
;;
-c|--cleanup)
cleanup_only=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
print_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Handle cleanup only
if [[ "$cleanup_only" == true ]]; then
cleanup
exit 0
fi
# Handle status only
if [[ "$status_only" == true ]]; then
show_status
exit 0
fi
# Check if kubectl is available
if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed or not in PATH"
exit 1
fi
# Check if we can connect to Kubernetes cluster
if ! kubectl cluster-info &> /dev/null; then
print_error "Cannot connect to Kubernetes cluster"
print_info "Make sure your kubectl is configured correctly"
exit 1
fi
print_info "🚀 Deploying Medication Reminder App to Kubernetes"
echo ""
# Load environment variables
load_env "$env_file"
# Set default namespace if not provided in environment
NAMESPACE="${NAMESPACE:-rxminder}"
# Validate required environment variables
validate_env
# Ensure namespace exists
ensure_namespace
# Process templates
substitute_templates
if [[ "$dry_run" == true ]]; then
print_info "Dry run mode - showing generated manifests:"
echo ""
for manifest_file in "$TEMP_DIR"/*.yaml; do
if [[ -f "$manifest_file" ]]; then
echo "=== $(basename "$manifest_file") ==="
cat "$manifest_file"
echo ""
fi
done
else
# Apply manifests
apply_manifests "$K8S_DIR"
print_success "Deployment completed!"
echo ""
# Show status
show_status
fi
# Cleanup
cleanup
}
# Trap to ensure cleanup happens
trap cleanup EXIT
# Run main function
main "$@"
-224
View File
@@ -1,224 +0,0 @@
#!/bin/bash
# 🧪 Deployment Validation Script
# Validates complete deployment with all environment variables and health checks
set -e
echo "🚀 Starting deployment validation..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function
cleanup() {
print_status "Cleaning up test containers..."
APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]')
docker stop ${APP_NAME_LOWER}-validation-test 2>/dev/null || true
docker rm ${APP_NAME_LOWER}-validation-test 2>/dev/null || true
docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation down 2>/dev/null || true
}
# Set trap for cleanup
trap cleanup EXIT
print_status "1. Validating environment files..."
# Check if required environment files exist
if [[ ! -f .env ]]; then
print_error ".env file not found. Run 'cp .env.example .env' and configure it."
exit 1
fi
if [[ ! -f .env.example ]]; then
print_error ".env.example file not found."
exit 1
fi
print_success "Environment files exist"
# Validate environment consistency
print_status "2. Checking environment variable consistency..."
./validate-env.sh
print_status "3. Setting up Docker Buildx..."
# Ensure buildx is available
if ! docker buildx version >/dev/null 2>&1; then
print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx."
exit 1
fi
# Create a new builder instance if it doesn't exist
if ! docker buildx ls | grep -q "meds-builder"; then
print_status "Creating new buildx builder instance..."
docker buildx create --name meds-builder --driver docker-container --bootstrap
fi
# Use the builder
docker buildx use meds-builder
print_status "4. Building multi-platform Docker image with buildx..."
# Build single-platform image for testing
APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]')
docker buildx build --no-cache \
--platform linux/amd64 \
--build-arg APP_NAME="${APP_NAME:-RxMinder}" \
--build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \
--build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg VITE_COUCHDB_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \
--build-arg VITE_COUCHDB_USER="${VITE_COUCHDB_USER:-admin}" \
--build-arg VITE_COUCHDB_PASSWORD="${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" \
--build-arg APP_BASE_URL="${APP_BASE_URL:-http://localhost:8080}" \
--build-arg VITE_GOOGLE_CLIENT_ID="${VITE_GOOGLE_CLIENT_ID:-}" \
--build-arg VITE_GITHUB_CLIENT_ID="${VITE_GITHUB_CLIENT_ID:-}" \
--build-arg MAILGUN_API_KEY="${MAILGUN_API_KEY:-}" \
--build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \
--build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \
--build-arg NODE_ENV="${NODE_ENV:-production}" \
-t ${APP_NAME_LOWER}-validation \
--load \
.
print_success "Docker image built successfully"
print_status "5. Testing container startup and health..."
# Run container in background
docker run --rm -d \
-p 8083:80 \
--name ${APP_NAME_LOWER}-validation-test \
${APP_NAME_LOWER}-validation
# Wait for container to start
sleep 5
# Check if container is running
if ! docker ps | grep -q ${APP_NAME_LOWER}-validation-test; then
print_error "Container failed to start"
docker logs ${APP_NAME_LOWER}-validation-test
exit 1
fi
print_success "Container started successfully"
# Test health endpoint
print_status "5. Testing health endpoint..."
for i in {1..10}; do
if curl -s -f http://localhost:8083/health > /dev/null; then
print_success "Health endpoint responding"
break
elif [[ $i -eq 10 ]]; then
print_error "Health endpoint not responding after 10 attempts"
exit 1
else
print_warning "Health endpoint not ready, retrying... ($i/10)"
sleep 2
fi
done
# Test main application
print_status "6. Testing main application..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083)
if [[ $HTTP_CODE -eq 200 ]]; then
print_success "Main application responding (HTTP $HTTP_CODE)"
else
print_error "Main application not responding properly (HTTP $HTTP_CODE)"
exit 1
fi
# Test docker-compose build
print_status "7. Testing Docker Compose build..."
docker compose -f docker/docker-compose.yaml build frontend --no-cache
print_success "Docker Compose build successful"
# Test docker-compose with validation project name
print_status "8. Testing Docker Compose deployment..."
docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation up -d --build
# Wait for services to start
sleep 10
# Check service health
if docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation ps | grep -q "Up"; then
print_success "Docker Compose services started successfully"
else
print_error "Docker Compose services failed to start"
docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation logs
exit 1
fi
# Test health of compose deployment
if curl -s -f http://localhost:8080/health > /dev/null; then
print_success "Docker Compose health endpoint responding"
else
print_warning "Docker Compose health endpoint not responding (may need CouchDB)"
fi
print_status "9. Checking image size..."
IMAGE_SIZE=$(docker image inspect ${APP_NAME_LOWER}-validation --format='{{.Size}}' | numfmt --to=iec)
print_success "Image size: $IMAGE_SIZE"
print_status "10. Validating security configuration..."
# Check if image runs as non-root
USER_INFO=$(docker run --rm ${APP_NAME_LOWER}-validation whoami)
if [[ "$USER_INFO" != "root" ]]; then
print_success "Container runs as non-root user: $USER_INFO"
else
print_warning "Container runs as root user (security consideration)"
fi
# Check nginx configuration
if docker run --rm ${APP_NAME_LOWER}-validation nginx -t 2>/dev/null; then
print_success "Nginx configuration is valid"
else
print_error "Nginx configuration has issues"
exit 1
fi
print_status "11. Final validation complete!"
echo
echo "🎉 Deployment validation successful!"
echo
echo "Summary:"
echo "✅ Environment files validated"
echo "✅ Docker image builds successfully"
echo "✅ Container starts and runs healthy"
echo "✅ Health endpoints respond correctly"
echo "✅ Docker Compose deployment works"
echo "✅ Security configuration validated"
echo "✅ Image size optimized ($IMAGE_SIZE)"
echo
echo "Your deployment is ready for production! 🚀"
echo
echo "Next steps:"
echo "1. Configure production environment variables in .env"
echo "2. Run './deploy.sh production' for production deployment"
echo "3. Set up monitoring and backups"
echo "4. Configure SSL/TLS certificates"
echo
-274
View File
@@ -1,274 +0,0 @@
#!/bin/bash
# Environment validation script
# Validates all .env files for consistency and completeness
set -e
print_header() {
echo "🔍 Environment Configuration Validation"
echo "======================================"
echo ""
}
print_section() {
echo ""
echo "📋 $1"
echo "$(printf '%*s' ${#1} '' | tr ' ' '-')"
}
print_success() {
echo "$1"
}
print_warning() {
echo "⚠️ $1"
}
print_error() {
echo "$1"
}
# Required variables for each environment
CORE_VARS=(
"COUCHDB_USER"
"COUCHDB_PASSWORD"
"VITE_COUCHDB_URL"
"VITE_COUCHDB_USER"
"VITE_COUCHDB_PASSWORD"
"APP_BASE_URL"
"MAILGUN_API_KEY"
"MAILGUN_DOMAIN"
"MAILGUN_FROM_EMAIL"
)
K8S_VARS=(
"INGRESS_HOST"
)
OPTIONAL_VARS=(
"NODE_ENV"
"VITE_GOOGLE_CLIENT_ID"
"VITE_GITHUB_CLIENT_ID"
)
validate_file() {
local file="$1"
local file_type="$2"
print_section "Validating $file ($file_type)"
if [[ ! -f "$file" ]]; then
print_error "File not found: $file"
return 1
fi
local missing_vars=()
local found_vars=()
# Check core variables
for var in "${CORE_VARS[@]}"; do
if grep -q "^${var}=" "$file" || grep -q "^#.*${var}=" "$file"; then
found_vars+=("$var")
else
missing_vars+=("$var")
fi
done
# Check K8s variables for relevant files
if [[ "$file_type" != "template" ]]; then
for var in "${K8S_VARS[@]}"; do
if grep -q "^${var}=" "$file" || grep -q "^#.*${var}=" "$file"; then
found_vars+=("$var")
else
missing_vars+=("$var")
fi
done
fi
# Report results
print_success "Found ${#found_vars[@]} variables"
if [[ ${#missing_vars[@]} -gt 0 ]]; then
print_warning "Missing variables:"
for var in "${missing_vars[@]}"; do
echo " - $var"
done
fi
# Check for old VITE_MAILGUN variables
if grep -q "VITE_MAILGUN" "$file"; then
print_error "Found deprecated VITE_MAILGUN variables (should be MAILGUN_*)"
fi
# Check variable format
local malformed_vars=()
while IFS= read -r line; do
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
local var_name="${line%%=*}"
if [[ ! "$var_name" =~ ^[A-Z_][A-Z0-9_]*$ ]]; then
malformed_vars+=("$var_name")
fi
fi
done < "$file"
if [[ ${#malformed_vars[@]} -gt 0 ]]; then
print_warning "Malformed variable names:"
for var in "${malformed_vars[@]}"; do
echo " - $var"
done
fi
echo ""
}
validate_consistency() {
print_section "Cross-file Consistency Check"
# Extract variable names from each file
local example_vars=()
local env_vars=()
local prod_vars=()
if [[ -f ".env.example" ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
example_vars+=("${line%%=*}")
fi
done < ".env.example"
fi
if [[ -f ".env" ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
env_vars+=("${line%%=*}")
fi
done < ".env"
fi
if [[ -f ".env.production" ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
prod_vars+=("${line%%=*}")
fi
done < ".env.production"
fi
# Check if .env and .env.production have all variables from .env.example
local missing_in_env=()
local missing_in_prod=()
for var in "${example_vars[@]}"; do
if [[ ! " ${env_vars[@]} " =~ " ${var} " ]]; then
missing_in_env+=("$var")
fi
if [[ ! " ${prod_vars[@]} " =~ " ${var} " ]]; then
missing_in_prod+=("$var")
fi
done
if [[ ${#missing_in_env[@]} -eq 0 ]]; then
print_success ".env has all variables from .env.example"
else
print_warning ".env missing variables from .env.example:"
for var in "${missing_in_env[@]}"; do
echo " - $var"
done
fi
if [[ ${#missing_in_prod[@]} -eq 0 ]]; then
print_success ".env.production has all variables from .env.example"
else
print_warning ".env.production missing variables from .env.example:"
for var in "${missing_in_prod[@]}"; do
echo " - $var"
done
fi
echo ""
}
validate_k8s_template() {
print_section "Kubernetes Template Validation"
local template_file="k8s/ingress.yaml.template"
if [[ ! -f "$template_file" ]]; then
print_error "Template file not found: $template_file"
return 1
fi
# Check for template variables
local template_vars=()
while IFS= read -r line; do
if [[ "$line" =~ \$\{([A-Z_][A-Z0-9_]*)\} ]]; then
local var_name="${BASH_REMATCH[1]}"
if [[ ! " ${template_vars[@]} " =~ " ${var_name} " ]]; then
template_vars+=("$var_name")
fi
fi
done < "$template_file"
print_success "Found ${#template_vars[@]} template variables:"
for var in "${template_vars[@]}"; do
echo " - \${$var}"
done
# Check if template variables are defined in env files
for var in "${template_vars[@]}"; do
local found_in_files=()
if grep -q "^${var}=" ".env.example" 2>/dev/null; then
found_in_files+=(".env.example")
fi
if grep -q "^${var}=" ".env" 2>/dev/null; then
found_in_files+=(".env")
fi
if grep -q "^${var}=" ".env.production" 2>/dev/null; then
found_in_files+=(".env.production")
fi
if [[ ${#found_in_files[@]} -gt 0 ]]; then
print_success "$var defined in: ${found_in_files[*]}"
else
print_error "$var not defined in any environment file"
fi
done
echo ""
}
main() {
print_header
# Validate individual files
if [[ -f ".env.example" ]]; then
validate_file ".env.example" "template"
fi
if [[ -f ".env" ]]; then
validate_file ".env" "development"
fi
if [[ -f ".env.production" ]]; then
validate_file ".env.production" "production"
fi
# Cross-file validation
validate_consistency
# Kubernetes template validation
validate_k8s_template
print_section "Summary"
print_success "Environment validation complete!"
echo ""
echo "💡 Tips:"
echo " - Copy .env.example to .env for local development"
echo " - Use .env.production for production deployments"
echo " - Run './deploy-k8s.sh --dry-run' to test Kubernetes deployment"
echo " - All Mailgun variables use server-side naming (no VITE_ prefix)"
echo ""
}
main "$@"