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:
- Plano empresárial de internet: para ter as portas 25, 80 e 443 desbloqueadas. O bloqueio é ilegal no Brasil, mas enfim. Sem esse desbloqueio o único jeito é usando VPN.
- IP fixo: junto com o plano empresarial foi o mais dificil, pelo menos uns 3 meses falando com as operadoras, atendimento péssimo, pessoal despreparado.
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:- Copiar o template.
- Renomear lembrando que o nome também é o título.
- Escrever o conteúdo.
- 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:- Usar uma placa solar e bateria para não gastar energia.
- Separar o git do projeto.
- Migrar o código do git para uma implementação minha.