diff --git a/servers/legion/apps/plane.yaml b/servers/legion/apps/plane.yaml new file mode 100644 index 0000000..4d85305 --- /dev/null +++ b/servers/legion/apps/plane.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: plane + namespace: argocd +spec: + project: default + source: + repoURL: https://git.neuralplatform.ai/will/infrastructure.git + targetRevision: main + path: servers/legion/k8s/plane + destination: + server: https://kubernetes.default.svc + namespace: plane + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/servers/legion/k8s/plane/api.yaml b/servers/legion/k8s/plane/api.yaml new file mode 100644 index 0000000..a25c67d --- /dev/null +++ b/servers/legion/k8s/plane/api.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plane-api + namespace: plane +spec: + replicas: 1 + selector: + matchLabels: + app: plane-api + template: + metadata: + labels: + app: plane-api + spec: + containers: + - name: plane-api + image: makeplane/plane-backend:stable + command: ["./bin/takeoff"] + ports: + - containerPort: 8000 + envFrom: + - configMapRef: + name: plane-config + env: + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: plane-secrets + key: secret_key + readinessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 6 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: plane-api + namespace: plane +spec: + selector: + app: plane-api + ports: + - port: 8000 + targetPort: 8000 diff --git a/servers/legion/k8s/plane/beat.yaml b/servers/legion/k8s/plane/beat.yaml new file mode 100644 index 0000000..066de27 --- /dev/null +++ b/servers/legion/k8s/plane/beat.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plane-beat + namespace: plane +spec: + replicas: 1 + selector: + matchLabels: + app: plane-beat + template: + metadata: + labels: + app: plane-beat + spec: + containers: + - name: plane-beat + image: makeplane/plane-backend:stable + command: ["./bin/beat"] + envFrom: + - configMapRef: + name: plane-config + env: + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: plane-secrets + key: secret_key + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi diff --git a/servers/legion/k8s/plane/configmap.yaml b/servers/legion/k8s/plane/configmap.yaml new file mode 100644 index 0000000..24204c2 --- /dev/null +++ b/servers/legion/k8s/plane/configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: plane-config + namespace: plane +data: + DATABASE_URL: "postgresql://plane:plane@plane-postgres:5432/plane" + REDIS_URL: "redis://plane-redis:6379/" + WEB_URL: "https://plane.neuralplatform.ai" + CORS_ALLOWED_ORIGINS: "https://plane.neuralplatform.ai" + EMAIL_BACKEND: "django.core.mail.backends.console.EmailBackend" + DEFAULT_FROM_EMAIL: "noreply@neuralplatform.ai" + ENABLE_SIGNUP: "0" + DJANGO_SETTINGS_MODULE: "plane.settings.production" + GUNICORN_WORKERS: "2" diff --git a/servers/legion/k8s/plane/externalsecret.yaml b/servers/legion/k8s/plane/externalsecret.yaml new file mode 100644 index 0000000..368f604 --- /dev/null +++ b/servers/legion/k8s/plane/externalsecret.yaml @@ -0,0 +1,18 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: plane-secrets + namespace: plane +spec: + refreshInterval: 1h + secretStoreRef: + name: vault + kind: ClusterSecretStore + target: + name: plane-secrets + creationPolicy: Owner + data: + - secretKey: secret_key + remoteRef: + key: secret/data/plane/config + property: secret_key diff --git a/servers/legion/k8s/plane/ingress.yaml b/servers/legion/k8s/plane/ingress.yaml new file mode 100644 index 0000000..9f6fc29 --- /dev/null +++ b/servers/legion/k8s/plane/ingress.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: plane + namespace: plane + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + tls: + - hosts: + - plane.neuralplatform.ai + secretName: plane-tls + rules: + - host: plane.neuralplatform.ai + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: plane-api + port: + number: 8000 + - path: /auth + pathType: Prefix + backend: + service: + name: plane-api + port: + number: 8000 + - path: / + pathType: Prefix + backend: + service: + name: plane-web + port: + number: 3000 diff --git a/servers/legion/k8s/plane/kustomization.yaml b/servers/legion/k8s/plane/kustomization.yaml new file mode 100644 index 0000000..b4882c5 --- /dev/null +++ b/servers/legion/k8s/plane/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - namespace.yaml + - externalsecret.yaml + - configmap.yaml + - postgres.yaml + - redis.yaml + - api.yaml + - worker.yaml + - beat.yaml + - web.yaml + - ingress.yaml diff --git a/servers/legion/k8s/plane/namespace.yaml b/servers/legion/k8s/plane/namespace.yaml new file mode 100644 index 0000000..0a68e7e --- /dev/null +++ b/servers/legion/k8s/plane/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: plane + labels: + app.kubernetes.io/managed-by: argocd diff --git a/servers/legion/k8s/plane/postgres.yaml b/servers/legion/k8s/plane/postgres.yaml new file mode 100644 index 0000000..7cf2434 --- /dev/null +++ b/servers/legion/k8s/plane/postgres.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: plane-postgres-data + namespace: plane +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plane-postgres + namespace: plane +spec: + replicas: 1 + selector: + matchLabels: + app: plane-postgres + template: + metadata: + labels: + app: plane-postgres + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - containerPort: 5432 + env: + - name: POSTGRES_USER + value: "plane" + - name: POSTGRES_PASSWORD + value: "plane" + - name: POSTGRES_DB + value: "plane" + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: ["pg_isready", "-U", "plane"] + initialDelaySeconds: 10 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + volumes: + - name: data + persistentVolumeClaim: + claimName: plane-postgres-data +--- +apiVersion: v1 +kind: Service +metadata: + name: plane-postgres + namespace: plane +spec: + selector: + app: plane-postgres + ports: + - port: 5432 + targetPort: 5432 diff --git a/servers/legion/k8s/plane/redis.yaml b/servers/legion/k8s/plane/redis.yaml new file mode 100644 index 0000000..e88e0d4 --- /dev/null +++ b/servers/legion/k8s/plane/redis.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plane-redis + namespace: plane +spec: + replicas: 1 + selector: + matchLabels: + app: plane-redis + template: + metadata: + labels: + app: plane-redis + spec: + containers: + - name: redis + image: redis:7-alpine + ports: + - containerPort: 6379 + readinessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: plane-redis + namespace: plane +spec: + selector: + app: plane-redis + ports: + - port: 6379 + targetPort: 6379 diff --git a/servers/legion/k8s/plane/web.yaml b/servers/legion/k8s/plane/web.yaml new file mode 100644 index 0000000..bd16975 --- /dev/null +++ b/servers/legion/k8s/plane/web.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plane-web + namespace: plane +spec: + replicas: 1 + selector: + matchLabels: + app: plane-web + template: + metadata: + labels: + app: plane-web + spec: + containers: + - name: plane-web + image: makeplane/plane-frontend:stable + ports: + - containerPort: 3000 + env: + - name: NEXT_PUBLIC_API_BASE_URL + value: "https://plane.neuralplatform.ai" + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 6 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: plane-web + namespace: plane +spec: + selector: + app: plane-web + ports: + - port: 3000 + targetPort: 3000 diff --git a/servers/legion/k8s/plane/worker.yaml b/servers/legion/k8s/plane/worker.yaml new file mode 100644 index 0000000..39c96ca --- /dev/null +++ b/servers/legion/k8s/plane/worker.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plane-worker + namespace: plane +spec: + replicas: 1 + selector: + matchLabels: + app: plane-worker + template: + metadata: + labels: + app: plane-worker + spec: + containers: + - name: plane-worker + image: makeplane/plane-backend:stable + command: ["./bin/worker"] + envFrom: + - configMapRef: + name: plane-config + env: + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: plane-secrets + key: secret_key + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi diff --git a/servers/legion/plane-access.tf b/servers/legion/plane-access.tf new file mode 100644 index 0000000..0b892ed --- /dev/null +++ b/servers/legion/plane-access.tf @@ -0,0 +1,32 @@ +# Cloudflare Zero Trust Access — Plane project management +# Protects plane.neuralplatform.ai with Google OAuth. + +locals { + plane_zone_id = local.zone_neuralplatform_ai + plane_allowed_emails = [ + "andersonwilliam85@gmail.com", + "1timlingo@gmail.com", + ] +} + +resource "cloudflare_zero_trust_access_application" "plane" { + zone_id = local.plane_zone_id + name = "Plane" + domain = "plane.neuralplatform.ai" + type = "self_hosted" + session_duration = "24h" + allowed_idps = ["808f1913-5a8e-4a97-9e68-8ec58e362110"] # Google + auto_redirect_to_identity = true +} + +resource "cloudflare_zero_trust_access_policy" "plane_allow" { + application_id = cloudflare_zero_trust_access_application.plane.id + account_id = var.cloudflare_account_id + name = "Allow Will and Tim" + precedence = 1 + decision = "allow" + + include { + email = local.plane_allowed_emails + } +}