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"}}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.O conteúdo do post.
{{end}}`}}
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.