Published: Aug 29, 2024 by Isaac Johnson
Today I wanted to dig into a few quick Open-source apps on my to-do list to check out; Favicon Downloader and Shiori.
Favicon Downloader is a very simple, but quite useful, containerized utility for downloading favicon files from websites. These are the small images in the upper left on your tabs when you view websites.
Shiori is a bookmark manager, but a bit more than that as it can download websites and turn them into ePubs. We look at the containerized version before building a Kubernetes YAML manifest and hosting in k8s.
Let’s dig in.
Favicon Downloader
I can pull down the git repo here.
builder@LuiGi:~/Workspaces$ git clone https://github.com/seadfeng/favicon-downloader.git
Cloning into 'favicon-downloader'...
remote: Enumerating objects: 811, done.
remote: Counting objects: 100% (204/204), done.
remote: Compressing objects: 100% (135/135), done.
remote: Total 811 (delta 99), reused 148 (delta 52), pack-reused 607 (from 1)
Receiving objects: 100% (811/811), 2.45 MiB | 4.92 MiB/s, done.
Resolving deltas: 100% (368/368), done.
Next, I’ll build the docker image locally
builder@LuiGi:~/Workspaces/favicon-downloader$ cp .docker/Dockerfile .
builder@LuiGi:~/Workspaces/favicon-downloader$ docker build -t favicondownloader:latest .
[+] Building 91.6s (25/25) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 1.78kB 0.0s
=> [internal] load metadata for docker.io/library/node:20-alpine 0.4s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 5.27MB 0.1s
=> [base 1/1] FROM docker.io/library/node:20-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87 6.7s
=> => resolve docker.io/library/node:20-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139 0.0s
=> => sha256:2ba186860f733cc6abe783afaec3d55af4852d15cafc3f030544609c22d6e217 448B / 448B 0.4s
=> => sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45 7.67kB / 7.67kB 0.0s
=> => sha256:2cc3d19887bfea8bf52574716d5f16d4668e35158de866099711ddfb2b16b6e0 1.72kB / 1.72kB 0.0s
=> => sha256:d58137ded569a53fbc27bc227ab40807d2306033d1b7eed800f2b2f0c8c083bf 6.38kB / 6.38kB 0.0s
=> => sha256:731f5f878c76e7151b9c66968cbb701e7e815fd48718033cf20ba30a2317cff1 42.31MB / 42.31MB 5.1s
=> => sha256:17ea4cb95273cd126d8bd46096946efa3d2bad82680b0c838ead8400a98c055a 1.39MB / 1.39MB 1.0s
=> => extracting sha256:731f5f878c76e7151b9c66968cbb701e7e815fd48718033cf20ba30a2317cff1 1.1s
=> => extracting sha256:17ea4cb95273cd126d8bd46096946efa3d2bad82680b0c838ead8400a98c055a 0.0s
=> => extracting sha256:2ba186860f733cc6abe783afaec3d55af4852d15cafc3f030544609c22d6e217 0.0s
=> [deps 1/6] RUN apk add --no-cache libc6-compat 1.7s
=> [builder 1/5] WORKDIR /app 0.2s
=> [runner 2/9] RUN addgroup --system --gid 1001 nodejs 0.6s
=> [runner 3/9] RUN adduser --system --uid 1001 nextjs 0.5s
=> [deps 2/6] WORKDIR /app 0.1s
=> [deps 3/6] RUN node -v 0.6s
=> [deps 4/6] COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ 0.2s
=> [deps 5/6] RUN ls -la 0.5s
=> [deps 6/6] RUN if [ -f yarn.lock ]; then yarn --frozen-lockfile; elif [ -f package-lock.json ]; then npm 33.6s
=> [builder 2/5] COPY --from=deps /app/node_modules ./node_modules 3.2s
=> [builder 3/5] COPY . . 0.1s
=> [builder 4/5] RUN yarn build 38.9s
=> [builder 5/5] RUN ls -la /app/.next/standalone 0.6s
=> [runner 4/9] COPY --from=builder /app/public ./public 0.1s
=> [runner 5/9] RUN mkdir .next 0.4s
=> [runner 6/9] RUN chown nextjs:nodejs .next 0.5s
=> [runner 7/9] COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 0.3s
=> [runner 8/9] COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 0.2s
=> [runner 9/9] RUN ls -la /app 0.5s
=> exporting to image 0.4s
=> => exporting layers 0.4s
=> => writing image sha256:49de564120978f189395746c4b1320ff2b8ba369d7c10e06f01158d9c8f5f60e 0.0s
=> => naming to docker.io/library/favicondownloader:latest 0.0s
What's Next?
View a summary of image vulnerabilities and recommendations → docker scout quickview
builder@LuiGi:~/Workspaces/favicon-downloader$
I can then run it locally
builder@LuiGi:~/Workspaces/favicon-downloader$ docker run -p 3000:3000 favicondownloader:latest
▲ Next.js 14.2.5
- Local: http://localhost:3000
- Network: http://0.0.0.0:3000
✓ Starting...
✓ Ready in 57ms
I can then access the browser
For instance, I can pull my own
I can now see it
Hosting
First, I’ll push my build out to Dockerhub
builder@LuiGi:~/Workspaces/favicon-downloader$ docker tag favicondownloader:latest idjohnson/favicondownloader:latest
builder@LuiGi:~/Workspaces/favicon-downloader$ docker push idjohnson/favicondownloader:latest
The push refers to repository [docker.io/idjohnson/favicondownloader]
24f5bdcc031b: Pushed
75a7df487577: Pushed
194ec978536f: Pushed
12653338691a: Pushed
6cf421852d24: Pushed
c96ae09775ca: Pushed
714363458333: Pushed
9f13ff84d634: Pushed
bdd35ac0454c: Pushed
186d35b3efff: Mounted from library/node
32abe9a81c15: Mounted from library/node
3762a94f9dbd: Mounted from library/node
78561cef0761: Mounted from library/node
latest: digest: sha256:18d63c1b4ab2b57e160f9395a7e38fdff1b6fab9f92fd8e68422a9beba3022d8 size: 3030
I can then make a manifest that would expose it
$ cat manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: favicondownloader-deployment
spec:
replicas: 1
selector:
matchLabels:
app: favicondownloader
template:
metadata:
labels:
app: favicondownloader
spec:
containers:
- name: favicondownloader
image: idjohnson/favicondownloader:latest
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: favicondownloader-service
spec:
selector:
app: favicondownloader
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP
I can then apply it
$ kubectl apply -f manifest.yaml
deployment.apps/favicondownloader-deployment created
service/favicondownloader-service created
I will then create a DNS record
$ gcloud dns --project=myanthosproject2 record-sets create favicon.steeped.space --zone="steepedspace" --type="A" --ttl="300" --rrdatas="75.73.224.240"
NAME TYPE TTL DATA
favicon.steeped.space. A 300 75.73.224.240
I’ll now add the ingress
$ cat 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"
name: favicongcpingress
spec:
rules:
- host: favicon.steeped.space
http:
paths:
- backend:
service:
name: favicondownloader-service
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- favicon.steeped.space
secretName: favicongcp-tls
And apply
$ kubectl apply -f ./ingress.yaml
ingress.networking.k8s.io/favicongcpingress created
We can now try favicon.steeped.space
Shiori installation
First, I’ll try and run with docker
builder@LuiGi:~/Workspaces/favicon-downloader$ mkdir shiori
builder@LuiGi:~/Workspaces/favicon-downloader$ docker run -d --rm --name shiori -p 8080:8080 -v $(pwd):/shiori ghcr.io/go-shiori/shiori
Unable to find image 'ghcr.io/go-shiori/shiori:latest' locally
latest: Pulling from go-shiori/shiori
94d04197b5f5: Pull complete
e795eb5935ad: Pull complete
28e8a13529cb: Pull complete
61b19bf916ce: Pull complete
f8084089c2e1: Pull complete
Digest: sha256:85a47b264a5b96dec5e558b385f03cf867397dcc37d36d08b7f035681c846649
Status: Downloaded newer image for ghcr.io/go-shiori/shiori:latest
52b5241c9031bc876d687934441f99d80af3e7a266e910f13661f7e090ecb39a
I can then hit it on port 8080
From the FAQ we can see the login/pass is shiori:gopher
My next step was to try and get it to run in Kubernetes natively
The installation
I can the use a manifest
$ cat shiori.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: shiori
labels:
app: shiori
spec:
replicas: 1
selector:
matchLabels:
app: shiori
template:
metadata:
labels:
app: shiori
spec:
volumes:
- name: app
persistentVolumeClaim:
claimName: shiori-pvc
containers:
- name: shiori
image: ghcr.io/go-shiori/shiori:latest
command: ["/usr/bin/shiori", "serve", "--webroot", "/shiori"]
imagePullPolicy: Always
ports:
- containerPort: 8080
volumeMounts:
- mountPath: /srv/shiori
name: app
env:
- name: SHIORI_DIR
value: /srv/shiori
- name: HTTP_ROOT_PATH
value: "/shiori"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shiori-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-path
---
apiVersion: v1
kind: Service
metadata:
name: shiori
spec:
type: NodePort
selector:
app: shiori
ports:
- protocol: TCP
port: 8080
targetPort: 8080
nodePort: 32654
Then apply
$ kubectl apply -f ./shiori.yaml
deployment.apps/shiori created
persistentvolumeclaim/shiori-pvc created
service/shiori created
I’ll then port-forward
$ kubectl port-forward svc/shiori 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
I use the shiori
and gopher
password to start
This worked just fine
I’ll go to settings to change the password
I logged out and verified the old password was changed
Just to be sure it saved in the PVC, i rotate the pod and verified the password was still changed
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl get pods | grep shiori
shiori-577d7bb646-jc5mq 1/1 Running 0 5m9s
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl delete pod shiori-577d7bb646-jc5mq
pod "shiori-577d7bb646-jc5mq" deleted
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl port-forward svc/shiori 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
As before, let’s create an Ingress for it
$ gcloud dns --project=myanthosproject2 record-sets create shiori.steeped.space --zone="steepedspace" --type="A" --ttl="300" --rrdatas="75.73.224.240"
NAME TYPE TTL DATA
shiori.steeped.space. A 300 75.73.224.240
Which needs an ingress YAML
$ cat shiori.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"
name: shiorigcpingress
spec:
rules:
- host: shiori.steeped.space
http:
paths:
- backend:
service:
name: shiori
port:
number: 8080
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- shiori.steeped.space
secretName: shiorigcp-tls
easily applied
$ kubectl apply -f ./shiori.ingress.yaml
ingress.networking.k8s.io/shiorigcpingress created
Once I saw the Cert created the site came up
$ kubectl get cert shiorigcp-tls
NAME READY SECRET AGE
shiorigcp-tls True shiorigcp-tls 2m
Usage
Let’s create an eBook from the website. I really have no idea what this will end up doing, to be fair.
It looks like it to the main page and created a quick HTML book
It’s pretty much the summary and the current front-page articles
I’m curious how the pseudo-paywall type articles are handled.
Here is a medium article about a Job at Google and it cuts off half-way if you aren’t logged in.
I’ll add that
I see I can pull that down as an ePub which would work on my kindles and Nook (1st Gens).
But, alas, a paywall defeater it is not and I got cut off in the same place
Summary
Favicon Downloader and Shiori are small light simple utility containers that were easy to host in Kubernetes. I can see both coming into use from time to time.