Nessa publicação vou falar um pouco de como é o meu fluxo de trabalho para publicar conteúdo. Eu acho bem simples e por isso resolvi compartilhar com vocês.

A primeira parte é conhecer a infraestrutura que construí para meu servidor. Meu blog e a pagina inicial residem no meu Raspberry Pi Zero W que está ligado no meu roteador com um pequeno cabo USB, para receber energia. E o roteador ligado no No-break. Essa parte foi feita dessa maneira pois queria me aventurar no mundo da hospedagem. E gostei dos resultados, porém é bem mais fácil pagar uma VM em qualquer nuvem.

Configuração do roteador

Essa parte não é dificil, o objetivo é rotear o tráfego para meu servidor, para isso entrei na configuração do Arris e criei um virtual host, coloquei um IP estático para o RPi e adicionei um port forwarder da porta 443 para 4433. Assim eu consigo subir um serviço sem precisar de privilegios de raiz.

Alguns opcionais que eu decidi ter, e com muita dificuldade, foram:

Sem dúvida essa foi a parte mais entristecedora do setup. Porém esses opcionais me facilitam no código, pois não preciso configurar DDNS. E isso impede interrupção do acesso pois o meu DNS sempre aponta para o IP correto.

O servidor

Agora chegamos na programação de verdade. O servidor é escrito em Go e escuta a porta 4433, portanto eu executo ele como usuário desprivilegiado, isso me dá mais segurança e simplifica muito o processo, pois meu usuário tem todas as permissões para o fluxo de publicação. O código pode ser encontrado no git, também hospedado no meu RPI: repositório.

No servidor eu decidi usar templates, o go tem um pacote nativo muito bom. Por isso eu tenho um template da pagina inicial do blog, com a listagem dos artigos; e um template do artigo. Porém se eu quisesse poderia ter usado paginas estáticas também, isso seria ainda mais simples.

O código de listagem de artigos é simples:

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())
}

Daria pra fazer melhor e utilizar o que foi definido dentro do template para mostrar um nome mais interessante. Eu realizei dessa forma em um outro projeto meu, e ficou bem interessante. Mas aqui usei essa forma mais simples.

Para os artigos eu utilizei um método para não precisar reiniciar o servidor quando houvesse alguma alteração nas páginas, pois utilizando templates normalmente o parsing dos templates é feito no início do programa. Veja:

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())
}

Assim o template base (posts) é precarregado e a cada execução somente o artigo requisitado é lido. Portanto eu não preciso reiniciar o servidor para alterações nas páginas, somente quando há mudanças no código do servidor.

Para esses casos o RPi tem um cron job que reinicia o servidor a cada 1 hora, simples e funcional, aliás o cron é sem privilégios também. A atualização dos arquivos é feita usando o bom e velho scp que eu utilizo através do comando make para diminuir o risco de erros:

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/

Aqui também gero o RSS usando um shell script simples que eu fiz, é um clássico fazer o seu próprio RSS a maioria dos blogs tem o seu próprio, uma pesquisa no google e você encontrará alguns.

Os templates

Essa parte é a cara do blog, e pode mudar completamente de um projeto para outro simplesmente mudando os estilos CSS e o HTML. Porém as funcionalidades são basicamente as mesmas: na página inicial temos algo sobre o blog e a lista de publicações. Usando o pacote html/template do go é muito simples:
<!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>

Essa é a página inicial do blog, daria pra reduzir ainda mais o tamanho mas já estou satisfeito. Note o trecho {{`{{range .}}`}} que cria a lista de artigos com os dados que foram passados no comando ExecuteTemplate. Assim os links são populados.

O template do artigo é um pouco diferente pois uso as redefinições na hora em que o artigo é requisitado:

<!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>

Basicamente a mesma coisa, porém aqui eu uso o {{`{{template "content"}}`}}, que substitui a si mesmo pelo conteúdo indicado, esse método é interessante pois é dinâmico, e os valores são preenchidos usando o ParseFiles() que mostrei anteriormente.

Por último o conteúdo em si:
{{`
{{define "lang"}}en{{end}}
{{define "title"}}titulo{{end}}
{{define "desc"}}descricao{{end}}
{{define "revised"}}2021-11-02{{end}}
{{define "content"}}

O conteúdo do post.

{{end}}`}}
Os {{`{{define ...}}`}} justamente definem o valor do template que será usado acima. Desse modo é muito simples escrever um novo artigo: basta copiar esse molde, renomear o arquivo e preencher os campos.

Outro opcional aqui, coloquei uma integração com email para poder receber o feedback dos usuários. Aqui uso o Dovel, um servidor de emails rudimentar que tem uma interface go para ser utilizada com os templates.

O git

Se fosse só o blog no servidor o código seria basicamente esse acima. Porém adicionei acessoe visualização do git usando uma biblioteca go, a configuração é simplesmente um aquivo de configuração. Mas esse é um assunto para outro artigo.

Adicionando conteúdo

Agora que toda a configuração e desenvolvimento foi feita a criação e implantação do conteúdo é simples:
  1. Copiar o template.
  2. Renomear lembrando que o nome também é o título.
  3. Escrever o conteúdo.
  4. Rodar make deploy-blog.

Conclusão

Não foi um processo fácil ao todo, minha impressão é que estamos bem atrasados tecnologicamente, pois a pior parte foi o plano de internet, se não houvessem tantas complicações de portas bloqueadas e configuração de rede por parte das operadoras o projeto teria se encerrado num final de semana (pelo menos a parte funcional). É claro que a estilização e elaboração de conteúdo pode demorar um tempo indefinido. Como eu quis integrar com o git e o email o projeto ficou mais complexo.

A parte de código não é complicada e pode ser ainda mais facil ao se utilizar projetos prontos ou imagens docker preconfiguradas. Aqui eu quis fazer tudo do zero por dois motivos: 1. aprender como as muitas partes funcionam internamente, e 2. criar uma versão mais leve do que os projetos atuais.

É desse segundo ponto que me orgulho mais, tudo ficou muito leve e eficiente: a pagina inicial do blog tem 2700 bytes e carrega em 80ms, ela é HTML válido e simples, meu portfolio, a página superior ao blog tem 575 bytes; isso permite o projeto ser servido do meu Rpi Zero W que precisa de apenas 5V para funcionar. Além disso ele ainda carrega outros projetos como o meu git e servidor de email.

São essas as dificuldades que você poderá encontrar se decidir se aventurar por esse caminho. Espero ter ajudado de alguma forma. Eu digo que vale a pena se você preza for simplicidade extrema, gosta de fazer as coisas do seu jeito e quer sair da dependencia das famigeradas big techs e sobretudo aprender muito.

Planos futuros

Ainda quero mudar algumas coisas no projeto, por pura curiosidade e zêlo: