From bbe98c95265d35d95fc654abed1055b43bc3c4e7 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 20 Jun 2024 14:24:02 +0100 Subject: [PATCH 01/11] update etag --- CHANGELOG.md | 3 +++ jinjafx_server.py | 2 +- www/logs.css | 13 ++++++++++ www/logs.html | 61 ++--------------------------------------------- www/logs.js | 42 ++++++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 60 deletions(-) create mode 100644 www/logs.css create mode 100644 www/logs.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bd63833..d4a7701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## CHANGELOG +### [24.6.3] - In Development + ### [24.6.2] - Jun 20, 2024 - The ETag hash is now across all additional headers including `Content-Type` and `Content-Security-Policy` as well as the content itself @@ -315,6 +317,7 @@ ### 21.11.0 - Nov 29, 2021 - Initial release +[24.6.3]: https://github.com/cmason3/jinjafx_server/compare/24.6.2...24.6.3 [24.6.2]: https://github.com/cmason3/jinjafx_server/compare/24.6.1...24.6.2 [24.6.1]: https://github.com/cmason3/jinjafx_server/compare/24.6.0...24.6.1 [24.6.0]: https://github.com/cmason3/jinjafx_server/compare/24.5.0...24.6.0 diff --git a/jinjafx_server.py b/jinjafx_server.py index 7c4dcda..4893822 100755 --- a/jinjafx_server.py +++ b/jinjafx_server.py @@ -28,7 +28,7 @@ import re, argparse, hashlib, traceback, glob, hmac, uuid, struct, binascii, gzip, requests, ctypes, subprocess import cmarkgfm, emoji -__version__ = '24.6.2' +__version__ = '24.6.3' llock = threading.RLock() rlock = threading.RLock() diff --git a/www/logs.css b/www/logs.css new file mode 100644 index 0000000..2547c55 --- /dev/null +++ b/www/logs.css @@ -0,0 +1,13 @@ +body { + color: white; + background: #000040; +} +pre { + height: 100%; + font-family: 'Fira Code', monospace; + font-size: 14px; + font-variant-ligatures: none; + white-space: pre-wrap; + word-break: break-all; + overflow-y: hidden; +} diff --git a/www/logs.html b/www/logs.html index 859f669..b24c0eb 100644 --- a/www/logs.html +++ b/www/logs.html @@ -8,65 +8,8 @@ - - + +

diff --git a/www/logs.js b/www/logs.js
new file mode 100644
index 0000000..1b027df
--- /dev/null
+++ b/www/logs.js
@@ -0,0 +1,42 @@
+(function() {
+  let interval = 60;
+
+  function scroll() {
+    let e = document.getElementById('container');
+    e.scrollTop = e.scrollHeight;
+  }
+
+  function update() {
+    var xHR = new XMLHttpRequest();
+    xHR.open("GET", '/logs?raw', true);
+
+    xHR.onload = function() {
+      if (this.status == 200) {
+        document.getElementById('container').innerHTML = xHR.responseText;
+        scroll();
+        setTimeout(update, interval * 1000);
+      }
+      else {
+        document.getElementById('container').innerHTML = 'HTTP ERROR ' + this.status;
+      }
+    };
+
+    xHR.onerror = function() {
+      document.getElementById('container').innerHTML = 'XMLHttpRequest ERROR';
+      setTimeout(update, interval * 1000);
+    };
+
+    xHR.ontimeout = function() {
+      document.getElementById('container').innerHTML = 'XMLHttpRequest TIMEOUT';
+      setTimeout(update, interval * 1000);
+    };
+
+    xHR.timeout = 3000;
+    xHR.send();
+  }
+
+  window.onresize = scroll;
+  window.onload = function() {
+    update();
+  };
+})();

From 1deaf6297088176699ab8f8eb599c277862d5635 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Thu, 20 Jun 2024 14:31:35 +0100
Subject: [PATCH 02/11] update logging due to csp violation

---
 jinjafx_server.py | 2 +-
 www/logs.html     | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/jinjafx_server.py b/jinjafx_server.py
index 4893822..c5120b8 100755
--- a/jinjafx_server.py
+++ b/jinjafx_server.py
@@ -392,7 +392,7 @@ def do_GET(self, head=False, cache=True, versioned=False):
 
       headers = {
         'X-Content-Type-Options': 'nosniff',
-        'Content-Security-Policy': "default-src 'self'; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com; img-src data: *; frame-ancestors 'none'",
+        'Content-Security-Policy': "default-src 'self'; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src data: *; frame-ancestors 'none'",
         'Referrer-Policy': 'strict-origin-when-cross-origin'
       }
       etag = '"' + hashlib.sha224(repr(headers).encode('utf-8') + b'|' + r[0].encode('utf-8') + b'; ' + r[2]).hexdigest() + '"'
diff --git a/www/logs.html b/www/logs.html
index b24c0eb..bdcdf14 100644
--- a/www/logs.html
+++ b/www/logs.html
@@ -8,8 +8,8 @@
     
     
     
-    
-    
+    
+    
   
   
     


From 91d7199a8cdada13fe09f2827d6139d4d6f96e42 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Fri, 21 Jun 2024 16:04:47 +0100
Subject: [PATCH 03/11] update kubernetes

---
 CHANGELOG.md                      |  2 +
 {podman => kubernetes}/Dockerfile |  0
 {podman => kubernetes}/README.md  | 15 ++++++-
 kubernetes/kubernetes.yml         | 75 +++++++++++++++++++++++++++++++
 podman/jinjafx.container          | 21 ---------
 5 files changed, 91 insertions(+), 22 deletions(-)
 rename {podman => kubernetes}/Dockerfile (100%)
 rename {podman => kubernetes}/README.md (83%)
 create mode 100644 kubernetes/kubernetes.yml
 delete mode 100644 podman/jinjafx.container

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4a7701..3293d8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
 ## CHANGELOG
 
 ### [24.6.3] - In Development
+- Fixed an issue with `/logs` as it didn't adhere to the default `Content-Security-Policy`
+- Updated instructions for deploying JinjaFx Server as a Container using Kubernetes
 
 ### [24.6.2] - Jun 20, 2024
 - The ETag hash is now across all additional headers including `Content-Type` and `Content-Security-Policy` as well as the content itself
diff --git a/podman/Dockerfile b/kubernetes/Dockerfile
similarity index 100%
rename from podman/Dockerfile
rename to kubernetes/Dockerfile
diff --git a/podman/README.md b/kubernetes/README.md
similarity index 83%
rename from podman/README.md
rename to kubernetes/README.md
index 0d31ba2..6a2a1fc 100644
--- a/podman/README.md
+++ b/kubernetes/README.md
@@ -1,7 +1,20 @@
-## Rootless Podman for JinjaFx Server
+## JinjaFx Server as a Container in Kubernetes
 
 JinjaFx Server will always be available in Docker Hub at [https://hub.docker.com/repository/docker/cmason3/jinjafx_server](https://hub.docker.com/repository/docker/cmason3/jinjafx_server) - the `latest` tag will always refer to the latest released version.
 
+
+
+
+```
+openssl req -nodes -newkey rsa:2048 -keyout ingress.key -out ingress.csr -subj "/CN={CN}/emailAddress={emailAddress}/O={O}/L={L}/ST={ST}/C={C}" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:*.{CLUSTER}.{DOMAIN}"))
+```
+
+```
+kubectl create secret generic jinjafx --from-literal=jfx-weblog-key={KEY}
+```
+
+
+
 The following commands will launch a container for JinjaFx Server which listens on localhost on port 8080.
 
 Rootless Podman has two methods of running, either with root inside the container, which is mapped to the current non-root user outside the container, or if we use `UserNS=keep-id` then we use the current outside non-root user inside the container as well. Running with non-root inside and non-root outside is always preferred from a security perspective and as JinjaFx Server doesn't require root priviledges this is what this does.
diff --git a/kubernetes/kubernetes.yml b/kubernetes/kubernetes.yml
new file mode 100644
index 0000000..66b91c4
--- /dev/null
+++ b/kubernetes/kubernetes.yml
@@ -0,0 +1,75 @@
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: jinjafx-ingress
+spec:
+  ingressClassName: public
+  rules:
+  - host: jinjafx.
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: jinjafx-service
+            port:
+              number: 8080
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: jinjafx-service
+spec:
+  selector:
+    app: jinjafx
+  ports:
+  - protocol: TCP
+    port: 8080
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: jinjafx
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: jinjafx
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxUnavailable: 0
+      maxSurge: 1
+  template:
+    metadata:
+      labels:
+        app: jinjafx
+    spec:
+      containers:
+      - name: jinjafx
+        image: docker.io/cmason3/jinjafx_server:24.6.2-1
+        args: ["-pandoc", "-weblog"]
+        ports:
+        - containerPort: 8080
+        readinessProbe:
+          httpGet: { scheme: HTTP, port: 8080, path: /ping }
+          initialDelaySeconds: 30
+          periodSeconds: 30
+          timeoutSeconds: 5
+        livenessProbe:
+          httpGet: { scheme: HTTP, port: 8080, path: /ping }
+          periodSeconds: 30
+          timeoutSeconds: 5
+        env:
+        - name: TZ
+          value: "Europe/London"
+        - name: JFX_WEBLOG_KEY
+          valueFrom:
+            secretKeyRef:
+              name: jinjafx
+              key: jfx-weblog-key
+
diff --git a/podman/jinjafx.container b/podman/jinjafx.container
deleted file mode 100644
index fb970ad..0000000
--- a/podman/jinjafx.container
+++ /dev/null
@@ -1,21 +0,0 @@
-[Unit]
-Description=JinjaFx Server
-
-[Container]
-ContainerName=jinjafx
-Image=docker.io/cmason3/jinjafx_server:latest
-Volume=${HOME}/logs/jinjafx.log:/var/log/jinjafx.log:Z
-Secret=jfx_weblog_key,type=env,target=JFX_WEBLOG_KEY
-Exec=-weblog -pandoc -logfile /var/log/jinjafx.log
-PublishPort=127.0.0.1:8080:8080
-DropCapability=all
-Timezone=local
-LogDriver=none
-UserNS=keep-id
-
-[Service]
-TimeoutStartSec=300
-Restart=always
-
-[Install]
-WantedBy=multi-user.target default.target

From fa14845e89c64398139a85edfb97efe560cda13c Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 08:54:02 +0100
Subject: [PATCH 04/11] update kubernetes

---
 kubernetes/README.md      | 41 ++++++++++++++++++++-------------------
 kubernetes/kubernetes.yml | 15 +++++++++++---
 2 files changed, 33 insertions(+), 23 deletions(-)

diff --git a/kubernetes/README.md b/kubernetes/README.md
index 6a2a1fc..c1bcf40 100644
--- a/kubernetes/README.md
+++ b/kubernetes/README.md
@@ -1,42 +1,43 @@
 ## JinjaFx Server as a Container in Kubernetes
 
-JinjaFx Server will always be available in Docker Hub at [https://hub.docker.com/repository/docker/cmason3/jinjafx_server](https://hub.docker.com/repository/docker/cmason3/jinjafx_server) - the `latest` tag will always refer to the latest released version.
-
-
+JinjaFx Server will always be available in Docker Hub at [https://hub.docker.com/repository/docker/cmason3/jinjafx_server](https://hub.docker.com/repository/docker/cmason3/jinjafx_server) - the `latest` tag will always refer to the latest released version, although it is recommended to use explicit version tags.
 
+The following steps will run JinjaFx Server in a container using Kubernetes Ingress - Ingress is basically the same concept as Virtual Hosting (the default Ingress uses nginx), which works with HTTP and relies on the "Host" header to direct the request to the correct container. In a Virtual Hosting scenario you would typically point different DNS A records towards the same IP, but in our example we are using a Wildcard DNS entry for our whole Kubernetes cluster, e.g:
 
 ```
-openssl req -nodes -newkey rsa:2048 -keyout ingress.key -out ingress.csr -subj "/CN={CN}/emailAddress={emailAddress}/O={O}/L={L}/ST={ST}/C={C}" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:*.{CLUSTER}.{DOMAIN}"))
+*.{CLUSTER}.{DOMAIN}. 28800   IN      A       {HOST IP}
 ```
 
-```
-kubectl create secret generic jinjafx --from-literal=jfx-weblog-key={KEY}
-```
+This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes Manifest (`kubernetes.yml`) assumes we will be activating the weblog as well as using a GitHub backed repository to store JinjaFx DataTemplates. Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
 
+### Generate Certificate Signing Request
 
+The following step is used to generate a CSR for your TLS certificiate. The Common Name (CN) isn't actually used as we will be using the "subjectAltName" field as it allows multiple values (you could also use something like Let's Encrypt here, but this is out of scope of this document):
 
-The following commands will launch a container for JinjaFx Server which listens on localhost on port 8080.
+```
+openssl req -nodes -newkey rsa:2048 -keyout ingress.key -out ingress.csr -subj "/CN={CN}/emailAddress={emailAddress}/O={O}/L={L}/ST={ST}/C={C}" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:*.{CLUSTER}.{DOMAIN}"))
+```
 
-Rootless Podman has two methods of running, either with root inside the container, which is mapped to the current non-root user outside the container, or if we use `UserNS=keep-id` then we use the current outside non-root user inside the container as well. Running with non-root inside and non-root outside is always preferred from a security perspective and as JinjaFx Server doesn't require root priviledges this is what this does.
+### Generate TLS Secret with Signed Certificate
 
-For ease of logging, we will also create a persistent logfile outside of the container and will give the local user access to it via a mapped volume (as we are using a persistent logfile we will also disable logging inside the container using `LogDriver=none`).
+Once you have a signed certificate you would create a Kubernetes TLS secret called `ingress-tls` using the private key and signed public certificate:
 
 ```
-mkdir ~/logs
-touch ~/logs/jinjafx.log
+kubectl create secret tls ingress-tls --cert=ingress.crt --key=ingress.key
 ```
 
-The following commands require Podman v4.5 or higher and use the new quadlets method of deploying containers via systemd. We are also passing through the `JFX_WEBLOG_KEY` environment variable that we store as a Podman Secret.
+### Save Environment Variables as Kubernetes Secrets
 
-```
-printf  | podman secret create jfx_weblog_key -
+To pass the GitHub Token as well as the key used for the Web Log we use Kubernetes secrets that we map into environment variables in the manifest:
 
-curl https://raw.githubusercontent.com/cmason3/jinjafx_server/main/podman/jinjafx.container \
-  -Os --create-dirs --output-dir ~/.config/containers/systemd
+```
+kubectl create secret generic jinjafx --from-literal=github-token={TOKEN} --from-literal=jfx-weblog-key={KEY}
+```
 
-systemctl --user daemon-reload
+### Apply the Kubernetes Manifest
 
-systemctl --user start jinjafx
+```
+kubectl apply -f kubernetes.yml
 ```
 
-This will then run the 'jinjafx' container via systemd (and restart on reboot) as the current non-root user. You should be able to point your browser at http://127.0.0.1:8080 and it will be passed through to the JinjaFx Server (although the preferred approach is running HAProxy in front of JinjaFx Server so it can deal with TLS termination with HTTP/2 or HTTP/3).
+If everything has worked then you should be able to point your web browser at `https://jinjafx.{CLUSTER}.{DOMAIN}` and it should present you with JinjaFx.
diff --git a/kubernetes/kubernetes.yml b/kubernetes/kubernetes.yml
index 66b91c4..9706ca4 100644
--- a/kubernetes/kubernetes.yml
+++ b/kubernetes/kubernetes.yml
@@ -5,8 +5,12 @@ metadata:
   name: jinjafx-ingress
 spec:
   ingressClassName: public
+  tls:
+  - hosts:
+    - "*.{CLUSTER}.{DOMAIN}"
+    secretName: ingress-tls
   rules:
-  - host: jinjafx.
+  - host: "jinjafx.{CLUSTER}.{DOMAIN}"
     http:
       paths:
       - path: /
@@ -51,8 +55,8 @@ spec:
     spec:
       containers:
       - name: jinjafx
-        image: docker.io/cmason3/jinjafx_server:24.6.2-1
-        args: ["-pandoc", "-weblog"]
+        image: docker.io/cmason3/jinjafx_server:latest
+        args: ["-pandoc", "-github", "{OWNER}/{REPO}", "-weblog"]
         ports:
         - containerPort: 8080
         readinessProbe:
@@ -67,6 +71,11 @@ spec:
         env:
         - name: TZ
           value: "Europe/London"
+        - name: GITHUB_TOKEN
+          valueFrom:
+            secretKeyRef:
+              name: jinjafx
+              key: github-token
         - name: JFX_WEBLOG_KEY
           valueFrom:
             secretKeyRef:

From e7413e5d0ee17c18f969dbedd08adfb863181d9c Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 08:56:56 +0100
Subject: [PATCH 05/11] update kubernetes

---
 kubernetes/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kubernetes/README.md b/kubernetes/README.md
index c1bcf40..de43664 100644
--- a/kubernetes/README.md
+++ b/kubernetes/README.md
@@ -8,7 +8,7 @@ The following steps will run JinjaFx Server in a container using Kubernetes Ingr
 *.{CLUSTER}.{DOMAIN}. 28800   IN      A       {HOST IP}
 ```
 
-This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes Manifest (`kubernetes.yml`) assumes we will be activating the weblog as well as using a GitHub backed repository to store JinjaFx DataTemplates. Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
+This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes Manifest (`kubernetes.yml`) assumes we will be activating the Web Log as well as using a GitHub backed repository to store JinjaFx DataTemplates. Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
 
 ### Generate Certificate Signing Request
 

From 347cf278590446dd71a59c664a8ed60c1e0c4722 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 09:11:46 +0100
Subject: [PATCH 06/11] update kubernetes

---
 kubernetes/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kubernetes/README.md b/kubernetes/README.md
index de43664..2deaf20 100644
--- a/kubernetes/README.md
+++ b/kubernetes/README.md
@@ -8,7 +8,7 @@ The following steps will run JinjaFx Server in a container using Kubernetes Ingr
 *.{CLUSTER}.{DOMAIN}. 28800   IN      A       {HOST IP}
 ```
 
-This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes Manifest (`kubernetes.yml`) assumes we will be activating the Web Log as well as using a GitHub backed repository to store JinjaFx DataTemplates. Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
+This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes manifest (`kubernetes.yml`) assumes we will be activating the Web Log as well as using a GitHub backed repository to store JinjaFx DataTemplates. Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
 
 ### Generate Certificate Signing Request
 

From 117ddc346119b3e161289a73d91fc00db2eca653 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 09:15:18 +0100
Subject: [PATCH 07/11] update kubernetes

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 9110a7f..9e02ffa 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
    JFX_WEBLOG_KEY              - specify a key to allow access to web log interface
 ```
 
-For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs). The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 - please see the [/podman](https://github.com/cmason3/jinjafx_server/blob/main/podman) directory for more information about running JinjaFx as a Rootless Podman container.
+For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs). The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 (and more recently HTTP/3 using QUIC) or using a container orchestration tool like Kubernetes - please see the [/kubernetes](https://github.com/cmason3/jinjafx_server/blob/main/kubernetes) directory for more information about running JinjaFx using Kubernetes.
 
 The "-r", "-s3" or "-github" arguments (mutually exclusive) allow you to specify a repository ("-r" is a local directory, "-s3" is an AWS S3 URL and "-github" is a GitHub repository) that will be used to store DataTemplates on the server via the "Get Link" and "Update Link" buttons. The generated link is guaranteed to be unique and a different link will be created every time - version 1.3.0 changed the behaviour, where previously the same link was always generated for the same DataTemplate, but this made it difficult to update DataTemplates without the link changing as it was basically a cryptographic hash of your DataTemplate. If you use an AWS S3 bucket then you will also need to provide some credentials via the two environment variables which has read and write permissions to the S3 URL.
 

From a294aaba65baf2e94453d0690b8c76aca6d23a17 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 09:17:28 +0100
Subject: [PATCH 08/11] update kubernetes

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 9e02ffa..a3e3ceb 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
    JFX_WEBLOG_KEY              - specify a key to allow access to web log interface
 ```
 
-For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs). The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 (and more recently HTTP/3 using QUIC) or using a container orchestration tool like Kubernetes - please see the [/kubernetes](https://github.com/cmason3/jinjafx_server/blob/main/kubernetes) directory for more information about running JinjaFx using Kubernetes.
+For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs). The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 (and more recently HTTP/3 using QUIC) or using a container orchestration tool like Kubernetes - please see the [/kubernetes](/kubernetes) directory for more information about running JinjaFx using Kubernetes.
 
 The "-r", "-s3" or "-github" arguments (mutually exclusive) allow you to specify a repository ("-r" is a local directory, "-s3" is an AWS S3 URL and "-github" is a GitHub repository) that will be used to store DataTemplates on the server via the "Get Link" and "Update Link" buttons. The generated link is guaranteed to be unique and a different link will be created every time - version 1.3.0 changed the behaviour, where previously the same link was always generated for the same DataTemplate, but this made it difficult to update DataTemplates without the link changing as it was basically a cryptographic hash of your DataTemplate. If you use an AWS S3 bucket then you will also need to provide some credentials via the two environment variables which has read and write permissions to the S3 URL.
 
@@ -124,7 +124,7 @@ Under the field the `text` key is always mandatory, but the following optional k
 
 - `type` - if set to "password" then echo is turned off - used for inputting sensitive values
 
-In addition to the above prompt syntax, we also support the ability to specify a custom html input form to provide greater flexibility. As JinjaFx is built on Bootstrap 5, it uses the Bootstrap 5 Modal syntax to specify what is contained in the body of your modal form. Bootstrap works on a row and column grid with each row comprising of 12 columns - you use the various "col-n" classes to specify how wide each element is.
+In addition to the above prompt syntax, we also support the ability to specify a custom html input form to provide greater flexibility. As JinjaFx is built on Bootstrap 5, it uses the Bootstrap 5 Modal syntax to specify what is contained in the body of your modal form. Bootstrap works on a row and column grid with each row comprising of 12 columns - you use the various "col-n" classes to specify how wide each element is.
 
 You can specify a custom input form using the `body` variable under `jinjafx_input` within your "vars.yml" - if this exists then whatever you have in `prompt` is ignored.
 

From 1f92f6cfe4503f0021fdbf2adfc8d830c61995a6 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 09:47:49 +0100
Subject: [PATCH 09/11] update kubernetes

---
 README.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a3e3ceb..49aabfd 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,9 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
    JFX_WEBLOG_KEY              - specify a key to allow access to web log interface
 ```
 
-For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs). The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 (and more recently HTTP/3 using QUIC) or using a container orchestration tool like Kubernetes - please see the [/kubernetes](/kubernetes) directory for more information about running JinjaFx using Kubernetes.
+For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs).
+
+The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 (and more recently HTTP/3 using QUIC) or using a container orchestration tool like Kubernetes - please see the [/kubernetes](/kubernetes) directory for more information about running JinjaFx using Kubernetes.
 
 The "-r", "-s3" or "-github" arguments (mutually exclusive) allow you to specify a repository ("-r" is a local directory, "-s3" is an AWS S3 URL and "-github" is a GitHub repository) that will be used to store DataTemplates on the server via the "Get Link" and "Update Link" buttons. The generated link is guaranteed to be unique and a different link will be created every time - version 1.3.0 changed the behaviour, where previously the same link was always generated for the same DataTemplate, but this made it difficult to update DataTemplates without the link changing as it was basically a cryptographic hash of your DataTemplate. If you use an AWS S3 bucket then you will also need to provide some credentials via the two environment variables which has read and write permissions to the S3 URL.
 

From c5652b7388f601ad8d91b3a62d0435f76836d895 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 09:50:29 +0100
Subject: [PATCH 10/11] update kubernetes

---
 kubernetes/README.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kubernetes/README.md b/kubernetes/README.md
index 2deaf20..944751a 100644
--- a/kubernetes/README.md
+++ b/kubernetes/README.md
@@ -8,7 +8,9 @@ The following steps will run JinjaFx Server in a container using Kubernetes Ingr
 *.{CLUSTER}.{DOMAIN}. 28800   IN      A       {HOST IP}
 ```
 
-This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes manifest (`kubernetes.yml`) assumes we will be activating the Web Log as well as using a GitHub backed repository to store JinjaFx DataTemplates. Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
+This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes manifest (`kubernetes.yml`) assumes we will be activating the Web Log as well as using a GitHub backed repository to store JinjaFx DataTemplates.
+
+Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:
 
 ### Generate Certificate Signing Request
 

From 705aebec28496b1e766fdd11a8361cf7d8423113 Mon Sep 17 00:00:00 2001
From: Chris Mason 
Date: Mon, 24 Jun 2024 09:52:28 +0100
Subject: [PATCH 11/11] update kubernetes

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3293d8a..ab33920 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 ## CHANGELOG
 
-### [24.6.3] - In Development
+### [24.6.3] - Jun 24, 2024
 - Fixed an issue with `/logs` as it didn't adhere to the default `Content-Security-Policy`
 - Updated instructions for deploying JinjaFx Server as a Container using Kubernetes