diff --git a/Dockerfile b/Dockerfile
index 22f3387..2539050 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM docker.io/golang:1.23 AS build
+FROM docker.io/golang:1.24 AS build
WORKDIR /build/
COPY go.mod go.sum .
RUN go mod download
@@ -11,5 +11,6 @@ COPY html/ html/
COPY css/ css/
COPY static/ static/
COPY tmpl/ tmpl/
+COPY aliases.txt aliases.txt
EXPOSE 3000
CMD ["./app"]
diff --git a/aliases.txt b/aliases.txt
new file mode 100644
index 0000000..9c1cc38
--- /dev/null
+++ b/aliases.txt
@@ -0,0 +1,5 @@
+ssh /static/ssh
+pgp /static/pgp.asc
+termux.sh https://git.gmoker.com/ange/termux/raw/branch/main/install.sh
+dotfiles.sh https://git.gmoker.com/ange/.dotfiles
+arch.sh https://git.gmoker.com/ange/arch
diff --git a/css/resume.css b/css/resume.css
index a987a76..4e246ed 100644
--- a/css/resume.css
+++ b/css/resume.css
@@ -1,11 +1,19 @@
@media print {
- @page {
- size: A4;
- size: portrait;
- margin: 5mm;
- }
+ @page {
+ size: A4;
+ size: portrait;
+ margin: 5mm;
+ }
- #menu, #credits {
- display: none;
- }
+ #credits, #menu {
+ display: none;
+ }
+
+ #main {
+ margin: 0;
+ }
+
+ #nav {
+ float: right;
+ }
}
diff --git a/css/style.css b/css/style.css
index 6a1fee0..765d4f1 100644
--- a/css/style.css
+++ b/css/style.css
@@ -1,176 +1,176 @@
/* https://suckless.org/pub/style.css */
html {
- overflow-y: scroll;
+ overflow-y: scroll;
}
body {
- background-color: #fff;
- color: #000;
- font-family: sans-serif;
- padding: 0;
- margin: 0;
+ background-color: #fff;
+ color: #000;
+ font-family: sans-serif;
+ padding: 0;
+ margin: 0;
}
pre, code {
- margin: 0;
+ margin: 0;
}
a {
- color: #005386;
+ color: #005386;
}
#header a, #nav a, #menu a {
- text-decoration: none;
+ text-decoration: none;
}
#nav a:hover {
- background-color: #ddd;
+ background-color: #ddd;
}
#menu {
- clear: both;
- color: #069;
- overflow: hidden;
- background-color: #17a;
- padding: 0.7ex;
- border-top: 1px solid #ccc;
- border-bottom: 1px solid #069;
+ clear: both;
+ color: #069;
+ overflow: hidden;
+ background-color: #17a;
+ padding: 0.7ex;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #069;
}
#menu a {
- padding: 0.5ex 1ex;
- color: #fff;
+ padding: 0.5ex 1ex;
+ color: #fff;
}
#menu a:hover {
- background-color: #069;
+ background-color: #069;
}
#header {
- background-color: #eee;
- clear: both;
- color: #555;
- font-size: 1.78em;
- padding: 0.7ex 0.7ex 0.7ex 0.7em;
+ background-color: #eee;
+ clear: both;
+ color: #555;
+ font-size: 1.78em;
+ padding: 0.7ex 0.7ex 0.7ex 0.7em;
}
#headerLink {
- color: #17a;
- margin-left: 5px;
+ color: #17a;
+ margin-left: 5px;
}
h1 {
- margin: 1em 1ex 0.5ex 0;
- font-size: 1.4em;
+ margin: 1em 1ex 0.5ex 0;
+ font-size: 1.4em;
}
h2 {
- margin: 1em 1ex 0.5ex 0;
- font-size: 1.3em;
+ margin: 1em 1ex 0.5ex 0;
+ font-size: 1.3em;
}
h3 {
- margin: 1em 1ex 0.5ex 0;
- font-size: 1.0em;
+ margin: 1em 1ex 0.5ex 0;
+ font-size: 1.0em;
}
h4 {
- margin: 1em 1ex 0.5ex 0;
- font-size: 0.9em;
+ margin: 1em 1ex 0.5ex 0;
+ font-size: 0.9em;
}
#headerSubtitle {
- font-size: 0.75em;
- font-style: italic;
- margin-left: 1em;
- color: #fff;
+ font-size: 0.75em;
+ font-style: italic;
+ margin-left: 1em;
+ color: #fff;
}
#content {
- clear: both;
- margin: 0;
- padding: 0;
+ clear: both;
+ margin: 0;
+ padding: 0;
}
#nav {
- float: left;
- margin: 0 1px 0 0;
- padding: 1em 0;
- border-right: 1px dotted #ccc;
- width: 200px;
+ float: left;
+ margin: 0 1px 0 0;
+ padding: 1em 0;
+ border-right: 1px dotted #ccc;
+ width: 200px;
}
#nav ul {
- margin: 0;
- padding: 0;
+ margin: 0;
+ padding: 0;
}
#nav li {
- list-style: none;
- padding: 0;
- margin: 0;
+ list-style: none;
+ padding: 0;
+ margin: 0;
}
#nav li ul {
- padding-left: 0.6em !important;
+ padding-left: 0.6em !important;
}
#nav li a {
- display: block;
- margin: 0;
- padding: 0.8ex 2em 0.8ex 1em;
+ display: block;
+ margin: 0;
+ padding: 0.8ex 2em 0.8ex 1em;
}
#main {
- margin: 0 0 0 200px;
- max-width: 50em;
- padding: 1.5em;
+ margin: 0 0 0 200px;
+ max-width: 50em;
+ padding: 1.5em;
}
.left {
- float: left;
- margin: 0;
- padding: 0;
+ float: left;
+ margin: 0;
+ padding: 0;
}
.right {
- float: right;
- margin: 0;
- padding: 0;
+ float: right;
+ margin: 0;
+ padding: 0;
}
.hidden {
- display: none;
+ display: none;
}
@media (prefers-color-scheme: dark) {
- body {
- background-color: #000;
- color: #bdbdbd;
- }
- #menu {
- border-top: 1px solid #222;
- }
- #header {
- background-color: #111;
- }
- #nav a:hover {
- background-color: #222;
- }
- blockquote, pre, code {
- background-color: #111;
- border-color: #222;
- }
- a {
- color: #56c8ff;
- }
- #main img[src$=svg] {
- filter: invert(1);
- }
+ body {
+ background-color: #000;
+ color: #bdbdbd;
+ }
+ #menu {
+ border-top: 1px solid #222;
+ }
+ #header {
+ background-color: #111;
+ }
+ #nav a:hover {
+ background-color: #222;
+ }
+ blockquote, pre, code {
+ background-color: #111;
+ border-color: #222;
+ }
+ a {
+ color: #56c8ff;
+ }
+ #main img[src$=svg] {
+ filter: invert(1);
+ }
}
footer {
- position: fixed;
- bottom: 0;
+ position: fixed;
+ bottom: 0;
}
diff --git a/go.mod b/go.mod
index 3a3a5a6..e7f9816 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
module main
-go 1.23.3
+go 1.24.1
diff --git a/html/contact.html b/html/contact.html
index 0fd8f2a..d2be560 100644
--- a/html/contact.html
+++ b/html/contact.html
@@ -2,68 +2,57 @@
{{template "head.tmpl" .}}
-
+
{{template "header.tmpl" .}}
@@ -28,19 +25,22 @@
EXPERIENCE
-
Freelance - DevOps
+
Freelance - DevOps Engineer
SEPTEMBER 2023 - TODAY
- - Server setup (Debian, Docker/K8S, web server, customer application)
- - CI/CD, real-time monitoring
+ - Docker / K8S
+ - GitHub Actions
+ - Prometheus
+ - Python / Go backend
-
Selfhost - DevOps
+
Selfhost - DevOps Engineer
JANUARY 2022 - TODAY
- - Debian, K8S, Gitea CI/CD
+ - Bare metal K8S
+ - Gitea Actions
- Nextcloud, Matrix.org, Jellyfin, SearXNG
-
EPITECH CodingClub association, Toulouse - Cobra
+
EPITECH CodingClub association, Toulouse - Trainer
JANUARY 2021 - JUNE 2024
- Coding workshops for high school students
@@ -48,20 +48,21 @@
Predicloud, Toulouse - DevOps SRE
JUNE 2022 - JULY 2023
- - CI/CD, K8S monitoring
- - Authentication solution (LDAP, SSO, Webhook) + Python backend
- - Mail server (Postfix, Dovecot)
- - Customer communication and troubleshooting
+ - GitLab CI
+ - Prometheus, Grafana
+ - OpenLDAP + Python frontend, Keycloak, K8S webhook
+ - Postfix, Dovecot
+ - Customer troubleshooting
COMPETENCES
- LANGUAGES | SOFTWARE |
- C/C++ | *nix |
- Python | Git |
- Bash | Vim |
- Go | Docker |
- JS | K8S |
+ LANGUAGES | SOFTWARE |
+ C/C++ | *nix |
+ Python | Git |
+ Bash | Vim |
+ Go | Docker |
+ JS | K8S |
diff --git a/static/copy.js b/js/copy.js
similarity index 100%
rename from static/copy.js
rename to js/copy.js
diff --git a/manifests/bin/deploy.sh b/manifests/bin/deploy.sh
index 088dfbb..2cc0c9e 100755
--- a/manifests/bin/deploy.sh
+++ b/manifests/bin/deploy.sh
@@ -3,32 +3,35 @@ set -o pipefail
function kapply() {
for f in "$@"; do
- kubectl apply -f \
- <(envsubst "$(env | xargs printf '$%s ')" < "manifests/$f")
+ kubectl apply -f <(envsubst < "manifests/$f")
done
-}
+}; export -f kapply
function kcreatesec() {
- kubectl create secret generic --save-config --dry-run=client -oyaml "$@" | kubectl apply -f-
-}
+ kubectl create secret generic --dry-run=client -oyaml "$@" | kubectl replace -f-
+}; export -f kcreatesec
function kcreatecm() {
- kubectl create configmap --dry-run=client -oyaml "$@" | kubectl apply -f-
-}
+ kubectl create configmap --dry-run=client -oyaml "$@" | kubectl replace -f-
+}; export -f kcreatecm
function kgseckey() {
local sec="$1"; shift
local key="$1"; shift
- kubectl get secret "$sec" -o jsonpath="{.data.$key}" | base64 -d
-}
+ if ! kubectl get secret "$sec" -ojson | jq -re ".data.\"$key\" // empty" | base64 -d; then
+ return 1
+ fi
+}; export -f kgseckey
function kgcmkey() {
- local cm="$1"; shift
+ local cm="$1"; shift
local key="$1"; shift
- kubectl get configmap "$cm" -o jsonpath="{.data.$key}"
-}
+ if ! kubectl get configmap "$cm" -ojson | jq -re ".data.\"$key\" // empty"; then
+ return 1
+ fi
+}; export -f kgcmkey
kapply common/app.yaml
diff --git a/manifests/bin/devel.sh b/manifests/bin/devel.sh
index 464c4d0..65675aa 100755
--- a/manifests/bin/devel.sh
+++ b/manifests/bin/devel.sh
@@ -1,4 +1,5 @@
#!/bin/bash -e
+set -o pipefail
export NB_REPLICAS=1
diff --git a/manifests/bin/prod.sh b/manifests/bin/prod.sh
index c97fc9e..b7b5f83 100755
--- a/manifests/bin/prod.sh
+++ b/manifests/bin/prod.sh
@@ -1,4 +1,5 @@
#!/bin/bash -e
+set -o pipefail
export NB_REPLICAS=3
diff --git a/src/aliases.go b/src/aliases.go
new file mode 100644
index 0000000..93a9761
--- /dev/null
+++ b/src/aliases.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "log"
+ "os"
+ "strings"
+)
+
+var ALIASES map[string]string
+
+func generateAliases() {
+ f, err := os.ReadFile("aliases.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ ALIASES = make(map[string]string)
+ for l := range strings.SplitSeq(string(f), "\n") {
+ sp := strings.Fields(l)
+
+ if len(sp) == 2 {
+ ALIASES[sp[0]] = sp[1]
+ }
+ }
+}
diff --git a/src/main.go b/src/main.go
index 823c600..bb5fca5 100644
--- a/src/main.go
+++ b/src/main.go
@@ -7,6 +7,7 @@ import (
func main() {
http.HandleFunc("/", route)
+ generateAliases()
generateTmpl()
if err := http.ListenAndServe(":3000", nil); err != nil {
log.Fatal(err)
diff --git a/src/route.go b/src/route.go
index 43b8231..1b92eca 100644
--- a/src/route.go
+++ b/src/route.go
@@ -17,7 +17,7 @@ var routes = []struct {
}{
{[]string{"GET"}, url(""), index},
{[]string{"GET"}, url("/static/.+"), static},
- {[]string{"GET"}, url("/(.+\\.css)"), css},
+ {[]string{"GET"}, url("/(.+\\.(css|js))"), file},
{[]string{"GET"}, url("/([^/]+)"), html},
}
@@ -42,8 +42,8 @@ func route(w http.ResponseWriter, r *http.Request) {
}
fmt.Println(r.Method, r.URL.Path)
rt.handler(w, r.WithContext(
- context.WithValue(r.Context(), URLParam{}, matches[1:])),
- )
+ context.WithValue(r.Context(), URLParam{}, matches[1:]),
+ ))
return
}
}
diff --git a/src/tmpl.go b/src/tmpl.go
index 8f7932d..56de3e7 100644
--- a/src/tmpl.go
+++ b/src/tmpl.go
@@ -12,10 +12,10 @@ var TMPL map[string][]byte
func generateTmpl() {
files, _ := filepath.Glob("html/*.html")
re := regexp.MustCompile("html/(.+).html")
- names := make([]string, len(files))
+ pages := make([]string, len(files))
for i, f := range files {
- names[i] = re.FindStringSubmatch(f)[1]
+ pages[i] = re.FindStringSubmatch(f)[1]
}
TMPL = make(map[string][]byte, len(files))
for i, f := range files {
@@ -23,9 +23,9 @@ func generateTmpl() {
t, _ := template.ParseFiles(f)
t.ParseGlob("tmpl/*.tmpl")
t.Execute(b, map[string]any{
- "name": names[i],
- "names": names,
+ "page": pages[i],
+ "pages": pages,
})
- TMPL[names[i]] = b.Bytes()
+ TMPL[pages[i]] = b.Bytes()
}
}
diff --git a/src/views.go b/src/views.go
index 94f7f1a..0c5677f 100644
--- a/src/views.go
+++ b/src/views.go
@@ -1,9 +1,8 @@
package main
import (
- "path/filepath"
"net/http"
- "fmt"
+ "path/filepath"
)
func index(w http.ResponseWriter, r *http.Request) {
@@ -14,13 +13,14 @@ func static(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path)
}
-func css(w http.ResponseWriter, r *http.Request) {
- fmt.Println(filepath.Join("css", getParam(r, 0)))
- http.ServeFile(w, r, filepath.Join("css", getParam(r, 0)))
+func file(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, filepath.Join(getParam(r, 1), getParam(r, 0)))
}
func html(w http.ResponseWriter, r *http.Request) {
- if t, found := TMPL[getParam(r, 0)]; found {
+ if a, found := ALIASES[getParam(r, 0)]; found {
+ http.Redirect(w, r, a, http.StatusFound)
+ } else if t, found := TMPL[getParam(r, 0)]; found {
w.Write(t)
} else {
http.NotFound(w, r)
diff --git a/tmpl/head.tmpl b/tmpl/head.tmpl
index 37f8360..c8cb210 100644
--- a/tmpl/head.tmpl
+++ b/tmpl/head.tmpl
@@ -1,3 +1,3 @@
-
Ange DUHAYON
+
Ange DUHAYON - {{.page}}
diff --git a/tmpl/header.tmpl b/tmpl/header.tmpl
index 90fcadf..e602fcf 100644
--- a/tmpl/header.tmpl
+++ b/tmpl/header.tmpl
@@ -4,9 +4,9 @@
Website heavily inspired from suckless.org