In this post I will talk a little about my workflow for publishing content. I find it quite simple and therefore decided to share it with you.
The first part is to know the infrastructure that I built for my server. My blog and homepage resides on my Raspberry Pi Zero W which is connected to my router with a small USB cable to receive power. And the router is connected to a UPS. This part was done this way because I wanted to venture into the world of hosting. And I liked the results, but it is much easier to pay for a VM in any cloud.
Some optional things that I decided to have, and with great difficulty, were:
In the server, I decided to use templates, Go has a very good native package. That's why I have a template of the blog's homepage, with the article listing; and a template of the article. But if I wanted to, I could have used static pages too, which would be even simpler.
The article listing code is simple:
postDir, err := os.ReadDir(path.Join(root, "b")) if err != nil { println("read posts error:", err.Error()) } for _, p := range postDir { if l := len(p.Name()); p.Name()[l-5:] != ".html" { continue } name := strings.ReplaceAll(p.Name(), "-", " ") name = name[3 : len(name)-5] info, _ := p.Info() data = append( data, post{ Name: name, Link: p.Name(), CTime: info.ModTime(), }, ) } sort.Slice(data, func(i, j int) bool {return data[i].Link > data[j].Link}) if err := temp.ExecuteTemplate(w, "blog.html", data); err != nil { println("execute error:", err.Error()) }
I could have done better and used what was defined within the template to show a more interesting name. I did it this way in another project of mine, and it turned out quite interesting. But here I used this simpler way.
For the articles, I used a method so that I wouldn't have to restart the server when there was any change in the pages because when using templates, the parsing of the templates is usually done at the beginning of the program. See:
temp, err := posts.Clone() if err != nil { println("clone template", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } _, err = temp.ParseFiles(path.Join(root, r.URL.Path)) if err != nil { println("parse template", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } i := strings.LastIndex(r.URL.Path, "/") + 1 if err := temp.ExecuteTemplate(w, "template.html", r.URL.Path[i:]); err != nil { println("execute error:", err.Error()) }
This way, the base template (posts) is preloaded, and only the requested article is read with each execution. Therefore, I don't need to restart the server for changes in the pages, only when there are changes in the server code.
For these cases, the RPi has a cron job that restarts the server every 1 hour, simple and functional, by the way, the cron runs without privileges too. The file update is done using the good old scp , which I use through the command make to reduce the risk of errors:
deploy-blog: $(wildcard www/b/*.*) cd www && rssgen -a blmayer.dev -c "b" -d "blog by b" -t "feed" -l "en_US" b/*.html > b/feed.xml scp $^ zero:blmayer.dev/www/b/
Here I also generate the RSS using a simple shell script that I made. It's a classic to make your own RSS; most blogs have their own. A Google search will find some.
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>posts</title> <link rel=icon href=data:,> <style> {{template "style.html"}} </style> </head> <body> // ← <a href=/>back</a> <h1>Blog!</h1> {{range .}} // <a href=/b/{{.Link}}>{{.Name}}</a><br> {{end}} <h2>what is this?</h2> this is a small space on the internet that i post topics like programming, linux, mathematics and some random stuff. we are a happy member of the <a href="//250kb.club">250Kb</a> and <a href="//512kb.club">512Kb</a> clubs. <hr> <center> <a href=/dp>←</a> derelict garden webring <a href=/dn>→</a> </center> <br> This work is licensed under <a href=//creativecommons.org/licenses/by/4.0/>CC BY 4.0</a>. </body> </html>
This is the home page of the blog, the size could be further reduced, but I am already satisfied. Note the code snippet {{range .}} that creates the list of articles with the data that was passed in the ExecuteTemplate command. This way, the links are populated.
The article template is a bit different because I use the definitions when the article is requested:
<!DOCTYPE html> <html lang="{{template "lang"}}"> <head> <title>{{template "title"}}</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="description" content="{{template "desc"}}"> <meta name="revised" content="{{template "revised"}}"> <style> {{template "style.html"}} </style> </head> <body> // ← <a href=/b>back to posts</a> <h1>{{template "title"}}</h1> {{template "content"}} hr> <h3>responses</h3> {{range (responses .)}} <small> <b>From:</b> {{(index .From 0).Address}} <i style="float:right">{{.Date.Format "Mon, 02 Jan 2006 15:04:05 MST"}}</i> </small> <blockquote>{{.TextBody}}</blockquote> {{end}} <p><a href="mailto:blog@mail.blmayer.dev?subject={{.}}">+</a></p> </body> </html>
Basically the same thing, but here I use {{template "content"}}, which replaces itself with the indicated content. This method is interesting because it is dynamic, and the values are filled using ParseFiles(), which I showed previously.
Lastly, the content itself:{{define "lang"}}en{{end}} {{define "title"}}title{{end}} {{define "desc"}}description{{end}} {{define "revised"}}2021-11-02{{end}} {{define "content"}} <p>The content of the post.</p> {{end}}The {{define ...}} precisely define the value of the template that will be used above. This way, it is very simple to write a new article: just copy this template, rename the file, and fill in the fields.
Another optional feature here, I added an email integration to be able to receive user feedback. Here, I use Dovel, a rudimentary email server that has a go interface to be used with templates.
The code part is not complicated and can be even easier when using ready-made projects or pre-configured Docker images. Here I wanted to do everything from scratch for two reasons: 1. to learn how the many parts work internally, and 2. to create a lighter version than current projects.
It's this second point that I'm most proud of, everything is very light and efficient: the blog's homepage has 2700 bytes and loads in 80ms, it's valid and simple HTML, my portfolio, the page above the blog, has 575 bytes; this allows the project to be served from my Raspberry Pi Zero W, which only needs 5V to operate. In addition, it still loads other projects like my Git and email server.
These are the difficulties you may encounter if you decide to venture down this path, at least here in Brazil. I hope I've helped in some way. I say it's worth it if you value extreme simplicity, like to do things your way, and want to get away from the dependence of the infamous big techs, libraries or frameworks, and above all, learn a lot.