Featured image of post Setting up comments with Remark42

Setting up comments with Remark42

I had been thinking about adding a commenting system to this blog for a while, but I didn’t want to use a external provider like Disqus or Commento. Those are totally valid options, but I just wanted to self-host my stuff all the way.

The task of setting up a commenting system seemed pretty complicated at first. I had to take care of HTTPS, user authentication, persistent storage, and blog integration. Recently, though, I realized that I had already set up most of the stuff needed, and there was a project that could help me with the rest: Remark42.

This self-hosted solution can handle user registration/authentication through OAuth providers, and stores blog comments in a file-based data store.

In my case, the prerequisites are satisfied as such:

  1. HTTPS: taken care by Traefik: Hosting an HTTPS website on a HA k3s cluster through SSH (I know, this needs to be updated)
  2. User authentication: handled by Remark42
  3. Persistent storage: available from my Ceph cluster
  4. Blog integration: The theme I’m using supports Remark42 integration. Hugo Theme Stack

Deploy Remark42

Remark42 can be deployed either with a helm chart or with some manifests. I chose the latter to better integrate with Kustomize.

The manifest available here pretty much worked out of the box. I just extracted the secrets to the kustomization file and defined a traefik ingress. The process is pretty similar to this post: Define application manifest using Kustomize.

I used Kustomize since I wanted a staging and a production environment, but feel free to just use the manifests.

Manifests

Example directory structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
|-- base
|   |-- blog.yml
|   |-- comment.yml
|   `-- kustomization.yml
`-- overlays
    |-- prd
    |   |-- certificate.yml
    |   |-- ingress.yml
    |   `-- kustomization.yml
    `-- stg
        |-- certificate.yml
        |-- ingress.yml
        `-- kustomization.yml

Example manifests:

1
2
3
4
5
6
7
8
# base/kustomization.yml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - blog.yml
  - comment.yml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
# base/comment.yml

# use the default storage class
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: remark42
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: remark42
  labels:
    app: remark42
spec:
  replicas: 1
  selector:
    matchLabels:
      app: remark42
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: remark42
    spec:
      containers:
        - name: remark42
          image: ghcr.io/umputun/remark42:v1.14.0
          ports:
            - containerPort: 8080
          env:
            - name: REMARK_URL
              valueFrom:
                configMapKeyRef:
                  name: blog-cm
                  key: COMMENT_URL
            - name: SITE
              valueFrom:
                configMapKeyRef:
                  name: blog-cm
                  key: BLOG_NAME
            - name: SECRET
              valueFrom:
                secretKeyRef:
                  name: remark42
                  key: SECRET
            - name: AUTH_GOOGLE_CID
              valueFrom:
                secretKeyRef:
                  name: remark42
                  key: AUTH_GOOGLE_CID
            - name: AUTH_GOOGLE_CSEC
              valueFrom:
                secretKeyRef:
                  name: remark42
                  key: AUTH_GOOGLE_CSEC
            - name: AUTH_GITHUB_CID
              valueFrom:
                secretKeyRef:
                  name: remark42
                  key: AUTH_GITHUB_CID
            - name: AUTH_GITHUB_CSEC
              valueFrom:
                secretKeyRef:
                  name: remark42
                  key: AUTH_GITHUB_CSEC
            - name: ADMIN_SHARED_ID
              value: <admin-shared-id> # this can be arbitrary
            - name: TIME_ZONE
              value: "America/Denver"
          volumeMounts:
            - name: srvvar
              mountPath: /srv/var
          securityContext:
            readOnlyRootFilesystem: false
          resources:
            requests:
              cpu: "100m"
              memory: "25Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
      securityContext:
        # Has its own root privilege drop. Can't do runAsUser / runAsGroup.
      volumes:
        - name: srvvar
          persistentVolumeClaim:
            claimName: remark42

---

apiVersion: v1
kind: Service
metadata:
  name: remark42-web
spec:
  selector:
    app: remark42
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 8080
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# overlays/prd/kustomization.yml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- ingress.yml
- certificate.yml
namespace: blog
replicas:
- name: blog
  count: 2
images:
- name: jyking99/blog # image for the blog itself
  digest: <digest> # updated by ArgoCD Image Updater
configMapGenerator:
- name: blog-cm
  literals:
  - BLOG_NAME=jy-blog
  - COMMENT_URL=https://comment.blog.junyi.me
secretGenerator:
- name: remark42
  literals:
  - SECRET=<secret> # this can be arbitrary
  - AUTH_GOOGLE_CID=<google-client-id>
  - AUTH_GOOGLE_CSEC=<google-client-secret>
  - AUTH_GITHUB_CID=<github-client-id>
  - AUTH_GITHUB_CSEC=<github-client-secret>
Info

See below for how to obtain the client IDs and secrets.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# overlays/prd/ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog
  annotations:
    spec.ingressClassName: traefik
spec:
  rules:
    - host: blog.junyi.me
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: blog
              port:
                number: 80
    - host: comment.blog.junyi.me
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: remark42-web
              port:
                number: 8080
  tls:
    - hosts:
        - "blog.junyi.me"
        - "comment.blog.junyi.me"
      secretName: junyi-me-production
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# overlays/prd/certificate.yml

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: junyi-me-prod
spec:
  secretName: junyi-me-production
  issuerRef:
    name: letsencrypt-production
    kind: ClusterIssuer
  commonName: "*.junyi.me"
  dnsNames:
  - "junyi.me"
  - "*.junyi.me"
  - "*.blog.junyi.me"

In the provided manifest, we have to specify the Google and GitHub OAuth client IDs and secrets. Here is how to set up OAuth clients for each provider and get their IDs and secrets.

Google OAuth

  1. Go to Google Auth Platform / Clients (create a project if you don’t have one)
  2. Click on “Create client”
  3. Fill in the details like this:
    Google OAuth client
  4. Clicking on “Create” will give you the client ID and secret.

GitHub OAuth

  1. Go to settings / OAuth Apps in your GitHub account or organization.
  2. Click on “New OAuth App”
  3. Fill in the details like this:
    GitHub OAuth client
  4. Clicking on “Register application” will give you the client ID and secret.

Hugo Theme Stack integration

Confirm the above is working by running kubectl apply -k overlays/prd. If everything is set up correctly, A Remark42 instance should be running in your cluster.

Info

This part is specific to the theme Hugo Theme Stack. For other themes, consult the theme documentation.

Modify the params.comments section as such:

1
2
3
4
5
6
7
8
    comments:
        enabled: true
        provider: remark42

        remark42:
            host: REMARK42_URL
            site: jy-blog
            locale: en

The URL REMARK42_URL is just a placeholder here. In my case it will be updated by the GitHub Actions workflow. Feel free to hardcode it.

GitHub Actions workflow

Since I have an existing workflow that updates the blog image, I just added a step to update the Remark42 URL. Here is the relevant part:

1
2
3
4
5
6
7
8
  - name: Set remark42 URL
    run: |
      if [ $BRANCH_NAME == 'master' ]; then
        REMARK42_URL=https://comment.blog.junyi.me
      else
        REMARK42_URL=https://comment.blog.i.junyi.me
      fi
      sed -i "s|REMARK42_URL|$REMARK42_URL|g" hugo.yml      

This will separate the production and staging Remark42 instances.

Conclusion

This is all the steps I took to get a comments section as below working.

Built with Hugo
Theme Stack designed by Jimmy