Set up a simple homepage with an auto-deploy tekton pipeline.
This commit is contained in:
commit
85f3b31480
63
.lighthouse/triggers.yaml
Normal file
63
.lighthouse/triggers.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: config.lighthouse.jenkins-x.io/v1alpha1
|
||||||
|
kind: TriggerConfig
|
||||||
|
spec:
|
||||||
|
postsubmits:
|
||||||
|
- name: semver
|
||||||
|
agent: tekton-pipeline
|
||||||
|
branches:
|
||||||
|
- ^main$
|
||||||
|
- ^master$
|
||||||
|
context: homepage
|
||||||
|
max_concurrency: 1
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/homepage.git"
|
||||||
|
pipeline_run_spec:
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
pipelineRef:
|
||||||
|
name: semver
|
||||||
|
namespace: lighthouse
|
||||||
|
workspaces:
|
||||||
|
- name: git-source
|
||||||
|
volumeClaimTemplate:
|
||||||
|
spec:
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
subPath: homepage-source
|
||||||
|
params: []
|
||||||
|
- name: build-homepage
|
||||||
|
agent: tekton-pipeline
|
||||||
|
branches:
|
||||||
|
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||||
|
context: build-docker
|
||||||
|
max_concurrency: 1
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/homepage.git"
|
||||||
|
pipeline_run_spec:
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
pipelineRef:
|
||||||
|
name: build-docker-pipeline
|
||||||
|
workspaces:
|
||||||
|
- name: git-source
|
||||||
|
volumeClaimTemplate:
|
||||||
|
spec:
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
subPath: git-source
|
||||||
|
- name: docker-credentials
|
||||||
|
secret:
|
||||||
|
secretName: harbor-plain
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
value: "harbor.fizz.buzz/private/homepage"
|
||||||
|
- name: path-to-image-context
|
||||||
|
value: .
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
value: docker/server/Dockerfile
|
11
docker/server/Dockerfile
Normal file
11
docker/server/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM alpine:3.18
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash nginx
|
||||||
|
RUN addgroup web && adduser -D -G web web && install -d -D -o web -g web -m 700 /srv/http/public
|
||||||
|
RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log
|
||||||
|
|
||||||
|
COPY --chown=web:web docker/server/nginx.conf /srv/http
|
||||||
|
COPY --chown=web:web docker/server/headers.include /srv/http
|
||||||
|
COPY --chown=web:web static/ /srv/http/public/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/sbin/nginx", "-c", "/srv/http/nginx.conf", "-e", "stderr", "-g", "daemon off;"]
|
35
docker/server/Makefile
Normal file
35
docker/server/Makefile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
IMAGE_NAME:=homepage
|
||||||
|
# REMOTE_REPO:=harbor.fizz.buzz/private
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: build push
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
docker build -t $(IMAGE_NAME) -f Dockerfile ../
|
||||||
|
|
||||||
|
.PHONY: push
|
||||||
|
push:
|
||||||
|
ifdef REMOTE_REPO
|
||||||
|
docker tag $(IMAGE_NAME) $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||||
|
docker push $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||||
|
else
|
||||||
|
@echo "REMOTE_REPO not defined, not pushing to a remote repo."
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
docker rmi $(IMAGE_NAME)
|
||||||
|
ifdef REMOTE_REPO
|
||||||
|
docker rmi $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||||
|
else
|
||||||
|
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run:
|
||||||
|
docker run --rm -i -t -p "8080:8080" $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
|
shell:
|
||||||
|
docker run --rm -i -t -p "8080:8080" --entrypoint /bin/bash $(IMAGE_NAME)
|
16
docker/server/headers.include
Normal file
16
docker/server/headers.include
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Enable HTTP Strict Transport Security (HSTS) to force clients to
|
||||||
|
# always connect via HTTPS (do not use if only testing)
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000;" always;
|
||||||
|
# Enable cross-site filter (XSS) and tell browser to block detected
|
||||||
|
# attacks
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
# Prevent some browsers from MIME-sniffing a response away from the
|
||||||
|
# declared Content-Type
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
# Disallow the site to be rendered within a frame (clickjacking
|
||||||
|
# protection)
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
|
||||||
|
# Surrogate Control sets CDN caching behavior.
|
||||||
|
add_header Surrogate-Control "public, max-age=86400";
|
||||||
|
add_header Cache-Control "public, max-age=120";
|
42
docker/server/nginx.conf
Normal file
42
docker/server/nginx.conf
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
user web;
|
||||||
|
worker_processes 4;
|
||||||
|
|
||||||
|
# Speed up regular expressions.
|
||||||
|
pcre_jit on;
|
||||||
|
|
||||||
|
error_log stderr debug;
|
||||||
|
|
||||||
|
events {
|
||||||
|
# Connections per worker process.
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
server_tokens off;
|
||||||
|
client_max_body_size 1m;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
|
||||||
|
include headers.include;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
root /srv/http/public;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
index index.html index.htm;
|
||||||
|
if (-d $request_filename) {
|
||||||
|
rewrite [^/]$ $http_x_forwarded_proto://$http_host$uri/ redirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
location /healthz {
|
||||||
|
access_log off;
|
||||||
|
add_header 'Content-Type' 'application/json';
|
||||||
|
return 200 '{"status":"OK"}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
73
static/.well-known/keybase.txt
Normal file
73
static/.well-known/keybase.txt
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
==================================================================
|
||||||
|
https://keybase.io/tomalexander
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
I hereby claim:
|
||||||
|
|
||||||
|
* I am an admin of https://fizz.buzz
|
||||||
|
* I am tomalexander (https://keybase.io/tomalexander) on keybase.
|
||||||
|
* I have a public key with fingerprint 20BA 994C 3993 5467 A337 BCFF 7E36 5E73 37BD 466E
|
||||||
|
|
||||||
|
To do so, I am signing this object:
|
||||||
|
|
||||||
|
{
|
||||||
|
"body": {
|
||||||
|
"key": {
|
||||||
|
"eldest_kid": "0101b8ea765573023535452721958f4dbf5acdf01b75f6f11048c344ea77f14ab4b50a",
|
||||||
|
"fingerprint": "20ba994c39935467a337bcff7e365e7337bd466e",
|
||||||
|
"host": "keybase.io",
|
||||||
|
"key_id": "7e365e7337bd466e",
|
||||||
|
"kid": "010145c375961ef994499dae6f6096cf9294ec5e56ed5c2a29c146076dd78bd041930a",
|
||||||
|
"uid": "649686c722ecf6d6beb66a67cdcfa300",
|
||||||
|
"username": "tomalexander"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"hostname": "fizz.buzz",
|
||||||
|
"protocol": "https:"
|
||||||
|
},
|
||||||
|
"type": "web_service_binding",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"ctime": 1558368226,
|
||||||
|
"expire_in": 157680000,
|
||||||
|
"prev": "98cd64964fddf3863b5985ad3c15ca086a94d1ceb7a84ebfa83cfb3db5b26f22",
|
||||||
|
"seqno": 10,
|
||||||
|
"tag": "signature"
|
||||||
|
}
|
||||||
|
|
||||||
|
which yields the signature:
|
||||||
|
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
owFtkntQVHUUx3nGIxwhilcQcG0IlPA+f/deQmZkyFHMBxNuZsB6H78Ll8fusrss
|
||||||
|
sLxyQkBkHOiBBMa4NcZjCgxyIEubIUhWGUTDSVImJZ4VjmSChGX3Mjr90++f35zz
|
||||||
|
+34/55zfnLp1zg7ujhaX3PaXI1yWHS/N8w6pk5fnihFeLxYhscVINly7YI4ITWZt
|
||||||
|
tiwisQiKoRjPQI4GFEUTKE5QBEVSOI1jLMVIpMhLFCeIkiKiKQlIGIaSjECQpOKg
|
||||||
|
JYzkeJKnUA6JRiRZlwGNBqOsMytYHOU5liUFgmUVHqA5gqB5QZJoSAAK0mokkgBA
|
||||||
|
xZipN6kOpTmeM8EYWa/klEC71t7/6P/rm6QEgqZYgEFJqUWyrMhBIAGUBYLE4iwJ
|
||||||
|
BQpSAIqUgHM4K2AkQGkgijTDiyiJscRa3/lrOECygAECjeNQkIAIeMgDwAFaEAWJ
|
||||||
|
I1BUFZqgUcflQkVt1udyObCQ04nQiJRGI8qLRRag+rnqNI9Vkmy1xvD5VqtiNhj1
|
||||||
|
Zr2gz1HSmWazwRSr2sxFBlVXAHntY4KWl3Wi8pGKwwKNJlmvQ2IxRSmYZRWJURRD
|
||||||
|
AAbHQTQCCw2yEWplVUHRgEGVo9aBFgXJMoIIyAMsICVRlAgGEDzFMhQnEgJGCRzK
|
||||||
|
AI4lRUyAPM0xJOQljiEEiSdEnuJxIOE4og6Vp9MrcIVq5jIUqEnO0HHmfCNESj2P
|
||||||
|
OBEuDo7uDiF+4S7+e5v7lneF2b57vjrhyfa5Oqmr5+Dp4f0k89WG9f/EPSNFJwX1
|
||||||
|
XWrdjqYnJ61G2s6cCs0ejwv5+cxw74TwVkVkjbWlO/5cVv/p57ZmG0Jn/I8t4b8F
|
||||||
|
f3u65sLNv1tG9trf9Nt/rtPSFF85rn12o19H6uiLlSex4cDZD+/XhOY0bjpbpEtO
|
||||||
|
dWrdPXE+Zqzk7Zuzj6Le+bL49tYdUdTKSHXbtGYl6IfWY6vWAS84uH1zxpXEpg+u
|
||||||
|
/HLPZ6dHe9lTJQvTU1teP+BGhjdoKiuc/Pe9MetvDjvYmOH6p1OtH894LGOBeRsW
|
||||||
|
tzzd2fPeDe3XDYNVN+xfEKMNHg+8uzpXCydmHvqGdGs+2mjbf9Dz1fq2Te9evBWw
|
||||||
|
c9/3+vLYI2B2wjrVPr5nyDXFpa+sNT23/q+MjqIe+wthMzHbZpoD0hzv1nX7fhwX
|
||||||
|
dKIx0bEcTxj/ZOBQgmYu2fNkUttwdsvdkgiNWy1qRwYG9T7eXd0jYz8dr8qpsx3u
|
||||||
|
vSOXpo35pXstBY6+ZnjQtbCobb7wfvX4fNq6moXLZXlVvWfr0XuTh6NszcXo+Tup
|
||||||
|
KwXOXvWbp8Mrl1ofug0vNu0I+fTX/q5tu/tPZZK3HAoq7fjxH4dHj9o6Kioirn2O
|
||||||
|
pMuTwehQeVJQVuKItbsBzF/LCtdct43PRxx1T/ksxcu3P2b9Lmdq8VBEguVimEvW
|
||||||
|
iQDvnj1/xHs/usrelyO/6VnfMfe75bp96hWfq7py7PZUcO3QS/8C
|
||||||
|
=4Gzv
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
|
||||||
|
And finally, I am proving ownership of this host by posting or
|
||||||
|
appending to this document.
|
||||||
|
|
||||||
|
View my publicly-auditable identity here: https://keybase.io/tomalexander
|
||||||
|
|
||||||
|
==================================================================
|
33
static/index.html
Normal file
33
static/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>FizzBuzz</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>FizzBuzz Dev Blog.</h1>
|
||||||
|
<p><strong>Coming Eventually!</strong></p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p>Please check out my repos at <a href="https://code.fizz.buzz/explore/repos">code.fizz.buzz</a>.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Links
|
||||||
|
</br>
|
||||||
|
<ul>
|
||||||
|
<li>My Personal Repos: <a href="https://code.fizz.buzz/explore/repos">code.fizz.buzz</a></li>
|
||||||
|
<li>LinkedIn: <a href="https://www.linkedin.com/in/tom-alexander-b6a18216/">https://www.linkedin.com/in/tom-alexander-b6a18216/</a></li>
|
||||||
|
<li>GitHub: <a href="https://github.com/tomalexander">https://github.com/tomalexander</a></li>
|
||||||
|
<li>Resume: <a href="https://fizz.buzz/resume.pdf">https://fizz.buzz/resume.pdf</a></li>
|
||||||
|
<li>PGP Key: <a href="https://fizz.buzz/pgp.asc">https://fizz.buzz/pgp.asc</a></li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Why is your website the way it is?</h3>
|
||||||
|
<p>I used to have a developer blog hosted at this domain. I quickly developed an appreciation for the power of org-mode for writing the content of the blog but I grew tired of inconsistent build results. The static site generators at the time would function by calling out to emacs itself to parse the org-mode and export HTML which meant that updates to emacs, my elisp packages, or the static site generator could cause compatibility issues. This often lead to things like escaping issues in old blog posts going unnoticed.</p>
|
||||||
|
|
||||||
|
<p>To solve the issue, and to seize the opportunity to gain more experience in Rust, I decided to write my own static site generator that would not depend on outside tools. So far I have written <a href="https://code.fizz.buzz/talexander/duster">the template engine</a> and I am in the process of writing <a href="https://code.fizz.buzz/talexander/organic">an org-mode parser</a>. When that is done, it should just be a matter of tying those two together with some minor glue to make a static site generator to create the new version of this site. Until that is done, I am using this hastily thrown-together manually-written html file as a placeholder.</p>
|
||||||
|
|
||||||
|
<p>That isn't to say that there are no exciting things hosted on this server, just not at the root domain. For example, this server is running kubernetes that I set up manually following <a href="https://github.com/kelseyhightower/kubernetes-the-hard-way">kubernetes-the-hard-way</a> in a bunch of <a href="https://man.freebsd.org/cgi/man.cgi?bhyve">bhyve VMs</a> that I networked together using <a href="https://man.freebsd.org/cgi/man.cgi?netgraph(4)">netgraph</a>. On it I host my own <a href="https://www.powerdns.com/">PowerDNS</a> server as the authoratative DNS server for fizz.buzz. It is integrated with <a href="https://cert-manager.io/">cert-manager</a> and <a href="https://github.com/kubernetes-sigs/external-dns">ExternalDNS</a> so Ingresses/LoadBalancers on my cluster automatically get valid TLS certificates and update the DNS records. I have a fully open-source self-hosted gitops workflow where a commit to a git repo I'm hosting in <a href="https://code.fizz.buzz/">gitea</a>, triggers a <a href="https://tekton.dev/">tekton pipeline</a> through <a href="https://github.com/jenkins-x/lighthouse">lighthouse</a> to build a docker image with <a href="https://github.com/GoogleContainerTools/kaniko">kaniko</a>, which gets pushed to my self-hosted <a href="https://goharbor.io/">harbor</a> instance, which then gets deployed to my cluster via <a href="https://fluxcd.io/">flux</a>. The end result is I make a commit to a repo and the result is deployed to my website in minutes.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
34
static/pgp.asc
Normal file
34
static/pgp.asc
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=a/z4
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
BIN
static/resume.pdf
Normal file
BIN
static/resume.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user