diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 3d89dec..2c0b0d9 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: env: - IMAGE_NAME: batty1039.startdedicated.net:5000/cms-demo + IMAGE_NAME: batty1039.startdedicated.net:5000/cms FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..59a1594 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + cache-dependency-path: ui/app/package-lock.json + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.18.6 + + - name: Install frontend dependencies + run: npm ci + working-directory: ui/app + + - name: Build frontend + run: npm run build + working-directory: ui/app + + - name: Run Go checks + run: make check + + - name: Validate Helm environments + run: make helm-lint diff --git a/.github/workflows/deploy-rancher.yml b/.github/workflows/deploy-rancher.yml new file mode 100644 index 0000000..78c9dce --- /dev/null +++ b/.github/workflows/deploy-rancher.yml @@ -0,0 +1,148 @@ +name: Deploy to Rancher + +on: + workflow_run: + workflows: + - Build, test, and publish + types: + - completed + workflow_dispatch: + inputs: + environment: + description: Deployment environment + required: true + default: development + type: choice + options: + - development + - production + image_tag: + description: Image tag (defaults to latest) + required: false + default: latest + type: string + +permissions: + contents: read + +concurrency: + group: cms-${{ github.event_name == 'workflow_dispatch' && inputs.environment || 'production' }} + cancel-in-progress: false + +jobs: + deploy: + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main') + name: Deploy ${{ github.event_name == 'workflow_dispatch' && inputs.environment || 'production' }} + runs-on: ubuntu-latest + environment: ${{ github.event_name == 'workflow_dispatch' && inputs.environment || 'production' }} + env: + TARGET_ENVIRONMENT: ${{ github.event_name == 'workflow_dispatch' && inputs.environment || 'production' }} + KUBE_CONFIG_BASE64: ${{ secrets.RANCHER_KUBE_CONFIG_BASE64 }} + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + CMS_SSO_TOKEN: ${{ secrets.CMS_SSO_TOKEN }} + CMS_ALLOWED_ORIGINS: ${{ vars.CMS_ALLOWED_ORIGINS }} + CMS_SSO_HEADER: ${{ vars.CMS_SSO_HEADER }} + CMS_SSO_COOKIE: ${{ vars.CMS_SSO_COOKIE }} + RANCHER_NAMESPACE: ${{ vars.RANCHER_NAMESPACE }} + + steps: + - name: Check out source + uses: actions/checkout@v5 + with: + ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }} + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.18.6 + + - name: Set up kubectl + uses: azure/setup-kubectl@v4 + with: + version: v1.34.1 + + - name: Resolve deployment settings + env: + REQUESTED_TAG: ${{ inputs.image_tag }} + PUBLISHED_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + set -eu + if [ -z "$KUBE_CONFIG_BASE64" ]; then + echo "RANCHER_KUBE_CONFIG_BASE64 is required" >&2 + exit 1 + fi + if [ -z "$REGISTRY_USERNAME" ] || [ -z "$REGISTRY_PASSWORD" ]; then + echo "REGISTRY_USERNAME and REGISTRY_PASSWORD are required" >&2 + exit 1 + fi + namespace="${RANCHER_NAMESPACE:-cms-$TARGET_ENVIRONMENT}" + if [ -n "$PUBLISHED_SHA" ]; then + image_tag="$PUBLISHED_SHA" + else + image_tag="${REQUESTED_TAG:-latest}" + fi + allowed_origins="$CMS_ALLOWED_ORIGINS" + if [ -z "$allowed_origins" ]; then + if [ "$TARGET_ENVIRONMENT" = "production" ]; then + allowed_origins="https://cms.urpi.be" + else + allowed_origins="http://localhost:30081" + fi + fi + { + echo "DEPLOY_NAMESPACE=$namespace" + echo "IMAGE_TAG=$image_tag" + echo "RESOLVED_ALLOWED_ORIGINS=$allowed_origins" + } >> "$GITHUB_ENV" + + - name: Configure Rancher cluster access + run: | + set -eu + mkdir -p "$HOME/.kube" + printf '%s' "$KUBE_CONFIG_BASE64" | base64 --decode > "$HOME/.kube/config" + chmod 600 "$HOME/.kube/config" + kubectl cluster-info + + - name: Configure registry credentials + run: | + set -eu + kubectl create namespace "$DEPLOY_NAMESPACE" \ + --dry-run=client -o yaml | kubectl apply -f - + kubectl -n "$DEPLOY_NAMESPACE" create secret docker-registry cms-registry \ + --docker-server=batty1039.startdedicated.net:5000 \ + --docker-username="$REGISTRY_USERNAME" \ + --docker-password="$REGISTRY_PASSWORD" \ + --dry-run=client -o yaml | kubectl apply -f - + + - name: Deploy Helm release + run: | + set -eu + values_file="deploy/helm/cms/values-$TARGET_ENVIRONMENT.yaml" + set -- \ + --set-string "image.tag=$IMAGE_TAG" \ + --set "imagePullSecrets[0].name=cms-registry" \ + --set-string "app.allowedOrigins=$RESOLVED_ALLOWED_ORIGINS" \ + --set-string "secrets.ssoToken=$CMS_SSO_TOKEN" + if [ -n "$CMS_SSO_HEADER" ]; then + set -- "$@" --set-string "app.ssoHeader=$CMS_SSO_HEADER" + fi + if [ -n "$CMS_SSO_COOKIE" ]; then + set -- "$@" --set-string "app.ssoCookie=$CMS_SSO_COOKIE" + fi + helm upgrade --install cms deploy/helm/cms \ + --namespace "$DEPLOY_NAMESPACE" \ + --values "$values_file" \ + --atomic \ + --wait \ + --timeout 10m \ + "$@" + + - name: Verify deployment + run: | + kubectl -n "$DEPLOY_NAMESPACE" rollout status deployment/cms \ + --timeout=5m + helm test cms --namespace "$DEPLOY_NAMESPACE" --logs diff --git a/.gitignore b/.gitignore index a323705..183cba7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ cmd/server/web/dist/* .env data/ *.json.tmp +deploy/local.yaml # Node node_modules/ diff --git a/Makefile b/Makefile index c93ba08..a1dc6fc 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,15 @@ SHELL := /bin/sh -IMAGE ?= cms-demo +IMAGE ?= cms TAG ?= latest -REGISTRY_IMAGE ?= batty1039.startdedicated.net:5000/cms-demo +REGISTRY_IMAGE ?= batty1039.startdedicated.net:5000/cms +HELM ?= helm +HELM_CHART := deploy/helm/cms +HELM_RELEASE ?= cms +HELM_NAMESPACE ?= cms +KUBECONFIG ?= $(HOME)/.config/kubectl/rancher.urpi.local.yaml -.PHONY: help ui frontend backend demo production run test check docker docker-push docker-run clean +.PHONY: help ui frontend backend demo production run test check docker docker-push docker-run clean rancher-storage helm-lint helm-deploy-development helm-deploy-production help: @echo "make ui build the React app into cmd/server/web/dist" @@ -71,3 +76,25 @@ clean: rm -rf cmd/server/web/dist ui/dist mkdir -p cmd/server/web/dist printf 'Placeholder so the Go embed directive resolves before the frontend is built.\nRun `make ui` to replace this directory with the real React build.\n' > cmd/server/web/dist/placeholder.txt +rancher-storage: + KUBECONFIG="$(KUBECONFIG)" kubectl apply -f deploy/rancher/local-path.yaml + +helm-lint: + $(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-development.yaml + $(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-production.yaml + $(HELM) template $(HELM_RELEASE) $(HELM_CHART) \ + -f $(HELM_CHART)/values-development.yaml >/dev/null + $(HELM) template $(HELM_RELEASE) $(HELM_CHART) \ + -f $(HELM_CHART)/values-production.yaml >/dev/null + +helm-deploy-development: + KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \ + --namespace $(HELM_NAMESPACE)-development --create-namespace \ + -f $(HELM_CHART)/values-development.yaml \ + --atomic --wait --timeout 10m + +helm-deploy-production: + KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \ + --namespace $(HELM_NAMESPACE)-production --create-namespace \ + -f $(HELM_CHART)/values-production.yaml \ + --atomic --wait --timeout 10m \ No newline at end of file diff --git a/deploy/helm/cms/.helmignore b/deploy/helm/cms/.helmignore new file mode 100644 index 0000000..cb173e2 --- /dev/null +++ b/deploy/helm/cms/.helmignore @@ -0,0 +1,5 @@ +.git/ +.github/ +.idea/ +.vscode/ +*.tgz diff --git a/deploy/helm/cms/Chart.yaml b/deploy/helm/cms/Chart.yaml new file mode 100644 index 0000000..191b973 --- /dev/null +++ b/deploy/helm/cms/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: cms +description: CMS application +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/deploy/helm/cms/templates/NOTES.txt b/deploy/helm/cms/templates/NOTES.txt new file mode 100644 index 0000000..1323af0 --- /dev/null +++ b/deploy/helm/cms/templates/NOTES.txt @@ -0,0 +1,12 @@ +CMS has been installed in namespace {{ .Release.Namespace }}. + +{{- if .Values.ingress.enabled }} +Open the host configured in ingress.hosts. +{{- else if eq .Values.service.type "NodePort" }} +The application is exposed on cluster node port {{ .Values.service.nodePort }}. +Open it through the configured external reverse proxy. +{{- else }} +Run: + kubectl -n {{ .Release.Namespace }} port-forward service/{{ include "cms.fullname" . }} 8080:{{ .Values.service.port }} +Then open http://127.0.0.1:8080. +{{- end }} diff --git a/deploy/helm/cms/templates/_helpers.tpl b/deploy/helm/cms/templates/_helpers.tpl new file mode 100644 index 0000000..1a79b2b --- /dev/null +++ b/deploy/helm/cms/templates/_helpers.tpl @@ -0,0 +1,33 @@ +{{- define "cms.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "cms.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "cms.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} +app.kubernetes.io/name: {{ include "cms.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "cms.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cms.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "cms.secretName" -}} +{{- printf "%s-secrets" (include "cms.fullname" .) }} +{{- end }} diff --git a/deploy/helm/cms/templates/app-deployment.yaml b/deploy/helm/cms/templates/app-deployment.yaml new file mode 100644 index 0000000..bee05cb --- /dev/null +++ b/deploy/helm/cms/templates/app-deployment.yaml @@ -0,0 +1,108 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cms.fullname" . }} + labels: + {{- include "cms.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: {{ .Values.strategy.type }} + selector: + matchLabels: + {{- include "cms.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: application + template: + metadata: + annotations: + checksum/secrets: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "cms.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: application + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + automountServiceAccountToken: false + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: cms + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + env: + - name: CMS_PROFILE + value: {{ .Values.app.environment | quote }} + - name: CMS_ALLOWED_ORIGINS + value: {{ .Values.app.allowedOrigins | quote }} + - name: CMS_CONTENT_FILE + value: {{ ternary .Values.app.contentFile "" .Values.persistence.enabled | quote }} + - name: CMS_POSTGRES_URL + value: {{ .Values.app.postgresURL | quote }} + - name: CMS_SSO_HEADER + value: {{ .Values.app.ssoHeader | quote }} + - name: CMS_SSO_COOKIE + value: {{ .Values.app.ssoCookie | quote }} + - name: CMS_SSO_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "cms.secretName" . }} + key: sso-token + ports: + - name: http + containerPort: 8080 + protocol: TCP + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 12 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: temporary-files + mountPath: /tmp + {{- if .Values.persistence.enabled }} + - name: content + mountPath: /data + {{- end }} + volumes: + - name: temporary-files + emptyDir: {} + {{- if .Values.persistence.enabled }} + - name: content + persistentVolumeClaim: + claimName: {{ include "cms.fullname" . }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/helm/cms/templates/app-service.yaml b/deploy/helm/cms/templates/app-service.yaml new file mode 100644 index 0000000..659ed29 --- /dev/null +++ b/deploy/helm/cms/templates/app-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cms.fullname" . }} + labels: + {{- include "cms.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "cms.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: application + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} diff --git a/deploy/helm/cms/templates/ingress.yaml b/deploy/helm/cms/templates/ingress.yaml new file mode 100644 index 0000000..830fb50 --- /dev/null +++ b/deploy/helm/cms/templates/ingress.yaml @@ -0,0 +1,35 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "cms.fullname" . }} + labels: + {{- include "cms.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- with .Values.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "cms.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deploy/helm/cms/templates/secret.yaml b/deploy/helm/cms/templates/secret.yaml new file mode 100644 index 0000000..151a838 --- /dev/null +++ b/deploy/helm/cms/templates/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cms.secretName" . }} + labels: + {{- include "cms.labels" . | nindent 4 }} +type: Opaque +stringData: + sso-token: {{ .Values.secrets.ssoToken | quote }} diff --git a/deploy/helm/cms/templates/tests/test-connection.yaml b/deploy/helm/cms/templates/tests/test-connection.yaml new file mode 100644 index 0000000..10c7b51 --- /dev/null +++ b/deploy/helm/cms/templates/tests/test-connection.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "cms.fullname" . }}-test-connection" + labels: + {{- include "cms.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation +spec: + automountServiceAccountToken: false + restartPolicy: Never + containers: + - name: wget + image: busybox:1.37 + command: + - wget + args: + - -qO- + - "http://{{ include "cms.fullname" . }}:{{ .Values.service.port }}/health" diff --git a/deploy/helm/cms/values-development.yaml b/deploy/helm/cms/values-development.yaml new file mode 100644 index 0000000..16924ec --- /dev/null +++ b/deploy/helm/cms/values-development.yaml @@ -0,0 +1,26 @@ +image: + tag: latest + pullPolicy: Always + +app: + environment: demo + allowedOrigins: http://localhost:30081 + contentFile: "" + +service: + type: NodePort + nodePort: 30081 + +ingress: + enabled: false + +resources: + requests: + cpu: 50m + memory: 96Mi + limits: + cpu: 300m + memory: 384Mi + +persistence: + enabled: false diff --git a/deploy/helm/cms/values-production.yaml b/deploy/helm/cms/values-production.yaml new file mode 100644 index 0000000..eb829b2 --- /dev/null +++ b/deploy/helm/cms/values-production.yaml @@ -0,0 +1,27 @@ +image: + tag: latest + pullPolicy: IfNotPresent + +app: + environment: production + allowedOrigins: https://cms.urpi.be + contentFile: /data/content.json + +service: + type: NodePort + nodePort: 31373 + +ingress: + enabled: false + +resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: "1" + memory: 768Mi + +persistence: + enabled: true + size: 5Gi diff --git a/deploy/helm/cms/values.yaml b/deploy/helm/cms/values.yaml new file mode 100644 index 0000000..6182449 --- /dev/null +++ b/deploy/helm/cms/values.yaml @@ -0,0 +1,76 @@ +nameOverride: "" +fullnameOverride: "" + +replicaCount: 1 + +image: + repository: batty1039.startdedicated.net:5000/cms + tag: latest + pullPolicy: IfNotPresent + +imagePullSecrets: [] + +app: + environment: production + allowedOrigins: http://localhost:8080 + contentFile: /data/content.json + postgresURL: "" + ssoHeader: X-SSO-Token + ssoCookie: "" + +secrets: + ssoToken: "" + +service: + type: ClusterIP + port: 80 + nodePort: null + +ingress: + enabled: false + className: nginx + annotations: {} + hosts: + - host: cms.local + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +podAnnotations: {} +podLabels: {} + +podSecurityContext: + fsGroup: 10001 + fsGroupChangePolicy: OnRootMismatch + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 10001 + +strategy: + type: Recreate + +persistence: + enabled: true + size: 2Gi + accessModes: + - ReadWriteOnce + storageClass: "" + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/deploy/nginx/README.md b/deploy/nginx/README.md new file mode 100644 index 0000000..4754e2f --- /dev/null +++ b/deploy/nginx/README.md @@ -0,0 +1,51 @@ +# Dedicated-server reverse proxy + +Production exposes the CMS application through Kubernetes NodePort `31373`. + +## DNS + +In one.com, create an `A` record for `cms.urpi.be` pointing to the dedicated +server's public IPv4 address. Add an `AAAA` record only when the server and its +firewall are configured for public IPv6. Wait for: + +```bash +dig +short cms.urpi.be +``` + +to return the dedicated server address before requesting a certificate. + +## Nginx and Certbot + +The included virtual host assumes Nginx runs on the Kubernetes node and can +reach the NodePort at `127.0.0.1:31373`. + +```bash +sudo install -m 0644 deploy/nginx/cms.urpi.be.conf \ + /etc/nginx/sites-available/cms.urpi.be.conf +sudo ln -s /etc/nginx/sites-available/cms.urpi.be.conf \ + /etc/nginx/sites-enabled/cms.urpi.be.conf +sudo nginx -t +sudo systemctl reload nginx +sudo certbot --nginx -d cms.urpi.be +sudo certbot renew --dry-run +``` + +Certbot adds the HTTPS listener, certificate paths, and HTTP-to-HTTPS redirect +to this virtual host. Allow inbound TCP ports `80` and `443`. Do not expose +NodePort `31373` to the public internet. If Nginx is not on a Kubernetes node, replace +`127.0.0.1` in `proxy_pass` with a private cluster-node address and allow +`31373` only from the Nginx server. + +## Deploy and verify + +Deploy the production profile: + +```bash +make helm-deploy-production +kubectl -n cms-production get pods +kubectl -n cms-production get service cms +curl --fail http://127.0.0.1:31373/health +curl --fail https://cms.urpi.be/health +``` + +The `cms` service should show `80:31373/TCP`. diff --git a/deploy/nginx/cms.urpi.be.conf b/deploy/nginx/cms.urpi.be.conf new file mode 100644 index 0000000..ea0edae --- /dev/null +++ b/deploy/nginx/cms.urpi.be.conf @@ -0,0 +1,21 @@ +server { + listen 80; + listen [::]:80; + server_name cms.urpi.be; + + client_max_body_size 10m; + + location / { + proxy_pass http://127.0.0.1:31373; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_request_buffering off; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } +} diff --git a/deploy/rancher/local-path.yaml b/deploy/rancher/local-path.yaml new file mode 100644 index 0000000..6cf033d --- /dev/null +++ b/deploy/rancher/local-path.yaml @@ -0,0 +1,188 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: local-path-storage + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: local-path-provisioner-service-account + namespace: local-path-storage + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: local-path-provisioner-role + namespace: local-path-storage +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: local-path-provisioner-role +rules: + - apiGroups: [""] + resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: local-path-provisioner-bind + namespace: local-path-storage +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: local-path-provisioner-role +subjects: + - kind: ServiceAccount + name: local-path-provisioner-service-account + namespace: local-path-storage + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: local-path-provisioner-bind +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: local-path-provisioner-role +subjects: + - kind: ServiceAccount + name: local-path-provisioner-service-account + namespace: local-path-storage + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: local-path-provisioner + namespace: local-path-storage +spec: + replicas: 1 + selector: + matchLabels: + app: local-path-provisioner + template: + metadata: + labels: + app: local-path-provisioner + spec: + serviceAccountName: local-path-provisioner-service-account + containers: + - name: local-path-provisioner + image: docker.io/rancher/local-path-provisioner:v0.0.37 + imagePullPolicy: IfNotPresent + command: + - local-path-provisioner + - --debug + - start + - --config + - /etc/config/config.json + ports: + - name: health + containerPort: 8080 + protocol: TCP + startupProbe: + httpGet: + path: /health + port: health + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 12 + livenessProbe: + httpGet: + path: /health + port: health + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /ready + port: health + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: config-volume + mountPath: /etc/config/ + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_MOUNT_PATH + value: /etc/config/ + - name: HEALTH_PORT + value: "8080" + volumes: + - name: config-volume + configMap: + name: local-path-config + +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: local-path + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: rancher.io/local-path +volumeBindingMode: WaitForFirstConsumer +reclaimPolicy: Retain + +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: local-path-config + namespace: local-path-storage +data: + config.json: |- + { + "nodePathMap":[ + { + "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", + "paths":["/var/lib/rancher/k3s/storage"] + } + ] + } + setup: |- + #!/bin/sh + set -eu + mkdir -m 0777 -p "$VOL_DIR" + teardown: |- + #!/bin/sh + set -eu + rm -rf "$VOL_DIR" + helperPod.yaml: |- + apiVersion: v1 + kind: Pod + metadata: + name: helper-pod + spec: + priorityClassName: system-node-critical + tolerations: + - key: node.kubernetes.io/disk-pressure + operator: Exists + effect: NoSchedule + containers: + - name: helper-pod + image: docker.io/library/busybox + imagePullPolicy: IfNotPresent diff --git a/deploy/rancher/registries.yaml b/deploy/rancher/registries.yaml new file mode 100644 index 0000000..da1e7e4 --- /dev/null +++ b/deploy/rancher/registries.yaml @@ -0,0 +1,9 @@ +mirrors: + "batty1039.startdedicated.net:5000": + endpoint: + - "http://batty1039.startdedicated.net:5000" + +configs: + "batty1039.startdedicated.net:5000": + tls: + insecure_skip_verify: true diff --git a/ui/app/src/api.ts b/ui/app/src/api.ts index 2b4ff4d..6b1bedd 100644 --- a/ui/app/src/api.ts +++ b/ui/app/src/api.ts @@ -54,7 +54,7 @@ export async function requestSession(): Promise { } function demoAuthHeaders(): Record { - const token = window.sessionStorage.getItem('cms-demo-token') + const token = window.sessionStorage.getItem('cms-token') return token ? {Authorization: `Bearer ${token}`} : {} } diff --git a/ui/app/src/auth.tsx b/ui/app/src/auth.tsx index 2f5a844..0bab508 100644 --- a/ui/app/src/auth.tsx +++ b/ui/app/src/auth.tsx @@ -47,13 +47,13 @@ export function AuthProvider({ children }: { children: ReactNode }) { const signIn = useCallback(() => { signInWithGoogle() }, []) const sudo = useCallback(async (username: string, password: string) => { const result = await sudoLogin(username, password) - window.sessionStorage.setItem('cms-demo-token', result.token) + window.sessionStorage.setItem('cms-token', result.token) setAuthenticated(true) setCanEdit(true) setUsername(result.user.username) }, []) const signOut = useCallback(() => { - window.sessionStorage.removeItem('cms-demo-token') + window.sessionStorage.removeItem('cms-token') setAuthenticated(false) setCanEdit(false) setUsername(undefined)