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:
@@ -1,45 +0,0 @@
|
||||
# Gitea Actions CI/CD Docker Compose Override
|
||||
# This file provides CI-specific configurations for Gitea Actions
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Frontend service with CI optimizations
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
target: builder
|
||||
cache_from:
|
||||
- ${REGISTRY:-gitea.example.com}/${IMAGE_NAME:-rxminder}:buildcache
|
||||
args:
|
||||
# Use build args from CI environment
|
||||
- VITE_COUCHDB_URL=${VITE_COUCHDB_URL:-http://couchdb:5984}
|
||||
- VITE_COUCHDB_USER=${VITE_COUCHDB_USER:-admin}
|
||||
- VITE_COUCHDB_PASSWORD=${VITE_COUCHDB_PASSWORD:-change-this-secure-password}
|
||||
- APP_BASE_URL=${APP_BASE_URL:-http://localhost:8080}
|
||||
- VITE_GOOGLE_CLIENT_ID=${VITE_GOOGLE_CLIENT_ID:-}
|
||||
- VITE_GITHUB_CLIENT_ID=${VITE_GITHUB_CLIENT_ID:-}
|
||||
- NODE_ENV=production
|
||||
environment:
|
||||
- CI=true
|
||||
labels:
|
||||
- 'gitea.ci=true'
|
||||
- 'gitea.project=rxminder'
|
||||
|
||||
# Test database for CI
|
||||
couchdb-test:
|
||||
image: couchdb:3.3.2
|
||||
environment:
|
||||
- COUCHDB_USER=admin
|
||||
- COUCHDB_PASSWORD=test-secure-password
|
||||
ports:
|
||||
- '5985:5984'
|
||||
volumes:
|
||||
- couchdb_test_data:/opt/couchdb/data
|
||||
labels:
|
||||
- 'gitea.ci=true'
|
||||
- 'gitea.service=test-database'
|
||||
|
||||
volumes:
|
||||
couchdb_test_data:
|
||||
driver: local
|
||||
@@ -1,156 +0,0 @@
|
||||
# Gitea-specific Docker Bake file for advanced multi-platform builds
|
||||
# Usage: docker buildx bake -f gitea-bake.hcl
|
||||
|
||||
variable "GITEA_REGISTRY" {
|
||||
default = notequal("", GITEA_REGISTRY) ? GITEA_REGISTRY : "ghcr.io"
|
||||
}
|
||||
|
||||
variable "GITEA_REPOSITORY" {
|
||||
default = notequal("", GITEA_REPOSITORY) ? GITEA_REPOSITORY : "user/rxminder"
|
||||
}
|
||||
|
||||
variable "TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "GITEA_SHA" {
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "VITE_COUCHDB_URL" {
|
||||
default = "http://localhost:5984"
|
||||
}
|
||||
|
||||
variable "VITE_COUCHDB_USER" {
|
||||
default = "admin"
|
||||
}
|
||||
|
||||
variable "VITE_COUCHDB_PASSWORD" {
|
||||
default = "change-this-secure-password"
|
||||
}
|
||||
|
||||
variable "APP_BASE_URL" {
|
||||
default = "http://localhost:8080"
|
||||
}
|
||||
|
||||
variable "VITE_GOOGLE_CLIENT_ID" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "VITE_GITHUB_CLIENT_ID" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["app"]
|
||||
}
|
||||
|
||||
group "ci" {
|
||||
targets = ["app-ci"]
|
||||
}
|
||||
|
||||
target "app" {
|
||||
dockerfile = "Dockerfile"
|
||||
context = "."
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
|
||||
tags = [
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:${TAG}",
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:latest"
|
||||
]
|
||||
|
||||
args = {
|
||||
VITE_COUCHDB_URL = "${VITE_COUCHDB_URL}"
|
||||
VITE_COUCHDB_USER = "${VITE_COUCHDB_USER}"
|
||||
VITE_COUCHDB_PASSWORD = "${VITE_COUCHDB_PASSWORD}"
|
||||
APP_BASE_URL = "${APP_BASE_URL}"
|
||||
VITE_GOOGLE_CLIENT_ID = "${VITE_GOOGLE_CLIENT_ID}"
|
||||
VITE_GITHUB_CLIENT_ID = "${VITE_GITHUB_CLIENT_ID}"
|
||||
NODE_ENV = "production"
|
||||
}
|
||||
|
||||
# Gitea registry caching
|
||||
cache-from = [
|
||||
"type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache"
|
||||
]
|
||||
|
||||
cache-to = [
|
||||
"type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache,mode=max"
|
||||
]
|
||||
}
|
||||
|
||||
# CI-specific target with commit SHA tagging
|
||||
target "app-ci" {
|
||||
inherits = ["app"]
|
||||
tags = [
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:${GITEA_SHA}",
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:latest"
|
||||
]
|
||||
|
||||
# Enhanced CI-specific features
|
||||
attest = [
|
||||
"type=provenance,mode=max",
|
||||
"type=sbom"
|
||||
]
|
||||
|
||||
# CI registry push
|
||||
output = ["type=registry"]
|
||||
}
|
||||
|
||||
# Development target for local builds
|
||||
target "dev" {
|
||||
inherits = ["app"]
|
||||
platforms = ["linux/amd64"]
|
||||
tags = ["rxminder:dev"]
|
||||
|
||||
# Local caching only
|
||||
cache-from = ["type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache"]
|
||||
cache-to = ["type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache"]
|
||||
|
||||
# Load locally instead of push
|
||||
output = ["type=docker"]
|
||||
}
|
||||
|
||||
# Production target with full attestations
|
||||
target "prod" {
|
||||
inherits = ["app-ci"]
|
||||
|
||||
# Production-specific tags
|
||||
tags = [
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:prod-${TAG}",
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:production"
|
||||
]
|
||||
|
||||
# Full security attestations for production
|
||||
attest = [
|
||||
"type=provenance,mode=max",
|
||||
"type=sbom"
|
||||
]
|
||||
}
|
||||
|
||||
# Staging target
|
||||
target "staging" {
|
||||
inherits = ["app"]
|
||||
platforms = ["linux/amd64"] # Single platform for staging
|
||||
|
||||
tags = [
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:staging-${TAG}",
|
||||
"${GITEA_REGISTRY}/${GITEA_REPOSITORY}:staging"
|
||||
]
|
||||
|
||||
# Staging-specific build args
|
||||
args = {
|
||||
VITE_COUCHDB_URL = "${VITE_COUCHDB_URL}"
|
||||
VITE_COUCHDB_USER = "${VITE_COUCHDB_USER}"
|
||||
VITE_COUCHDB_PASSWORD = "${VITE_COUCHDB_PASSWORD}"
|
||||
APP_BASE_URL = "http://staging.localhost:8080"
|
||||
VITE_GOOGLE_CLIENT_ID = "${VITE_GOOGLE_CLIENT_ID}"
|
||||
VITE_GITHUB_CLIENT_ID = "${VITE_GITHUB_CLIENT_ID}"
|
||||
NODE_ENV = "staging"
|
||||
}
|
||||
|
||||
output = ["type=registry"]
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
# Kustomize Migration Complete! 🎉
|
||||
|
||||
## Migration Summary
|
||||
|
||||
The rxminder application has been successfully migrated from shell script-based Kubernetes deployments to **Kustomize**, providing a more maintainable, scalable, and GitOps-ready deployment strategy.
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
### ✅ 1. Complete Base Resources
|
||||
|
||||
- ✅ Converted all template files to base Kustomize resources
|
||||
- ✅ Created `frontend-deployment.yaml`, `frontend-service.yaml`
|
||||
- ✅ Created `couchdb-statefulset.yaml`, `couchdb-service.yaml`, `couchdb-pvc.yaml`
|
||||
- ✅ Created `ingress.yaml`, `network-policy.yaml`, `hpa.yaml`, `db-seed-job.yaml`
|
||||
- ✅ Removed template variables and used Kustomize's generators
|
||||
- ✅ Set up ConfigMap generation from `config.env`
|
||||
- ✅ Configured Secret generation for CouchDB credentials
|
||||
|
||||
### ✅ 2. Environment Overlays
|
||||
|
||||
- ✅ **Development Overlay**: Optimized for local development
|
||||
- Namespace: `rxminder-dev`
|
||||
- Minimal resources (16Mi-32Mi memory)
|
||||
- Debug logging enabled
|
||||
- Weak passwords for development
|
||||
- Single replica for resource conservation
|
||||
- ✅ **Production Overlay**: Enterprise-ready configuration
|
||||
- Namespace: `rxminder-prod`
|
||||
- Production resources (256Mi-512Mi memory)
|
||||
- Security hardening (security contexts, network policies)
|
||||
- TLS/HTTPS enabled
|
||||
- High availability (3 frontend replicas)
|
||||
- Production storage (10Gi SSD)
|
||||
- Monitoring and alerting enabled
|
||||
|
||||
### ✅ 3. Updated Makefile
|
||||
|
||||
- ✅ Added comprehensive Kustomize targets
|
||||
- ✅ Maintained backward compatibility with legacy shell scripts
|
||||
- ✅ Created convenient aliases for quick deployment
|
||||
- ✅ Added validation and debugging commands
|
||||
|
||||
### ✅ 4. Documentation
|
||||
|
||||
- ✅ Comprehensive `k8s-kustomize/README.md`
|
||||
- ✅ Migration guide and troubleshooting
|
||||
- ✅ Best practices and security considerations
|
||||
|
||||
## Key Benefits Achieved
|
||||
|
||||
### 🚀 Simplified Deployment
|
||||
|
||||
```bash
|
||||
# Before (shell scripts with variable substitution)
|
||||
APP_NAME=rxminder NAMESPACE=production ./scripts/deploy.sh
|
||||
|
||||
# After (Kustomize)
|
||||
make deploy-prod
|
||||
# or
|
||||
kubectl apply -k k8s-kustomize/overlays/prod
|
||||
```
|
||||
|
||||
### 🔒 Environment Isolation
|
||||
|
||||
- **Clear separation** between dev, staging, and production
|
||||
- **Namespace isolation** prevents cross-environment contamination
|
||||
- **Environment-specific configurations** without duplication
|
||||
|
||||
### 🔧 GitOps Ready
|
||||
|
||||
- **ArgoCD/Flux compatible** out of the box
|
||||
- **Declarative configuration** with no templating complexity
|
||||
- **Git-based workflow** for deployment automation
|
||||
|
||||
### ✅ Better Validation
|
||||
|
||||
- **Built-in YAML validation** catches errors early
|
||||
- **Dry-run capabilities** for safe deployments
|
||||
- **Configuration drift detection**
|
||||
|
||||
### 📈 Standard Approach
|
||||
|
||||
- **Kubernetes-native** solution (no external dependencies)
|
||||
- **Industry standard** approach
|
||||
- **Better team onboarding** with familiar tooling
|
||||
|
||||
## Available Commands
|
||||
|
||||
### Quick Start Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
make deploy-dev # Deploy to development
|
||||
make quick-deploy-dev # Build and deploy to development
|
||||
make status-dev # Check development status
|
||||
|
||||
# Production
|
||||
make deploy-prod # Deploy to production
|
||||
make quick-deploy-prod # Build and deploy to production
|
||||
make status-prod # Check production status
|
||||
```
|
||||
|
||||
### Validation & Debugging
|
||||
|
||||
```bash
|
||||
make validate-kustomize # Validate all configurations
|
||||
make kustomize-dry-run-dev # Test development deployment
|
||||
make kustomize-diff-prod # Show production differences
|
||||
make kustomize-build-dev # Build development manifests
|
||||
```
|
||||
|
||||
### Legacy Support (Still Available)
|
||||
|
||||
```bash
|
||||
make deploy-dev # Deploy to development environment
|
||||
make deploy-prod # Deploy to production environment
|
||||
make undeploy-dev # Remove development deployment
|
||||
make undeploy-prod # Remove production deployment
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
k8s-kustomize/
|
||||
├── base/ # Shared base configuration
|
||||
│ ├── kustomization.yaml # Base kustomization
|
||||
│ ├── config.env # Environment variables
|
||||
│ ├── frontend-deployment.yaml # Frontend workload
|
||||
│ ├── couchdb-statefulset.yaml # Database workload
|
||||
│ ├── *-service.yaml # Services
|
||||
│ ├── ingress.yaml # Ingress configuration
|
||||
│ ├── network-policy.yaml # Security policies
|
||||
│ └── ... # Other resources
|
||||
├── overlays/
|
||||
│ ├── dev/ # Development environment
|
||||
│ │ └── kustomization.yaml # Dev customizations
|
||||
│ └── prod/ # Production environment
|
||||
│ ├── kustomization.yaml # Prod customizations
|
||||
│ ├── frontend-resources.yaml # Prod frontend config
|
||||
│ ├── couchdb-resources.yaml # Prod database config
|
||||
│ └── ingress-prod.yaml # Prod ingress config
|
||||
└── README.md # Comprehensive documentation
|
||||
```
|
||||
|
||||
## Security Enhancements
|
||||
|
||||
### Production Security Features
|
||||
|
||||
- ✅ **Security contexts**: Non-root containers, read-only filesystems
|
||||
- ✅ **Network policies**: Restricted pod-to-pod communication
|
||||
- ✅ **Resource limits**: Prevent resource exhaustion attacks
|
||||
- ✅ **TLS encryption**: HTTPS with cert-manager integration
|
||||
- ✅ **RBAC ready**: Role-based access control compatible
|
||||
- ✅ **Secret management**: External secret store integration points
|
||||
|
||||
### Development Security
|
||||
|
||||
- ✅ **Namespace isolation**: Separated from production
|
||||
- ✅ **Weak credentials**: Safe for development environment
|
||||
- ✅ **Relaxed policies**: Optimized for developer productivity
|
||||
|
||||
## Next Steps & Recommendations
|
||||
|
||||
### Immediate Actions (Week 1)
|
||||
|
||||
1. **Test deployments** in development environment
|
||||
2. **Validate configuration** with your team
|
||||
3. **Update CI/CD pipelines** to use Kustomize commands
|
||||
4. **Train team members** on new deployment process
|
||||
|
||||
### Short-term Goals (Month 1)
|
||||
|
||||
1. **Production deployment**: Schedule maintenance window
|
||||
2. **Secret management**: Implement external secret store
|
||||
- Consider: External Secrets Operator, HashiCorp Vault, or cloud-native solutions
|
||||
3. **Monitoring integration**: Connect with your monitoring stack
|
||||
4. **Documentation updates**: Update runbooks and procedures
|
||||
|
||||
### Long-term Goals (Quarter 1)
|
||||
|
||||
1. **GitOps implementation**: Set up ArgoCD or Flux
|
||||
2. **Multi-environment**: Add staging environment overlay
|
||||
3. **Advanced features**: Implement blue-green or canary deployments
|
||||
4. **Cleanup**: Remove legacy shell scripts after validation
|
||||
|
||||
### Adding New Environments
|
||||
|
||||
To add a staging environment:
|
||||
|
||||
```bash
|
||||
# 1. Create staging overlay
|
||||
mkdir -p k8s-kustomize/overlays/staging
|
||||
|
||||
# 2. Create staging kustomization.yaml
|
||||
cat > k8s-kustomize/overlays/staging/kustomization.yaml << EOF
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
namespace: rxminder-staging
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
environment: staging
|
||||
|
||||
images:
|
||||
- name: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newTag: staging
|
||||
EOF
|
||||
|
||||
# 3. Add Makefile targets
|
||||
# Add to Makefile:
|
||||
# deploy-staging: kustomize-deploy-staging
|
||||
# kustomize-deploy-staging:
|
||||
# kubectl apply -k k8s-kustomize/overlays/staging
|
||||
```
|
||||
|
||||
## Migration Path Options
|
||||
|
||||
### Option 1: Gradual Migration (Recommended)
|
||||
|
||||
1. ✅ **Development first**: Already completed and tested
|
||||
2. **Staging next**: Validate with staging workloads
|
||||
3. **Production last**: Schedule maintenance window for production migration
|
||||
|
||||
### Option 2: Parallel Running
|
||||
|
||||
1. **Keep both systems**: Run legacy and Kustomize side-by-side
|
||||
2. **Test extensively**: Validate Kustomize deployments
|
||||
3. **Switch over**: Move traffic from legacy to Kustomize deployments
|
||||
|
||||
### Option 3: Feature Flag Approach
|
||||
|
||||
1. **Environment variable**: Control deployment method via feature flag
|
||||
2. **Gradual rollout**: Enable Kustomize for percentage of deployments
|
||||
3. **Full migration**: Switch to 100% Kustomize when validated
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise, legacy deployment is still available:
|
||||
|
||||
```bash
|
||||
# Emergency rollback procedures
|
||||
make undeploy-dev # Remove development deployment
|
||||
make undeploy-prod # Remove production deployment
|
||||
# Then redeploy using current configurations
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before production migration:
|
||||
|
||||
- [ ] Development deployment working correctly
|
||||
- [ ] All services accessible and functional
|
||||
- [ ] Database persistence working
|
||||
- [ ] Ingress and networking functional
|
||||
- [ ] Resource limits appropriate
|
||||
- [ ] Security policies working
|
||||
- [ ] Monitoring and logging operational
|
||||
- [ ] Team trained on new commands
|
||||
- [ ] CI/CD updated
|
||||
- [ ] Rollback plan tested
|
||||
|
||||
## Success Metrics
|
||||
|
||||
Track these metrics to validate the migration success:
|
||||
|
||||
- **Deployment time**: Should be faster and more reliable
|
||||
- **Error rate**: Fewer deployment failures
|
||||
- **Time to recovery**: Faster rollbacks and fixes
|
||||
- **Team productivity**: Easier environment management
|
||||
- **Configuration drift**: Better consistency across environments
|
||||
|
||||
## Support & Resources
|
||||
|
||||
- **Documentation**: `k8s-kustomize/README.md`
|
||||
- **Troubleshooting**: Check events and logs with provided commands
|
||||
- **Validation**: Use `make validate-kustomize` for configuration checks
|
||||
- **Testing**: Use dry-run commands before actual deployments
|
||||
|
||||
## Conclusion
|
||||
|
||||
The Kustomize migration provides a robust foundation for scaling your Kubernetes deployments. The new system offers:
|
||||
|
||||
- **Maintainability**: Clear separation of concerns
|
||||
- **Scalability**: Easy to add new environments
|
||||
- **Security**: Production-grade security configurations
|
||||
- **Reliability**: Better validation and error handling
|
||||
- **Team Efficiency**: Standardized, well-documented processes
|
||||
|
||||
The legacy shell script approach is still available as a fallback, ensuring zero downtime during the migration period. Take your time to validate the new system thoroughly before fully committing to the migration.
|
||||
|
||||
**Happy deploying! 🚀**
|
||||
|
||||
---
|
||||
|
||||
_This migration was completed on $(date). For questions or issues, refer to the troubleshooting section in the README or consult your team's Kubernetes documentation._
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
# Docker Bake configuration for RxMinder
|
||||
# Provides advanced multi-platform build configuration
|
||||
|
||||
variable "DOCKER_REGISTRY" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "DOCKER_TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "APP_NAME" {
|
||||
default = "RxMinder"
|
||||
}
|
||||
|
||||
variable "NODE_ENV" {
|
||||
default = "production"
|
||||
}
|
||||
|
||||
# Get git information for tagging
|
||||
function "git_hash" {
|
||||
params = []
|
||||
result = notequal("", GIT_COMMIT) ? substr(GIT_COMMIT, 0, 7) : "unknown"
|
||||
}
|
||||
|
||||
function "git_branch" {
|
||||
params = []
|
||||
result = notequal("", GIT_BRANCH) ? replace(GIT_BRANCH, "/", "-") : "unknown"
|
||||
}
|
||||
|
||||
# Main target group
|
||||
group "default" {
|
||||
targets = ["app"]
|
||||
}
|
||||
|
||||
# Production target group
|
||||
group "production" {
|
||||
targets = ["app-prod"]
|
||||
}
|
||||
|
||||
# Development target group
|
||||
group "development" {
|
||||
targets = ["app-dev"]
|
||||
}
|
||||
|
||||
# All targets group
|
||||
group "all" {
|
||||
targets = ["app", "app-dev", "app-prod"]
|
||||
}
|
||||
|
||||
# Base application target
|
||||
target "app" {
|
||||
dockerfile = "docker/Dockerfile"
|
||||
contexts = {
|
||||
src = "."
|
||||
}
|
||||
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
|
||||
args = {
|
||||
APP_NAME = APP_NAME
|
||||
NODE_ENV = NODE_ENV
|
||||
VITE_COUCHDB_URL = "http://couchdb:5984"
|
||||
VITE_COUCHDB_USER = "admin"
|
||||
VITE_COUCHDB_PASSWORD = "change-this-secure-password"
|
||||
APP_BASE_URL = "http://localhost:8080"
|
||||
VITE_GOOGLE_CLIENT_ID = ""
|
||||
VITE_GITHUB_CLIENT_ID = ""
|
||||
MAILGUN_API_KEY = ""
|
||||
MAILGUN_DOMAIN = ""
|
||||
MAILGUN_FROM_EMAIL = ""
|
||||
}
|
||||
|
||||
tags = [
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:${DOCKER_TAG}" : "rxminder:${DOCKER_TAG}",
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:latest" : "rxminder:latest",
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:${git_hash()}" : "rxminder:${git_hash()}"
|
||||
]
|
||||
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "RxMinder"
|
||||
"org.opencontainers.image.description" = "Medication reminder application"
|
||||
"org.opencontainers.image.version" = DOCKER_TAG
|
||||
"org.opencontainers.image.revision" = git_hash()
|
||||
"org.opencontainers.image.source" = "https://github.com/username/rxminder"
|
||||
"org.opencontainers.image.created" = timestamp()
|
||||
"org.opencontainers.image.licenses" = "MIT"
|
||||
}
|
||||
|
||||
cache-from = [
|
||||
"type=gha"
|
||||
]
|
||||
|
||||
cache-to = [
|
||||
"type=gha,mode=max"
|
||||
]
|
||||
}
|
||||
|
||||
# Production-specific target
|
||||
target "app-prod" {
|
||||
inherits = ["app"]
|
||||
|
||||
args = {
|
||||
APP_NAME = APP_NAME
|
||||
NODE_ENV = "production"
|
||||
VITE_COUCHDB_URL = "https://your-production-couchdb.com"
|
||||
VITE_COUCHDB_USER = "admin"
|
||||
VITE_COUCHDB_PASSWORD = "secure-production-password"
|
||||
APP_BASE_URL = "https://your-domain.com"
|
||||
VITE_GOOGLE_CLIENT_ID = ""
|
||||
VITE_GITHUB_CLIENT_ID = ""
|
||||
MAILGUN_API_KEY = ""
|
||||
MAILGUN_DOMAIN = ""
|
||||
MAILGUN_FROM_EMAIL = ""
|
||||
}
|
||||
|
||||
tags = [
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:prod-${DOCKER_TAG}" : "rxminder:prod-${DOCKER_TAG}",
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:prod-latest" : "rxminder:prod-latest",
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:prod-${git_hash()}" : "rxminder:prod-${git_hash()}"
|
||||
]
|
||||
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "RxMinder Production"
|
||||
"org.opencontainers.image.description" = "Medication reminder application - Production build"
|
||||
"org.opencontainers.image.version" = DOCKER_TAG
|
||||
"org.opencontainers.image.revision" = git_hash()
|
||||
"org.opencontainers.image.source" = "https://github.com/username/rxminder"
|
||||
"org.opencontainers.image.created" = timestamp()
|
||||
"org.opencontainers.image.licenses" = "MIT"
|
||||
"build.environment" = "production"
|
||||
}
|
||||
}
|
||||
|
||||
# Development-specific target
|
||||
target "app-dev" {
|
||||
inherits = ["app"]
|
||||
|
||||
args = {
|
||||
APP_NAME = APP_NAME
|
||||
NODE_ENV = "development"
|
||||
VITE_COUCHDB_URL = "http://localhost:5984"
|
||||
VITE_COUCHDB_USER = "admin"
|
||||
VITE_COUCHDB_PASSWORD = "change-this-secure-password"
|
||||
APP_BASE_URL = "http://localhost:8080"
|
||||
VITE_GOOGLE_CLIENT_ID = ""
|
||||
VITE_GITHUB_CLIENT_ID = ""
|
||||
MAILGUN_API_KEY = ""
|
||||
MAILGUN_DOMAIN = ""
|
||||
MAILGUN_FROM_EMAIL = ""
|
||||
}
|
||||
|
||||
tags = [
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:dev-${DOCKER_TAG}" : "rxminder:dev-${DOCKER_TAG}",
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:dev-latest" : "rxminder:dev-latest",
|
||||
notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:dev-${git_hash()}" : "rxminder:dev-${git_hash()}"
|
||||
]
|
||||
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "RxMinder Development"
|
||||
"org.opencontainers.image.description" = "Medication reminder application - Development build"
|
||||
"org.opencontainers.image.version" = DOCKER_TAG
|
||||
"org.opencontainers.image.revision" = git_hash()
|
||||
"org.opencontainers.image.source" = "https://github.com/username/rxminder"
|
||||
"org.opencontainers.image.created" = timestamp()
|
||||
"org.opencontainers.image.licenses" = "MIT"
|
||||
"build.environment" = "development"
|
||||
}
|
||||
}
|
||||
|
||||
# Local development target (single platform)
|
||||
target "app-local" {
|
||||
inherits = ["app-dev"]
|
||||
|
||||
platforms = ["linux/amd64"]
|
||||
|
||||
tags = [
|
||||
"rxminder:local",
|
||||
"rxminder:dev-local"
|
||||
]
|
||||
|
||||
output = ["type=docker"]
|
||||
}
|
||||
|
||||
# Testing target
|
||||
target "app-test" {
|
||||
inherits = ["app"]
|
||||
|
||||
args = {
|
||||
APP_NAME = "RxMinder-Test"
|
||||
NODE_ENV = "test"
|
||||
VITE_COUCHDB_URL = "http://localhost:5984"
|
||||
VITE_COUCHDB_USER = "admin"
|
||||
VITE_COUCHDB_PASSWORD = "test-password"
|
||||
APP_BASE_URL = "http://localhost:8080"
|
||||
}
|
||||
|
||||
tags = [
|
||||
"rxminder:test",
|
||||
"rxminder:test-${git_hash()}"
|
||||
]
|
||||
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "RxMinder Test"
|
||||
"org.opencontainers.image.description" = "Medication reminder application - Test build"
|
||||
"build.environment" = "test"
|
||||
}
|
||||
|
||||
output = ["type=docker"]
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
bun-debug.log*
|
||||
|
||||
# Build output
|
||||
dist
|
||||
build
|
||||
|
||||
# Environment files (security)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Development files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Version control
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
README_*.md
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
||||
LICENSE
|
||||
docs/
|
||||
|
||||
# Docker files (avoid recursion)
|
||||
Dockerfile
|
||||
docker-compose.yaml
|
||||
.dockerignore
|
||||
|
||||
# Scripts and testing (not needed in container)
|
||||
scripts/
|
||||
tests/
|
||||
coverage/
|
||||
**/__tests__
|
||||
**/*.test.*
|
||||
**/*.spec.*
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
.tmp
|
||||
|
||||
# CouchDB data
|
||||
couchdb-data/
|
||||
|
||||
# Scripts (not needed in container)
|
||||
setup.sh
|
||||
deploy.sh
|
||||
deploy-k8s.sh
|
||||
validate-env.sh
|
||||
validate-deployment.sh
|
||||
|
||||
# Kubernetes manifests
|
||||
k8s/
|
||||
@@ -1,18 +0,0 @@
|
||||
# Docker environment for development
|
||||
# Generated on: 2025-09-09T03:27:30.600Z
|
||||
|
||||
# Container Configuration
|
||||
DOCKER_IMAGE=gitea-http.taildb3494.ts.net/will/meds:latest
|
||||
CONTAINER_REGISTRY=gitea-http.taildb3494.ts.net
|
||||
CONTAINER_REPOSITORY=will/meds
|
||||
CONTAINER_TAG=latest
|
||||
|
||||
# Application Configuration
|
||||
APP_NAME=rxminder
|
||||
NODE_ENV=development
|
||||
PORT=5173
|
||||
|
||||
# Database Configuration
|
||||
COUCHDB_URL=http://rxminder-couchdb-service:5984
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=L7tfqHyg0T4sIYiWK
|
||||
@@ -1,116 +0,0 @@
|
||||
# check=skip=SecretsUsedInArgOrEnv
|
||||
# Multi-stage Docker build for RxMinder application
|
||||
# Uses centralized configuration and follows security best practices
|
||||
|
||||
# Build stage
|
||||
FROM oven/bun:alpine AS builder
|
||||
|
||||
# Install system dependencies for native modules
|
||||
RUN apk add --no-cache python3 make gcc g++ musl-dev gettext
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup -g 1001 -S nodeuser && adduser -S nodeuser -u 1001 -G nodeuser
|
||||
|
||||
# Create and set permissions for the working directory
|
||||
RUN mkdir -p /app && chown -R nodeuser:nodeuser /app
|
||||
WORKDIR /app
|
||||
USER nodeuser
|
||||
|
||||
# Copy package files first for better Docker layer caching
|
||||
COPY --chown=nodeuser:nodeuser package.json ./
|
||||
COPY --chown=nodeuser:nodeuser bun.lock ./
|
||||
|
||||
# Install dependencies
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
# Copy source code
|
||||
COPY --chown=nodeuser:nodeuser . ./
|
||||
|
||||
# Build arguments for environment configuration
|
||||
# Application Configuration
|
||||
ARG APP_NAME=RxMinder
|
||||
ARG APP_VERSION=1.0.0
|
||||
ARG APP_BASE_URL=http://localhost:5173
|
||||
|
||||
# Database Configuration
|
||||
ARG VITE_COUCHDB_URL=http://localhost:5984
|
||||
ARG VITE_COUCHDB_USER=admin
|
||||
ARG VITE_COUCHDB_PASSWORD=change-this-secure-password
|
||||
|
||||
# Authentication Configuration
|
||||
ARG JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
||||
ARG SESSION_SECRET=your-super-secret-session-key-change-in-production
|
||||
|
||||
# Email Configuration (Optional)
|
||||
ARG VITE_MAILGUN_API_KEY=""
|
||||
ARG VITE_MAILGUN_DOMAIN=""
|
||||
ARG VITE_MAILGUN_FROM_NAME="RxMinder"
|
||||
ARG VITE_MAILGUN_FROM_EMAIL=""
|
||||
|
||||
# OAuth Configuration (Optional)
|
||||
ARG VITE_GOOGLE_CLIENT_ID=""
|
||||
ARG VITE_GITHUB_CLIENT_ID=""
|
||||
|
||||
# Feature Flags
|
||||
ARG ENABLE_EMAIL_VERIFICATION=true
|
||||
ARG ENABLE_OAUTH=true
|
||||
ARG ENABLE_ADMIN_INTERFACE=true
|
||||
ARG DEBUG_MODE=false
|
||||
|
||||
# Build Environment
|
||||
ARG NODE_ENV=production
|
||||
|
||||
# Set environment variables for build process
|
||||
# These are embedded into the static build at compile time
|
||||
ENV VITE_APP_NAME=$APP_NAME
|
||||
ENV APP_VERSION=$APP_VERSION
|
||||
ENV APP_BASE_URL=$APP_BASE_URL
|
||||
ENV VITE_COUCHDB_URL=$VITE_COUCHDB_URL
|
||||
ENV VITE_COUCHDB_USER=$VITE_COUCHDB_USER
|
||||
ENV VITE_COUCHDB_PASSWORD=$VITE_COUCHDB_PASSWORD
|
||||
ENV JWT_SECRET=$JWT_SECRET
|
||||
ENV VITE_MAILGUN_API_KEY=$VITE_MAILGUN_API_KEY
|
||||
ENV VITE_MAILGUN_DOMAIN=$VITE_MAILGUN_DOMAIN
|
||||
ENV VITE_MAILGUN_FROM_NAME=$VITE_MAILGUN_FROM_NAME
|
||||
ENV VITE_MAILGUN_FROM_EMAIL=$VITE_MAILGUN_FROM_EMAIL
|
||||
ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID
|
||||
ENV VITE_GITHUB_CLIENT_ID=$VITE_GITHUB_CLIENT_ID
|
||||
ENV ENABLE_EMAIL_VERIFICATION=$ENABLE_EMAIL_VERIFICATION
|
||||
ENV ENABLE_OAUTH=$ENABLE_OAUTH
|
||||
ENV ENABLE_ADMIN_INTERFACE=$ENABLE_ADMIN_INTERFACE
|
||||
ENV DEBUG_MODE=$DEBUG_MODE
|
||||
ENV NODE_ENV=$NODE_ENV
|
||||
|
||||
# Process HTML template with APP_NAME
|
||||
RUN envsubst '$APP_NAME' < index.html.template > index.html || cp index.html.template index.html
|
||||
|
||||
# Build the application
|
||||
RUN bun run build
|
||||
|
||||
# Production stage - serve with nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Install wget for health checks
|
||||
RUN apk add --no-cache wget
|
||||
|
||||
# Copy built files from builder stage
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy nginx configuration
|
||||
COPY --from=builder /app/docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Set proper permissions for nginx
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
chown -R nginx:nginx /etc/nginx/conf.d
|
||||
|
||||
# Add health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
|
||||
|
||||
# Expose port 80
|
||||
EXPOSE 80
|
||||
|
||||
# Start nginx (runs as nginx user by default in alpine)
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,76 +0,0 @@
|
||||
# 🐳 Docker Configuration
|
||||
|
||||
This directory contains all Docker and containerization-related files for RxMinder.
|
||||
|
||||
## Files
|
||||
|
||||
- **`Dockerfile`** - Multi-stage Docker build configuration with buildx support
|
||||
- **`docker-compose.yaml`** - Service orchestration with multi-platform support
|
||||
- **`docker-bake.hcl`** - Advanced buildx configuration for multi-platform builds
|
||||
- **`nginx.conf`** - Production web server configuration
|
||||
- **`.dockerignore`** - Files and directories to exclude from Docker build context
|
||||
|
||||
## Docker Buildx Support
|
||||
|
||||
This project now supports Docker Buildx for multi-platform builds (AMD64 and ARM64).
|
||||
|
||||
### Quick Start with Buildx
|
||||
|
||||
```bash
|
||||
# Setup buildx builder (run once)
|
||||
../scripts/buildx-helper.sh setup
|
||||
|
||||
# Build for local platform only (faster for development)
|
||||
../scripts/buildx-helper.sh build-local
|
||||
|
||||
# Build for multiple platforms
|
||||
../scripts/buildx-helper.sh build-multi
|
||||
|
||||
# Build and push to registry
|
||||
../scripts/buildx-helper.sh push docker.io/username latest
|
||||
|
||||
# Build using Docker Bake (advanced)
|
||||
../scripts/buildx-helper.sh bake
|
||||
```
|
||||
|
||||
### Manual Buildx Commands
|
||||
|
||||
```bash
|
||||
# Create and use buildx builder
|
||||
docker buildx create --name rxminder-builder --driver docker-container --bootstrap --use
|
||||
|
||||
# Build for multiple platforms
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t rxminder:latest --load .
|
||||
|
||||
# Build with bake file
|
||||
docker buildx bake -f docker-bake.hcl
|
||||
```
|
||||
|
||||
## Traditional Usage
|
||||
|
||||
From the project root directory:
|
||||
|
||||
```bash
|
||||
# Build and start services
|
||||
docker compose -f docker/docker-compose.yaml up -d
|
||||
|
||||
# View logs
|
||||
docker compose -f docker/docker-compose.yaml logs
|
||||
|
||||
# Stop services
|
||||
docker compose -f docker/docker-compose.yaml down
|
||||
```
|
||||
|
||||
## Build Process
|
||||
|
||||
The Dockerfile uses a multi-stage build:
|
||||
|
||||
1. **Builder stage**: Installs dependencies and builds the React app
|
||||
2. **Production stage**: Serves the built app with nginx
|
||||
|
||||
## Services
|
||||
|
||||
- **frontend**: React application served by nginx
|
||||
- **couchdb**: Database for medication and user data
|
||||
|
||||
Both services include health checks and proper security configurations.
|
||||
@@ -1,101 +0,0 @@
|
||||
# Docker Bake file for advanced multi-platform builds
|
||||
# Usage: docker buildx bake -f docker-bake.hcl
|
||||
|
||||
variable "TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "REGISTRY" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "VITE_COUCHDB_URL" {
|
||||
default = "http://localhost:5984"
|
||||
}
|
||||
|
||||
variable "VITE_COUCHDB_USER" {
|
||||
default = "admin"
|
||||
}
|
||||
|
||||
variable "VITE_COUCHDB_PASSWORD" {
|
||||
default = "change-this-secure-password"
|
||||
}
|
||||
|
||||
variable "APP_BASE_URL" {
|
||||
default = "http://localhost:8080"
|
||||
}
|
||||
|
||||
variable "VITE_GOOGLE_CLIENT_ID" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "VITE_GITHUB_CLIENT_ID" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["app"]
|
||||
}
|
||||
|
||||
target "app" {
|
||||
dockerfile = "Dockerfile"
|
||||
context = "."
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
|
||||
tags = [
|
||||
"${REGISTRY}rxminder:${TAG}",
|
||||
"${REGISTRY}rxminder:latest"
|
||||
]
|
||||
|
||||
args = {
|
||||
# CouchDB Configuration
|
||||
VITE_COUCHDB_URL = "${VITE_COUCHDB_URL}"
|
||||
VITE_COUCHDB_USER = "${VITE_COUCHDB_USER}"
|
||||
VITE_COUCHDB_PASSWORD = "${VITE_COUCHDB_PASSWORD}"
|
||||
|
||||
# Application Configuration
|
||||
APP_BASE_URL = "${APP_BASE_URL}"
|
||||
|
||||
# OAuth Configuration (Optional)
|
||||
VITE_GOOGLE_CLIENT_ID = "${VITE_GOOGLE_CLIENT_ID}"
|
||||
VITE_GITHUB_CLIENT_ID = "${VITE_GITHUB_CLIENT_ID}"
|
||||
|
||||
# Build environment
|
||||
NODE_ENV = "production"
|
||||
}
|
||||
|
||||
# Advanced buildx features
|
||||
cache-from = [
|
||||
"type=gha",
|
||||
"type=registry,ref=${REGISTRY}rxminder:buildcache"
|
||||
]
|
||||
|
||||
cache-to = [
|
||||
"type=gha,mode=max",
|
||||
"type=registry,ref=${REGISTRY}rxminder:buildcache,mode=max"
|
||||
]
|
||||
|
||||
# Attestations for supply chain security
|
||||
attest = [
|
||||
"type=provenance,mode=max",
|
||||
"type=sbom"
|
||||
]
|
||||
}
|
||||
|
||||
# Development target for faster local builds
|
||||
target "dev" {
|
||||
inherits = ["app"]
|
||||
platforms = ["linux/amd64"]
|
||||
tags = ["rxminder:dev"]
|
||||
cache-from = ["type=gha"]
|
||||
cache-to = ["type=gha,mode=max"]
|
||||
}
|
||||
|
||||
# Production target with registry push
|
||||
target "prod" {
|
||||
inherits = ["app"]
|
||||
output = ["type=registry"]
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
services:
|
||||
# Frontend service
|
||||
frontend:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
args:
|
||||
# Application Configuration
|
||||
- APP_NAME=${APP_NAME:-RxMinder}
|
||||
# CouchDB Configuration
|
||||
- VITE_COUCHDB_URL=${VITE_COUCHDB_URL:-http://couchdb:5984}
|
||||
- VITE_COUCHDB_USER=${VITE_COUCHDB_USER:-admin}
|
||||
- VITE_COUCHDB_PASSWORD=${VITE_COUCHDB_PASSWORD:-change-this-secure-password}
|
||||
# Application Configuration
|
||||
- APP_BASE_URL=${APP_BASE_URL:-http://localhost:8080}
|
||||
# OAuth Configuration (Optional)
|
||||
- VITE_GOOGLE_CLIENT_ID=${VITE_GOOGLE_CLIENT_ID:-}
|
||||
- VITE_GITHUB_CLIENT_ID=${VITE_GITHUB_CLIENT_ID:-}
|
||||
# Build Environment
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
# Enable buildx for multi-platform builds
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
ports:
|
||||
- '8080:80'
|
||||
depends_on:
|
||||
couchdb:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
# Health check for the frontend container
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost/']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
labels:
|
||||
- 'monitoring=true'
|
||||
- 'service=frontend'
|
||||
- 'app=${APP_NAME:-meds}'
|
||||
|
||||
# CouchDB service
|
||||
couchdb:
|
||||
image: couchdb:3.3.2
|
||||
volumes:
|
||||
- ./couchdb-data:/opt/couchdb/data
|
||||
environment:
|
||||
- COUCHDB_USER=${COUCHDB_USER:-admin}
|
||||
- COUCHDB_PASSWORD=${COUCHDB_PASSWORD:-change-this-secure-password}
|
||||
ports:
|
||||
- '5984:5984'
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:5984/_up']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
labels:
|
||||
- 'monitoring=true'
|
||||
- 'service=couchdb'
|
||||
- 'app=${APP_NAME:-meds}'
|
||||
|
||||
# Redis service (commented out as per requirements)
|
||||
# redis:
|
||||
# image: redis:alpine
|
||||
# restart: unless-stopped
|
||||
# labels:
|
||||
# - "monitoring=true"
|
||||
# - "service=redis"
|
||||
@@ -1,36 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Enable gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||
|
||||
# Handle client-side routing
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
# Quick Deployment Guide
|
||||
|
||||
This guide provides simple, practical deployment options for the RxMinder application, ranging from local development to production deployment.
|
||||
|
||||
## 🚀 Instant Local Deployment
|
||||
|
||||
For immediate testing and development, use the local Docker deployment:
|
||||
|
||||
```bash
|
||||
# Deploy locally with Docker (includes database)
|
||||
make deploy-local
|
||||
|
||||
# Access the application
|
||||
open http://localhost:8080
|
||||
|
||||
# Stop when done
|
||||
make stop-local
|
||||
```
|
||||
|
||||
This deployment:
|
||||
|
||||
- ✅ Includes CouchDB database
|
||||
- ✅ Uses development configuration (safe defaults)
|
||||
- ✅ Accessible at <http://localhost:8080>
|
||||
- ✅ No additional configuration required
|
||||
- ✅ Perfect for testing and development
|
||||
|
||||
## 📋 Deployment Options Overview
|
||||
|
||||
| Method | Use Case | Requirements | URL | Command |
|
||||
| ----------------------- | ------------------------------ | -------------------- | ----------------------- | ----------------------------- |
|
||||
| **Development Server** | Active development | Node.js/Bun | <http://localhost:5173> | `make dev` |
|
||||
| **Local Docker** | Testing/Demo | Docker | <http://localhost:8080> | `make deploy-local` |
|
||||
| **Production (Auto)** | Production with auto-secrets | Kubernetes cluster | Configured domain | `make deploy-prod-quick` |
|
||||
| **Production (Manual)** | Production with custom secrets | Kubernetes + secrets | Configured domain | `make deploy-prod-configured` |
|
||||
|
||||
## 🔧 Development Deployment
|
||||
|
||||
### Option 1: Development Server (Fastest)
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
make install
|
||||
|
||||
# Start development server
|
||||
make dev
|
||||
```
|
||||
|
||||
- Hot reloading enabled
|
||||
- Access at <http://localhost:5173>
|
||||
- Uses mock database (in-memory)
|
||||
- Best for active development
|
||||
|
||||
### Option 2: Built Application Preview
|
||||
|
||||
```bash
|
||||
# Build and preview
|
||||
make build
|
||||
make preview
|
||||
```
|
||||
|
||||
- Simulates production build
|
||||
- Access at <http://localhost:4173>
|
||||
- Uses mock database
|
||||
- Good for testing build process
|
||||
|
||||
## 🐳 Docker Deployment
|
||||
|
||||
### Local Docker Deployment (Recommended for Testing)
|
||||
|
||||
```bash
|
||||
# Deploy with Docker Compose
|
||||
make deploy-local
|
||||
```
|
||||
|
||||
**What this includes:**
|
||||
|
||||
- Frontend application (Nginx)
|
||||
- CouchDB database with persistent data
|
||||
- Health checks and monitoring
|
||||
- Automatic restart policies
|
||||
|
||||
**Access:**
|
||||
|
||||
- Application: <http://localhost:8080>
|
||||
- CouchDB Admin: <http://localhost:5984/_utils>
|
||||
|
||||
**Management:**
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker-compose -f docker/docker-compose.yaml logs -f
|
||||
|
||||
# Stop deployment
|
||||
make stop-local
|
||||
|
||||
# Restart deployment
|
||||
make deploy-local
|
||||
|
||||
# Clean up completely
|
||||
make docker-clean
|
||||
```
|
||||
|
||||
### Docker Compose Manual Commands
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
docker-compose -f docker/docker-compose.yaml up -d
|
||||
|
||||
# Stop services
|
||||
docker-compose -f docker/docker-compose.yaml down
|
||||
|
||||
# View logs
|
||||
docker-compose -f docker/docker-compose.yaml logs -f frontend
|
||||
docker-compose -f docker/docker-compose.yaml logs -f couchdb
|
||||
|
||||
# Rebuild and restart
|
||||
docker-compose -f docker/docker-compose.yaml up -d --build
|
||||
```
|
||||
|
||||
## ☸️ Kubernetes Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Kubernetes cluster** with kubectl configured
|
||||
2. **Cluster admin access** to create namespaces
|
||||
3. **Ingress controller** (optional, for external access)
|
||||
|
||||
### Create Required Namespaces
|
||||
|
||||
```bash
|
||||
# Create development namespace
|
||||
kubectl create namespace rxminder-dev
|
||||
|
||||
# Create production namespace
|
||||
kubectl create namespace rxminder-prod
|
||||
```
|
||||
|
||||
### Development Deployment
|
||||
|
||||
```bash
|
||||
# Deploy to development environment
|
||||
make deploy-dev
|
||||
|
||||
# Check status
|
||||
make status-dev
|
||||
|
||||
# View logs
|
||||
kubectl logs -n rxminder-dev -l app=rxminder
|
||||
|
||||
# Remove deployment
|
||||
make undeploy-dev
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
#### Option A: Auto-Generated Secrets (Quick)
|
||||
|
||||
```bash
|
||||
# Deploy with automatically generated secrets
|
||||
make deploy-prod-quick
|
||||
```
|
||||
|
||||
#### Option B: Custom Configuration (Recommended)
|
||||
|
||||
```bash
|
||||
# Set your production secrets
|
||||
export JWT_SECRET="your-secure-jwt-secret-here"
|
||||
export SESSION_SECRET="your-secure-session-secret-here"
|
||||
export VITE_COUCHDB_URL="https://your-couchdb-instance.com"
|
||||
|
||||
# Deploy with your configuration
|
||||
make deploy-prod-configured
|
||||
```
|
||||
|
||||
#### Option C: Using Configuration Files
|
||||
|
||||
```bash
|
||||
# Generate production configuration
|
||||
make generate-config-prod
|
||||
|
||||
# Edit the generated files
|
||||
nano .env.production
|
||||
|
||||
# Deploy
|
||||
make build-prod
|
||||
make deploy-prod
|
||||
```
|
||||
|
||||
### Kubernetes Management
|
||||
|
||||
```bash
|
||||
# Check deployment status
|
||||
make status-prod
|
||||
make status-dev
|
||||
|
||||
# View differences before applying
|
||||
make diff-prod
|
||||
make diff-dev
|
||||
|
||||
# Validate configurations
|
||||
make validate-k8s
|
||||
|
||||
# Remove deployments
|
||||
make undeploy-prod
|
||||
make undeploy-dev
|
||||
make undeploy-all
|
||||
```
|
||||
|
||||
## 🔒 Production Configuration
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
For production deployment, you must set:
|
||||
|
||||
```bash
|
||||
# Authentication (Required)
|
||||
JWT_SECRET="your-secure-32-character-secret"
|
||||
SESSION_SECRET="your-secure-32-character-secret"
|
||||
|
||||
# Database (Required for persistence)
|
||||
VITE_COUCHDB_URL="https://your-couchdb.com"
|
||||
COUCHDB_USER="your-username"
|
||||
COUCHDB_PASSWORD="your-password"
|
||||
```
|
||||
|
||||
### Optional Configuration
|
||||
|
||||
```bash
|
||||
# Email notifications
|
||||
MAILGUN_API_KEY="your-mailgun-key"
|
||||
MAILGUN_DOMAIN="your-domain.com"
|
||||
|
||||
# OAuth authentication
|
||||
GOOGLE_CLIENT_ID="your-google-client-id"
|
||||
GITHUB_CLIENT_ID="your-github-client-id"
|
||||
```
|
||||
|
||||
### Generate Secure Secrets
|
||||
|
||||
```bash
|
||||
# Generate secure secrets
|
||||
JWT_SECRET=$(openssl rand -base64 32)
|
||||
SESSION_SECRET=$(openssl rand -base64 32)
|
||||
|
||||
echo "JWT_SECRET=$JWT_SECRET"
|
||||
echo "SESSION_SECRET=$SESSION_SECRET"
|
||||
```
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Docker Issues
|
||||
|
||||
```bash
|
||||
# If deployment fails
|
||||
make docker-clean
|
||||
make deploy-local
|
||||
|
||||
# If ports are in use
|
||||
docker-compose -f docker/docker-compose.yaml down
|
||||
# Wait a moment, then retry
|
||||
make deploy-local
|
||||
```
|
||||
|
||||
#### Build Issues
|
||||
|
||||
```bash
|
||||
# For production build failures
|
||||
# Check that secrets are set
|
||||
echo $JWT_SECRET
|
||||
echo $SESSION_SECRET
|
||||
|
||||
# Try development build instead
|
||||
make build
|
||||
```
|
||||
|
||||
#### Kubernetes Issues
|
||||
|
||||
```bash
|
||||
# If namespace doesn't exist
|
||||
kubectl create namespace rxminder-dev
|
||||
kubectl create namespace rxminder-prod
|
||||
|
||||
# If deployment fails
|
||||
kubectl get pods -n rxminder-dev
|
||||
kubectl logs -n rxminder-dev <pod-name>
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
|
||||
#### Local Docker Deployment
|
||||
|
||||
```bash
|
||||
# Check if services are running
|
||||
docker-compose -f docker/docker-compose.yaml ps
|
||||
|
||||
# Test frontend
|
||||
curl http://localhost:8080
|
||||
|
||||
# Test CouchDB
|
||||
curl http://localhost:5984/_up
|
||||
```
|
||||
|
||||
#### Kubernetes Deployment
|
||||
|
||||
```bash
|
||||
# Check pod status
|
||||
kubectl get pods -n rxminder-prod
|
||||
|
||||
# Check service status
|
||||
kubectl get services -n rxminder-prod
|
||||
|
||||
# View logs
|
||||
kubectl logs -n rxminder-prod -l app=rxminder
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```bash
|
||||
# View real-time logs
|
||||
docker-compose -f docker/docker-compose.yaml logs -f
|
||||
|
||||
# Monitor resource usage
|
||||
docker stats
|
||||
```
|
||||
|
||||
### Kubernetes Deployment
|
||||
|
||||
```bash
|
||||
# Monitor pods
|
||||
kubectl top pods -n rxminder-prod
|
||||
|
||||
# View events
|
||||
kubectl get events -n rxminder-prod --sort-by='.lastTimestamp'
|
||||
|
||||
# Port forward for local access
|
||||
kubectl port-forward -n rxminder-prod service/rxminder 8080:80
|
||||
```
|
||||
|
||||
## 🔄 Updates and Maintenance
|
||||
|
||||
### Update Local Deployment
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull
|
||||
|
||||
# Rebuild and restart
|
||||
make deploy-local
|
||||
```
|
||||
|
||||
### Update Production Deployment
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull
|
||||
|
||||
# Build and deploy
|
||||
make deploy-prod-quick
|
||||
|
||||
# Or with custom configuration
|
||||
make deploy-prod-configured
|
||||
```
|
||||
|
||||
### Backup Data
|
||||
|
||||
#### Docker CouchDB
|
||||
|
||||
```bash
|
||||
# CouchDB data is stored in docker/couchdb-data/
|
||||
# Backup this directory
|
||||
tar -czf couchdb-backup-$(date +%Y%m%d).tar.gz docker/couchdb-data/
|
||||
```
|
||||
|
||||
#### Kubernetes CouchDB
|
||||
|
||||
```bash
|
||||
# Access CouchDB pod
|
||||
kubectl exec -n rxminder-prod -it <couchdb-pod> -- bash
|
||||
|
||||
# Use CouchDB backup tools
|
||||
# Or backup persistent volume
|
||||
```
|
||||
|
||||
## 🚀 Quick Commands Reference
|
||||
|
||||
```bash
|
||||
# Development
|
||||
make dev # Start development server
|
||||
make build # Build for development
|
||||
make test # Run tests
|
||||
|
||||
# Local deployment
|
||||
make deploy-local # Deploy with Docker
|
||||
make stop-local # Stop Docker deployment
|
||||
|
||||
# Production deployment
|
||||
make deploy-prod-quick # Deploy with auto-generated secrets
|
||||
make deploy-prod-configured # Deploy with custom secrets
|
||||
|
||||
# Management
|
||||
make help # Show all available commands
|
||||
make validate-k8s # Validate Kubernetes configs
|
||||
make docker-clean # Clean up Docker resources
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Need more detailed information?**
|
||||
|
||||
- [Production Build Guide](deployment/PRODUCTION_BUILD.md) - Detailed production configuration
|
||||
- [Docker Configuration](deployment/DOCKER_IMAGE_CONFIGURATION.md) - Docker setup details
|
||||
- [Database Service](development/DATABASE.md) - Database configuration
|
||||
- [Main README](../README.md) - Complete project overview
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** January 2024
|
||||
**Status:** Ready for deployment
|
||||
@@ -1,538 +0,0 @@
|
||||
# Deployment Guide
|
||||
|
||||
## 🚀 Complete Deployment Guide for Medication Reminder App
|
||||
|
||||
### **Prerequisites**
|
||||
|
||||
#### **System Requirements**
|
||||
|
||||
- Docker 20.10+ and Docker Compose 2.0+
|
||||
- 2GB RAM minimum, 4GB recommended
|
||||
- 10GB disk space for application and data
|
||||
- Linux/macOS/Windows with WSL2
|
||||
|
||||
#### **Required Accounts**
|
||||
|
||||
- [Mailgun Account](https://mailgun.com) for email services
|
||||
- Domain name for production deployment (optional)
|
||||
- SSL certificate for HTTPS (recommended)
|
||||
|
||||
### **Environment Setup**
|
||||
|
||||
#### **1. Clone Repository**
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd meds
|
||||
```
|
||||
|
||||
#### **2. Configure Environment**
|
||||
|
||||
```bash
|
||||
# Copy template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit with your credentials
|
||||
nano .env
|
||||
```
|
||||
|
||||
**Required Variables:**
|
||||
|
||||
```bash
|
||||
# Application Configuration
|
||||
APP_BASE_URL=https://yourdomain.com
|
||||
|
||||
# CouchDB Configuration
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=super-secure-password-123!
|
||||
VITE_COUCHDB_URL=http://couchdb:5984
|
||||
VITE_COUCHDB_USER=admin
|
||||
VITE_COUCHDB_PASSWORD=super-secure-password-123!
|
||||
|
||||
# Mailgun Configuration
|
||||
MAILGUN_API_KEY=key-1234567890abcdef1234567890abcdef
|
||||
MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
MAILGUN_FROM_EMAIL=noreply@yourdomain.com
|
||||
|
||||
# Production Settings
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
### **Local Development Deployment**
|
||||
|
||||
#### **Quick Start**
|
||||
|
||||
```bash
|
||||
# Automated setup
|
||||
./setup.sh
|
||||
|
||||
# Manual setup
|
||||
bun install
|
||||
docker compose up -d
|
||||
bun run seed-production.js
|
||||
```
|
||||
|
||||
#### **Development URLs**
|
||||
|
||||
- Frontend: http://localhost:8080
|
||||
- CouchDB: http://localhost:5984
|
||||
- Admin Panel: http://localhost:5984/\_utils
|
||||
|
||||
### **Production Deployment**
|
||||
|
||||
#### **Method 1: Automated Script**
|
||||
|
||||
```bash
|
||||
# Secure deployment with validation
|
||||
./deploy.sh production
|
||||
```
|
||||
|
||||
#### **Method 2: Manual Docker Compose**
|
||||
|
||||
```bash
|
||||
# Build images
|
||||
docker compose build --no-cache
|
||||
|
||||
# Start services
|
||||
docker compose up -d
|
||||
|
||||
# Seed database
|
||||
node seed-production.js
|
||||
|
||||
# Verify deployment
|
||||
bun test-production.js
|
||||
```
|
||||
|
||||
#### **Method 3: Docker Swarm**
|
||||
|
||||
```bash
|
||||
# Initialize swarm
|
||||
docker swarm init
|
||||
|
||||
# Deploy stack
|
||||
docker stack deploy -c docker/docker-compose.yaml meds-stack
|
||||
|
||||
# Scale services
|
||||
docker service scale meds-stack_frontend=3
|
||||
```
|
||||
|
||||
### **Cloud Platform Deployments**
|
||||
|
||||
#### **AWS EC2 Deployment**
|
||||
|
||||
**1. Launch EC2 Instance**
|
||||
|
||||
```bash
|
||||
# Amazon Linux 2 AMI
|
||||
# Instance type: t3.medium or larger
|
||||
# Security group: Allow ports 22, 80, 443, 8080
|
||||
```
|
||||
|
||||
**2. Install Dependencies**
|
||||
|
||||
```bash
|
||||
# Connect to instance
|
||||
ssh -i your-key.pem ec2-user@your-instance-ip
|
||||
|
||||
# Install Docker
|
||||
sudo yum update -y
|
||||
sudo yum install -y docker
|
||||
sudo service docker start
|
||||
sudo usermod -a -G docker ec2-user
|
||||
|
||||
# Install Docker Compose
|
||||
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
```
|
||||
|
||||
**3. Deploy Application**
|
||||
|
||||
```bash
|
||||
# Clone and configure
|
||||
git clone <repository-url>
|
||||
cd meds
|
||||
cp .env.example .env
|
||||
# Edit .env with production values
|
||||
|
||||
# Deploy
|
||||
./deploy.sh production
|
||||
```
|
||||
|
||||
#### **Google Cloud Platform Deployment**
|
||||
|
||||
**1. Cloud Run Deployment**
|
||||
|
||||
```bash
|
||||
# Build and push image
|
||||
gcloud builds submit --tag gcr.io/PROJECT-ID/meds-app
|
||||
|
||||
# Deploy service
|
||||
gcloud run deploy meds-app \
|
||||
--image gcr.io/PROJECT-ID/meds-app \
|
||||
--platform managed \
|
||||
--region us-central1 \
|
||||
--set-env-vars COUCHDB_URL=your-couchdb-url \
|
||||
--set-env-vars MAILGUN_API_KEY=your-key \
|
||||
--allow-unauthenticated
|
||||
```
|
||||
|
||||
**2. Compute Engine Deployment**
|
||||
|
||||
```bash
|
||||
# Create instance
|
||||
gcloud compute instances create meds-server \
|
||||
--image-family debian-11 \
|
||||
--image-project debian-cloud \
|
||||
--machine-type e2-medium \
|
||||
--tags http-server,https-server
|
||||
|
||||
# SSH and install
|
||||
gcloud compute ssh meds-server
|
||||
# Follow standard installation steps
|
||||
```
|
||||
|
||||
#### **Digital Ocean Deployment**
|
||||
|
||||
**1. Droplet Setup**
|
||||
|
||||
```bash
|
||||
# Create droplet with Docker pre-installed
|
||||
# Or install Docker manually on Ubuntu droplet
|
||||
|
||||
# Connect and deploy
|
||||
ssh root@your-droplet-ip
|
||||
git clone <repository-url>
|
||||
cd meds
|
||||
./setup.sh
|
||||
./deploy.sh production
|
||||
```
|
||||
|
||||
**2. App Platform Deployment**
|
||||
|
||||
```bash
|
||||
# Create app.yaml
|
||||
version: 1
|
||||
services:
|
||||
- name: meds-app
|
||||
source_dir: /
|
||||
github:
|
||||
repo: your-username/meds
|
||||
branch: main
|
||||
build_command: bun run build
|
||||
environment_slug: node-js
|
||||
instance_count: 1
|
||||
instance_size_slug: basic-xxs
|
||||
envs:
|
||||
- key: COUCHDB_URL
|
||||
value: ${COUCHDB_URL}
|
||||
- key: MAILGUN_API_KEY
|
||||
value: ${MAILGUN_API_KEY}
|
||||
|
||||
# Deploy
|
||||
doctl apps create --spec app.yaml
|
||||
```
|
||||
|
||||
### **Kubernetes Deployment**
|
||||
|
||||
#### **Method 1: Automated Deployment Script (Recommended)**
|
||||
|
||||
```bash
|
||||
# Configure environment
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings:
|
||||
# INGRESS_HOST=app.meds.192.168.1.100.nip.io # For local cluster
|
||||
# INGRESS_HOST=meds.yourdomain.com # For production
|
||||
|
||||
# Deploy with environment substitution
|
||||
./deploy-k8s.sh
|
||||
|
||||
# Check deployment status
|
||||
./deploy-k8s.sh --status
|
||||
|
||||
# Deploy with custom environment file
|
||||
./deploy-k8s.sh --env .env.production
|
||||
|
||||
# Preview deployment (dry run)
|
||||
./deploy-k8s.sh --dry-run
|
||||
```
|
||||
|
||||
#### **Method 2: Manual Deployment**
|
||||
|
||||
#### **1. Create Namespace and Secrets**
|
||||
|
||||
```bash
|
||||
# Create namespace
|
||||
kubectl create namespace meds-app
|
||||
|
||||
# Create secrets
|
||||
kubectl create secret generic meds-secrets \
|
||||
--from-literal=couchdb-user=admin \
|
||||
--from-literal=couchdb-password=secure-password \
|
||||
--from-literal=mailgun-api-key=your-api-key \
|
||||
--namespace meds-app
|
||||
```
|
||||
|
||||
#### **2. Deploy Services**
|
||||
|
||||
```bash
|
||||
# Apply Kubernetes manifests
|
||||
kubectl apply -f k8s/ --namespace meds-app
|
||||
|
||||
# Check deployment status
|
||||
kubectl get pods -n meds-app
|
||||
kubectl get services -n meds-app
|
||||
```
|
||||
|
||||
#### **3. Configure Ingress (Manual)**
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: meds-ingress
|
||||
namespace: meds-app
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- meds.yourdomain.com
|
||||
secretName: meds-tls
|
||||
rules:
|
||||
- host: meds.yourdomain.com # Update this to your domain
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: meds-frontend
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
### **SSL/HTTPS Configuration**
|
||||
|
||||
#### **Let's Encrypt with Nginx**
|
||||
|
||||
```bash
|
||||
# Install certbot
|
||||
sudo apt-get install certbot python3-certbot-nginx
|
||||
|
||||
# Get certificate
|
||||
sudo certbot --nginx -d yourdomain.com
|
||||
|
||||
# Auto-renewal
|
||||
sudo crontab -e
|
||||
# Add: 0 12 * * * /usr/bin/certbot renew --quiet
|
||||
```
|
||||
|
||||
#### **Cloudflare SSL**
|
||||
|
||||
```bash
|
||||
# Update docker/nginx.conf for Cloudflare
|
||||
# Set ssl_certificate and ssl_certificate_key
|
||||
# Configure Cloudflare for Full (Strict) SSL
|
||||
```
|
||||
|
||||
### **Database Backup and Recovery**
|
||||
|
||||
#### **CouchDB Backup**
|
||||
|
||||
```bash
|
||||
# Create backup script
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="/backup/couchdb"
|
||||
|
||||
# Backup all databases
|
||||
curl -X GET http://admin:password@localhost:5984/_all_dbs | \
|
||||
jq -r '.[]' | while read db; do
|
||||
curl -X GET "http://admin:password@localhost:5984/$db/_all_docs?include_docs=true" \
|
||||
> "$BACKUP_DIR/${db}_${DATE}.json"
|
||||
done
|
||||
```
|
||||
|
||||
#### **Automated Backups**
|
||||
|
||||
```bash
|
||||
# Add to crontab
|
||||
0 2 * * * /opt/meds/backup-couchdb.sh
|
||||
|
||||
# Upload to cloud storage
|
||||
aws s3 cp /backup/couchdb/ s3://your-backup-bucket/ --recursive
|
||||
```
|
||||
|
||||
### **Monitoring and Logging**
|
||||
|
||||
#### **Health Checks**
|
||||
|
||||
```bash
|
||||
# Application health
|
||||
curl -f http://localhost:8080/health
|
||||
|
||||
# CouchDB health
|
||||
curl -f http://admin:password@localhost:5984/_up
|
||||
|
||||
# Docker container health
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
#### **Log Management**
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker compose logs -f frontend
|
||||
docker compose logs -f couchdb
|
||||
|
||||
# Log rotation
|
||||
# Configure in docker/docker-compose.yaml:
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
```
|
||||
|
||||
#### **Performance Monitoring**
|
||||
|
||||
```bash
|
||||
# Resource usage
|
||||
docker stats
|
||||
|
||||
# Application metrics
|
||||
# Implement custom metrics endpoint
|
||||
# Use Prometheus/Grafana for monitoring
|
||||
```
|
||||
|
||||
### **Scaling and Load Balancing**
|
||||
|
||||
#### **Horizontal Scaling**
|
||||
|
||||
```bash
|
||||
# Scale frontend containers
|
||||
docker compose up -d --scale frontend=3
|
||||
|
||||
# Load balancer configuration
|
||||
# Use nginx, HAProxy, or cloud load balancer
|
||||
```
|
||||
|
||||
#### **Database Scaling**
|
||||
|
||||
```bash
|
||||
# CouchDB clustering
|
||||
# Configure multiple CouchDB nodes
|
||||
# Set up replication between nodes
|
||||
```
|
||||
|
||||
### **Security Hardening**
|
||||
|
||||
#### **Firewall Configuration**
|
||||
|
||||
```bash
|
||||
# UFW (Ubuntu)
|
||||
sudo ufw allow 22/tcp
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
sudo ufw deny 5984/tcp # CouchDB admin (internal only)
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
#### **Container Security**
|
||||
|
||||
```bash
|
||||
# Run security scan
|
||||
docker scout cves meds-frontend:latest
|
||||
|
||||
# Update base images regularly
|
||||
docker compose build --no-cache
|
||||
```
|
||||
|
||||
### **Troubleshooting**
|
||||
|
||||
#### **Common Issues**
|
||||
|
||||
**1. Environment Variables Not Loading**
|
||||
|
||||
```bash
|
||||
# Check file format
|
||||
cat -A .env
|
||||
|
||||
# Verify Docker Compose config
|
||||
docker compose config
|
||||
```
|
||||
|
||||
**2. Database Connection Issues**
|
||||
|
||||
```bash
|
||||
# Test CouchDB connection
|
||||
curl -u admin:password http://localhost:5984/
|
||||
|
||||
# Check container logs
|
||||
docker compose logs couchdb
|
||||
```
|
||||
|
||||
**3. Email Not Sending**
|
||||
|
||||
```bash
|
||||
# Verify Mailgun configuration
|
||||
curl -s --user 'api:YOUR_API_KEY' \
|
||||
https://api.mailgun.net/v3/YOUR_DOMAIN/messages \
|
||||
-F from='test@YOUR_DOMAIN' \
|
||||
-F to='you@example.com' \
|
||||
-F subject='Test' \
|
||||
-F text='Testing'
|
||||
```
|
||||
|
||||
**4. Frontend Build Failures**
|
||||
|
||||
```bash
|
||||
# Clear cache and rebuild
|
||||
docker compose build --no-cache frontend
|
||||
```
|
||||
|
||||
### **Maintenance**
|
||||
|
||||
#### **Regular Tasks**
|
||||
|
||||
- Update dependencies monthly
|
||||
- Rotate credentials quarterly
|
||||
- Backup database daily
|
||||
- Monitor disk space weekly
|
||||
- Review security logs daily
|
||||
|
||||
#### **Update Process**
|
||||
|
||||
```bash
|
||||
# 1. Backup current deployment
|
||||
./backup.sh
|
||||
|
||||
# 2. Pull latest changes
|
||||
git pull origin main
|
||||
|
||||
# 3. Update dependencies
|
||||
bun install
|
||||
|
||||
# 4. Rebuild and deploy
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
|
||||
# 5. Verify deployment
|
||||
bun test-production.js
|
||||
```
|
||||
|
||||
### **Support and Documentation**
|
||||
|
||||
#### **Getting Help**
|
||||
|
||||
- GitHub Issues: Create issue for bugs/features
|
||||
- Documentation: Check README.md and docs/
|
||||
- Community: Join our Discord/Slack channel
|
||||
|
||||
#### **Professional Support**
|
||||
|
||||
- Enterprise support available
|
||||
- Custom deployment assistance
|
||||
- Security auditing services
|
||||
- Performance optimization consulting
|
||||
@@ -1,265 +0,0 @@
|
||||
# 🐳 Docker Image Configuration
|
||||
|
||||
## Overview
|
||||
|
||||
RxMinder now supports configurable Docker images via environment variables, enabling flexible deployment across different registries, environments, and versions.
|
||||
|
||||
## 🎯 Docker Image Variable
|
||||
|
||||
### **DOCKER_IMAGE**
|
||||
|
||||
The complete Docker image specification including registry, repository, and tag.
|
||||
|
||||
**Format:** `[registry/]repository:tag`
|
||||
|
||||
## 🌐 Registry Examples
|
||||
|
||||
### Public Registries
|
||||
|
||||
#### Docker Hub
|
||||
|
||||
```bash
|
||||
# Official image on Docker Hub
|
||||
DOCKER_IMAGE=rxminder/rxminder:latest
|
||||
DOCKER_IMAGE=rxminder/rxminder:v1.2.0
|
||||
DOCKER_IMAGE=rxminder/rxminder:stable
|
||||
```
|
||||
|
||||
#### GitHub Container Registry (ghcr.io)
|
||||
|
||||
```bash
|
||||
# GitHub Packages
|
||||
DOCKER_IMAGE=ghcr.io/username/rxminder:latest
|
||||
DOCKER_IMAGE=ghcr.io/organization/rxminder:v1.2.0
|
||||
DOCKER_IMAGE=ghcr.io/username/rxminder:dev-branch
|
||||
```
|
||||
|
||||
#### GitLab Container Registry
|
||||
|
||||
```bash
|
||||
# GitLab Registry
|
||||
DOCKER_IMAGE=registry.gitlab.com/username/rxminder:latest
|
||||
DOCKER_IMAGE=registry.gitlab.com/group/rxminder:production
|
||||
```
|
||||
|
||||
### Private/Self-Hosted Registries
|
||||
|
||||
#### Gitea Registry
|
||||
|
||||
```bash
|
||||
# Current default (Gitea)
|
||||
DOCKER_IMAGE=gitea-http.taildb3494.ts.net/will/meds:latest
|
||||
DOCKER_IMAGE=gitea-http.taildb3494.ts.net/will/meds:v1.2.0
|
||||
```
|
||||
|
||||
#### Harbor Registry
|
||||
|
||||
```bash
|
||||
# Harbor enterprise registry
|
||||
DOCKER_IMAGE=harbor.company.com/rxminder/rxminder:latest
|
||||
DOCKER_IMAGE=harbor.company.com/rxminder/rxminder:production
|
||||
```
|
||||
|
||||
#### Local Registry
|
||||
|
||||
```bash
|
||||
# Local development registry
|
||||
DOCKER_IMAGE=localhost:5000/rxminder:latest
|
||||
DOCKER_IMAGE=registry.local:5000/rxminder:dev
|
||||
```
|
||||
|
||||
### Cloud Provider Registries
|
||||
|
||||
#### AWS Elastic Container Registry (ECR)
|
||||
|
||||
```bash
|
||||
# AWS ECR
|
||||
DOCKER_IMAGE=123456789012.dkr.ecr.us-west-2.amazonaws.com/rxminder:latest
|
||||
DOCKER_IMAGE=123456789012.dkr.ecr.us-west-2.amazonaws.com/rxminder:v1.2.0
|
||||
```
|
||||
|
||||
#### Google Container Registry (GCR)
|
||||
|
||||
```bash
|
||||
# Google Cloud Registry
|
||||
DOCKER_IMAGE=gcr.io/project-id/rxminder:latest
|
||||
DOCKER_IMAGE=us.gcr.io/project-id/rxminder:production
|
||||
```
|
||||
|
||||
#### Azure Container Registry (ACR)
|
||||
|
||||
```bash
|
||||
# Azure Container Registry
|
||||
DOCKER_IMAGE=myregistry.azurecr.io/rxminder:latest
|
||||
DOCKER_IMAGE=myregistry.azurecr.io/rxminder:stable
|
||||
```
|
||||
|
||||
## 🏷️ Tagging Strategies
|
||||
|
||||
### Environment-Based Tagging
|
||||
|
||||
```bash
|
||||
# Development
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:dev
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:develop-20250906
|
||||
|
||||
# Staging
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:staging
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:release-candidate
|
||||
|
||||
# Production
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:stable
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:v1.2.0
|
||||
```
|
||||
|
||||
### Git-Based Tagging
|
||||
|
||||
```bash
|
||||
# Branch-based
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:main
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:feature-auth
|
||||
|
||||
# Commit-based
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:sha-abc1234
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:pr-123
|
||||
```
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
```bash
|
||||
# Semantic versions
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:v1.0.0
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:v1.2.3-beta
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:v2.0.0-rc1
|
||||
```
|
||||
|
||||
## 🎪 Environment-Specific Configurations
|
||||
|
||||
### Development (.env)
|
||||
|
||||
```bash
|
||||
APP_NAME=rxminder-dev
|
||||
DOCKER_IMAGE=localhost:5000/rxminder:dev
|
||||
STORAGE_CLASS=local-path
|
||||
STORAGE_SIZE=5Gi
|
||||
INGRESS_HOST=rxminder-dev.local
|
||||
```
|
||||
|
||||
### Staging (.env.staging)
|
||||
|
||||
```bash
|
||||
APP_NAME=rxminder-staging
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:staging
|
||||
STORAGE_CLASS=longhorn
|
||||
STORAGE_SIZE=10Gi
|
||||
INGRESS_HOST=staging.rxminder.company.com
|
||||
```
|
||||
|
||||
### Production (.env.production)
|
||||
|
||||
```bash
|
||||
APP_NAME=rxminder
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:v1.2.0 # Fixed version for stability
|
||||
STORAGE_CLASS=fast-ssd
|
||||
STORAGE_SIZE=50Gi
|
||||
INGRESS_HOST=rxminder.company.com
|
||||
```
|
||||
|
||||
## 🚀 CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
# .github/workflows/deploy.yml
|
||||
- name: Deploy to Kubernetes
|
||||
env:
|
||||
DOCKER_IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
run: |
|
||||
echo "DOCKER_IMAGE=${DOCKER_IMAGE}" >> .env
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
```
|
||||
|
||||
### GitLab CI Example
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
deploy:
|
||||
variables:
|
||||
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
script:
|
||||
- echo "DOCKER_IMAGE=${DOCKER_IMAGE}" >> .env
|
||||
- ./scripts/k8s-deploy-template.sh deploy
|
||||
```
|
||||
|
||||
## 🔒 Registry Authentication
|
||||
|
||||
### Docker Registry Secrets
|
||||
|
||||
```bash
|
||||
# Create registry secret for private registries
|
||||
kubectl create secret docker-registry regcred \
|
||||
--docker-server=myregistry.com \
|
||||
--docker-username=username \
|
||||
--docker-password=password \
|
||||
--docker-email=email@company.com
|
||||
|
||||
# Update deployment to use the secret
|
||||
# (Add imagePullSecrets to deployment template if needed)
|
||||
```
|
||||
|
||||
### Cloud Provider Authentication
|
||||
|
||||
```bash
|
||||
# AWS ECR
|
||||
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com
|
||||
|
||||
# Google GCR
|
||||
gcloud auth configure-docker
|
||||
|
||||
# Azure ACR
|
||||
az acr login --name myregistry
|
||||
```
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
### Production Recommendations
|
||||
|
||||
- ✅ **Use specific tags** (not `:latest`) for production
|
||||
- ✅ **Pin to exact versions** for stability
|
||||
- ✅ **Use semantic versioning** for releases
|
||||
- ✅ **Separate registries** for different environments
|
||||
- ✅ **Enable vulnerability scanning** on registries
|
||||
|
||||
### Development Workflow
|
||||
|
||||
- ✅ **Use `:dev` or `:latest`** for development
|
||||
- ✅ **Branch-based tags** for feature development
|
||||
- ✅ **Local registries** for fast iteration
|
||||
- ✅ **Automated builds** on code changes
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- ✅ **Private registries** for proprietary code
|
||||
- ✅ **Registry authentication** properly configured
|
||||
- ✅ **Image scanning** for vulnerabilities
|
||||
- ✅ **Supply chain security** with signed images
|
||||
|
||||
## 🎭 Example Deployments
|
||||
|
||||
### Multi-Environment Setup
|
||||
|
||||
```bash
|
||||
# Development
|
||||
export DOCKER_IMAGE=localhost:5000/rxminder:dev
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
|
||||
# Staging
|
||||
export DOCKER_IMAGE=registry.company.com/rxminder:staging
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
|
||||
# Production
|
||||
export DOCKER_IMAGE=registry.company.com/rxminder:v1.2.0
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
```
|
||||
|
||||
This flexible Docker image configuration makes RxMinder truly **portable** and **CI/CD-ready** across any container registry and deployment environment!
|
||||
@@ -1,242 +0,0 @@
|
||||
# 🦌 Gitea CI/CD Setup Complete!
|
||||
|
||||
Your RxMinder app now has comprehensive Gitea Actions CI/CD support! Here's what's been created:
|
||||
|
||||
## 📁 New Files Structure
|
||||
|
||||
```
|
||||
.gitea/
|
||||
├── workflows/
|
||||
│ └── ci-cd.yml # Main CI/CD workflow
|
||||
├── docker-compose.ci.yml # CI-specific compose override
|
||||
├── gitea-bake.hcl # Gitea-optimized buildx config
|
||||
└── README.md # Detailed Gitea configuration guide
|
||||
|
||||
scripts/
|
||||
├── gitea-deploy.sh # Gitea-specific deployment script
|
||||
└── gitea-helper.sh # Comprehensive Gitea operations helper
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. **Setup Environment Configuration**
|
||||
|
||||
```bash
|
||||
# Copy the example environment file and customize
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env with your registry and configuration:
|
||||
CONTAINER_REGISTRY=gitea.yourdomain.com
|
||||
CONTAINER_REPOSITORY=username/rxminder
|
||||
GITEA_REGISTRY=gitea.yourdomain.com
|
||||
GITEA_REPOSITORY=username/rxminder
|
||||
```
|
||||
|
||||
### 2. **Setup Gitea Repository**
|
||||
|
||||
```bash
|
||||
# Configure in Gitea Repository Settings → Actions
|
||||
|
||||
# Required Secrets:
|
||||
GITEA_TOKEN # Personal access token with package write permissions
|
||||
VITE_COUCHDB_PASSWORD # CouchDB password
|
||||
DEPLOYMENT_WEBHOOK_URL # Optional: deployment notifications
|
||||
|
||||
# Repository Variables (optional - will use .env defaults):
|
||||
GITEA_REGISTRY # Override registry from .env
|
||||
VITE_COUCHDB_URL # http://localhost:5984
|
||||
VITE_COUCHDB_USER # admin
|
||||
APP_BASE_URL # http://localhost:8080
|
||||
```
|
||||
|
||||
### 3. **Local Development with Gitea**
|
||||
|
||||
```bash
|
||||
# Setup Gitea buildx builder
|
||||
bun run gitea:setup
|
||||
|
||||
# Build for local development
|
||||
bun run gitea:build-local
|
||||
|
||||
# Run tests
|
||||
bun run gitea:test
|
||||
|
||||
# Check status
|
||||
bun run gitea:status
|
||||
```
|
||||
|
||||
### 4. **Production Deployment**
|
||||
|
||||
```bash
|
||||
# Build and push to registry
|
||||
export GITEA_TOKEN=your_token
|
||||
export GITEA_REGISTRY=your-gitea.com
|
||||
export GITEA_REPOSITORY=username/rxminder
|
||||
|
||||
bun run gitea:build-prod v1.0.0
|
||||
|
||||
# Deploy to production
|
||||
bun run gitea:deploy production v1.0.0
|
||||
```
|
||||
|
||||
## 🔧 Gitea Actions Features
|
||||
|
||||
### **Multi-Platform Builds**
|
||||
|
||||
- ✅ AMD64 (Intel/AMD processors)
|
||||
- ✅ ARM64 (Apple Silicon, AWS Graviton)
|
||||
- ✅ Optimized layer caching
|
||||
- ✅ Registry-based build cache
|
||||
|
||||
### **Security & Quality**
|
||||
|
||||
- ✅ Trivy vulnerability scanning
|
||||
- ✅ Supply chain attestations (SBOM, provenance)
|
||||
- ✅ Dependency auditing
|
||||
- ✅ Lint and type checking
|
||||
|
||||
### **Deployment Options**
|
||||
|
||||
- ✅ Docker Compose deployment
|
||||
- ✅ Kubernetes deployment
|
||||
- ✅ Staging environment support
|
||||
- ✅ Health checks and monitoring
|
||||
|
||||
### **Automation**
|
||||
|
||||
- ✅ Automatic builds on push/PR
|
||||
- ✅ Multi-environment deployments
|
||||
- ✅ Image cleanup and maintenance
|
||||
- ✅ Deployment notifications
|
||||
|
||||
## 📋 Available Commands
|
||||
|
||||
### **Gitea Helper Script**
|
||||
|
||||
```bash
|
||||
./scripts/gitea-helper.sh setup # Setup buildx for Gitea
|
||||
./scripts/gitea-helper.sh build-local # Local development build
|
||||
./scripts/gitea-helper.sh build-multi # Multi-platform build
|
||||
./scripts/gitea-helper.sh build-staging # Staging build
|
||||
./scripts/gitea-helper.sh build-prod # Production build
|
||||
./scripts/gitea-helper.sh test # Run all tests
|
||||
./scripts/gitea-helper.sh deploy # Deploy to environment
|
||||
./scripts/gitea-helper.sh status # Show CI/CD status
|
||||
./scripts/gitea-helper.sh cleanup # Cleanup builders/images
|
||||
```
|
||||
|
||||
### **Package.json Scripts**
|
||||
|
||||
```bash
|
||||
bun run gitea:setup # Setup Gitea buildx
|
||||
bun run gitea:build # Multi-platform build
|
||||
bun run gitea:build-local # Local development
|
||||
bun run gitea:build-staging # Staging build
|
||||
bun run gitea:build-prod # Production build
|
||||
bun run gitea:test # Run tests
|
||||
bun run gitea:deploy # Deploy application
|
||||
bun run gitea:status # Check status
|
||||
bun run gitea:cleanup # Cleanup
|
||||
```
|
||||
|
||||
## 🎯 Workflow Triggers
|
||||
|
||||
### **Automatic Triggers**
|
||||
|
||||
- **Push to main/develop**: Full build, test, and deploy
|
||||
- **Pull Request**: Build, test, and security scan
|
||||
- **Manual dispatch**: On-demand deployment
|
||||
|
||||
### **Environment-Specific**
|
||||
|
||||
- **Development**: Fast single-platform builds
|
||||
- **Staging**: Full testing with staging configs
|
||||
- **Production**: Multi-platform with attestations
|
||||
|
||||
## 🔒 Security Features
|
||||
|
||||
### **Image Security**
|
||||
|
||||
- Vulnerability scanning with Trivy
|
||||
- Base image security updates
|
||||
- Minimal attack surface
|
||||
- Supply chain attestations
|
||||
|
||||
### **Secrets Management**
|
||||
|
||||
- Gitea-native secrets storage
|
||||
- Environment-specific variables
|
||||
- Token rotation support
|
||||
- Secure registry authentication
|
||||
|
||||
## 📊 Monitoring & Notifications
|
||||
|
||||
### **Health Checks**
|
||||
|
||||
- Frontend application health
|
||||
- Database connectivity
|
||||
- Service dependency checks
|
||||
- Container resource monitoring
|
||||
|
||||
### **Notifications**
|
||||
|
||||
- Deployment success/failure alerts
|
||||
- Security scan results
|
||||
- Build status updates
|
||||
- Custom webhook integration
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. **Configure Gitea Repository**:
|
||||
- Enable Actions in repository settings
|
||||
- Add required secrets and variables
|
||||
- Configure container registry
|
||||
|
||||
2. **Set up Gitea Runner**:
|
||||
- Install and configure Gitea Actions runner
|
||||
- Ensure Docker and buildx support
|
||||
- Configure appropriate labels
|
||||
|
||||
3. **Test the Pipeline**:
|
||||
|
||||
```bash
|
||||
# Push to trigger the workflow
|
||||
git add .
|
||||
git commit -m "Setup Gitea CI/CD"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
4. **Customize for Your Environment**:
|
||||
- Update registry URLs in `.gitea/gitea-bake.hcl`
|
||||
- Modify deployment targets in `scripts/gitea-deploy.sh`
|
||||
- Configure environment-specific variables
|
||||
|
||||
## 🔄 Migration Notes
|
||||
|
||||
- ✅ **Fully compatible** with existing Docker Buildx setup
|
||||
- ✅ **No breaking changes** to development workflow
|
||||
- ✅ **Parallel support** with GitHub Actions if needed
|
||||
- ✅ **Easy rollback** - simply delete `.gitea/` directory
|
||||
|
||||
Your RxMinder app is now ready for professional-grade CI/CD with Gitea! 🎉
|
||||
|
||||
## 📞 Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **Build failures**: Check Gitea runner has Docker buildx
|
||||
2. **Registry push errors**: Verify GITEA_TOKEN permissions
|
||||
3. **Deployment issues**: Check environment variables and secrets
|
||||
|
||||
### Debug Commands:
|
||||
|
||||
```bash
|
||||
# Check Gitea environment
|
||||
./scripts/gitea-helper.sh status
|
||||
|
||||
# Test local build
|
||||
./scripts/gitea-helper.sh build-local
|
||||
|
||||
# Verify registry login
|
||||
docker login your-gitea.com
|
||||
```
|
||||
@@ -1,407 +0,0 @@
|
||||
# Production Build Guide
|
||||
|
||||
This guide explains how to properly configure and build the RxMinder application for production deployment.
|
||||
|
||||
## Overview
|
||||
|
||||
The RxMinder application has strict security validations that prevent production builds with default or insecure configurations. This ensures that production deployments are properly secured.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Development Build (Default)
|
||||
|
||||
```bash
|
||||
make build # Builds with development configuration
|
||||
NODE_ENV=development bun run build
|
||||
```
|
||||
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
make build-prod # Builds with production configuration
|
||||
bun run build # Requires proper production environment setup
|
||||
```
|
||||
|
||||
## Production Configuration Requirements
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
Before running a production build, you must set the following environment variables:
|
||||
|
||||
#### Authentication Secrets
|
||||
|
||||
```bash
|
||||
# Required: Change these from defaults
|
||||
JWT_SECRET=your-secure-jwt-secret-here
|
||||
SESSION_SECRET=your-secure-session-secret-here
|
||||
```
|
||||
|
||||
#### Database Configuration
|
||||
|
||||
```bash
|
||||
# Required for production deployments
|
||||
VITE_COUCHDB_URL=https://your-couchdb-instance.com
|
||||
COUCHDB_USER=your-couchdb-username
|
||||
COUCHDB_PASSWORD=your-couchdb-password
|
||||
```
|
||||
|
||||
#### Email Configuration (Optional)
|
||||
|
||||
```bash
|
||||
# Mailgun configuration
|
||||
MAILGUN_API_KEY=your-mailgun-api-key
|
||||
MAILGUN_DOMAIN=your-domain.com
|
||||
MAILGUN_FROM_NAME="Your App Name"
|
||||
MAILGUN_FROM_EMAIL=noreply@your-domain.com
|
||||
```
|
||||
|
||||
#### OAuth Configuration (Optional)
|
||||
|
||||
```bash
|
||||
# Google OAuth
|
||||
GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
|
||||
# GitHub OAuth
|
||||
GITHUB_CLIENT_ID=your-github-client-id
|
||||
GITHUB_CLIENT_SECRET=your-github-client-secret
|
||||
```
|
||||
|
||||
## Configuration Methods
|
||||
|
||||
### Method 1: Environment Variables
|
||||
|
||||
Set environment variables directly:
|
||||
|
||||
```bash
|
||||
export JWT_SECRET="your-secure-jwt-secret"
|
||||
export SESSION_SECRET="your-secure-session-secret"
|
||||
export VITE_COUCHDB_URL="https://your-couchdb.com"
|
||||
make build-prod
|
||||
```
|
||||
|
||||
### Method 2: .env File
|
||||
|
||||
Create a `.env.production` file:
|
||||
|
||||
```bash
|
||||
# Copy and modify the example
|
||||
cp .env.example .env.production
|
||||
|
||||
# Edit with your production values
|
||||
nano .env.production
|
||||
```
|
||||
|
||||
Then build:
|
||||
|
||||
```bash
|
||||
NODE_ENV=production make build-prod
|
||||
```
|
||||
|
||||
### Method 3: Generate Production Config
|
||||
|
||||
Use the unified configuration system:
|
||||
|
||||
```bash
|
||||
# Generate production configuration
|
||||
make generate-config-prod
|
||||
|
||||
# Then build
|
||||
make build-prod
|
||||
```
|
||||
|
||||
## Security Validations
|
||||
|
||||
The build process validates the following:
|
||||
|
||||
### Critical Errors (Will Fail Build)
|
||||
|
||||
- `JWT_SECRET` must not be the default value in production
|
||||
- `SESSION_SECRET` must not be the default value in production
|
||||
|
||||
### Warnings (Will Show But Allow Build)
|
||||
|
||||
- Missing OAuth client IDs (OAuth will be disabled)
|
||||
- Missing email configuration (Email features disabled)
|
||||
- Using default database URL (Will use mock database)
|
||||
|
||||
## Production Build Process
|
||||
|
||||
### 1. Validate Configuration
|
||||
|
||||
```bash
|
||||
# Check current configuration
|
||||
make config-debug
|
||||
|
||||
# Validate configuration
|
||||
make validate-config
|
||||
```
|
||||
|
||||
### 2. Set Production Environment
|
||||
|
||||
```bash
|
||||
export NODE_ENV=production
|
||||
```
|
||||
|
||||
### 3. Configure Secrets
|
||||
|
||||
```bash
|
||||
# Generate secure secrets
|
||||
export JWT_SECRET=$(openssl rand -base64 32)
|
||||
export SESSION_SECRET=$(openssl rand -base64 32)
|
||||
```
|
||||
|
||||
### 4. Build Application
|
||||
|
||||
```bash
|
||||
make build-prod
|
||||
```
|
||||
|
||||
### 5. Verify Build
|
||||
|
||||
```bash
|
||||
# Check dist directory
|
||||
ls -la dist/
|
||||
|
||||
# Test build locally
|
||||
make preview
|
||||
```
|
||||
|
||||
## Docker Production Build
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
```bash
|
||||
# Build production image
|
||||
docker-compose -f docker/docker-compose.prod.yml build
|
||||
|
||||
# Deploy with production configuration
|
||||
docker-compose -f docker/docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Using Makefile
|
||||
|
||||
```bash
|
||||
# Build production Docker images
|
||||
make docker-build
|
||||
|
||||
# With production environment
|
||||
NODE_ENV=production make docker-build
|
||||
```
|
||||
|
||||
## Kubernetes Production Deployment
|
||||
|
||||
### 1. Generate Kubernetes Configuration
|
||||
|
||||
```bash
|
||||
# Generate production K8s configs
|
||||
make generate-config-prod
|
||||
```
|
||||
|
||||
### 2. Apply Configuration
|
||||
|
||||
```bash
|
||||
# Deploy to production
|
||||
make deploy-prod
|
||||
|
||||
# Check deployment status
|
||||
make status-prod
|
||||
```
|
||||
|
||||
### 3. Verify Deployment
|
||||
|
||||
```bash
|
||||
# Check differences before applying
|
||||
make diff-prod
|
||||
|
||||
# Validate configurations
|
||||
make validate-k8s
|
||||
```
|
||||
|
||||
## Environment-Specific Builds
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
NODE_ENV=development make build
|
||||
# Uses development defaults, allows insecure configurations
|
||||
```
|
||||
|
||||
### Staging
|
||||
|
||||
```bash
|
||||
NODE_ENV=staging make build-prod
|
||||
# Uses staging configuration with production validations
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
NODE_ENV=production make build-prod
|
||||
# Requires all production security validations
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "JWT_SECRET must be changed in production"
|
||||
|
||||
```bash
|
||||
# Solution: Set a secure JWT secret
|
||||
export JWT_SECRET=$(openssl rand -base64 32)
|
||||
export SESSION_SECRET=$(openssl rand -base64 32)
|
||||
make build-prod
|
||||
```
|
||||
|
||||
### Error: "Configuration validation failed"
|
||||
|
||||
```bash
|
||||
# Check what's missing
|
||||
make validate-config
|
||||
|
||||
# Debug current configuration
|
||||
make config-debug
|
||||
|
||||
# Generate complete configuration
|
||||
make generate-config-prod
|
||||
```
|
||||
|
||||
### Build Succeeds But Features Missing
|
||||
|
||||
Check warnings in build output:
|
||||
|
||||
- Missing OAuth: Set `GOOGLE_CLIENT_ID` and `GITHUB_CLIENT_ID`
|
||||
- Missing email: Set `MAILGUN_API_KEY` and `MAILGUN_DOMAIN`
|
||||
- Mock database: Set `VITE_COUCHDB_URL` to real database
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Secret Management
|
||||
|
||||
1. **Never commit secrets** to version control
|
||||
2. **Use environment variables** or secure secret management
|
||||
3. **Rotate secrets regularly** in production
|
||||
4. **Use different secrets** for each environment
|
||||
|
||||
### Database Security
|
||||
|
||||
1. **Use HTTPS** for CouchDB connections
|
||||
2. **Configure authentication** with strong passwords
|
||||
3. **Limit database access** to application only
|
||||
4. **Regular backups** and security updates
|
||||
|
||||
### OAuth Security
|
||||
|
||||
1. **Restrict redirect URIs** to your domains only
|
||||
2. **Use HTTPS** for OAuth redirects
|
||||
3. **Regularly review** OAuth application permissions
|
||||
4. **Monitor OAuth usage** for suspicious activity
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
- name: Build Production
|
||||
env:
|
||||
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
||||
SESSION_SECRET: ${{ secrets.SESSION_SECRET }}
|
||||
VITE_COUCHDB_URL: ${{ secrets.COUCHDB_URL }}
|
||||
run: make build-prod
|
||||
```
|
||||
|
||||
### GitLab CI Example
|
||||
|
||||
```yaml
|
||||
build:production:
|
||||
variables:
|
||||
NODE_ENV: 'production'
|
||||
script:
|
||||
- export JWT_SECRET="$PRODUCTION_JWT_SECRET"
|
||||
- export SESSION_SECRET="$PRODUCTION_SESSION_SECRET"
|
||||
- make build-prod
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Bundle Analysis
|
||||
|
||||
```bash
|
||||
# Build with bundle analysis
|
||||
ANALYZE=true make build-prod
|
||||
|
||||
# Check bundle size
|
||||
ls -lh dist/assets/
|
||||
```
|
||||
|
||||
### Code Splitting
|
||||
|
||||
The build automatically splits code for optimal loading:
|
||||
|
||||
- Main application bundle
|
||||
- Vendor dependencies
|
||||
- Dynamic imports for large features
|
||||
|
||||
### Compression
|
||||
|
||||
Production builds include:
|
||||
|
||||
- Gzip compression
|
||||
- Minification
|
||||
- Tree shaking
|
||||
- Dead code elimination
|
||||
|
||||
## Deployment Verification
|
||||
|
||||
### Health Checks
|
||||
|
||||
After deployment, verify:
|
||||
|
||||
1. Application loads without errors
|
||||
2. Database connection works
|
||||
3. Authentication functions properly
|
||||
4. Email notifications work (if configured)
|
||||
5. OAuth login works (if configured)
|
||||
|
||||
### Performance Checks
|
||||
|
||||
1. Initial load time < 3 seconds
|
||||
2. Bundle size reasonable (check warnings)
|
||||
3. No console errors in browser
|
||||
4. Proper caching headers set
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
### Quick Rollback
|
||||
|
||||
```bash
|
||||
# Rollback Kubernetes deployment
|
||||
kubectl rollout undo deployment/rxminder -n rxminder-prod
|
||||
|
||||
# Rollback Docker deployment
|
||||
docker-compose -f docker/docker-compose.prod.yml down
|
||||
# Deploy previous image version
|
||||
```
|
||||
|
||||
### Configuration Rollback
|
||||
|
||||
```bash
|
||||
# Revert to previous configuration
|
||||
git checkout HEAD~1 -- .env.production
|
||||
make build-prod
|
||||
make deploy-prod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Deployment Guide](DEPLOYMENT.md) - General deployment instructions
|
||||
- [Docker Configuration](DOCKER_IMAGE_CONFIGURATION.md) - Docker setup
|
||||
- [Environment Variables](../ENVIRONMENT_VARIABLES.md) - All environment variables
|
||||
- [Security](../development/SECURITY.md) - Security guidelines
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** January 2024
|
||||
**Version:** 2.0.0
|
||||
**Status:** Production Ready
|
||||
@@ -1,226 +0,0 @@
|
||||
# 📦 Storage Configuration Examples
|
||||
|
||||
## Overview
|
||||
|
||||
RxMinder now supports configurable storage through environment variables, making it easy to adapt to different Kubernetes environments and storage requirements.
|
||||
|
||||
## 🗂️ Storage Configuration Variables
|
||||
|
||||
### **STORAGE_CLASS**
|
||||
|
||||
The Kubernetes StorageClass to use for persistent volumes.
|
||||
|
||||
**Common Options:**
|
||||
|
||||
- `longhorn` - Longhorn distributed storage (Raspberry Pi clusters)
|
||||
- `local-path` - Local path provisioner (k3s default)
|
||||
- `standard` - Cloud provider standard storage
|
||||
- `fast-ssd` - High-performance SSD storage
|
||||
- `gp2` - AWS General Purpose SSD
|
||||
- `pd-standard` - Google Cloud Standard Persistent Disk
|
||||
- `azure-disk` - Azure Standard Disk
|
||||
|
||||
### **STORAGE_SIZE**
|
||||
|
||||
The amount of storage to allocate for the CouchDB database.
|
||||
|
||||
**Sizing Guidelines:**
|
||||
|
||||
- `1Gi` - Minimal testing (not recommended for production)
|
||||
- `5Gi` - Small deployment (default, good for development)
|
||||
- `10Gi` - Medium deployment (suitable for small teams)
|
||||
- `20Gi` - Large deployment (production use)
|
||||
- `50Gi+` - Enterprise deployment (high-volume usage)
|
||||
|
||||
## 🎯 Environment-Specific Examples
|
||||
|
||||
### Development (.env)
|
||||
|
||||
```bash
|
||||
# Development environment
|
||||
APP_NAME=rxminder-dev
|
||||
STORAGE_CLASS=local-path
|
||||
STORAGE_SIZE=5Gi
|
||||
INGRESS_HOST=rxminder-dev.local
|
||||
```
|
||||
|
||||
### Staging (.env.staging)
|
||||
|
||||
```bash
|
||||
# Staging environment
|
||||
APP_NAME=rxminder-staging
|
||||
STORAGE_CLASS=longhorn
|
||||
STORAGE_SIZE=10Gi
|
||||
INGRESS_HOST=staging.rxminder.company.com
|
||||
```
|
||||
|
||||
### Production (.env.production)
|
||||
|
||||
```bash
|
||||
# Production environment
|
||||
APP_NAME=rxminder
|
||||
STORAGE_CLASS=fast-ssd
|
||||
STORAGE_SIZE=50Gi
|
||||
INGRESS_HOST=rxminder.company.com
|
||||
```
|
||||
|
||||
### Cloud Providers
|
||||
|
||||
#### AWS EKS
|
||||
|
||||
```bash
|
||||
APP_NAME=rxminder
|
||||
STORAGE_CLASS=gp2 # General Purpose SSD
|
||||
STORAGE_SIZE=20Gi
|
||||
INGRESS_HOST=rxminder.aws.company.com
|
||||
```
|
||||
|
||||
#### Google GKE
|
||||
|
||||
```bash
|
||||
APP_NAME=rxminder
|
||||
STORAGE_CLASS=pd-standard # Standard Persistent Disk
|
||||
STORAGE_SIZE=20Gi
|
||||
INGRESS_HOST=rxminder.gcp.company.com
|
||||
```
|
||||
|
||||
#### Azure AKS
|
||||
|
||||
```bash
|
||||
APP_NAME=rxminder
|
||||
STORAGE_CLASS=managed-premium # Premium SSD
|
||||
STORAGE_SIZE=20Gi
|
||||
INGRESS_HOST=rxminder.azure.company.com
|
||||
```
|
||||
|
||||
## 🏗️ Generated Kubernetes Resources
|
||||
|
||||
### Before (Hardcoded)
|
||||
|
||||
```yaml
|
||||
# Old approach - hardcoded values
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: couchdb-pvc
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
```
|
||||
|
||||
### After (Template-Based)
|
||||
|
||||
```yaml
|
||||
# Template: k8s/couchdb-pvc.yaml.template
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ${APP_NAME}-couchdb-pvc
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
spec:
|
||||
storageClassName: ${STORAGE_CLASS}
|
||||
resources:
|
||||
requests:
|
||||
storage: ${STORAGE_SIZE}
|
||||
```
|
||||
|
||||
### Deployed Result
|
||||
|
||||
```yaml
|
||||
# After envsubst processing
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: rxminder-couchdb-pvc
|
||||
labels:
|
||||
app: rxminder
|
||||
spec:
|
||||
storageClassName: fast-ssd
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
```
|
||||
|
||||
## 🚀 Deployment Examples
|
||||
|
||||
### Quick Development Setup
|
||||
|
||||
```bash
|
||||
# Development with local storage
|
||||
export APP_NAME=rxminder-dev
|
||||
export STORAGE_CLASS=local-path
|
||||
export STORAGE_SIZE=5Gi
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
```bash
|
||||
# Copy production environment
|
||||
cp .env.production .env
|
||||
# Edit with your specific values
|
||||
nano .env
|
||||
|
||||
# Deploy to production
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```bash
|
||||
# Override specific values
|
||||
export STORAGE_CLASS=custom-storage
|
||||
export STORAGE_SIZE=100Gi
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
```
|
||||
|
||||
## 🔍 Storage Class Discovery
|
||||
|
||||
### Find Available Storage Classes
|
||||
|
||||
```bash
|
||||
# List available storage classes in your cluster
|
||||
kubectl get storageclass
|
||||
|
||||
# Get details about a specific storage class
|
||||
kubectl describe storageclass longhorn
|
||||
```
|
||||
|
||||
### Common Storage Class Names by Platform
|
||||
|
||||
| Platform | Common Storage Classes |
|
||||
| -------------- | ------------------------------------------ |
|
||||
| **k3s** | `local-path` (default) |
|
||||
| **Longhorn** | `longhorn` |
|
||||
| **AWS EKS** | `gp2`, `gp3`, `io1`, `io2` |
|
||||
| **Google GKE** | `standard`, `ssd`, `pd-standard`, `pd-ssd` |
|
||||
| **Azure AKS** | `default`, `managed-premium` |
|
||||
| **Rancher** | `longhorn`, `local-path` |
|
||||
|
||||
## 💡 Benefits
|
||||
|
||||
### Flexibility
|
||||
|
||||
- ✅ **Environment-specific** storage configuration
|
||||
- ✅ **Cloud-agnostic** deployment
|
||||
- ✅ **Performance tuning** via storage class selection
|
||||
- ✅ **Cost optimization** through appropriate sizing
|
||||
|
||||
### Maintainability
|
||||
|
||||
- ✅ **Single source of truth** via `.env` files
|
||||
- ✅ **Easy scaling** by changing STORAGE_SIZE
|
||||
- ✅ **Environment promotion** using different .env files
|
||||
- ✅ **Disaster recovery** with consistent configurations
|
||||
|
||||
### Developer Experience
|
||||
|
||||
- ✅ **No hardcoded values** in manifests
|
||||
- ✅ **Clear documentation** of requirements
|
||||
- ✅ **Validation** of required variables
|
||||
- ✅ **Automated deployment** with proper storage setup
|
||||
|
||||
This approach makes RxMinder truly **portable** across different Kubernetes environments while maintaining **production-grade** storage management!
|
||||
@@ -1,352 +0,0 @@
|
||||
# Kustomize Deployment Configuration
|
||||
|
||||
This directory contains the Kustomize configuration for deploying the rxminder (Medication Reminder) application to Kubernetes. Kustomize provides a template-free way to customize application configuration that simplifies the use of off-the-shelf applications.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
k8s-kustomize/
|
||||
├── base/ # Base configuration (shared resources)
|
||||
│ ├── kustomization.yaml # Base kustomization file
|
||||
│ ├── config.env # Environment variables for ConfigMap
|
||||
│ ├── registry-config.json # Docker registry configuration
|
||||
│ ├── frontend-deployment.yaml # Frontend deployment
|
||||
│ ├── frontend-service.yaml # Frontend service
|
||||
│ ├── couchdb-statefulset.yaml # CouchDB database
|
||||
│ ├── couchdb-service.yaml # CouchDB service
|
||||
│ ├── couchdb-pvc.yaml # CouchDB persistent volume
|
||||
│ ├── configmap.yaml # Application configuration
|
||||
│ ├── ingress.yaml # Ingress configuration
|
||||
│ ├── network-policy.yaml # Network policies
|
||||
│ ├── hpa.yaml # Horizontal Pod Autoscaler
|
||||
│ └── db-seed-job.yaml # Database seeding job
|
||||
├── overlays/ # Environment-specific overrides
|
||||
│ ├── dev/ # Development environment
|
||||
│ │ └── kustomization.yaml # Development customizations
|
||||
│ └── prod/ # Production environment
|
||||
│ ├── kustomization.yaml # Production customizations
|
||||
│ ├── frontend-resources.yaml # Production frontend resources
|
||||
│ ├── couchdb-resources.yaml # Production database resources
|
||||
│ └── ingress-prod.yaml # Production ingress config
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **kubectl** installed and configured
|
||||
2. **Kubernetes cluster** access
|
||||
3. **Docker images** built and pushed to registry
|
||||
|
||||
### Deploy to Development
|
||||
|
||||
```bash
|
||||
# Using Makefile (recommended)
|
||||
make deploy-dev
|
||||
|
||||
# Or directly with kubectl
|
||||
kubectl apply -k k8s-kustomize/overlays/dev
|
||||
```
|
||||
|
||||
### Deploy to Production
|
||||
|
||||
```bash
|
||||
# Using Makefile (recommended)
|
||||
make deploy-prod
|
||||
|
||||
# Or directly with kubectl
|
||||
kubectl apply -k k8s-kustomize/overlays/prod
|
||||
```
|
||||
|
||||
## Environment Configurations
|
||||
|
||||
### Development Environment
|
||||
|
||||
- **Namespace**: `rxminder-dev`
|
||||
- **Replicas**: 1 frontend pod
|
||||
- **Resources**: Minimal (16Mi-32Mi memory)
|
||||
- **Image Tag**: `dev`
|
||||
- **Domain**: `rxminder-dev.local`
|
||||
- **Storage**: 1Gi
|
||||
- **Security**: Relaxed for development
|
||||
|
||||
### Production Environment
|
||||
|
||||
- **Namespace**: `rxminder-prod`
|
||||
- **Replicas**: 3 frontend pods (high availability)
|
||||
- **Resources**: Production-grade (256Mi-512Mi memory)
|
||||
- **Image Tag**: `v1.0.0` (semantic versioning)
|
||||
- **Domain**: `rxminder.yourdomain.com`
|
||||
- **Storage**: 10Gi SSD
|
||||
- **Security**: Hardened with security contexts, network policies
|
||||
- **TLS**: Enabled with cert-manager
|
||||
- **Monitoring**: Enabled
|
||||
|
||||
## Makefile Commands
|
||||
|
||||
### Deployment Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
make deploy-dev # Deploy to development
|
||||
make undeploy-dev # Remove development deployment
|
||||
make quick-deploy-dev # Build and deploy to development
|
||||
make status-dev # Show development status
|
||||
|
||||
# Production
|
||||
make deploy-prod # Deploy to production
|
||||
make undeploy-prod # Remove production deployment
|
||||
make quick-deploy-prod # Build and deploy to production
|
||||
make status-prod # Show production status
|
||||
```
|
||||
|
||||
### Validation Commands
|
||||
|
||||
```bash
|
||||
make validate-kustomize # Validate all configurations
|
||||
make kustomize-validate-dev # Validate development config
|
||||
make kustomize-validate-prod # Validate production config
|
||||
```
|
||||
|
||||
### Debugging Commands
|
||||
|
||||
```bash
|
||||
make kustomize-dry-run-dev # Dry run development deployment
|
||||
make kustomize-dry-run-prod # Dry run production deployment
|
||||
make kustomize-diff-dev # Show differences for development
|
||||
make kustomize-diff-prod # Show differences for production
|
||||
make kustomize-build-dev # Build development manifests
|
||||
make kustomize-build-prod # Build production manifests
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### ConfigMaps
|
||||
|
||||
Configuration is managed through:
|
||||
|
||||
1. **Base config.env**: Common environment variables
|
||||
2. **Overlay literals**: Environment-specific overrides
|
||||
3. **ConfigMap generation**: Automatic from environment files
|
||||
|
||||
### Secrets
|
||||
|
||||
Secrets are managed through:
|
||||
|
||||
1. **Development**: Simple literals in kustomization.yaml
|
||||
2. **Production**: External secret management (recommended)
|
||||
- Kubernetes External Secrets Operator
|
||||
- HashiCorp Vault
|
||||
- AWS Secrets Manager
|
||||
- Azure Key Vault
|
||||
|
||||
### Images
|
||||
|
||||
Image management uses Kustomize's image transformer:
|
||||
|
||||
```yaml
|
||||
images:
|
||||
- name: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newTag: v1.0.0 # Override in overlays
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Adding a New Environment
|
||||
|
||||
1. Create new directory: `overlays/staging/`
|
||||
2. Create `kustomization.yaml` with base reference
|
||||
3. Add environment-specific patches
|
||||
4. Update Makefile with new targets
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# overlays/staging/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
namespace: rxminder-staging
|
||||
|
||||
commonLabels:
|
||||
environment: staging
|
||||
|
||||
images:
|
||||
- name: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newTag: staging
|
||||
```
|
||||
|
||||
### Resource Patches
|
||||
|
||||
Use strategic merge patches for complex modifications:
|
||||
|
||||
```yaml
|
||||
# Example: frontend-patch.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rxminder-frontend
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
resources:
|
||||
requests:
|
||||
memory: '512Mi'
|
||||
cpu: '200m'
|
||||
```
|
||||
|
||||
### JSON Patches
|
||||
|
||||
Use JSON patches for precise modifications:
|
||||
|
||||
```yaml
|
||||
patches:
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/replicas
|
||||
value: 5
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Production Security Features
|
||||
|
||||
1. **Security Contexts**: Non-root containers, read-only filesystems
|
||||
2. **Network Policies**: Restricted pod-to-pod communication
|
||||
3. **Resource Limits**: Prevent resource exhaustion
|
||||
4. **Image Security**: Signed images, vulnerability scanning
|
||||
5. **Secret Management**: External secret stores
|
||||
6. **TLS Encryption**: HTTPS with cert-manager
|
||||
7. **RBAC**: Role-based access control
|
||||
|
||||
### Development Security
|
||||
|
||||
- Relaxed for development efficiency
|
||||
- Still follows basic security practices
|
||||
- Isolated in separate namespace
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### Production Monitoring
|
||||
|
||||
- **Health Checks**: Liveness and readiness probes
|
||||
- **Metrics**: Resource usage monitoring
|
||||
- **Logging**: Structured logging with appropriate levels
|
||||
- **Alerting**: Production-grade alert rules
|
||||
|
||||
### Development Monitoring
|
||||
|
||||
- **Debug Logging**: Verbose logging for troubleshooting
|
||||
- **Resource Monitoring**: Basic resource tracking
|
||||
- **Health Checks**: Relaxed timing for development
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Image Pull Errors**
|
||||
|
||||
```bash
|
||||
# Check image exists and credentials are correct
|
||||
kubectl describe pod <pod-name> -n <namespace>
|
||||
```
|
||||
|
||||
2. **ConfigMap Issues**
|
||||
|
||||
```bash
|
||||
# Check generated ConfigMap
|
||||
kubectl get configmap rxminder-config -n <namespace> -o yaml
|
||||
```
|
||||
|
||||
3. **Service Discovery**
|
||||
|
||||
```bash
|
||||
# Test service connectivity
|
||||
kubectl exec -it <pod-name> -n <namespace> -- curl rxminder-couchdb-service:5984
|
||||
```
|
||||
|
||||
4. **Resource Constraints**
|
||||
|
||||
```bash
|
||||
# Check resource usage
|
||||
kubectl top pods -n <namespace>
|
||||
kubectl describe node
|
||||
```
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# View all resources
|
||||
kubectl get all -n rxminder-dev -l app=rxminder
|
||||
|
||||
# Check events
|
||||
kubectl get events -n rxminder-dev --sort-by='.lastTimestamp'
|
||||
|
||||
# View logs
|
||||
kubectl logs deployment/rxminder-frontend -n rxminder-dev -f
|
||||
|
||||
# Describe problematic resources
|
||||
kubectl describe deployment rxminder-frontend -n rxminder-dev
|
||||
```
|
||||
|
||||
## Migration from Legacy Deployment
|
||||
|
||||
### Migration Steps
|
||||
|
||||
1. **Test Kustomize**: Deploy to development first
|
||||
2. **Validate Configuration**: Compare with existing deployment
|
||||
3. **Update CI/CD**: Switch to Kustomize commands
|
||||
4. **Production Migration**: Schedule maintenance window
|
||||
5. **Cleanup**: Remove old template files after validation
|
||||
|
||||
### Rollback Plan
|
||||
|
||||
Legacy deployment scripts are still available:
|
||||
|
||||
```bash
|
||||
make deploy-dev # Deploy to development environment
|
||||
make deploy-prod # Deploy to production environment
|
||||
make undeploy-dev # Remove development deployment
|
||||
make undeploy-prod # Remove production deployment
|
||||
```
|
||||
|
||||
## Benefits of Kustomize
|
||||
|
||||
1. **Template-free**: No complex templating logic
|
||||
2. **Composable**: Layer configurations naturally
|
||||
3. **Validation**: Built-in YAML validation
|
||||
4. **GitOps Ready**: Works with ArgoCD, Flux
|
||||
5. **Standard**: Kubernetes-native solution
|
||||
6. **Maintainable**: Clear separation of concerns
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Base Configuration**: Keep base as generic as possible
|
||||
2. **Environment Isolation**: Use separate namespaces
|
||||
3. **Secret Management**: Use external secret stores in production
|
||||
4. **Image Tags**: Use specific tags, avoid `latest` in production
|
||||
5. **Resource Limits**: Always set resource requests and limits
|
||||
6. **Health Checks**: Configure appropriate probes
|
||||
7. **Documentation**: Keep this README updated
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
|
||||
1. Check troubleshooting section above
|
||||
2. Review Kubernetes events and logs
|
||||
3. Validate Kustomize configuration
|
||||
4. Consult team documentation
|
||||
|
||||
---
|
||||
|
||||
**Note**: This Kustomize configuration replaces the previous shell script-based deployment. The old scripts are still available for backward compatibility but Kustomize is the recommended approach going forward.
|
||||
@@ -1,60 +0,0 @@
|
||||
# Base configuration for rxminder application
|
||||
# Generated automatically from environment variables
|
||||
# Generated on: Sun Sep 7 08:46:59 PM PDT 2025
|
||||
# Environment: dev
|
||||
|
||||
# Application Environment
|
||||
NODE_ENV=production
|
||||
LOG_LEVEL=info
|
||||
|
||||
# API Configuration
|
||||
REACT_APP_API_URL=http://rxminder-couchdb-service:5984
|
||||
|
||||
# Feature Flags
|
||||
ENABLE_MONITORING=false
|
||||
DEBUG=false
|
||||
|
||||
# Cache Configuration
|
||||
CACHE_TTL=1800
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST=rxminder-couchdb-service
|
||||
DB_PORT=5984
|
||||
COUCHDB_DATABASE_NAME=meds_app
|
||||
|
||||
# Security Configuration
|
||||
ENABLE_CORS=true
|
||||
CORS_ORIGIN=*
|
||||
|
||||
# Performance Configuration
|
||||
REQUEST_TIMEOUT=30000
|
||||
MAX_CONNECTIONS=100
|
||||
|
||||
# Logging Configuration
|
||||
LOG_FORMAT=json
|
||||
LOG_TIMESTAMP=true
|
||||
|
||||
# Health Check Configuration
|
||||
HEALTH_CHECK_INTERVAL=30
|
||||
READINESS_CHECK_TIMEOUT=5
|
||||
|
||||
# Application Metadata
|
||||
APP_NAME=rxminder
|
||||
APP_VERSION=1.0.0
|
||||
|
||||
# Container Registry
|
||||
REGISTRY_URL=gitea-http.taildb3494.ts.net
|
||||
IMAGE_REPOSITORY=will/rxminder
|
||||
|
||||
# Ingress Configuration
|
||||
INGRESS_CLASS=nginx
|
||||
CERT_MANAGER_ISSUER=letsencrypt-prod
|
||||
|
||||
# Monitoring and Observability
|
||||
ENABLE_METRICS=false
|
||||
METRICS_PORT=9090
|
||||
ENABLE_TRACING=false
|
||||
|
||||
# Development specific (will be overridden in overlays)
|
||||
DEV_MODE=false
|
||||
HOT_RELOAD=false
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: rxminder-couchdb-pvc
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: standard
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: rxminder-couchdb-service
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
selector:
|
||||
app: rxminder
|
||||
component: database
|
||||
ports:
|
||||
- name: couchdb
|
||||
port: 5984
|
||||
targetPort: 5984
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
@@ -1,70 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: rxminder-couchdb
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
serviceName: rxminder-couchdb-service
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: rxminder
|
||||
component: database
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
containers:
|
||||
- name: couchdb
|
||||
image: couchdb:3.3.2
|
||||
ports:
|
||||
- containerPort: 5984
|
||||
env:
|
||||
- name: COUCHDB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: username
|
||||
- name: COUCHDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: password
|
||||
resources:
|
||||
requests:
|
||||
memory: '64Mi'
|
||||
cpu: '30m'
|
||||
limits:
|
||||
memory: '128Mi'
|
||||
cpu: '60m'
|
||||
volumeMounts:
|
||||
- name: couchdb-data
|
||||
mountPath: /opt/couchdb/data
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: couchdb-data
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
accessModes: ['ReadWriteOnce']
|
||||
storageClassName: standard
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -1,107 +0,0 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: rxminder-db-seed
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
containers:
|
||||
- name: db-seeder
|
||||
image: couchdb:3.3.2
|
||||
env:
|
||||
- name: COUCHDB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: username
|
||||
- name: COUCHDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: password
|
||||
command: ['/bin/sh', '-c']
|
||||
args:
|
||||
- |
|
||||
# Wait for CouchDB to be ready
|
||||
echo "Waiting for CouchDB to be ready..."
|
||||
until curl -f http://couchdb-service:5984/_up 2>/dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Create databases
|
||||
echo "Creating databases..."
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app
|
||||
|
||||
# Create default admin user
|
||||
echo "Creating default admin user..."
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"$COUCHDB_USER\",
|
||||
\"password\": \"$COUCHDB_PASSWORD\",
|
||||
\"roles\": [\"admin\"],
|
||||
\"type\": \"user\"
|
||||
}"
|
||||
|
||||
# Create design documents for views
|
||||
echo "Creating design documents..."
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"views": {
|
||||
"by_name": {
|
||||
"map": "function(doc) { if (doc.type === \"medication\") emit(doc.name, doc); }"
|
||||
},
|
||||
"by_user": {
|
||||
"map": "function(doc) { if (doc.type === \"medication\") emit(doc.userId, doc); }"
|
||||
}
|
||||
}
|
||||
}'
|
||||
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"views": {
|
||||
"by_medication": {
|
||||
"map": "function(doc) { if (doc.type === \"reminder\") emit(doc.medicationId, doc); }"
|
||||
},
|
||||
"by_user": {
|
||||
"map": "function(doc) { if (doc.type === \"reminder\") emit(doc.userId, doc); }"
|
||||
}
|
||||
}
|
||||
}'
|
||||
|
||||
# Create a sample user document for reference
|
||||
# Create design document for authentication users
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"views": {
|
||||
"by_username": {
|
||||
"map": "function(doc) { if (doc.type === \"user\" && doc.username) emit(doc.username, doc); }"
|
||||
},
|
||||
"by_email": {
|
||||
"map": "function(doc) { if (doc.type === \"user\" && doc.email) emit(doc.email, doc); }"
|
||||
}
|
||||
}
|
||||
}'
|
||||
echo "Creating sample user document..."
|
||||
curl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "user",
|
||||
"name": "sample_user",
|
||||
"email": "user@example.com",
|
||||
"createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||
}'
|
||||
|
||||
echo "Database seeding completed with default admin user"
|
||||
restartPolicy: Never
|
||||
backoffLimit: 4
|
||||
@@ -1,45 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rxminder-frontend
|
||||
labels:
|
||||
component: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: frontend
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: rxminder-registry-secret
|
||||
containers:
|
||||
- name: frontend
|
||||
image: gitea-http.taildb3494.ts.net/will/rxminder:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: rxminder-config
|
||||
resources:
|
||||
requests:
|
||||
memory: '32Mi'
|
||||
cpu: '20m'
|
||||
limits:
|
||||
memory: '64Mi'
|
||||
cpu: '40m'
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: rxminder-frontend-service
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
spec:
|
||||
selector:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
@@ -1,21 +0,0 @@
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: rxminder-frontend-hpa
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
minReplicas: 1
|
||||
maxReplicas: 3
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 50
|
||||
@@ -1,29 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: rxminder-ingress
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
annotations:
|
||||
# Add SSL redirect if using HTTPS
|
||||
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
# Uncomment for HTTPS with cert-manager
|
||||
# tls:
|
||||
# - hosts:
|
||||
# - rxminder.example.com
|
||||
# secretName: frontend-tls
|
||||
rules:
|
||||
- host: rxminder.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: rxminder-frontend-service
|
||||
port:
|
||||
number: 80
|
||||
@@ -1,62 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
metadata:
|
||||
name: rxminder-base
|
||||
|
||||
namespace: rxminder
|
||||
|
||||
resources:
|
||||
- frontend-deployment.yaml
|
||||
- frontend-service.yaml
|
||||
- couchdb-statefulset.yaml
|
||||
- couchdb-service.yaml
|
||||
- couchdb-pvc.yaml
|
||||
- ingress.yaml
|
||||
- network-policy.yaml
|
||||
- hpa.yaml
|
||||
- db-seed-job.yaml
|
||||
|
||||
# Common labels applied to all resources
|
||||
labels:
|
||||
- pairs:
|
||||
app: rxminder
|
||||
version: v1.0.0
|
||||
|
||||
# Generate ConfigMap from environment files
|
||||
configMapGenerator:
|
||||
- name: rxminder-config
|
||||
envs:
|
||||
- config.env
|
||||
behavior: create
|
||||
|
||||
# Generate Secret for CouchDB
|
||||
secretGenerator:
|
||||
- name: couchdb-secret
|
||||
literals:
|
||||
- username=admin
|
||||
- password=changeme
|
||||
type: Opaque
|
||||
|
||||
# Note: Registry secret should be created manually or via external secret management
|
||||
# For now, this is commented out to avoid issues with missing files
|
||||
# - name: rxminder-registry-secret
|
||||
# files:
|
||||
# - .dockerconfigjson=registry-config.json
|
||||
# type: kubernetes.io/dockerconfigjson
|
||||
|
||||
# Images to be used (can be overridden in overlays)
|
||||
images:
|
||||
- name: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newName: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newTag: latest
|
||||
- name: couchdb
|
||||
newName: couchdb
|
||||
newTag: 3.3.2
|
||||
|
||||
# Replicas (can be overridden in overlays)
|
||||
replicas:
|
||||
- name: rxminder-frontend
|
||||
count: 1
|
||||
- name: rxminder-couchdb
|
||||
count: 1
|
||||
@@ -1,68 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: rxminder-frontend-policy
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: database
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5984
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: rxminder-database-policy
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
component: database
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5984
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: database
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5984
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"auths": {
|
||||
"gitea-http.taildb3494.ts.net": {
|
||||
"username": "REGISTRY_USERNAME",
|
||||
"password": "REGISTRY_PASSWORD",
|
||||
"email": "REGISTRY_EMAIL",
|
||||
"auth": "REGISTRY_AUTH_TOKEN"
|
||||
},
|
||||
"docker.io": {
|
||||
"username": "DOCKER_USERNAME",
|
||||
"password": "DOCKER_PASSWORD",
|
||||
"email": "DOCKER_EMAIL",
|
||||
"auth": "DOCKER_AUTH_TOKEN"
|
||||
}
|
||||
},
|
||||
"HttpHeaders": {
|
||||
"User-Agent": "Docker-Client/20.10.0 (linux)"
|
||||
},
|
||||
"credsStore": "secretservice",
|
||||
"experimental": "disabled"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# Development environment configuration
|
||||
# Generated on: Sun Sep 7 08:46:59 PM PDT 2025
|
||||
|
||||
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=http://rxminder-couchdb-service:5984
|
||||
CORS_ORIGIN=*
|
||||
|
||||
# Development domain
|
||||
INGRESS_HOST=rxminder-dev.local
|
||||
|
||||
# Relaxed timeouts for debugging
|
||||
REQUEST_TIMEOUT=60000
|
||||
HEALTH_CHECK_INTERVAL=60
|
||||
|
||||
# Development image tag
|
||||
IMAGE_TAG=dev
|
||||
@@ -1,92 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
metadata:
|
||||
name: rxminder-dev
|
||||
|
||||
namespace: rxminder-dev
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
# Development-specific labels
|
||||
labels:
|
||||
- pairs:
|
||||
environment: dev
|
||||
|
||||
# Override images for development
|
||||
images:
|
||||
- name: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newName: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newTag: dev
|
||||
- name: couchdb
|
||||
newName: couchdb
|
||||
newTag: 3.3.2
|
||||
|
||||
# Development replicas (lower for resource conservation)
|
||||
replicas:
|
||||
- name: rxminder-frontend
|
||||
count: 1
|
||||
|
||||
# Development-specific patches
|
||||
patches:
|
||||
# Development environment variables via ConfigMap
|
||||
- target:
|
||||
kind: ConfigMap
|
||||
name: rxminder-config
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /data/NODE_ENV
|
||||
value: "development"
|
||||
- op: replace
|
||||
path: /data/LOG_LEVEL
|
||||
value: "debug"
|
||||
- op: add
|
||||
path: /data/DEBUG
|
||||
value: "true"
|
||||
|
||||
# Development resource limits
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources/requests/memory
|
||||
value: "16Mi"
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources/limits/memory
|
||||
value: "32Mi"
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/env
|
||||
value:
|
||||
- name: NODE_ENV
|
||||
value: "development"
|
||||
- name: LOG_LEVEL
|
||||
value: "debug"
|
||||
|
||||
# Development ingress host
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: rxminder-ingress
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: "rxminder-dev.local"
|
||||
|
||||
# Development storage size
|
||||
- target:
|
||||
kind: PersistentVolumeClaim
|
||||
name: rxminder-couchdb-pvc
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/resources/requests/storage
|
||||
value: "1Gi"
|
||||
|
||||
# Development secrets (use weak passwords for dev)
|
||||
secretGenerator:
|
||||
- name: couchdb-secret
|
||||
literals:
|
||||
- username=admin
|
||||
- password=devpass123
|
||||
type: Opaque
|
||||
behavior: replace
|
||||
@@ -1,43 +0,0 @@
|
||||
# Kubernetes configuration for development
|
||||
# Generated automatically from unified configuration
|
||||
# Generated on: 2025-09-09T03:27:30.598Z
|
||||
|
||||
APP_NAME=rxminder
|
||||
APP_VERSION=1.0.0
|
||||
NODE_ENV=development
|
||||
APP_BASE_URL=rxminder.192.168.153.243.nip.io
|
||||
PORT=5173
|
||||
COUCHDB_URL=http://rxminder-couchdb-service:5984
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=L7tfqHyg0T4sIYiWK
|
||||
COUCHDB_DATABASE_NAME=meds_app
|
||||
USE_MOCK_DB=false
|
||||
KUBERNETES_NAMESPACE=rxminder-dev
|
||||
INGRESS_HOST=rxminder.192.168.153.243.nip.io
|
||||
INGRESS_CLASS=nginx
|
||||
CERT_MANAGER_ISSUER=selfsigned
|
||||
STORAGE_CLASS=longhorn
|
||||
STORAGE_SIZE=1Gi
|
||||
ENABLE_EMAIL_VERIFICATION=true
|
||||
ENABLE_OAUTH=true
|
||||
ENABLE_ADMIN_INTERFACE=true
|
||||
ENABLE_MONITORING=false
|
||||
ENABLE_METRICS=false
|
||||
DEBUG_MODE=true
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=text
|
||||
CACHE_TTL=300
|
||||
REQUEST_TIMEOUT=30000
|
||||
MAX_CONNECTIONS=100
|
||||
ENABLE_CORS=true
|
||||
CORS_ORIGIN=*
|
||||
FRONTEND_REPLICAS=1
|
||||
DATABASE_REPLICAS=1
|
||||
FRONTEND_MEMORY_REQUEST=128Mi
|
||||
FRONTEND_CPU_REQUEST=50m
|
||||
FRONTEND_MEMORY_LIMIT=256Mi
|
||||
FRONTEND_CPU_LIMIT=200m
|
||||
DATABASE_MEMORY_REQUEST=256Mi
|
||||
DATABASE_CPU_REQUEST=100m
|
||||
DATABASE_MEMORY_LIMIT=512Mi
|
||||
DATABASE_CPU_LIMIT=500m
|
||||
@@ -1,73 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
metadata:
|
||||
name: rxminder-development
|
||||
|
||||
# Reference the base configuration
|
||||
resources:
|
||||
- ../../base
|
||||
- namespace.yaml
|
||||
|
||||
# Override namespace for development
|
||||
namespace: rxminder-dev
|
||||
|
||||
# Development-specific labels
|
||||
labels:
|
||||
- pairs:
|
||||
environment: development
|
||||
tier: development
|
||||
|
||||
# Development image tags and configurations
|
||||
images:
|
||||
- name: frontend-image
|
||||
newName: gitea-http.taildb3494.ts.net/will/meds
|
||||
newTag: latest
|
||||
- name: couchdb-image
|
||||
newName: couchdb
|
||||
newTag: 3.3.2
|
||||
|
||||
# Development replicas
|
||||
replicas:
|
||||
- name: rxminder-frontend
|
||||
count: 1
|
||||
- name: rxminder-couchdb
|
||||
count: 1
|
||||
|
||||
# Environment-specific patches
|
||||
patches:
|
||||
# Resource limits
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
|
||||
- target:
|
||||
kind: StatefulSet
|
||||
name: rxminder-couchdb
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# ConfigMap generation
|
||||
configMapGenerator:
|
||||
- name: rxminder-config
|
||||
envs:
|
||||
- config.env
|
||||
behavior: create
|
||||
@@ -1,126 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: rxminder-couchdb
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: couchdb
|
||||
resources:
|
||||
requests:
|
||||
memory: '512Mi'
|
||||
cpu: '200m'
|
||||
limits:
|
||||
memory: '1Gi'
|
||||
cpu: '1000m'
|
||||
# Production environment variables
|
||||
env:
|
||||
- name: COUCHDB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: username
|
||||
- name: COUCHDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: password
|
||||
# Production CouchDB configuration
|
||||
- name: ERL_FLAGS
|
||||
value: '-setcookie monster'
|
||||
- name: COUCHDB_SECRET
|
||||
value: 'changeme_in_production'
|
||||
# Production health checks with tighter timings
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# Security context for production
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 5984
|
||||
runAsGroup: 5984
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
# Volume mounts with proper permissions
|
||||
volumeMounts:
|
||||
- name: couchdb-data
|
||||
mountPath: /opt/couchdb/data
|
||||
- name: couchdb-config
|
||||
mountPath: /opt/couchdb/etc/local.d
|
||||
readOnly: true
|
||||
# Pod-level security and scheduling for production
|
||||
securityContext:
|
||||
fsGroup: 5984
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
# Production scheduling preferences for database
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- rxminder
|
||||
- key: component
|
||||
operator: In
|
||||
values:
|
||||
- database
|
||||
topologyKey: kubernetes.io/hostname
|
||||
nodeAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
preference:
|
||||
matchExpressions:
|
||||
- key: node-type
|
||||
operator: In
|
||||
values:
|
||||
- database
|
||||
- storage
|
||||
# Toleration for production node taints
|
||||
tolerations:
|
||||
- key: 'node-role.kubernetes.io/production'
|
||||
operator: 'Equal'
|
||||
value: 'true'
|
||||
effect: 'NoSchedule'
|
||||
- key: 'node-role.kubernetes.io/database'
|
||||
operator: 'Equal'
|
||||
value: 'true'
|
||||
effect: 'NoSchedule'
|
||||
# Additional volumes for production configuration
|
||||
volumes:
|
||||
- name: couchdb-config
|
||||
configMap:
|
||||
name: couchdb-production-config
|
||||
# Production volume claim template with SSD storage
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: couchdb-data
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
environment: production
|
||||
spec:
|
||||
accessModes: ['ReadWriteOnce']
|
||||
storageClassName: ssd
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
@@ -1,79 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rxminder-frontend
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
resources:
|
||||
requests:
|
||||
memory: '256Mi'
|
||||
cpu: '100m'
|
||||
limits:
|
||||
memory: '512Mi'
|
||||
cpu: '500m'
|
||||
# Production environment variables
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: 'production'
|
||||
- name: LOG_LEVEL
|
||||
value: 'warn'
|
||||
- name: ENABLE_MONITORING
|
||||
value: 'true'
|
||||
# Production readiness and liveness probes with tighter timings
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
# Security context for production
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
# Pod-level security and scheduling for production
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
# Production scheduling preferences
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- rxminder
|
||||
- key: component
|
||||
operator: In
|
||||
values:
|
||||
- frontend
|
||||
topologyKey: kubernetes.io/hostname
|
||||
# Toleration for production node taints
|
||||
tolerations:
|
||||
- key: 'node-role.kubernetes.io/production'
|
||||
operator: 'Equal'
|
||||
value: 'true'
|
||||
effect: 'NoSchedule'
|
||||
@@ -1,65 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: rxminder-ingress
|
||||
annotations:
|
||||
# Enable SSL redirect for production
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
|
||||
# Use production certificate issuer
|
||||
cert-manager.io/cluster-issuer: 'letsencrypt-prod'
|
||||
# Security headers for production
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
more_set_headers "X-Frame-Options: DENY";
|
||||
more_set_headers "X-Content-Type-Options: nosniff";
|
||||
more_set_headers "X-XSS-Protection: 1; mode=block";
|
||||
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload";
|
||||
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
|
||||
# Rate limiting for production
|
||||
nginx.ingress.kubernetes.io/rate-limit: '100'
|
||||
nginx.ingress.kubernetes.io/rate-limit-window: '1m'
|
||||
# Enable CORS for production API access
|
||||
nginx.ingress.kubernetes.io/enable-cors: 'true'
|
||||
nginx.ingress.kubernetes.io/cors-allow-origin: 'https://rxminder.yourdomain.com'
|
||||
# Compression for better performance
|
||||
nginx.ingress.kubernetes.io/compression: 'gzip'
|
||||
# Client body size limit
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: '10m'
|
||||
# Connection and read timeouts
|
||||
nginx.ingress.kubernetes.io/proxy-connect-timeout: '60'
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: '60'
|
||||
# Enable modsecurity WAF for production
|
||||
nginx.ingress.kubernetes.io/enable-modsecurity: 'true'
|
||||
nginx.ingress.kubernetes.io/modsecurity-snippet: |
|
||||
SecRuleEngine On
|
||||
SecAuditEngine RelevantOnly
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
# Production TLS configuration
|
||||
tls:
|
||||
- hosts:
|
||||
- rxminder.yourdomain.com
|
||||
- api.rxminder.yourdomain.com
|
||||
secretName: rxminder-tls-prod
|
||||
rules:
|
||||
# Main application domain
|
||||
- host: rxminder.yourdomain.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: rxminder-frontend-service
|
||||
port:
|
||||
number: 80
|
||||
# API subdomain for direct database access (if needed)
|
||||
- host: api.rxminder.yourdomain.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: rxminder-couchdb-service
|
||||
port:
|
||||
number: 5984
|
||||
@@ -1,128 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
metadata:
|
||||
name: rxminder-production
|
||||
|
||||
# Reference the base configuration
|
||||
resources:
|
||||
- ../../base
|
||||
- namespace.yaml
|
||||
|
||||
# Override namespace for production
|
||||
namespace: rxminder-prod
|
||||
|
||||
# Production-specific labels
|
||||
labels:
|
||||
- pairs:
|
||||
environment: production
|
||||
tier: prod
|
||||
|
||||
# Production image tags and configurations
|
||||
images:
|
||||
- name: frontend-image
|
||||
newName: gitea-http.taildb3494.ts.net/will/rxminder
|
||||
newTag: v1.0.0
|
||||
- name: couchdb-image
|
||||
newName: couchdb
|
||||
newTag: 3.3.2
|
||||
|
||||
# Production replicas - higher for availability
|
||||
replicas:
|
||||
- name: rxminder-frontend
|
||||
count: 3
|
||||
- name: rxminder-couchdb
|
||||
count: 1
|
||||
|
||||
# Production resource patches
|
||||
patchesStrategicMerge:
|
||||
- frontend-resources.yaml
|
||||
- couchdb-resources.yaml
|
||||
- ingress-prod.yaml
|
||||
|
||||
# Production secrets (to be created manually or via external secret management)
|
||||
secretGenerator:
|
||||
- name: couchdb-secret
|
||||
behavior: replace
|
||||
literals:
|
||||
- username=admin
|
||||
# Note: In production, use external secret management like:
|
||||
# - Kubernetes External Secrets Operator
|
||||
# - HashiCorp Vault
|
||||
# - AWS Secrets Manager
|
||||
# This is just a placeholder
|
||||
- password=CHANGE_IN_PRODUCTION
|
||||
|
||||
# Production-specific patches for security and performance
|
||||
patches:
|
||||
# Production-specific ConfigMap patches
|
||||
- target:
|
||||
kind: ConfigMap
|
||||
name: rxminder-config
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /data/NODE_ENV
|
||||
value: "production"
|
||||
- op: replace
|
||||
path: /data/LOG_LEVEL
|
||||
value: "warn"
|
||||
- op: add
|
||||
path: /data/ENABLE_MONITORING
|
||||
value: "true"
|
||||
- op: add
|
||||
path: /data/CACHE_TTL
|
||||
value: "3600"
|
||||
|
||||
# Enable HTTPS redirect on ingress
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: rxminder-ingress
|
||||
patch: |-
|
||||
- op: add
|
||||
path: /metadata/annotations/nginx.ingress.kubernetes.io~1ssl-redirect
|
||||
value: "true"
|
||||
- op: add
|
||||
path: /metadata/annotations/cert-manager.io~1cluster-issuer
|
||||
value: "letsencrypt-prod"
|
||||
|
||||
# Add resource limits for production workloads
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Production storage class and size
|
||||
- target:
|
||||
kind: StatefulSet
|
||||
name: rxminder-couchdb
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/volumeClaimTemplates/0/spec/storageClassName
|
||||
value: "ssd"
|
||||
- op: replace
|
||||
path: /spec/volumeClaimTemplates/0/spec/resources/requests/storage
|
||||
value: "10Gi"
|
||||
|
||||
# Production CouchDB resources
|
||||
- target:
|
||||
kind: StatefulSet
|
||||
name: rxminder-couchdb
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: rxminder-prod
|
||||
labels:
|
||||
app: rxminder
|
||||
environment: production
|
||||
tier: prod
|
||||
version: v1.0.0
|
||||
@@ -1,43 +0,0 @@
|
||||
# Kubernetes configuration for production
|
||||
# Generated automatically from unified configuration
|
||||
# Generated on: 2025-09-09T03:27:30.604Z
|
||||
|
||||
APP_NAME=rxminder
|
||||
APP_VERSION=1.0.0
|
||||
NODE_ENV=production
|
||||
APP_BASE_URL=rxminder.192.168.153.243.nip.io
|
||||
PORT=5173
|
||||
COUCHDB_URL=http://rxminder-couchdb-service:5984
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=L7tfqHyg0T4sIYiWK
|
||||
COUCHDB_DATABASE_NAME=meds_app
|
||||
USE_MOCK_DB=false
|
||||
KUBERNETES_NAMESPACE=rxminder-prod
|
||||
INGRESS_HOST=rxminder.192.168.153.243.nip.io
|
||||
INGRESS_CLASS=nginx
|
||||
CERT_MANAGER_ISSUER=letsencrypt-prod
|
||||
STORAGE_CLASS=longhorn
|
||||
STORAGE_SIZE=1Gi
|
||||
ENABLE_EMAIL_VERIFICATION=true
|
||||
ENABLE_OAUTH=true
|
||||
ENABLE_ADMIN_INTERFACE=true
|
||||
ENABLE_MONITORING=false
|
||||
ENABLE_METRICS=false
|
||||
DEBUG_MODE=true
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=json
|
||||
CACHE_TTL=3600
|
||||
REQUEST_TIMEOUT=120000
|
||||
MAX_CONNECTIONS=200
|
||||
ENABLE_CORS=true
|
||||
CORS_ORIGIN=https://rxminder.com
|
||||
FRONTEND_REPLICAS=3
|
||||
DATABASE_REPLICAS=1
|
||||
FRONTEND_MEMORY_REQUEST=256Mi
|
||||
FRONTEND_CPU_REQUEST=100m
|
||||
FRONTEND_MEMORY_LIMIT=512Mi
|
||||
FRONTEND_CPU_LIMIT=500m
|
||||
DATABASE_MEMORY_REQUEST=512Mi
|
||||
DATABASE_CPU_REQUEST=200m
|
||||
DATABASE_MEMORY_LIMIT=1Gi
|
||||
DATABASE_CPU_LIMIT=1000m
|
||||
@@ -1,73 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
metadata:
|
||||
name: rxminder-production
|
||||
|
||||
# Reference the base configuration
|
||||
resources:
|
||||
- ../../base
|
||||
- namespace.yaml
|
||||
|
||||
# Override namespace for production
|
||||
namespace: rxminder-prod
|
||||
|
||||
# Production-specific labels
|
||||
labels:
|
||||
- pairs:
|
||||
environment: production
|
||||
tier: prod
|
||||
|
||||
# Production image tags and configurations
|
||||
images:
|
||||
- name: frontend-image
|
||||
newName: gitea-http.taildb3494.ts.net/will/meds
|
||||
newTag: latest
|
||||
- name: couchdb-image
|
||||
newName: couchdb
|
||||
newTag: 3.3.2
|
||||
|
||||
# Production replicas
|
||||
replicas:
|
||||
- name: rxminder-frontend
|
||||
count: 3
|
||||
- name: rxminder-couchdb
|
||||
count: 1
|
||||
|
||||
# Environment-specific patches
|
||||
patches:
|
||||
# Resource limits
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
- target:
|
||||
kind: StatefulSet
|
||||
name: rxminder-couchdb
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
# ConfigMap generation
|
||||
configMapGenerator:
|
||||
- name: rxminder-config
|
||||
envs:
|
||||
- config.env
|
||||
behavior: create
|
||||
@@ -1,43 +0,0 @@
|
||||
# Kubernetes configuration for staging
|
||||
# Generated automatically from unified configuration
|
||||
# Generated on: 2025-09-09T03:27:30.602Z
|
||||
|
||||
APP_NAME=rxminder
|
||||
APP_VERSION=1.0.0
|
||||
NODE_ENV=staging
|
||||
APP_BASE_URL=rxminder.192.168.153.243.nip.io
|
||||
PORT=5173
|
||||
COUCHDB_URL=http://rxminder-couchdb-service:5984
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=L7tfqHyg0T4sIYiWK
|
||||
COUCHDB_DATABASE_NAME=meds_app
|
||||
USE_MOCK_DB=false
|
||||
KUBERNETES_NAMESPACE=rxminder-staging
|
||||
INGRESS_HOST=rxminder.192.168.153.243.nip.io
|
||||
INGRESS_CLASS=nginx
|
||||
CERT_MANAGER_ISSUER=letsencrypt-staging
|
||||
STORAGE_CLASS=longhorn
|
||||
STORAGE_SIZE=1Gi
|
||||
ENABLE_EMAIL_VERIFICATION=true
|
||||
ENABLE_OAUTH=true
|
||||
ENABLE_ADMIN_INTERFACE=true
|
||||
ENABLE_MONITORING=false
|
||||
ENABLE_METRICS=false
|
||||
DEBUG_MODE=true
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=json
|
||||
CACHE_TTL=1800
|
||||
REQUEST_TIMEOUT=60000
|
||||
MAX_CONNECTIONS=150
|
||||
ENABLE_CORS=true
|
||||
CORS_ORIGIN=https://staging.rxminder.com
|
||||
FRONTEND_REPLICAS=2
|
||||
DATABASE_REPLICAS=1
|
||||
FRONTEND_MEMORY_REQUEST=256Mi
|
||||
FRONTEND_CPU_REQUEST=100m
|
||||
FRONTEND_MEMORY_LIMIT=512Mi
|
||||
FRONTEND_CPU_LIMIT=500m
|
||||
DATABASE_MEMORY_REQUEST=512Mi
|
||||
DATABASE_CPU_REQUEST=200m
|
||||
DATABASE_MEMORY_LIMIT=1Gi
|
||||
DATABASE_CPU_LIMIT=1000m
|
||||
@@ -1,73 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
metadata:
|
||||
name: rxminder-staging
|
||||
|
||||
# Reference the base configuration
|
||||
resources:
|
||||
- ../../base
|
||||
- namespace.yaml
|
||||
|
||||
# Override namespace for staging
|
||||
namespace: rxminder-staging
|
||||
|
||||
# Staging-specific labels
|
||||
labels:
|
||||
- pairs:
|
||||
environment: staging
|
||||
tier: staging
|
||||
|
||||
# Staging image tags and configurations
|
||||
images:
|
||||
- name: frontend-image
|
||||
newName: gitea-http.taildb3494.ts.net/will/meds
|
||||
newTag: latest
|
||||
- name: couchdb-image
|
||||
newName: couchdb
|
||||
newTag: 3.3.2
|
||||
|
||||
# Staging replicas
|
||||
replicas:
|
||||
- name: rxminder-frontend
|
||||
count: 2
|
||||
- name: rxminder-couchdb
|
||||
count: 1
|
||||
|
||||
# Environment-specific patches
|
||||
patches:
|
||||
# Resource limits
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: rxminder-frontend
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
- target:
|
||||
kind: StatefulSet
|
||||
name: rxminder-couchdb
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
# ConfigMap generation
|
||||
configMapGenerator:
|
||||
- name: rxminder-config
|
||||
envs:
|
||||
- config.env
|
||||
behavior: create
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
# Kubernetes Manifests for RxMinder
|
||||
|
||||
This directory contains Kubernetes manifests and templates for deploying RxMinder on a Kubernetes cluster.
|
||||
|
||||
## 🎯 Template-Based Deployment (Recommended)
|
||||
|
||||
RxMinder uses **template files** with environment variable substitution for secure, user-friendly deployment.
|
||||
|
||||
### **Template Files**
|
||||
|
||||
- `couchdb-secret.yaml.template` - Database credentials (uses `stringData` - no base64 encoding needed!)
|
||||
- `ingress.yaml.template` - Ingress configuration with customizable hostname
|
||||
- `configmap.yaml.template` - Application configuration
|
||||
- `frontend-deployment.yaml.template` - Frontend deployment
|
||||
|
||||
### **Static Files**
|
||||
|
||||
- `couchdb-statefulset.yaml` - StatefulSet for CouchDB database
|
||||
- `couchdb-service.yaml` - Service to expose CouchDB
|
||||
- `couchdb-pvc.yaml` - PersistentVolumeClaim for CouchDB storage
|
||||
- `db-seed-job.yaml` - Job to seed initial database data
|
||||
- `frontend-service.yaml` - Service to expose frontend
|
||||
- `hpa.yaml` - Horizontal Pod Autoscaler
|
||||
- `network-policy.yaml` - Network security policies
|
||||
|
||||
## 🚀 Deployment Instructions
|
||||
|
||||
### **Option 1: Template-Based Deployment (Recommended)**
|
||||
|
||||
```bash
|
||||
# 1. Copy and configure environment
|
||||
cp .env.example .env
|
||||
|
||||
# 2. Edit .env with your settings
|
||||
nano .env
|
||||
# Set: APP_NAME, COUCHDB_PASSWORD, INGRESS_HOST, etc.
|
||||
|
||||
# 3. Deploy with templates
|
||||
./scripts/k8s-deploy-template.sh deploy
|
||||
|
||||
# 4. Check status
|
||||
./scripts/k8s-deploy-template.sh status
|
||||
|
||||
# 5. Cleanup (if needed)
|
||||
./scripts/k8s-deploy-template.sh delete
|
||||
```
|
||||
|
||||
**Benefits of template approach:**
|
||||
|
||||
- ✅ No manual base64 encoding required
|
||||
- ✅ Secure credential management via `.env`
|
||||
- ✅ Automatic dependency ordering
|
||||
- ✅ Built-in validation and status checking
|
||||
- ✅ Easy customization of app name and configuration
|
||||
|
||||
### **Option 2: Manual Deployment**
|
||||
|
||||
For advanced users who want manual control:
|
||||
|
||||
```bash
|
||||
# Manual template processing (requires envsubst)
|
||||
envsubst < couchdb-secret.yaml.template > /tmp/couchdb-secret.yaml
|
||||
envsubst < ingress.yaml.template > /tmp/ingress.yaml
|
||||
|
||||
# Apply resources in order
|
||||
kubectl apply -f /tmp/couchdb-secret.yaml
|
||||
kubectl apply -f couchdb-pvc.yaml
|
||||
kubectl apply -f couchdb-service.yaml
|
||||
kubectl apply -f couchdb-statefulset.yaml
|
||||
kubectl apply -f configmap.yaml.template
|
||||
kubectl apply -f frontend-deployment.yaml.template
|
||||
kubectl apply -f frontend-service.yaml
|
||||
kubectl apply -f /tmp/ingress.yaml
|
||||
kubectl apply -f network-policy.yaml
|
||||
kubectl apply -f hpa.yaml
|
||||
kubectl apply -f db-seed-job.yaml
|
||||
```
|
||||
|
||||
### **Environment Configuration**
|
||||
|
||||
Create `.env` with these required variables:
|
||||
|
||||
```bash
|
||||
# Application Configuration
|
||||
APP_NAME=rxminder # Customize your app name
|
||||
INGRESS_HOST=rxminder.yourdomain.com # Your external hostname
|
||||
|
||||
# Docker Image Configuration
|
||||
DOCKER_IMAGE=myregistry.com/rxminder:v1.0.0 # Your container image
|
||||
|
||||
# Database Credentials
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=super-secure-password-123
|
||||
|
||||
# Storage Configuration
|
||||
STORAGE_CLASS=longhorn # Your cluster's storage class
|
||||
STORAGE_SIZE=20Gi # Database storage allocation
|
||||
|
||||
# Optional: Advanced Configuration
|
||||
VITE_COUCHDB_URL=http://localhost:5984
|
||||
APP_BASE_URL=https://rxminder.yourdomain.com
|
||||
```
|
||||
|
||||
### **Docker Image Options**
|
||||
|
||||
Configure the container image based on your registry:
|
||||
|
||||
| Registry Type | Example Image | Use Case |
|
||||
| ----------------------------- | -------------------------------------------------------------- | ------------------ |
|
||||
| **Docker Hub** | `rxminder/rxminder:v1.0.0` | Public releases |
|
||||
| **GitHub Container Registry** | `ghcr.io/username/rxminder:latest` | GitHub integration |
|
||||
| **AWS ECR** | `123456789012.dkr.ecr.us-west-2.amazonaws.com/rxminder:v1.0.0` | AWS deployments |
|
||||
| **Google GCR** | `gcr.io/project-id/rxminder:stable` | Google Cloud |
|
||||
| **Private Registry** | `registry.company.com/rxminder:production` | Enterprise |
|
||||
| **Local Registry** | `localhost:5000/rxminder:dev` | Development |
|
||||
|
||||
### **Storage Class Options**
|
||||
|
||||
Choose the appropriate storage class for your environment:
|
||||
|
||||
| Platform | Recommended Storage Class | Notes |
|
||||
| --------------------------- | ------------------------- | -------------------------------- |
|
||||
| **Raspberry Pi + Longhorn** | `longhorn` | Distributed storage across nodes |
|
||||
| **k3s** | `local-path` | Single-node local storage |
|
||||
| **AWS EKS** | `gp3` or `gp2` | General Purpose SSD |
|
||||
| **Google GKE** | `pd-ssd` | SSD Persistent Disk |
|
||||
| **Azure AKS** | `managed-premium` | Premium SSD |
|
||||
|
||||
**Check available storage classes:**
|
||||
|
||||
```bash
|
||||
kubectl get storageclass
|
||||
```
|
||||
|
||||
```bash
|
||||
# Kubernetes Ingress Configuration
|
||||
INGRESS_HOST=app.meds.192.168.1.100.nip.io # Your cluster IP
|
||||
|
||||
# For production with custom domain
|
||||
INGRESS_HOST=meds.yourdomain.com
|
||||
```
|
||||
|
||||
## Credentials
|
||||
|
||||
The CouchDB credentials are stored in a Kubernetes secret. **IMPORTANT**: Update the credentials in `couchdb-secret.yaml` with your own secure values before deploying to production.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Frontend Pod │
|
||||
│ ┌─────────────────────────────────┐│
|
||||
│ │ React Application ││
|
||||
│ │ • Authentication Service ││ ← Embedded in frontend
|
||||
│ │ • UI Components ││
|
||||
│ │ • Medication Management ││
|
||||
│ │ • Email Integration ││
|
||||
│ └─────────────────────────────────┘│
|
||||
└─────────────────────────────────────┘
|
||||
↓ HTTP API
|
||||
┌─────────────────────────────────────┐
|
||||
│ CouchDB StatefulSet │
|
||||
│ • User Data & Authentication │
|
||||
│ • Medication Records │
|
||||
│ • Persistent Storage │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- **Monolithic Frontend**: Single container with all functionality
|
||||
- **Database**: CouchDB running as a StatefulSet with persistent storage
|
||||
- **Storage**: Longhorn for persistent volume management
|
||||
- **Networking**: Services configured for proper communication between components
|
||||
|
||||
## Raspberry Pi Compatibility
|
||||
|
||||
All manifests use multi-architecture images and are optimized for ARM architecture commonly used in Raspberry Pi clusters.
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The PVC uses Longhorn storage class for persistent storage
|
||||
- CouchDB runs as a StatefulSet for stable network identifiers
|
||||
- Frontend is exposed via LoadBalancer service
|
||||
- CouchDB is exposed via ClusterIP service (internal access only)
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ${APP_NAME:-rxminder}-config
|
||||
labels:
|
||||
app: ${APP_NAME:-rxminder}
|
||||
data:
|
||||
NODE_ENV: 'production'
|
||||
REACT_APP_API_URL: 'http://couchdb-service:5984'
|
||||
LOG_LEVEL: 'info'
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ${APP_NAME}-config
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
REACT_APP_API_URL: "http://${APP_NAME}-couchdb-service:5984"
|
||||
LOG_LEVEL: "info"
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ${APP_NAME}-couchdb-pvc
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ${STORAGE_CLASS}
|
||||
resources:
|
||||
requests:
|
||||
storage: ${STORAGE_SIZE}
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: couchdb-secret
|
||||
labels:
|
||||
app: ${APP_NAME:-rxminder}
|
||||
component: database
|
||||
type: Opaque
|
||||
stringData:
|
||||
# These values will be automatically base64 encoded by Kubernetes
|
||||
# Update these in your .env file before deployment
|
||||
username: ${COUCHDB_USER:-admin}
|
||||
password: ${COUCHDB_PASSWORD:-change-this-secure-password}
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: couchdb-secret
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
type: Opaque
|
||||
stringData:
|
||||
# These values will be automatically base64 encoded by Kubernetes
|
||||
# Update these in your .env file before deployment
|
||||
username: ${COUCHDB_USER}
|
||||
password: ${COUCHDB_PASSWORD}
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${APP_NAME}-couchdb-service
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
selector:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
ports:
|
||||
- name: couchdb
|
||||
port: 5984
|
||||
targetPort: 5984
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${APP_NAME}-couchdb-service
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
selector:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
ports:
|
||||
- name: couchdb
|
||||
port: 5984
|
||||
targetPort: 5984
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
@@ -1,70 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: ${APP_NAME}-couchdb
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
serviceName: ${APP_NAME}-couchdb-service
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
containers:
|
||||
- name: couchdb
|
||||
image: couchdb:3.3.2
|
||||
ports:
|
||||
- containerPort: 5984
|
||||
env:
|
||||
- name: COUCHDB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: username
|
||||
- name: COUCHDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: password
|
||||
resources:
|
||||
requests:
|
||||
memory: '64Mi'
|
||||
cpu: '30m'
|
||||
limits:
|
||||
memory: '128Mi'
|
||||
cpu: '60m'
|
||||
volumeMounts:
|
||||
- name: couchdb-data
|
||||
mountPath: /opt/couchdb/data
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: couchdb-data
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
accessModes: ['ReadWriteOnce']
|
||||
storageClassName: ${STORAGE_CLASS}
|
||||
resources:
|
||||
requests:
|
||||
storage: ${STORAGE_SIZE}
|
||||
@@ -1,70 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: ${APP_NAME}-couchdb
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
serviceName: ${APP_NAME}-couchdb-service
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
containers:
|
||||
- name: couchdb
|
||||
image: couchdb:3.3.2
|
||||
ports:
|
||||
- containerPort: 5984
|
||||
env:
|
||||
- name: COUCHDB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: username
|
||||
- name: COUCHDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: password
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "30m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "60m"
|
||||
volumeMounts:
|
||||
- name: couchdb-data
|
||||
mountPath: /opt/couchdb/data
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_up
|
||||
port: 5984
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: couchdb-data
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: database
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: ${STORAGE_CLASS}
|
||||
resources:
|
||||
requests:
|
||||
storage: ${STORAGE_SIZE}
|
||||
@@ -1,107 +0,0 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: db-seed
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
containers:
|
||||
- name: db-seeder
|
||||
image: couchdb:3.3.2
|
||||
env:
|
||||
- name: COUCHDB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: username
|
||||
- name: COUCHDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: couchdb-secret
|
||||
key: password
|
||||
command: ['/bin/sh', '-c']
|
||||
args:
|
||||
- |
|
||||
# Wait for CouchDB to be ready
|
||||
echo "Waiting for CouchDB to be ready..."
|
||||
until curl -f http://couchdb-service:5984/_up 2>/dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Create databases
|
||||
echo "Creating databases..."
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app
|
||||
|
||||
# Create default admin user
|
||||
echo "Creating default admin user..."
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"$COUCHDB_USER\",
|
||||
\"password\": \"$COUCHDB_PASSWORD\",
|
||||
\"roles\": [\"admin\"],
|
||||
\"type\": \"user\"
|
||||
}"
|
||||
|
||||
# Create design documents for views
|
||||
echo "Creating design documents..."
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"views": {
|
||||
"by_name": {
|
||||
"map": "function(doc) { if (doc.type === \"medication\") emit(doc.name, doc); }"
|
||||
},
|
||||
"by_user": {
|
||||
"map": "function(doc) { if (doc.type === \"medication\") emit(doc.userId, doc); }"
|
||||
}
|
||||
}
|
||||
}'
|
||||
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"views": {
|
||||
"by_medication": {
|
||||
"map": "function(doc) { if (doc.type === \"reminder\") emit(doc.medicationId, doc); }"
|
||||
},
|
||||
"by_user": {
|
||||
"map": "function(doc) { if (doc.type === \"reminder\") emit(doc.userId, doc); }"
|
||||
}
|
||||
}
|
||||
}'
|
||||
|
||||
# Create a sample user document for reference
|
||||
# Create design document for authentication users
|
||||
curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"views": {
|
||||
"by_username": {
|
||||
"map": "function(doc) { if (doc.type === \"user\" && doc.username) emit(doc.username, doc); }"
|
||||
},
|
||||
"by_email": {
|
||||
"map": "function(doc) { if (doc.type === \"user\" && doc.email) emit(doc.email, doc); }"
|
||||
}
|
||||
}
|
||||
}'
|
||||
echo "Creating sample user document..."
|
||||
curl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "user",
|
||||
"name": "sample_user",
|
||||
"email": "user@example.com",
|
||||
"createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||
}'
|
||||
|
||||
echo "Database seeding completed with default admin user"
|
||||
restartPolicy: Never
|
||||
backoffLimit: 4
|
||||
@@ -1,46 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ${APP_NAME}-frontend
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: ${DOCKER_IMAGE}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ${APP_NAME}-config
|
||||
resources:
|
||||
requests:
|
||||
memory: '32Mi'
|
||||
cpu: '20m'
|
||||
limits:
|
||||
memory: '64Mi'
|
||||
cpu: '40m'
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
@@ -1,48 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ${APP_NAME}-frontend
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ${APP_NAME}-registry-secret
|
||||
containers:
|
||||
- name: frontend
|
||||
image: ${DOCKER_IMAGE}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ${APP_NAME}-config
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "20m"
|
||||
limits:
|
||||
memory: "64Mi"
|
||||
cpu: "40m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${APP_NAME}-frontend-service
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
spec:
|
||||
selector:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${APP_NAME}-frontend-service
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
spec:
|
||||
selector:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
@@ -1,21 +0,0 @@
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: frontend-hpa
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: frontend
|
||||
minReplicas: 1
|
||||
maxReplicas: 3
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 50
|
||||
@@ -1,29 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: frontend-ingress
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
annotations: {}
|
||||
# Add SSL redirect if using HTTPS
|
||||
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
# Uncomment for HTTPS with cert-manager
|
||||
# tls:
|
||||
# - hosts:
|
||||
# - ${INGRESS_HOST}
|
||||
# secretName: frontend-tls
|
||||
rules:
|
||||
- host: app.meds.192.168.153.243.nip.io # TODO: Make configurable via deployment script
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: frontend-service
|
||||
port:
|
||||
number: 80
|
||||
@@ -1,29 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ${APP_NAME}-ingress
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: frontend
|
||||
annotations:
|
||||
# Add SSL redirect if using HTTPS
|
||||
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
# Uncomment for HTTPS with cert-manager
|
||||
# tls:
|
||||
# - hosts:
|
||||
# - ${INGRESS_HOST}
|
||||
# secretName: frontend-tls
|
||||
rules:
|
||||
- host: ${INGRESS_HOST}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ${APP_NAME}-frontend-service
|
||||
port:
|
||||
number: 80
|
||||
@@ -1,68 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: frontend-policy
|
||||
labels:
|
||||
app: rxminder
|
||||
component: frontend
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: database
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5984
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: database-policy
|
||||
labels:
|
||||
app: rxminder
|
||||
component: database
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
component: database
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5984
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: database
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5984
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${APP_NAME}-registry-secret
|
||||
labels:
|
||||
app: ${APP_NAME}
|
||||
component: registry
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: ${REGISTRY_AUTH_BASE64}
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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 };
|
||||
@@ -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! 🚀"
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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();
|
||||
@@ -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"
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user