diff options
| author | alex emery <[email protected]> | 2024-11-03 15:33:28 +0000 |
|---|---|---|
| committer | alex emery <[email protected]> | 2024-11-03 15:33:28 +0000 |
| commit | 508527f52de524a4fd174d386808e314b4138b11 (patch) | |
| tree | 2593af258b67decbf0207e2547b7ea55f6b051d7 | |
| parent | 22bfae8f9637633d5608caad3ce56b64c6819505 (diff) | |
feat: static builds
28 files changed, 1550 insertions, 22 deletions
@@ -6,7 +6,8 @@ COPY go.mod go.sum ./ RUN go mod download && go mod verify COPY . . -RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /usr/local/bin/app ./cmd/home/ +RUN CGO_ENABLED=0 go run ./cmd/build +RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /usr/local/bin/app ./cmd/serve/ FROM scratch @@ -1,9 +1,14 @@ VERSION=0.0.1 DOCKER_IMAGE=a73xsh/home:${VERSION} + +.PHONY: build +build: + go run ./cmd/build + .PHONY: serve serve: - go run ./cmd/home + go run ./cmd/serve .PHONY: css css: diff --git a/cmd/build/main.go b/cmd/build/main.go new file mode 100644 index 0000000..9c5cd15 --- /dev/null +++ b/cmd/build/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "errors" + "fmt" + "log" + "os" + "path" + + "git.sr.ht/~a73x/home/pages" +) + +func main() { + if err := Run(); err != nil { + log.Fatal(err) + } +} + +func Run() error { + pages, err := pages.Collect("content") + if err != nil { + return err + } + + var errs []error + for _, page := range pages { + err = writeFile(path.Join("public", page.Path), []byte(page.Content)) + if err != nil { + errs = append(errs, err) + } + } + + if errs != nil { + return errors.Join(errs...) + } + + return nil +} + +func writeFile(name string, contents []byte) error { + folders := path.Dir(name) + _, err := os.Stat(folders) + if os.IsNotExist(err) { + if err := os.MkdirAll(folders, 0744); err != nil { + return fmt.Errorf("failed to mkdir %s\n%w", folders, err) + } + } else if err != nil { + return fmt.Errorf("failed to stat folder %s\n%w", folders, err) + } + + err = os.WriteFile(name, contents, 0666) + if err != nil { + return fmt.Errorf("failed to write file %s\n%w", name, err) + } + + return nil +} diff --git a/cmd/home/main.go b/cmd/home/main.go index 9dc5cc6..3f04b39 100644 --- a/cmd/home/main.go +++ b/cmd/home/main.go @@ -16,7 +16,7 @@ func main() { logger.Fatal("failed to start webserver", zap.Error(err)) } - done := make(chan os.Signal, 1) + done := make(chan os.Signal, 0) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) logger.Info("Starting web server") diff --git a/cmd/serve/main.go b/cmd/serve/main.go new file mode 100644 index 0000000..9ce15cd --- /dev/null +++ b/cmd/serve/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "log" + "net/http" + "path/filepath" + "strings" + "time" + + "git.sr.ht/~a73x/home/public" + "go.uber.org/zap" +) + +func main() { + if err := Run(); err != nil { + log.Fatal(err) + } +} + +func Run() error { + logger, err := zap.NewProduction() + if err != nil { + return err + } + + loggingMiddleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + logger.Info("request received", + zap.String("url", r.URL.Path), + zap.String("method", r.Method), + zap.Duration("duration", time.Since(start)), + zap.String("user-agent", r.UserAgent()), + ) + }) + } + + mux := http.NewServeMux() + + mux.HandleFunc("GET /", serveFile) + + server := http.Server{ + Addr: ":8080", + Handler: loggingMiddleware(mux), + } + + return server.ListenAndServe() +} + +func serveFile(w http.ResponseWriter, r *http.Request) { + fsPath := strings.TrimRight(r.URL.Path, "/") + + if fsPath == "" { + fsPath = "index" + } + + if ext := filepath.Ext(fsPath); ext == "" { + fsPath += ".html" + } + + http.ServeFileFS(w, r, public.FS, fsPath) +} diff --git a/content/posts.md b/content/posts.md index 011720b..be1f70e 100644 --- a/content/posts.md +++ b/content/posts.md @@ -3,5 +3,5 @@ title: posts nav: /posts --- {{range .Collections.posts}} -- [{{.Meta.title}}]({{.Path}}) +- [{{.Meta.title}}](/{{.Path}}) {{end}}
\ No newline at end of file diff --git a/content/posts/001.md b/content/posts/2024-08-25-01.md index 8aee591..8aee591 100644 --- a/content/posts/001.md +++ b/content/posts/2024-08-25-01.md diff --git a/content/posts/002.md b/content/posts/2024-08-25-02.md index 8cb8b19..8cb8b19 100644 --- a/content/posts/002.md +++ b/content/posts/2024-08-25-02.md diff --git a/content/posts/003.md b/content/posts/2024-08-25-03.md index 1536c93..1536c93 100644 --- a/content/posts/003.md +++ b/content/posts/2024-08-25-03.md diff --git a/content/posts/004.md b/content/posts/2024-08-26-01.md index dc25d77..dc25d77 100644 --- a/content/posts/004.md +++ b/content/posts/2024-08-26-01.md diff --git a/content/posts/005.md b/content/posts/2024-08-31-01.md index e2b3dfc..e2b3dfc 100644 --- a/content/posts/005.md +++ b/content/posts/2024-08-31-01.md diff --git a/content/posts/006.md b/content/posts/2024-09-04-01.md index d6fad1f..d6fad1f 100644 --- a/content/posts/006.md +++ b/content/posts/2024-09-04-01.md diff --git a/content/posts/007.md b/content/posts/2024-09-08-01.md index 39d1f70..39d1f70 100644 --- a/content/posts/007.md +++ b/content/posts/2024-09-08-01.md @@ -1,6 +1,6 @@ module git.sr.ht/~a73x/home -go 1.22.5 +go 1.23 require ( github.com/adrg/frontmatter v0.2.0 diff --git a/markdown/markdown.go b/markdown/markdown.go index 1d5dbf0..83f94b7 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -66,12 +66,7 @@ func parseMarkdownFile(basePath, contentPath string) (Content, error) { return Content{}, fmt.Errorf("failed to parse frontmatter: %v", err) } - path := strings.Replace(contentPath, ".md", "", 1) - if path == "index" { - path = "" - } - - path = "/" + path + path := strings.Replace(contentPath, ".md", ".html", 1) mc := Content{ Body: string(rest), diff --git a/pages/pages.go b/pages/pages.go index 7eaf903..8d789b3 100644 --- a/pages/pages.go +++ b/pages/pages.go @@ -115,13 +115,8 @@ func Collect(contentPath string) ([]Page, error) { return nil, fmt.Errorf("failed to build site: %v", err) } - path := content.Path - if path == "index" { - path = "" - } - pages = append(pages, Page{ - Path: path, + Path: content.Path, Content: page, }) } diff --git a/public/embed.go b/public/embed.go new file mode 100644 index 0000000..43a129b --- /dev/null +++ b/public/embed.go @@ -0,0 +1,6 @@ +package public + +import "embed" + +//go:embed * +var FS embed.FS diff --git a/public/ethos.html b/public/ethos.html new file mode 100644 index 0000000..f142062 --- /dev/null +++ b/public/ethos.html @@ -0,0 +1,91 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>ethos</h1> +<p>Yes, this site has an ethos.</p> + +<p>This site was born out of the belief that the current state of the internet is wrong. +As things currently stand, the internet is predominantly controlled by big tech, but it is a decentralised system, a series of interconnected networked devices that communicate over a standardised protocol.</p> + +<p>Users flock to singular points on the modern internet, controlled and governed by megacorps. These points are where you go to give up your privacy and digital rights, in exchange for a service.</p> + +<p>Social media platforms offer a pretense of community. Google pretends it can help you navigate the vast web, serving you results relevant to what you’re looking for, funnelling you to whoever is willing to pay the most to appear at the top of that list, and tracking you for possibilities of targeted advertising.</p> + +<p>You’re digital data is farmed and harvested for surveillance capitalism because they couldn’t work out how else to squeeze out more money.</p> + +<p>So this is my pièce de résistance. It represents my part of the internet, a small space subsumed by big tech’s centralised web.</p> + +<p>I believe that a small amount of tech literacy would go a long way. We don’t need to commune on centralised platforms; rather, we can set up our own and own them.</p> + +<p>This site runs on a single-node Kubernetes cluster hosted on an old Lenovo M910Q. It runs an <code>i5-6500T</code> (4 core) and has 8GB of RAM. +I bought it in 2021 for £189.99.</p> + +<p>If I check this online against AWS, an equivalent machine might be a <code>c6g.xlarge</code> (4vcpu 8GiB) with an EC2 Instance Savings Plan; for three years, it would have cost $1597.82 upfront. I’ve ignored the cost of electricity. I have some breathing room, but this little device has already saved me considerable money. I’ve used it to host quite a few services over the years such as <a href="https://tt-rss.org/">Tiny Tiny RSS</a>,<a href="https://miniflux.app/">Miniflux</a>, <a href="https://ergo.chat/">Ergochat</a>, <a href="https://github.com/dani-garcia/vaultwarden">vaultwarden</a>, <a href="https://immich.app/">Immich</a>, <a href="https://nextcloud.com/">NextCloud</a>, <a href="https://matrix.org/">Matrix</a>, <a href="https://git.zx2c4.com/cgit/">cigt</a>, etc; the list goes on.</p> + +<p>Currently, it only runs Miniflux, ergochat, cgit, and this site.</p> + +<p>Someone somewhere might be angrily shaking their fist, declaring that this is no way to run a “production” environment. +I have no data backup or failover nodes — and I’d agree. +Miniflux exports its content as an XML, IRC is ephemeral, and this site is git versioned on sr.ht.</p> + +<p>I don’t need my RSS reader to be highly available; if it’s broken, I’ll fix it.</p> + +<p>For things to be cost-practical, I would have to sink down to a <code>t2.micro</code> (1 core 1GiB), which costs $152 for 3 years, but then I’d only have 1 core and 1GiB.</p> + +<p>And whilst I tend to my flock of a single node server, I learn more about the system. Kubernetes, firewalls, DNS, DHCP, etc., a responsibility delegated away or hidden behind interfaces when using cloud services.</p> + +<p>Using your equipment is cost-effective, leaving you room to trial services you’re interested in. You can also simply remove them when they’re no longer required.</p> + +<p>As for the site itself I’ve chosen a minimal design, keeping javascript to a minimum and serving only static content. +I want the site to work consistently across all browsers, be readable on all devices, and continue to work into the future.</p> + +<p>The aim here is to build a place to share the things I’ve learnt. This space serves as an attempt to counter the low-effort writings found on Medium and write more human content as we see more AI-generated drivel. Over time, I intend to curate my posts, merging, filtering, and ultimately removing irrelevant ones.</p> + +<p>I work as a cloud developer, writing primarily in go (this might explain my chosen minimal design choice), so the content I write through that lens.</p> + +<p>I intend to provide information about how one can go about setting up a site like this and administering it yourself. +As a go developer, I’ll also include things I’ve learned and am learning.</p> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..ecaf361 --- /dev/null +++ b/public/index.html @@ -0,0 +1,97 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>home</h1> +<p>Welcome!</p> + +<h1 id="me">me</h1> + +<ul> +<li>backend cloud software engineer</li> +<li>lang: go</li> +<li>infra: kubernetes</li> +</ul> + +<h1 id="tidbits">tidbits</h1> + +<h2 id="go">#go</h2> + +<ul> +<li>layout packages by what they do, not by their abstract type</li> +<li>use channels sparingly: write synchronous methods and allow the caller to make it async</li> +<li><code>append</code> modifies the underlying slice, you’ll only make this mistake once</li> +<li>define interfaces where you use them</li> +<li><code>make([]int, 5)</code> has a length and capacity of 5. <code>([]int, 0,5)</code> has a length of 0 and capacity of 5.<br> +<code>append()</code> will only do what you want with the latter</li> +<li>don’t use <code>init()</code></li> +<li>TFBO (test, fix, benchmark, optimise)</li> +<li>more CPU != more performance<br> +more CPU == more contention</li> +</ul> + +<h2 id="git">#git</h2> + +<ul> +<li><code>git reflog</code></li> +<li><code>git commit --fixup=<COMMITISH></code><br> +<code>git rebase origin/main --autosquash</code></li> +</ul> + +<h1 id="resources">resources</h1> + +<ul> +<li><a href="https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/runtime/proc.go"><code>proc.go</code></a></li> +<li><a href="https://github.com/golang/go/issues/67120">proposal: runtime/metrics: define a recommended set of metrics</a></li> +</ul> + +<h1 id="books">books</h1> + +<ul> +<li><a href="https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063">Designing Data Intensive Applications</a></li> +<li><a href="https://www.oreilly.com/library/view/database-internals/9781492040330">Database Internals</a></li> +<li><a href="https://www.oreilly.com/library/view/efficient-go/9781098105709">Efficient Go</a></li> +</ul> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts.html b/public/posts.html new file mode 100644 index 0000000..d6680fb --- /dev/null +++ b/public/posts.html @@ -0,0 +1,63 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>posts</h1> +<ul> +<li><p><a href="/posts/2024-08-25-01.html">Go Benchmarking</a></p></li> + +<li><p><a href="/posts/2024-08-25-02.html">Go Project Layouts</a></p></li> + +<li><p><a href="/posts/2024-08-25-03.html">Levels of Optimisation</a></p></li> + +<li><p><a href="/posts/2024-08-26-01.html">Writing HTTP Handlers</a></p></li> + +<li><p><a href="/posts/2024-08-31-01.html">Go’s unique pkg</a></p></li> + +<li><p><a href="/posts/2024-09-04-01.html">Kubernetes Intro</a></p></li> + +<li><p><a href="/posts/2024-09-08-01.html">Building a Static Site with Hugo and Docker</a></p></li> +</ul> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-08-25-01.html b/public/posts/2024-08-25-01.html new file mode 100644 index 0000000..4bd4371 --- /dev/null +++ b/public/posts/2024-08-25-01.html @@ -0,0 +1,117 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Go Benchmarking</h1> +<nav> + +<ul> +<li><a href="#cpuprofile">cpuprofile</a></li> + +<li><a href="#memprofile">memprofile</a></li> + +<li><a href="#pprof">pprof</a> +<ul> +<li><a href="#views">views</a></li> +</ul></li> +</ul> + +</nav> +<p>The benchmark cycle:</p> + +<ol> +<li>write a benchmark</li> +<li>run a benchmark</li> +<li>get a profile</li> +<li>optimise</li> +<li>run your tests</li> +<li>goto 2.</li> +</ol> + +<h1 id="cpuprofile">cpuprofile</h1> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">go <span class="nb">test</span> -test<span class="o">=</span>XXX -bench <regex> -cpuprofile <file> +</span></span></code></pre> +<h1 id="memprofile">memprofile</h1> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">go <span class="nb">test</span> -test<span class="o">=</span>XXX -bench <regex> -memprofile <file> -benchmem +</span></span></code></pre> +<h1 id="pprof">pprof</h1> + +<p><a href="https://github.com/google/pprof/blob/main/doc/README.md">pprof usage</a></p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">go pprof -http<span class="o">=</span>:8080 profile.pb.gz +</span></span></code></pre> +<p>will show a web UI for analysing the profile.</p> + +<h2 id="views">views</h2> + +<ul> +<li>flame graph: <code>localhost:8080/ui/flamegraph</code> + +<ul> +<li>shows percentage breakdown of how much resource each “call” made.</li> +<li>clicking a box will make it “100%” allowing for deep diving</li> +<li>right click “show source code” to view</li> +</ul></li> +<li>top: <code>localhost:8080/ui/top</code> + +<ul> +<li>shows top functions + +<ul> +<li><code>flat</code>: profile samples in this function</li> +<li><code>cum</code>: (cumulative) profile samples in this function and its callees</li> +</ul></li> +</ul></li> +<li>source: <code>localhost:8080/ui/source</code> + +<ul> +<li>each source line is annotated with the time spent in that source line + +<ul> +<li>the first number does not count time spent in functions called from the source line</li> +<li>the second number does</li> +</ul></li> +</ul></li> +</ul> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-08-25-02.html b/public/posts/2024-08-25-02.html new file mode 100644 index 0000000..81e8f77 --- /dev/null +++ b/public/posts/2024-08-25-02.html @@ -0,0 +1,153 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Go Project Layouts</h1> +<p>Do you lay awake at night and consider how to optimally layout your Go project? +No…? what about recommending Windows to a friend or colleague?? +yeah me either…</p> + +<p>I’ve seen a lot online that shows what I can only describe as endgame enterprise Go project layouts. These layouts are heavily folder-based and only make sense when your project has grown large enough to warrant the verbosity these folders provide. My only problem is that people often try to start there.</p> + +<p>A lot of design tells you to think about your project in layers.</p> + +<ul> +<li>api</li> +<li>domain</li> +<li>storage</li> +</ul> + +<p>If you read <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Architecture</a> +you get told the layers should be,</p> + +<ol> +<li>entities</li> +<li>use cases</li> +<li>interface adapters</li> +<li>frameworks and drivers.</li> +</ol> + +<p>and that all dependencies should point in (yeah I know, I didn’t do a circle so “in” doesn’t make sense but I’m sure you can follow).</p> + +<p>This is an excellent idea; separation of concerns is good.</p> + +<p>So you make your folders.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">. +</span></span><span class="line"><span class="cl">├── drivers +</span></span><span class="line"><span class="cl">├── entities +</span></span><span class="line"><span class="cl">├── interfaces +</span></span><span class="line"><span class="cl">└── usecases +</span></span></code></pre> +<p>aaand this is an awful idea. I don’t even want to go further into this hypothetical layout because it hurts too much.</p> + +<p>Find me a project that actually creates these folders, and I’ll find you the medium article titled “Clean Code in Go” which spawned it.</p> + +<p>The important parts of clean code, are the ideas presented, and how you apply them to a package orientated language. Creating a folder to represent each layer, doesn’t really carry much weight here.</p> + +<p>As a package orientated language, we want to think and reason about things in terms of packages. Yes there will be a point where you may want to group your packages into some group, but that is mostly ceremonial. +Go doesn’t care if you’re accessing <code>domain/bar</code> or <code>domain/foo/bar</code>. Either will simply be accessed as <code>bar</code>. This means that what matters what’s in that package <code>bar</code>. Since everything will be read as <code>bar.Thing</code> i.e <code>import bytes</code> and <code>bytes.Buffer</code>.</p> + +<p>So, the package name sets context and expectations. If I grab the <code>json</code> package, I expect that package to do things around <code>json</code>. I’d feel a bit confused if I was able to configure an smtp server.</p> + +<p>If you cannot come up with a package name that’s a meaningful prefix for the package’s contents, the package abstraction boundary may be wrong</p> + +<p>“but you’ve still not provided a good example?” +well +yes</p> + +<p>I think the project should grow organically to some degree. What we want to do is write code, and refactoring in Go is fairly cheap.</p> + +<p>Start with a <code>main.go</code> and make a <code>Run</code> function or some equivalent which it calls.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Run</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="c1">// actual important stuff here +</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">Run</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre> +<p>This allows you to test your run function in a unit test, and keeps your <code>main</code> func minimal.</p> + +<p>As your project grows, you can keep it flat inside the root directory</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">├── api.go +</span></span><span class="line"><span class="cl">├── go.mod +</span></span><span class="line"><span class="cl">├── go.sum +</span></span><span class="line"><span class="cl">├── main.go +</span></span><span class="line"><span class="cl">├── rss.go +</span></span><span class="line"><span class="cl">└── sqlite.go +</span></span></code></pre> +<p>Even just glancing at that, you can guess that this might be an RSS server, that uses sqlite to back it.</p> + +<p>Who knows what</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">├── drivers +</span></span><span class="line"><span class="cl">├── entities +</span></span><span class="line"><span class="cl">├── interfaces +</span></span><span class="line"><span class="cl">└── usecases +</span></span></code></pre> +<p>does.</p> + +<p>As things evolve you might want to put them in <code>internal</code> to hide them from being imported by other packages, or <code>cmd</code> as you develop multiple binaries. Placing things in <code>internal</code> means you’re free to mess around with it, without breaking any public contracts other users rely on. +I can’t be bothered rewriting my example, so here’s a random one I found online; it’s probably all right. +<a href="https://go.dev/doc/modules/layout#server-project">Server Project</a></p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">project-root-directory/ +</span></span><span class="line"><span class="cl"> go.mod +</span></span><span class="line"><span class="cl"> internal/ +</span></span><span class="line"><span class="cl"> auth/ +</span></span><span class="line"><span class="cl"> ... +</span></span><span class="line"><span class="cl"> metrics/ +</span></span><span class="line"><span class="cl"> ... +</span></span><span class="line"><span class="cl"> model/ +</span></span><span class="line"><span class="cl"> ... +</span></span><span class="line"><span class="cl"> cmd/ +</span></span><span class="line"><span class="cl"> api-server/ +</span></span><span class="line"><span class="cl"> main.go +</span></span><span class="line"><span class="cl"> metrics-analyzer/ +</span></span><span class="line"><span class="cl"> main.go +</span></span><span class="line"><span class="cl"> ... +</span></span><span class="line"><span class="cl"> ... the project<span class="err">'</span>s other directories with non-Go code +</span></span></code></pre> +<p>My vague summary is that clean code gives you a north star to follow, an idea of how you want to separate and reason about the packages you create. You don’t need to create the entities of abstraction that are also presented. Think about what things do or relate to and create packages for them. You should allow your project to grow organically but don’t expect architecture to appear without following a north star.</p> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-08-25-03.html b/public/posts/2024-08-25-03.html new file mode 100644 index 0000000..07e48a0 --- /dev/null +++ b/public/posts/2024-08-25-03.html @@ -0,0 +1,78 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Levels of Optimisation</h1> +<nav> + +<ul> +<li><a href="#benchmark-optimisation">Benchmark Optimisation</a></li> + +<li><a href="#profile-guided-optimisation">Profile guided optimisation</a></li> + +<li><a href="#runtime-optimisation">Runtime optimisation</a></li> +</ul> + +</nav> +<p>This probably isn’t strictly true, but it makes sense to me. +We’ve got three levels of “optimisation” (assuming your actual design doesn’t suck and needs optimising).</p> + +<h1 id="benchmark-optimisation">Benchmark Optimisation</h1> + +<p>To begin with, we have benchmark optimisation; you create a benchmark locally, dump a profile of it, and optimise it. Then, you run your tests because the most optimal solution is “return nil” and make sure you didn’t break your tests. +This is the first and easiest optimisation because it only requires a function, nothing else, and can be done in isolation. You don’t need a working “application” here, just the function you’re trying to benchmark. There are different types of benchmarks, micro, macro, etc., but I’m leaving them out of scope for this conversation. Go read <a href="https://learning.oreilly.com/library/view/efficient-go/9781098105709/">Efficient Go</a>.</p> + +<h1 id="profile-guided-optimisation">Profile guided optimisation</h1> + +<p>This is a mild step up from benchmark optimisation only because you need a live server load from which you use to pull a profile, but it is probably the most hands-off step. You import the <code>net/http/pprof</code> package into your service, call the <code>debug/profile?seconds=30</code> to get a profile, and compile your binary with <code>go build -pgo=profile.pgo</code>. The compiler will make optimisations for you, and even if your profile is garbage, it shouldn’t cause any regressions.</p> + +<p>You probably want to get a few profiles and merge them using <code>go tool pprof -proto a.out b.out > merged</code>. This will help provide optimisations that are more relevant to your overall system; instead of just a single 30s slice. +Also, if you have long-running calls that are chained together, a 30-second snapshot might not be enough, so try a sample with a longer window.</p> + +<h1 id="runtime-optimisation">Runtime optimisation</h1> + +<p>This is where you expose <code>/runtime/metrics</code> and monitor them continuously. There’s a list of metrics that you might be interested in, a recommended set of metrics, and generally, you are looking to optimise your interactions with the go runtime. There are a few stats here: goroutine counts, goroutines waiting to run, heap size, how often garbage collection runs, how long garbage collection takes, etc. All useful information to use when optimising - when garbage collection is running, your program ain’t. It’s also useful for finding memory leaks; it becomes pretty obvious you are leaking goroutines when you graph the count and just watch it go up and never down. +It’s also just lowkey fun to look at the exposed data and understand what your system is doing.</p> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-08-26-01.html b/public/posts/2024-08-26-01.html new file mode 100644 index 0000000..acf51a6 --- /dev/null +++ b/public/posts/2024-08-26-01.html @@ -0,0 +1,280 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Writing HTTP Handlers</h1> +<p>I’m sharing how I write handlers in Go.</p> + +<p>I write them like this for reasons that are probably fairly contextual. I’ve written a few applications and had to swap REST libraries or even swapped REST for GRPC, so things that make that easier speak to me a great deal.</p> + +<p>I’ve used <code>ints</code> instead of the <code>http.StatusXXXX</code> and omitted <code>JSON</code> tags in an attempt to try save up screen space.</p> + +<p>To begin with, you might have something like this:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"log"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"net/http"</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">handler</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">"Hello, World!"</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="nx">handler</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="kc">nil</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span></code></pre> +<p>Then you might get told off because you’ve just registered routes with the default mux, which isn’t very testable.</p> + +<p>So you tweak it a little bit.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"log"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"net/http"</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">handler</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">"Hello, World!"</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">newMux</span><span class="p">()</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">ServeMux</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span> <span class="o">:=</span> <span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="nx">handler</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">mux</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Run</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span> <span class="o">:=</span> <span class="nf">newMux</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="nx">mux</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">Run</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre> +<p><code>newMux()</code> gives you a <code>mux</code> to use when testing.</p> + +<p><code>Run</code> keeps <code>main</code> nice and clean, so you can just return errors as needed instead of going <code>log.Fatal</code> and just generally being messy.</p> + +<p>But now you need to do something real, you want to store and fetch data.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="s">"encoding/json"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"log"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"net/http"</span> +</span></span><span class="line"><span class="cl"> <span class="s">"strconv"</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">NewMux</span><span class="p">()</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">ServeMux</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span> <span class="o">:=</span> <span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="nx">s</span> <span class="o">:=</span> <span class="nx">Server</span><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">data</span><span class="p">:</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="nx">Content</span><span class="p">),</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nx">s</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="nx">mux</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">mux</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Run</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span> <span class="o">:=</span> <span class="nf">NewMux</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="nx">mux</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Server</span> <span class="kd">struct</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">data</span> <span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="nx">Content</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">Server</span><span class="p">)</span> <span class="nf">Register</span><span class="p">(</span><span class="nx">mux</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">ServeMux</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">"GET /{id}"</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">Get</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">"POST /"</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">Post</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">Server</span><span class="p">)</span> <span class="nf">Get</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">idStr</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nf">PathValue</span><span class="p">(</span><span class="s">"id"</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">id</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">strconv</span><span class="p">.</span><span class="nf">Atoi</span><span class="p">(</span><span class="nx">idStr</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">"failed to parse id: %v"</span><span class="p">,</span> <span class="nx">err</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">data</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">s</span><span class="p">.</span><span class="nx">data</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="mi">404</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">"not found"</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">json</span><span class="p">.</span><span class="nf">NewEncoder</span><span class="p">(</span><span class="nx">w</span><span class="p">).</span><span class="nf">Encode</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">ContentPostReq</span> <span class="kd">struct</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Foo</span> <span class="kt">string</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">Server</span><span class="p">)</span> <span class="nf">Post</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">req</span> <span class="o">:=</span> <span class="nx">ContentPostReq</span><span class="p">{}</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">).</span><span class="nf">Decode</span><span class="p">(</span><span class="o">&</span><span class="nx">req</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">"failed to parse request: %v"</span><span class="p">,</span> <span class="nx">err</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">id</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">content</span> <span class="o">:=</span> <span class="nx">Content</span><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">ID</span><span class="p">:</span> <span class="nx">id</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Foo</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">Foo</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">s</span><span class="p">.</span><span class="nx">data</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="p">=</span> <span class="nx">content</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">json</span><span class="p">.</span><span class="nf">NewEncoder</span><span class="p">(</span><span class="nx">w</span><span class="p">).</span><span class="nf">Encode</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Content</span> <span class="kd">struct</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">ID</span> <span class="kt">int</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Foo</span> <span class="kt">string</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">Run</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre><pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">❯ curl -X POST localhost:8080 --header <span class="s2">"Content-Type: application/json"</span> -d <span class="s1">'{"foo":"bar"}'</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">"ID"</span>:0,<span class="s2">"Foo"</span>:<span class="s2">"bar"</span><span class="o">}</span> +</span></span><span class="line"><span class="cl">❯ curl -X GET localhost:8080/0 +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">"ID"</span>:0,<span class="s2">"Foo"</span>:<span class="s2">"bar"</span><span class="o">}</span> +</span></span></code></pre> +<p>Erm, well, okay. Quite a bit has changed here, but I’m sure you can read it. We now save and fetch very, very safely from a map and return the response as <code>JSON</code>. I’ve done some things for brevity because I want to get to the main point.</p> + +<p>This API is inconsistent. It sometimes returns <code>JSON</code>, and the others return strings. Overall, it’s just a mess.</p> + +<p>So let’s try to standardise things. +First, let’s design some form of REST spec.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">JSONResp</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">]</span> <span class="kd">struct</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Resources</span> <span class="p">[]</span><span class="nx">T</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Errs</span> <span class="p">[]</span><span class="nx">ErrorResp</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">ErrorResp</span> <span class="kd">struct</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Status</span> <span class="kt">int</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Msg</span> <span class="kt">string</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre> +<p>We want to be able to support fetching multiple resources at once, if we can only fetch some resources, let’s return them under <code>resources</code> and show the errors under <code>errs</code></p> + +<p>Now, add some helpful functions to handle things.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">func</span> <span class="nx">Post</span><span class="p">[</span><span class="nx">In</span> <span class="nx">any</span><span class="p">,</span> <span class="nx">Out</span> <span class="nx">any</span><span class="p">](</span><span class="nx">successCode</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">fn</span> <span class="kd">func</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">In</span><span class="p">)</span> <span class="p">([]</span><span class="nx">Out</span><span class="p">,</span> <span class="p">[]</span><span class="nx">ErrorResp</span><span class="p">))</span> <span class="kd">func</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="kd">func</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">v</span> <span class="nx">In</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">).</span><span class="nf">Decode</span><span class="p">(</span><span class="o">&</span><span class="nx">v</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">writeJSONResp</span><span class="p">[</span><span class="nx">Out</span><span class="p">](</span><span class="nx">w</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="p">[]</span><span class="nx">ErrorResp</span><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Status</span><span class="p">:</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Msg</span><span class="p">:</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">"failed to parse request: %v"</span><span class="p">,</span> <span class="nx">err</span><span class="p">),</span> +</span></span><span class="line"><span class="cl"> <span class="p">},</span> +</span></span><span class="line"><span class="cl"> <span class="p">})</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nx">res</span><span class="p">,</span> <span class="nx">errs</span> <span class="o">:=</span> <span class="nf">fn</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">Context</span><span class="p">(),</span> <span class="nx">v</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nf">writeJSONResp</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">successCode</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">errs</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nx">writeJSONResp</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">successCode</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">res</span> <span class="p">[]</span><span class="nx">T</span><span class="p">,</span> <span class="nx">errs</span> <span class="p">[]</span><span class="nx">ErrorResp</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">body</span> <span class="o">:=</span> <span class="nx">JSONResp</span><span class="p">[</span><span class="nx">T</span><span class="p">]{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Resources</span><span class="p">:</span> <span class="nx">res</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Errs</span><span class="p">:</span> <span class="nx">errs</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nx">status</span> <span class="o">:=</span> <span class="nx">successCode</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">e</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">errs</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">e</span><span class="p">.</span><span class="nx">Status</span> <span class="p">></span> <span class="nx">status</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">status</span> <span class="p">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">Status</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">status</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">json</span><span class="p">.</span><span class="nf">NewEncoder</span><span class="p">(</span><span class="nx">w</span><span class="p">).</span><span class="nf">Encode</span><span class="p">(</span><span class="nx">body</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre> +<p>And we’ve standardised all <code>POST</code> requests!</p> + +<p>This function can be used by all <code>POST</code> requests, ensuring they adhere to the spec. It also removes the repetitive code around marshalling and unmarshalling to <code>JSON</code> and handles errors in a consistent manner. +The handler functions accept a <code>context</code> param and their expected struct input.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">Server</span><span class="p">)</span> <span class="nf">Register</span><span class="p">(</span><span class="nx">mux</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">ServeMux</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"><span class="o">...</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">"POST /"</span><span class="p">,</span> <span class="nf">Post</span><span class="p">(</span><span class="mi">201</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">Post</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">Server</span><span class="p">)</span> <span class="nf">Post</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">req</span> <span class="nx">ContentPostReq</span><span class="p">)</span> <span class="p">([]</span><span class="nx">Content</span><span class="p">,</span> <span class="p">[]</span><span class="nx">ErrorResp</span><span class="p">)</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">id</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">content</span> <span class="o">:=</span> <span class="nx">Content</span><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nx">ID</span><span class="p">:</span> <span class="nx">id</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nx">Foo</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">Foo</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">s</span><span class="p">.</span><span class="nx">data</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="p">=</span> <span class="nx">content</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">[]</span><span class="nx">Content</span><span class="p">{</span><span class="nx">content</span><span class="p">},</span> <span class="kc">nil</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre> +<p>As you can see, the post function is fairly cleaner now.</p> + +<p>You can extend this to all the other request types. If you have query or path parameters, you could either pass in the request, write a custom struct tag parser, or find someone else who has already done it: <a href="https://github.com/gorilla/schema">https://github.com/gorilla/schema</a>.</p> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-08-31-01.html b/public/posts/2024-08-31-01.html new file mode 100644 index 0000000..68d8563 --- /dev/null +++ b/public/posts/2024-08-31-01.html @@ -0,0 +1,96 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Go's unique pkg</h1> +<p><a href="https://pkg.go.dev/unique">https://pkg.go.dev/unique</a></p> + +<blockquote> +<p>The unique package provides facilities for canonicalising (“interning”) comparable values.<sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup></p> +</blockquote> + +<p>oh yeah, thats obvious I fully understand what this package does now, great read, tune in for the next post.</p> + +<p>Interning, is the re-use of an object of equal value instead of creating a new one. I’m pretending I knew this but really I’ve just reworded <a href="https://en.wikipedia.org/wiki/Interning_(computer_science)">Interning</a>.</p> + +<p>So lets try again.</p> + +<p>If you’re parsing out a csv of bank transactions, its very likely a lot of names will be repeated. Instead of allocating memory for each string representing a merchant, you can simply reuse the the same string.</p> + +<p>So the dumbed down version might look like</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">internedStrings</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Map</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Intern</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">val</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">internedStrings</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="nx">s</span><span class="p">);</span> <span class="nx">ok</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">val</span><span class="p">.(</span><span class="kt">string</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">internedStrings</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">s</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">s</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre> +<p>With a small demo here <a href="https://go.dev/play/p/piSYjCHIcLr">https://go.dev/play/p/piSYjCHIcLr</a></p> + +<p>This implementation is fairly naive, it can only grow and it only works with strings, so naturally go’s implementation is a better.</p> + +<p>It’s also worth noting, that since strings are a pointer under the hood</p> + +<blockquote> +<p>When comparing two strings, if the pointers are not equal, then we must compare their contents to determine equality. But if we know that two strings are canonicalized, then it <em>is</em> sufficient to just check their pointers.<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">2</a></sup></p> +</blockquote> + +<p>So to recap, goes <code>unique</code> package provides a way to reuse objects instead of creating new ones, if we consider the objects of equal value.</p> + +<div class="footnotes"> + +<hr> + +<ol> +<li id="fn:1"><a href="https://pkg.go.dev/unique">https://pkg.go.dev/unique</a> <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li> + +<li id="fn:2"><a href="https://go.dev/blog/unique">https://go.dev/blog/unique</a> <a class="footnote-return" href="#fnref:2"><sup>[return]</sup></a></li> +</ol> + +</div> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-09-04-01.html b/public/posts/2024-09-04-01.html new file mode 100644 index 0000000..2e1abae --- /dev/null +++ b/public/posts/2024-09-04-01.html @@ -0,0 +1,165 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Kubernetes Intro</h1> +<nav> + +<ul> +<li><a href="#pods">Pods</a> +<ul> +<li><a href="#why-multiple-containers-in-a-pod">Why Multiple Containers in a Pod?</a></li> + +<li><a href="#pod-placement">Pod Placement</a></li> + +<li><a href="#scaling">Scaling</a></li> +</ul></li> + +<li><a href="#services">Services</a></li> + +<li><a href="#volumes">Volumes</a> +<ul> +<li><a href="#key-components-of-kubernetes-storage">Key Components of Kubernetes Storage</a></li> + +<li><a href="#how-it-works-together">How It Works Together</a></li> +</ul></li> +</ul> + +</nav> +<p>My crash course to Kubernetes. +You’re welcome.</p> + +<h1 id="pods">Pods</h1> + +<p>In Kubernetes, if you wish to deploy an application the most basic component you would use to achieve that, is a pod. A Pod represents the smallest deployable unit in Kubernetes, encapsulating one or more containers that need to work together. While containers run the actual application code, Pods provide the environment necessary for these containers to operate, including shared networking and storage.</p> + +<p>A Pod usually represents an ephemeral single instance of a running process or application. For example, a Pod might contain just one container running a web server. In more complex scenarios, a Pod could contain multiple containers that work closely together, such as a web server container and a logging agent container.</p> + +<p>Additionally we consider a Pod as ephemeral because when a Pod dies, it can’t be brought back to life—Kubernetes would create a new instance instead. This behaviour reinforces the idea that Pods are disposable and should be designed to handle failures gracefully.</p> + +<p>When you use Docker, you might build a image with <code>docker build . -t foo:bar</code> and run a container with <code>docker run foo:bar</code>. In Kubernetes, to run that same container, you place it inside a Pod, since Kubernetes manages containers through Pods.</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l"><my pod name here> </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">containers</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l"><my container name here> </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">foo:bar</span><span class="w"> +</span></span></span></code></pre> +<p>In this YAML manifest, we define the creation of a Pod using the <code>v1</code> API version. The <code>metadata</code> field is used to provide a name for identifying the Pod within the Kubernetes cluster. Inside the <code>spec</code>, the <code>containers</code> section lists all the containers that will run within that Pod.</p> + +<p>Each container has its own name and image, similar to the <code>--name</code> and image parameters used in the <code>docker run</code> command. However, in Kubernetes, these containers are encapsulated within a Pod, ensuring that they are always co-scheduled, co-located, and share the same execution context.</p> + +<p>As a result, containers within a Pod should be tightly coupled, meaning they should work closely together, typically as parts of a single application or service. This design ensures that the containers can efficiently share resources like networking and storage while providing a consistent runtime environment.</p> + +<h2 id="why-multiple-containers-in-a-pod">Why Multiple Containers in a Pod?</h2> + +<p>Sometimes, you might need multiple containers within a single Pod. Containers in a Pod share the same network namespace, meaning they can communicate with each other via <code>localhost</code>. They also share storage volumes, which can be mounted at different paths within each container. This setup is particularly useful for patterns like sidecars, where an additional container enhances or augments the functionality of the main application without modifying it.</p> + +<p>For example, imagine your application writes logs to <code>/data/logs</code>. You could add a second container in the Pod running <code>fluent-bit</code>, a tool that reads in files and sends them to a user defined destination. <code>fluent-bit</code> reads these logs and forwards them to an external log management service, without changing the original application code. This separation also ensures that if the logging container fails, it won’t affect the main application’s operation.</p> + +<p>When deciding what containers go in a pod, consider how they’re coupled. Questions like “how should these scale” might be helpful. If you have two containers, one for a web server and one for a database, as your web server traffic goes up, it doesn’t really make sense to start creating more instances of the database. So you would put your web server in one pod and your database in another, allowing Kubernetes to scale them independently. On the other hand a container which shares a volume with the web server would need to scale on a 1:1 basis, so they go in the same pod.</p> + +<h2 id="pod-placement">Pod Placement</h2> + +<p>When a Pod is created, Kubernetes assigns it to a Node—a physical or virtual machine in the cluster—using a component called the scheduler. The scheduler considers factors like resource availability, node labels, and any custom scheduling rules you’ve defined (such as affinity or anti-affinity) when making this decision. Affinity means the pods go together, anti-affinity means keep them on separate nodes. Other rules can be used to direct Pods to specific Nodes, such as ensuring that GPU-based Pods run only on GPU-enabled Nodes.</p> + +<h2 id="scaling">Scaling</h2> + +<p>In practise, you won’t be managing pods manually. If a pod crashes, manual intervention would be required to start a new Pod and clean up the old one. Fortunately, Kubernetes provides controllers to manage Pods for you: Deployments, StatefulSets, DaemonSets, and Jobs.</p> + +<ul> +<li>Deployments are best used for stateless workloads, where Pods don’t need to persist state across restarts.</li> +<li>StatefulSets are ideal for when you need a Pod to be redeployed with the same storage volume to maintain data continuity.</li> +<li>DaemonSets ensure that a copy of a Pod runs on each Node, useful for tasks like Node level monitoring or logging.</li> +<li>Jobs are used for 1 off tasks that have an end goal, as opposed to a deployment which runs forever. Example jobs might be running a data migration script. A CronJob is a job that runs on a schedule, like running a weekly backup.</li> +</ul> + +<p>Deployments and StatefulSets also support scaling mechanisms, allowing you to increase or decrease the number of Pods to handle varying levels of traffic.</p> + +<h1 id="services">Services</h1> + +<p>As your application scales and you handle multiple Pods, you need a way to keep track of them for access. Since Pods can change names and IP addresses when they are recreated, you need a stable way to route traffic to them. This is where Kubernetes services come into play.</p> + +<p>Services provide an abstraction layer that allows you to access a set of Pods without needing to track their individual IP addresses. You define a selector in the Service configuration and traffic reaching the Service is routed to one of the Pods matching the selector.</p> + +<p>There are four types of services in Kubernetes: <code>ClusterIP</code>, <code>NodePort</code>, <code>LoadBalancer</code>, and <code>ExternalName</code>.</p> + +<ul> +<li><p>ClusterIP is the default service type. It provides an internal IP address accessible only within the cluster. Other Pods can use this IP or the DNS name of the service to connect.</p></li> + +<li><p>NodePort exposes a specific port on each Node. This allows external traffic to reach the service via the Node’s IP address and designated port. NodePort services also have a ClusterIP, so they are accessible within the cluster as well.</p></li> + +<li><p>LoadBalancer integrates with external load balancers (typically provided by cloud providers) to expose a service to the internet. Kubernetes itself doesn’t have a load balancer component so external infrastructure is required.</p></li> + +<li><p>ExternalName maps a Service to an external DNS name. This can be useful for migrating services into a cluster or for redirecting traffic to an external resource until the migration is complete.</p></li> +</ul> + +<h1 id="volumes">Volumes</h1> + +<p>Broadly speaking, Kubernetes offers two types of storage: ephemeral and persistent volumes.</p> + +<ul> +<li>Ephemeral volumes have the same lifespan as the Pod they are attached to, meaning they are deleted when the Pod is deleted. These are typically used for temporary data, such as caching, that doesn’t need to be preserved once the Pod’s lifecycle ends.</li> +<li>Persistent volumes, on the other hand, outlive individual Pods. They are essential for stateful applications that require data to persist across Pod restarts or replacements. This persistent storage ensures that critical data remains available even as the Pods using it are recreated or rescheduled.</li> +</ul> + +<p>Understanding storage in Kubernetes can be a bit complex due to its abstraction and reliance on third-party controllers. Kubernetes uses the Container Storage Interface (CSI), a standardised specification that allows it to request storage from different providers, which then manage the lifecycle of the underlying storage. This storage could be anything from a local directory on a node to an AWS Elastic Block Store (EBS) volume. Kubernetes abstracts the details and relies on the CSI-compliant controller to handle the specifics.</p> + +<h2 id="key-components-of-kubernetes-storage">Key Components of Kubernetes Storage</h2> + +<p>There are three main components to understand when dealing with storage in Kubernetes: Storage Classes, PersistentVolumes (PVs), and PersistentVolumeClaims (PVCs).</p> + +<ol> +<li>Storage Class: A Storage Class defines the type of storage and the parameters required to provision it. Each Storage Class corresponds to a specific storage provider or controller. For example, a Storage Class might define a template for AWS EBS volumes or Google Cloud Persistent Disks.</li> +<li>PersistentVolume (PV): A PersistentVolume represents a piece of storage in the cluster that has been provisioned according to the specifications in a Storage Class. PVs can be either statically created by a cluster administrator or dynamically provisioned by a controller. For instance, when a Storage Class is associated with AWS, the controller might create an EBS volume when a PV is needed.</li> +<li>PersistentVolumeClaim (PVC): A PersistentVolumeClaim is a user’s request for storage. It specifies the desired size, access modes, and Storage Class. When a PVC is created, Kubernetes will find a matching PV or trigger the dynamic provisioning of a new one through the associated Storage Class and controller. Once a PV is provisioned, it becomes bound to the PVC, ensuring that the requested storage is dedicated to that specific claim.</li> +</ol> + +<h2 id="how-it-works-together">How It Works Together</h2> + +<p>The typical workflow involves a user creating a PersistentVolumeClaim to request storage. The CSI controller picks up this request and, based on the associated Storage Class, dynamically provisions a PersistentVolume that meets the user’s specifications. This PersistentVolume is then bound to the PersistentVolumeClaim, making the storage available to the Pod that needs it.</p> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/public/posts/2024-09-08-01.html b/public/posts/2024-09-08-01.html new file mode 100644 index 0000000..72cde61 --- /dev/null +++ b/public/posts/2024-09-08-01.html @@ -0,0 +1,265 @@ + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="description" content="Home for a73x" /> + <meta name="author" content="a73x" /> + <meta name="viewport" + content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width" /> + <title>a73x</title> + <link rel="stylesheet" href="/static/styles.css"> + <link rel="stylesheet" href="/static/syntax.css"> +</head> + +<body> + <h1>a73x</h1> + <sub>high effort, low reward</sub> + <nav class="nav"> + <ul> + + + <li><a class="no-decorations" href="/">home</a></li> + + + + <li><a class="no-decorations" href="/posts">posts</a></li> + + + + <li><a class="no-decorations" href="/ethos">ethos</a></li> + + + </ul> + </nav> + +<h1>Building a Static Site with Hugo and Docker</h1> +<nav> + +<ul> +<li><a href="#step-1-installing-hugo">Step 1: Installing Hugo</a></li> + +<li><a href="#step-2-creating-a-new-hugo-site">Step 2: Creating a New Hugo Site</a></li> + +<li><a href="#step-3-setting-up-a-theme">Step 3: Setting Up a Theme</a></li> + +<li><a href="#step-4-personalising-your-site">Step 4: Personalising Your Site</a></li> + +<li><a href="#step-5-creating-a-new-post">Step 5: Creating a New Post</a></li> + +<li><a href="#step-6-previewing-your-website">Step 6: Previewing Your Website</a></li> + +<li><a href="#step-7-publishing-the-website">Step 7: Publishing the Website</a></li> + +<li><a href="#step-8-dockerising-your-site">Step 8: Dockerising your Site</a></li> + +<li><a href="#step-9-building-and-running-the-docker-image">Step 9: Building and Running the Docker Image</a></li> + +<li><a href="#conclusion">Conclusion</a></li> +</ul> + +</nav> +<p>This will be a quick walkthrough of how to create a static site using Hugo, and use Nginx to serve it.</p> + +<p><strong>Prerequisites</strong></p> + +<table> +<thead> +<tr> +<th><strong>Skill</strong></th> +<th><strong>Description</strong></th> +</tr> +</thead> + +<tbody> +<tr> +<td><strong>Basic Terminal Usage</strong></td> +<td>Familiarity with navigating directories and running basic commands in a terminal.</td> +</tr> + +<tr> +<td><strong>Git</strong></td> +<td>Ability to initialize a Git repository, commit changes, and interact with remote repositories.</td> +</tr> + +<tr> +<td><strong>Markdown</strong></td> +<td>Knowledge of writing basic content in Markdown format (used for posts).</td> +</tr> + +<tr> +<td><strong>Docker Basics</strong></td> +<td>Understanding of Docker commands for building images and running containers.</td> +</tr> + +<tr> +<td><strong>HTML/CSS Basics</strong></td> +<td>General awareness of HTML and CSS for customising static site content.<br></td> +</tr> + +<tr> +<td>Go</td> +<td>Go should be installed on your system to use Hugo with the <code>go install</code> method. Alternatively, you can download Hugo binaries directly or use a package manager.</td> +</tr> +</tbody> +</table> + +<h1 id="step-1-installing-hugo">Step 1: Installing Hugo</h1> + +<p>Hugo is a static site generator, meaning it builds HTML, CSS, and JavaScript that doesn’t need a backend server, since the website’s content is static.</p> + +<p>You can install Hugo in multiple ways. If you already have Go installed, you can use</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">go install github.com/gohugoio/hugo@latest +</span></span></code></pre> +<p>Alternatively, you can install Hugo following the <a href="https://gohugo.io/installation/">official install guide</a>></p> + +<h1 id="step-2-creating-a-new-hugo-site">Step 2: Creating a New Hugo Site</h1> + +<p>To create a new Hugo site, run:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">hugo new site website +</span></span></code></pre> +<p>This creates a new folder called <code>website</code> with the basic structure of a Hugo site.</p> + +<h1 id="step-3-setting-up-a-theme">Step 3: Setting Up a Theme</h1> + +<p>By default, Hugo doesn’t ship with any themes installed, so its likely you’ll want to add one. A list of pre-made themes exist <a href="https://themes.gohugo.io/">here</a>, which saves you from having to create one from scratch. Typically, this involves using a Git submodule to manage the theme:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nb">cd</span> website +</span></span><span class="line"><span class="cl">git init +</span></span><span class="line"><span class="cl">git submodule add --depth<span class="o">=</span><span class="m">1</span> https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod +</span></span></code></pre> +<p>A Git submodule is a way to link a separate repository (the theme) into your project without copying it directly. This keeps the theme up-to-date and lets you manage it separately from your main website’s content.</p> + +<p>To use the theme, add it to your <code>config.toml</code> file:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">'theme = ["PaperMod"]'</span> >> hugo.toml +</span></span></code></pre> +<p>Alternatively, you could manually download the theme and place it in the <code>themes</code> folder, but using submodules allows for easier updates.</p> + +<h1 id="step-4-personalising-your-site">Step 4: Personalising Your Site</h1> + +<p>Open <code>config.toml</code> in your favorite code editor (e.g., VS Code), and change the <code>title</code> line to peronsalise your site’s name:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nx">title</span> <span class="p">=</span> <span class="s2">"<insert name>'s blog"</span> +</span></span></code></pre> +<p>This will update the title of your site, which you’ll see when we generate the site in a moment.</p> + +<h1 id="step-5-creating-a-new-post">Step 5: Creating a New Post</h1> + +<p>To create a new post, run the following command:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">hugo new content content/posts/my-first-post.md +</span></span></code></pre> +<p>This will create a new file in the <code>content/posts</code> directory, named <code>my-first-post.md</code>. When you open it, you’ll see:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">+++ +</span></span><span class="line"><span class="cl">title = 'My First Post' +</span></span><span class="line"><span class="cl">date = 2024-09-08T15:44:30+01:00 +</span></span><span class="line"><span class="cl">draft = true +</span></span><span class="line"><span class="cl">+++ +</span></span></code></pre> +<p>The block of text wrapped in <code>+++</code> is called “front matter” and acts as metadata for your post. It won’t be visible in your generated website, the actual content of your post goes below this.</p> + +<p>Now, you can edit the file to include your first post:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">+++ +</span></span><span class="line"><span class="cl">title = 'My First Post' +</span></span><span class="line"><span class="cl">date = 2024-09-08T15:44:30+01:00 +</span></span><span class="line"><span class="cl">draft = true +</span></span><span class="line"><span class="cl">+++ +</span></span><span class="line"><span class="cl"><span class="gu">## Welcome! +</span></span></span><span class="line"><span class="cl"><span class="gu"></span> +</span></span><span class="line"><span class="cl">This is my first post on my new site. It's written in markdown and utilises hugo for generating html which browsers understand and can parse. +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Visit the [<span class="nt">Hugo</span>](<span class="na">https://gohugo.io</span>) website! +</span></span></code></pre> +<h1 id="step-6-previewing-your-website">Step 6: Previewing Your Website</h1> + +<p>To preview your website locally, run the following command:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">hugo server --buildDrafts +</span></span></code></pre> +<p>This will start a local server, and you can view your site by visiting <code>http://localhost:1313</code> in your browser.</p> + +<h1 id="step-7-publishing-the-website">Step 7: Publishing the Website</h1> + +<p>Once you’re ready to publish, update your post by changing <code>draft = true</code> to <code>draft = false</code> (or just delete the <code>draft</code> property) and run:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">hugo +</span></span></code></pre> +<p>This will build your site and place the generated files in the <code>public</code> folder. This folder contains all the HTML, CSS, and JavaScript that make up your static site.</p> + +<p>From here you can deploy it following [Hugo’s own guide](<a href="https://gohugo.io/categories/hosting-and-deployment/">https://gohugo.io/categories/hosting-and-deployment/</a>. However, most of these options involve using someone else’s compute, and our goal here is self hosting.</p> + +<h1 id="step-8-dockerising-your-site">Step 8: Dockerising your Site</h1> + +<p>Now that you have a static site in the <code>public</code> folder, let’s create a Docker image that will serve your site using Nginx, a lightweight web server. While you could install Nginx directly on a server and follow <a href="https://gohugo.io/hosting-and-deployment/deployment-with-rclone/">this guide</a> to deploy your site, using Docker offers several advantages. Containers provide a reproducible, isolated environment that makes it easy to package, run, and deploy your site across different systems. So, let’s use Docker to handle serving your site instead.</p> + +<p>Create a <code>Dockerfile</code> with the following content</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="k">FROM</span><span class="s"> nginx:1.27.1</span><span class="err"> +</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> public /usr/share/nginx/html<span class="err"> +</span></span></span></code></pre> +<p>This tells Docker to use the official Nginx image and copy the files from your <code>public</code> folder (which Hugo generated) into the default location that Nginx serves from.</p> + +<h1 id="step-9-building-and-running-the-docker-image">Step 9: Building and Running the Docker Image</h1> + +<p>To proceed, you’ll need a container registry. We’ll use Docker hub for this example (you’ll need an account) but you can use whatever container registry you have access to.</p> + +<p>To build your Docker image, run:</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">docker build . -t <dockerusername>website:0.0.1 +</span></span></code></pre> +<p>Here:</p> + +<ul> +<li><code>-t <dockerusername>website:0.0.1</code> tags the image with a name (<code><dockerusername/website</code>) and a version (<code>0.0.1</code>), which will be important later when you want to publish it.</li> +<li>the <code>.</code> tells Docker to build the image from the current directory (which contains your <code>Dockerfile</code> and <code>public</code> folder).</li> +</ul> + +<p>Now that you have the Docker image locally, you can run it using</p> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">docker run -p 8080:80 <dockerusername>website:0.0.1 +</span></span></code></pre> +<p>Here, <code>-p 8080:80</code> maps port 8080 on your local machine to port 80 in the Docker container, where Nginx serves the content.</p> + +<p>Now, open a browser and go to <code>http://localhost:8080</code>. You should see your static site served by Nginx!</p> + +<p>But your server is not your local machine (potentially), so you need a method of getting the image from your local machine, to your server. We can use container registries as an intermediary.</p> + +<ol> +<li>First login to Docker Hub:</li> +</ol> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">docker login +</span></span></code></pre> +<ol> +<li>Push your image to Docker Hub, by default this image will be public.</li> +</ol> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="sb">`</span>docker push <dockerusername>website:0.0.1 +</span></span></code></pre> +<ol> +<li>SSH into the server</li> +</ol> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">ssh user@server-ip +</span></span></code></pre> +<ol> +<li>Pull the Image (we don’t need to login since the image is public):</li> +</ol> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">docker <dockerusername>website:0.0.1 +</span></span></code></pre> +<ol> +<li>Run the Docker Image</li> +</ol> +<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl">docker run -d -p 80:80 <dockerusername>website:0.0.1 +</span></span></code></pre> +<ul> +<li><code>-d</code> runs the container in detached mode (in the background).</li> +<li><code>-p 80:80</code> maps port 80 of the container (where Nginx is running) to port 80 on the server, making the website accessible via the server’s IP address in a browser.</li> +</ul> + +<p>You will be able to access the website by visiting <code>http://server-ip:80</code></p> + +<h1 id="conclusion">Conclusion</h1> + +<p>Congratulations on creating and running your first static website with Hugo and Docker!</p> + + + <footer> + <br /> + <hr /><br /> + <p>see something you disagree with? email: <a href="mailto:[email protected]">[email protected]</a></p> + </footer> +</body> + +</html> + diff --git a/templates/layouts/_default.html b/templates/layouts/_default.html index ae6f8a8..5b9521c 100644 --- a/templates/layouts/_default.html +++ b/templates/layouts/_default.html @@ -12,18 +12,19 @@ <link rel="stylesheet" href="/static/styles.css"> <link rel="stylesheet" href="/static/syntax.css"> </head> + <body> <h1>a73x</h1> <sub>high effort, low reward</sub> <nav class="nav"> <ul> - {{ range $vvv, $navPair:= .Nav}} - {{ if eq $.Path $navPair.Path }} + {{ range $vvv, $navPair:= .Nav}} + {{ if eq $.Path $navPair.Path }} <li><a href="{{$navPair.Path}}">{{$navPair.Title}}</a></li> - {{else}} + {{else}} <li><a class="no-decorations" href="{{$navPair.Path}}">{{$navPair.Title}}</a></li> - {{end}} - {{end}} + {{end}} + {{end}} </ul> </nav> {{ template "content" . }} |
