Tools: Webcheck, Enclosed and Medama

Published: Oct 29, 2024 by Isaac Johnson

Today we will check out two interesting Open-Source monitor/trackers - WebCheck and Medama. We’ll also look at a great simple Open-source note/secret sharing app, Enclosed. Webcheck does have some additional features that are enabled with a GCP API key (which we’ll cover how to fetch).

Let’s start with Webcheck.

Webcheck

I became aware of Web Check from a MariusHosting Blog about hosting it on a NAS.

Let’s first start with a straightforward docker invokation

$ docker run -p 3000:3000 lissy93/web-check

Perhaps my internet is insufficent here

builder@LuiGi:~/Workspaces/jekyll-blog$ docker run -p 3000:3000 lissy93/web-check
Unable to find image 'lissy93/web-check:latest' locally
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).
See 'docker run --help'.
builder@LuiGi:~/Workspaces/jekyll-blog$ docker run -p 3000:3000 lissy93/web-check
Unable to find image 'lissy93/web-check:latest' locally
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded.
See 'docker run --help'.

I tried to fetch from the Dockerhub location directly:

docker pull lissy93/web-check
Using default tag: latest
latest: Pulling from lissy93/web-check
3d53ef4019fc: Pull complete
08f0bf643eb6: Pull complete
6b037c2b46ab: Pull complete
6043113e1c69: Pull complete
3124fb5fe8cc: Pull complete
cf0c111ae42e: Pull complete
e2abac14e0ec: Pull complete
169d84d2ff1d: Pull complete
c2d8fef65aa0: Pull complete
9dbd70f50705: Pull complete
3201d69f0bd0: Pull complete
70bc80b8eff3: Pull complete
Digest: sha256:a2ae048b601c7d44ab148d746f5836ace7b4e8514ba8f905c4890b90635c62c5
Status: Downloaded newer image for lissy93/web-check:latest
docker.io/lissy93/web-check:latest

What's Next?
  View a summary of image vulnerabilities and recommendations → docker scout quickview lissy93/web-check

I can now run it

builder@LuiGi:~/Workspaces/jekyll-blog$ !1999
docker run -p 3000:3000 lissy93/web-check
yarn run v1.22.19
$ node server

    __      __   _         ___ _           _
    \ \    / /__| |__ ___ / __| |_  ___ __| |__
     \ \/\/ / -_) '_ \___| (__| ' \/ -_) _| / /
      \_/\_/\___|_.__/    \___|_||_\___\__|_\_\

 🚀 Web-Check is up and running at http://localhost:3000

 🛟 For documentation and support, visit the GitHub repo: https://github.com/lissy93/web-check
 💖 Found Web-Check useful? Consider sponsoring us on GitHub to help fund maintenance & development.
(node:29) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

/content/images/2024/10/webcheck-01.png

I can then run a check against one of my hosted apps

/content/images/2024/10/webcheck-02.png

Some of these “failed” checks are because it needs a Google API key

/content/images/2024/10/webcheck-03.png

Let’s try creating a GCP API key to look at some of the additional quality checks

I can go to “APIs & Credentials” to create a new API key

/content/images/2024/10/webcheck-04.png

I can then create a new key

/content/images/2024/10/webcheck-05.png

I can relaunch with docker

$ docker run -e GOOGLE_CLOUD_API_KEY=AIzxxxxxxxxxxxxxxxxxxxxxxxxx -p 30
00:3000 lissy93/web-check
yarn run v1.22.19
$ node server

    __      __   _         ___ _           _
    \ \    / /__| |__ ___ / __| |_  ___ __| |__
     \ \/\/ / -_) '_ \___| (__| ' \/ -_) _| / /
      \_/\_/\___|_.__/    \___|_||_\___\__|_\_\

 🚀 Web-Check is up and running at http://localhost:3000

 🛟 For documentation and support, visit the GitHub repo: https://github.com/lissy93/web-check
 💖 Found Web-Check useful? Consider sponsoring us on GitHub to help fund maintenance & development.
(node:29) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

This time, I got a lot more details

/content/images/2024/10/webcheck-06.png

However, I dont really desire to run it on my laptop all the time, so let’s move this to a dockerhost instance instead.

builder@builder-T100:~$ docker run -d --name webcheck -e GOOGLE_CLOUD_API_KEY=AIzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -p 3020:3000 lissy93/web-check
Unable to find image 'lissy93/web-check:latest' locally
latest: Pulling from lissy93/web-check
3d53ef4019fc: Pull complete 
08f0bf643eb6: Pull complete 
6b037c2b46ab: Pull complete 
6043113e1c69: Pull complete 
3124fb5fe8cc: Pull complete 
cf0c111ae42e: Pull complete 
e2abac14e0ec: Pull complete 
169d84d2ff1d: Pull complete 
c2d8fef65aa0: Pull complete 
9dbd70f50705: Pull complete 
3201d69f0bd0: Pull complete 
70bc80b8eff3: Pull complete 
Digest: sha256:a2ae048b601c7d44ab148d746f5836ace7b4e8514ba8f905c4890b90635c62c5
Status: Downloaded newer image for lissy93/web-check:latest
0c8ca545e0359cda513f3babe845f5d26d435e270d682fdd630fdb2db6e76b6d

which works fine

/content/images/2024/10/webcheck-07.png

Enclosed

Let’s start by firing up an instance on a docker host

builder@builder-T100:~$ docker run -d --name enclosed -e PORT=8713 -v /home/builder/enclosed:/app/.data -p 8
713:8713 corentinth/enclosed:latest
Unable to find image 'corentinth/enclosed:latest' locally
latest: Pulling from corentinth/enclosed
43c4264eed91: Already exists 
4ddba3401738: Pull complete 
d17475b571f3: Pull complete 
f7c3aa8cb1f9: Pull complete 
d728484bbda6: Pull complete 
62ebd2eb3360: Pull complete 
1bb1a2de862b: Pull complete 
8f19f44523de: Pull complete 
Digest: sha256:d57b54553c10195c6deee92ea07bee5b7dc20e2d45da7c3bc009c3dd3901c570
Status: Downloaded newer image for corentinth/enclosed:latest
07addf17dafcf8954bdfb46bc53221c0435e737798c699ca072e7d1aea571f80

I’ll now create a DNS record

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n enclosed
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "3451bd2b-173f-4651-814c-3304e4fe1f81",
  "fqdn": "enclosed.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/enclosed",
  "name": "enclosed",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

Next I’ll create an Ingress, Service and Endpoint:

$ cat enclosed.ingress.yaml
---
apiVersion: v1
kind: Endpoints
metadata:
  name: enclosed-external-ip
subsets:
- addresses:
  - ip: 192.168.1.100
  ports:
  - name: enclosedint
    port: 8713
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: enclosed-external-ip
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: RequireDualStack
  ports:
  - name: enclosed
    port: 80
    protocol: TCP
    targetPort: 8713
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.org/websocket-services: enclosed-external-ip
  generation: 1
  labels:
    app.kubernetes.io/instance: enclosedingress
  name: enclosedingress
spec:
  rules:
  - host: enclosed.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: enclosed-external-ip
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - enclosed.tpk.pw
    secretName: enclosed-tls

$ kubectl apply -f enclosed.ingress.yaml
endpoints/enclosed-external-ip created
service/enclosed-external-ip created
ingress.networking.k8s.io/enclosedingress created

When the cert is satisified

$ kubectl get cert enclosed-tls
NAME           READY   SECRET         AGE
enclosed-tls   True    enclosed-tls   113s

I could then directly access it

/content/images/2024/10/enclosed-01.png

I’ll try creating a note with an hour expiry

/content/images/2024/10/enclosed-02.png

I then see it created a URL and QR

/content/images/2024/10/enclosed-03.png

And in an InPrivate window I could verify the link https://enclosed.tpk.pw/01jay091e5781nyydr8g0g27hw#H2hfBHIzc5zrvE6Xait3pGp2E9XETuL6UC4sxSshJZI

/content/images/2024/10/enclosed-04.png

I can create another note with a password and autodelete policy

/content/images/2024/10/enclosed-05.png

This time I get a warning that viewing it will delete it

/content/images/2024/10/enclosed-06.png

I can then enter the password

/content/images/2024/10/enclosed-07.png

That worked. I then just refreshed

/content/images/2024/10/enclosed-08.png

Let’s see some examples

Medama

One more self-hosted monitor akin to WebCheck we can check out is Medama. This also ended up on my list from an older MariusHosting post.

From their Installation docs, we can start with the docker steps.

I’m going to use port 8484 as 8080 is already used locally

$ docker run -d -p 8484:8080 -v medama-data:/app/data ghcr.io/medama-io/medama:latest
Unable to find image 'ghcr.io/medama-io/medama:latest' locally
latest: Pulling from medama-io/medama
a85ce7502e1a: Pull complete
e8d9a567199d: Pull complete
058cf3d8c2ba: Pull complete
b6824ed73363: Pull complete
7c12895b777b: Pull complete
33e068de2649: Pull complete                                                                                                                                        5664b15f108b: Pull complete
27be814a09eb: Pull complete
4aa0ea1413d3: Pull complete
da7816fa955e: Pull complete
9aee425378d2: Pull complete
209150bce63e: Pull complete
903010886edf: Pull complete
c9bc90a4de9d: Pull complete
2b3410766942: Pull complete
911458164fa1: Pull complete
9e6437a0de2e: Pull complete
879a3318ec60: Pull complete
Digest: sha256:1761c0f9881a8b90b1bc9175518c6c50122ac26f2899d825bb7d9ffa0a48ff8f
Status: Downloaded newer image for ghcr.io/medama-io/medama:latest
7666782213f7c2eb4019463205a2d403892086b3be6a39f704d6a3d113731fc1

Our first login does show 500, but I’ll try the “Log In” button regardless

/content/images/2024/10/medama-01.png

The default login is admin with password CHANGE_ME_ON_FIRST_LOGIN

/content/images/2024/10/medama-02.png

However, that did indeed just redirect to the landing page so something might be broken.

While there is nothing in the logs

$ docker logs 7666782213f7
{"level":"info","time":1729854439,"message":"Medama Analytics v0.5.1, commit=7f3ef9f"}
{"level":"warn","id":1,"name":"0001_sqlite_schema.go","type":"sqlite","time":1729854442,"message":"running migration, do not close the application"}
{"level":"info","id":1,"name":"0001_sqlite_schema.go","type":"sqlite","time":1729854442,"message":"migrated"}
{"level":"warn","id":6,"name":"0006_sqlite_settings.go","type":"sqlite","time":1729854442,"message":"running migration, do not close the application"}
{"level":"info","id":6,"name":"0006_sqlite_settings.go","type":"sqlite","time":1729854442,"message":"migrated"}
{"level":"warn","id":2,"name":"0002_duckdb_schema.go","type":"duckdb","time":1729854442,"message":"running migration, do not close the application"}
{"level":"info","id":2,"name":"0002_duckdb_schema.go","type":"duckdb","time":1729854442,"message":"migrated"}
{"level":"warn","id":3,"name":"0003_duckdb_referrer.go","type":"duckdb","time":1729854442,"message":"running migration, do not close the application"}
{"level":"info","id":3,"name":"0003_duckdb_referrer.go","type":"duckdb","time":1729854442,"message":"migrated"}
{"level":"warn","id":4,"name":"0004_duckdb_events.go","type":"duckdb","time":1729854442,"message":"running migration, do not close the application"}
{"level":"info","id":4,"name":"0004_duckdb_events.go","type":"duckdb","time":1729854442,"message":"migrated"}
{"level":"warn","id":5,"name":"0005_duckdb_event_bid.go","type":"duckdb","time":1729854442,"message":"running migration, do not close the application"}
{"level":"info","id":5,"name":"0005_duckdb_event_bid.go","type":"duckdb","time":1729854442,"message":"migrated"}
{"level":"warn","time":1729854442,"message":"no users found, creating default admin user"}
{"level":"warn","time":1729854442,"message":"default admin user created"}
{"level":"info","time":1729854442,"message":"Starting server at http://localhost:8080"}

I suspect it’s because I failed to create that medama-data volume when i launched.

I’ll stop and rm the existing instance (frosty_mcclintock), create the docker volume and re-launch

$ docker stop frosty_mcclintock
frosty_mcclintock
$ docker rm frosty_mcclintock
frosty_mcclintock
$ docker volume create medama-data
medama-data
$ docker run -d -p 8484:8080 -v medama-data:/app/data ghcr.io/medama-io/medama:latest
663ea8ae503916c0b3cfbec204f7766772ab6b6552974a7c4f1ccb53d3b43d14

This time I see a different login page

/content/images/2024/10/medama-03.png

But I still see an error

/content/images/2024/10/medama-04.png

I’m going to rule out Windows/WSL and move to my proper Dockerhost

builder@builder-T100:~$ mkdir medama
builder@builder-T100:~$ mkdir medama/data
builder@builder-T100:~$ docker run -d --name medama -v /home/builder/medama/data:/app/data -p 8484:8080 ghcr.io/medama-io/medama:latest
Unable to find image 'ghcr.io/medama-io/medama:latest' locally
latest: Pulling from medama-io/medama
a85ce7502e1a: Pull complete
e8d9a567199d: Pull complete
058cf3d8c2ba: Pull complete
b6824ed73363: Pull complete
7c12895b777b: Pull complete
33e068de2649: Pull complete
5664b15f108b: Pull complete
27be814a09eb: Pull complete
4aa0ea1413d3: Pull complete
da7816fa955e: Pull complete
9aee425378d2: Pull complete
209150bce63e: Pull complete
903010886edf: Pull complete
c9bc90a4de9d: Pull complete
2b3410766942: Pull complete
911458164fa1: Pull complete
9e6437a0de2e: Pull complete
879a3318ec60: Pull complete
Digest: sha256:1761c0f9881a8b90b1bc9175518c6c50122ac26f2899d825bb7d9ffa0a48ff8f
Status: Downloaded newer image for ghcr.io/medama-io/medama:latest

This time the login goes nowhere and I can see some errors in the logs

$ docker logs medama
{"level":"info","time":1729855146,"message":"Medama Analytics v0.5.1, commit=7f3ef9f"}
{"level":"warn","id":1,"name":"0001_sqlite_schema.go","type":"sqlite","time":1729855149,"message":"running migration, do not close the application"}
{"level":"info","id":1,"name":"0001_sqlite_schema.go","type":"sqlite","time":1729855149,"message":"migrated"}
{"level":"warn","id":6,"name":"0006_sqlite_settings.go","type":"sqlite","time":1729855149,"message":"running migration, do not close the application"}
{"level":"info","id":6,"name":"0006_sqlite_settings.go","type":"sqlite","time":1729855149,"message":"migrated"}
{"level":"warn","id":2,"name":"0002_duckdb_schema.go","type":"duckdb","time":1729855149,"message":"running migration, do not close the application"}
{"level":"info","id":2,"name":"0002_duckdb_schema.go","type":"duckdb","time":1729855149,"message":"migrated"}
{"level":"warn","id":3,"name":"0003_duckdb_referrer.go","type":"duckdb","time":1729855149,"message":"running migration, do not close the application"}
{"level":"info","id":3,"name":"0003_duckdb_referrer.go","type":"duckdb","time":1729855149,"message":"migrated"}
{"level":"warn","id":4,"name":"0004_duckdb_events.go","type":"duckdb","time":1729855149,"message":"running migration, do not close the application"}
{"level":"info","id":4,"name":"0004_duckdb_events.go","type":"duckdb","time":1729855149,"message":"migrated"}
{"level":"warn","id":5,"name":"0005_duckdb_event_bid.go","type":"duckdb","time":1729855149,"message":"running migration, do not close the application"}
{"level":"info","id":5,"name":"0005_duckdb_event_bid.go","type":"duckdb","time":1729855149,"message":"migrated"}
{"level":"warn","time":1729855149,"message":"no users found, creating default admin user"}
{"level":"warn","time":1729855149,"message":"default admin user created"}
{"level":"info","time":1729855149,"message":"Starting server at http://localhost:8080"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36","time":1729855202,"message":"unauthorised"}
{"level":"info","operation":"PostAuthLogin","operationId":"post-auth-login","method":"POST","path":"/auth/login","duration":71.26452,"time":1729855212,"message":"success"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36","time":1729855212,"message":"unauthorised"}
{"level":"info","operation":"PostAuthLogin","operationId":"post-auth-login","method":"POST","path":"/auth/login","duration":49.082554,"time":1729855222,"message":"success"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36","time":1729855222,"message":"unauthorised"}
{"level":"info","operation":"PostAuthLogin","operationId":"post-auth-login","method":"POST","path":"/auth/login","duration":102.11171,"time":1729855240,"message":"success"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36","time":1729855240,"message":"unauthorised"}
{"level":"info","operation":"PostAuthLogin","operationId":"post-auth-login","method":"POST","path":"/auth/login","duration":45.680127,"time":1729855270,"message":"success"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0","time":1729855270,"message":"unauthorised"}

This isn’t helping. Even setting Allowed Origins to “*” fails

builder@builder-T100:~$ docker stop medama
medama
builder@builder-T100:~$ docker rm medama
medama
builder@builder-T100:~$ docker run -d --name medama -v /home/builder/medama/data:/app/data -e CORS_ALLOWED_ORIGINS=* -p 8484:8080 ghcr.io/medama-io/medama:latest
2e65eca238e74f9c203aefa08f1d16e2c088e8c39343a8871e46cd4c9a7b3a15
builder@builder-T100:~$ docker logs medama
{"level":"info","time":1729855548,"message":"Medama Analytics v0.5.1, commit=7f3ef9f"}
{"level":"info","time":1729855551,"message":"Starting server at http://localhost:8080"}
{"level":"info","operation":"PostAuthLogin","operationId":"post-auth-login","method":"POST","path":"/auth/login","duration":53.990618,"time":1729855552,"message":"success"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0","time":1729855552,"message":"unauthorised"}
{"level":"info","operation":"PostAuthLogin","operationId":"post-auth-login","method":"POST","path":"/auth/login","duration":76.317719,"time":1729855564,"message":"success"}
{"level":"warn","path":"/websites","method":"GET","status_code":401,"message":"operation GetWebsites: security '': security requirement is not satisfied","Connection":"keep-alive","Content-Type":"application/json","Content-Length":"","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0","time":1729855564,"message":"unauthorised"}

Actually it took a lot of trial and error - but it does need a volume. And more-over, it really must use port 8080 or you get that 500 error.

Here I launched back in WSL (since my Dockerhost already allocated 8080)

builder@DESKTOP-QADGF36:~$ docker run -d --name medama -v medama-data:/app/data -p 8080:8080 ghcr.io/medama-io/medama:latest
266ddb29271c97e283062c3ba3b62f08dc4cb4deb92deec75f48b0bcd6e6af0b

/content/images/2024/10/medama-05.png

Even though I’m running locally, I’ll still practice setting the admin password to something other than default

/content/images/2024/10/medama-06.png

Next, let’s add a site

/content/images/2024/10/medama-07.png

I’ll add my own

/content/images/2024/10/medama-08.png

So far I just see a dashboard with no data

/content/images/2024/10/medama-09.png

Seems this backend assume we have already added the tracker as documented here.

So this assumes I expose Medama somehow to my website and embed a javascript tracker

<script defer src="https://[where-your-script-is-hosted].com/script.js" data-api="[different-analytics-server].com/api/" data-hash></script>

Which I can verify is there in the site

/content/images/2024/10/medama-10.png

I want to test before I ever release into the wild

builder@DESKTOP-QADGF36:~$ vi testsite.html
builder@DESKTOP-QADGF36:~$ cat testsite.html
<HTML>
        <HEAD>
                <TITLE>Test Site</TITLE>
        </HEAD>
        <BODY>
                <h1>test local stie</h1>
                <script defer src="http://127.0.0.1/script.js" data-api="127.0.0.1/api/" data-hash></script>
        </BODY>
</HTML>

I fired it up but don’t see any data (granted it’s not ‘freshbrewed.science’)

/content/images/2024/10/medama-11.png

But I thought at least I would see something here

/content/images/2024/10/medama-12.png

Since this is a bit hung-up, in my testing, with using port 8080, I’ll try pivoting to a Kubernetes deployment

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: medama-data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: medama-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: medama
  template:
    metadata:
      labels:
        app: medama
    spec:
      volumes:
        - name: medama-data
          persistentVolumeClaim:
            claimName: medama-data-pvc
      containers:
        - name: medama
          image: ghcr.io/medama-io/medama:latest
          ports:
            - containerPort: 8080
          volumeMounts:
            - mountPath: /app/data
              name: medama-data
---
apiVersion: v1
kind: Service
metadata:
  name: medama-service
spec:
  selector:
    app: medama
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

The apply it

$ kubectl apply -f ./medama.install.yaml
persistentvolumeclaim/medama-data-pvc created
deployment.apps/medama-deployment created
service/medama-service created

However, here we are stuck again in that it is really hung-up on 8080

$ kubectl port-forward svc/medama-service 8484:80
Forwarding from 127.0.0.1:8484 -> 8080
Forwarding from [::1]:8484 -> 8080
Handling connection for 8484
Handling connection for 8484

/content/images/2024/10/medama-13.png

Just to do the test all the way, I’ll try creating a GCP CloudDNS record

$ gcloud dns --project=myanthosproject2 record-sets create medama.steeped.space --zone="steepedspace" --type="A" --ttl="300" --rrdatas="75.73.224.240"
NAME                   TYPE  TTL  DATA
medama.steeped.space.  A     300  75.73.224.240

Then expose an ingress

$ cat ./medama.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: gcpleprod2
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "3600"
    nginx.org/proxy-read-timeout: "3600"
    nginx.org/websocket-services: medama-service
  name: medamagcpingress
spec:
  rules:
  - host: medama.steeped.space
    http:
      paths:
      - backend:
          service:
            name: medama-service
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - medama.steeped.space
    secretName: medamagcp-tls

$ kubectl apply -f ./medama.ingress.yaml
ingress.networking.k8s.io/medamagcpingress created

When I saw the cert satisified

$ kubectl get cert medamagcp-tls
NAME            READY   SECRET          AGE
medamagcp-tls   True    medamagcp-tls   111s

I tried the URL (expecting failure). But this time it worked

/content/images/2024/10/medama-14.png

This time the tracker page shows the right URL

/content/images/2024/10/medama-15.png

The line we’ll need to embed:

<script defer src="https://medama.steeped.space/script.js"></script>

I’ll add our site

/content/images/2024/10/medama-16.png

To test, I’ll add a line to my existing index page, though in reality this is generated with each release so this is just a fast-n-dirty way to test

builder@DESKTOP-QADGF36:~$ aws s3 cp s3://freshbrewed.science/index.html fbs.index.html
download: s3://freshbrewed.science/index.html to ./fbs.index.html
builder@DESKTOP-QADGF36:~$ aws s3 cp s3://freshbrewed.science/index.html fbs.index.html.bak
download: s3://freshbrewed.science/index.html to ./fbs.index.html.bak
builder@DESKTOP-QADGF36:~$ vi fbs.index.html
builder@DESKTOP-QADGF36:~$ diff -C 3 fbs.index.html.bak fbs.index.html
*** fbs.index.html.bak  2024-10-24 03:14:00.000000000 -0500
--- fbs.index.html      2024-10-25 07:01:59.385633091 -0500
***************
*** 29,34 ****
--- 29,35 ----
  </head>

  <body>
+ <script defer src="https://medama.steeped.space/script.js"></script>
  <div class="navbar is-white">
      <div class="container">
          <div class="navbar-brand">

I can then copy it out there and create a CF Invalidation (using a CDN means i need to invalidate to make it actually go live quicker)

$ aws s3 cp ./fbs.index.html s3://freshbrewed.science/index.html --acl public-read
upload: ./fbs.index.html to s3://freshbrewed.science/index.html

$ aws cloudfront create-invalidation --distribution-id E3U2HCN2ZRTBZN --paths "/index.html"
{
    "Location": "https://cloudfront.amazonaws.com/2019-03-26/distribution/E3U2HCN2ZRTBZN/invalidation/ID0IKH8FFE94GTUOVMGNLF33WM",
    "Invalidation": {
        "Id": "ID0IKH8FFE94GTUOVMGNLF33WM",
        "Status": "InProgress",
        "CreateTime": "2024-10-25T12:04:48.813Z",
        "InvalidationBatch": {
            "Paths": {
                "Quantity": 1,
                "Items": [
                    "/index.html"
                ]
            },
            "CallerReference": "cli-1729857885-67775"
        }
    }
}

I did a refresh and right away I saw some data

/content/images/2024/10/medama-17.png

I plan to let it run today and see what it shows…

After a few days - and mind you this is just the main page and likely for browsers that don’t block javascript, I saw a few views

/content/images/2024/10/medama-18.png

Compared to CloudFront (which will count all the pages)

/content/images/2024/10/medama-19.png

Since 3000 and 7 is a pretty wide gap, I figured I would look to data usage to clarify what is real

/content/images/2024/10/medama-20.png

While we can agree the data is limited, from what I can tell these folks must have just bookmarked this site as there is no defined referrer

/content/images/2024/10/medama-21.png

Summary

Today we looked at WebCheck, a nice containerized app for checking everything from SSL checks to security vulnrabities to HTTP checks. It really covers a lot, especially when we tie in our GCP Cloud API key.

We looked at enclosed, which I’ve left running on enclosed.tpk.pw. I find this to be a very handy and useful secret sharing app. The fact it allows attachments spooks me a bit since that could be abused. But still, if I need to securely share a PEM key or a private SSL key, this would be an excellent way to do it. Though with any secret sharing tool, the only real way to trust it is to build and host it yourself.

Lastly, we looked at Medama, a simple but functional open-source website tracker. I’m not sure what the requirements are but it did seem to show a lower number of visitors. I could see it useful as a self-contained self-hosted solution.

Of the three, I’ll likely continue to use Enclosed as I really like it. I don’t like getting tracked so I tend to not use trackers for things. The stats I get from AWS are usually sufficient for most metrics. Also, the main page is likely the least used as I tend to just post to Mastodon and LinkedIn direct links to articles.

Kubernetes Docker Opensource WebCheck Enclosed Medama Trackers

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes