Compilado por Pablo Murad — 2026
Data: 17/06/2026
Contexto: site pessoal https://pablo.su usando mkws, Debian 12, publicação em /home/pablosu/htdocs/pablo.su.
1. O que é o mkws?
mkws é um Static Site Generator minimalista. Ele gera HTML estático a partir de arquivos *.upphtml.
A ideia dele é bem diferente de Hugo, Jekyll, Astro ou Eleventy. Ele não tenta ser um framework completo. Ele é mais próximo de um pipeline Unix: arquivos texto, shell, templates simples e saída estática.
O projeto se vende com estes pontos principais:
- pequeno;
- feito com shell e pequenos binários;
- usa
shcomo linguagem de template; - permite chamar qualquer utilitário Unix dentro dos templates;
- não depende de Node, npm, bundler, banco de dados ou servidor próprio;
- não adiciona JavaScript por padrão;
- é altamente modificável.
A página oficial resume bem a filosofia: em vez de configurar um gerador gigante, você “script your static site generator”. Ou seja: você molda o gerador ao site, não o contrário.
Fonte principal: https://mkws.sh/
2. Por que escolhemos mkws para o pablo.su?
O objetivo do pablo.su é ser um site leve, pessoal, estático e compatível com a filosofia do no-js.club.
Para isso, o mkws é uma boa escolha porque:
- Não injeta JavaScript automaticamente.
- Gera HTML simples.
- É fácil de auditar.
- Não depende de ecossistema pesado.
- É customizável em nível brutal.
- Funciona muito bem em Debian 12.
- Permite deploy simples com
make+rsync.
A escolha não é porque ele é “mais confortável”. Ele não é. A escolha é porque ele dá controle.
3. A grande verdade sobre o mkws
O mkws é simples, mas não é necessariamente “fácil”.
Essa diferença importa.
Simples
Ele tem poucas peças:
bin/
mkws
pp
lmt
share/
l.upphtml
s.uppcss
sitemap.uppxml
E você cria páginas como:
index.upphtml
sobre.upphtml
links.upphtml
Depois roda:
./bin/mkws https://pablo.su
Ele gera:
index.html
sobre.html
links.html
sitemap.xml
Não necessariamente fácil
Ele exige que você entenda:
- shell;
- arquivos estáticos;
- HTML;
- CSS;
- permissões;
- publicação via terminal;
- risco de
rsync --delete; - como não misturar fonte e saída;
- como validar que não entrou JavaScript;
- como organizar o projeto antes de crescer.
Ou seja: mkws é ótimo para quem quer controle. É ruim para quem quer um painel bonito ou convenções prontas.
4. Como o mkws funciona por dentro
O mkws trabalha com arquivos *.upphtml.
Esses arquivos são processados pelo pp, um preprocessador que permite embutir comandos shell dentro do arquivo.
Um exemplo simples:
<main>
<h1>Pablo Murad</h1>
<p>Site pessoal, estático e sem JavaScript.</p>
</main>
Isso pode virar index.html.
Mas o poder real aparece quando usamos blocos shell:
<main>
#!
date
#!
</main>
O trecho entre:
#!
...
#!
é executado como shell.
Isso é poderoso, mas perigoso: se você começar a colocar lógica demais no template, seu site vira uma sopa de shell dentro de HTML.
A documentação do mkws também alerta para detalhes de quoting, porque o pp usa sh internamente. Em templates, aspas duplas podem exigir escape. Na prática, para HTML simples, prefira atributos sem aspas quando possível ou aspas simples quando necessário.
Fonte: https://mkws.sh/docs
5. Instalação no Debian 12
No servidor Debian 12:
sudo apt update
sudo apt install -y curl tar make rsync grep findutils coreutils
Para Markdown, recomendo também:
sudo apt install -y cmark
O cmark é uma implementação em C do CommonMark e fornece comando de terminal para converter Markdown em HTML.
Fonte: https://github.com/commonmark/cmark
6. Baixando o mkws
No nosso caso, usamos a pasta:
/home/pablosu/src/pablo.su-mkws
Comandos:
mkdir -p /home/pablosu/src
cd /home/pablosu/src
curl -fsSL https://mkws.sh | tar -xzvf -
mv ws.sh pablo.su-mkws
cd pablo.su-mkws
Verifique:
find . -maxdepth 3 -type f | sort
Resultado esperado:
./bin/lmt
./bin/mkws
./bin/pp
./share/l.upphtml
./share/sitemap.uppxml
./share/s.uppcss
7. Primeira página
Crie a página inicial:
cat > index.upphtml <<'EOF'
<main>
<h1>Pablo Murad</h1>
<p>
Este é meu canto pequeno, estático e deliberadamente simples na web.
</p>
<p>
O site roda sem JavaScript, sem framework de front-end e sem firula.
</p>
</main>
EOF
Gere:
./bin/mkws https://pablo.su
Saída esperada:
Making index.html
Making sitemap.xml
8. Estrutura atual recomendada
Para o pablo.su, a estrutura ideal neste momento é:
/home/pablosu/src/pablo.su-mkws/
├── bin/
│ ├── lmt
│ ├── mkws
│ └── pp
├── share/
│ ├── l.upphtml
│ ├── s.uppcss
│ └── sitemap.uppxml
├── index.upphtml
├── sobre.upphtml
├── Makefile
└── _site/
E a publicação final fica em:
/home/pablosu/htdocs/pablo.su/
A regra de ouro:
src/pablo.su-mkws = fonte e build
htdocs/pablo.su = site público
Não misture os dois.
9. O layout principal: share/l.upphtml
O arquivo mais importante é:
share/l.upphtml
Ele é o layout usado para envolver as páginas.
O padrão original é muito simples:
<!doctype html>
<html lang=${LANG%%_*}>
<title>My website</title>
<meta charset=${LANG##*.}>
<meta name=viewport content='width=device-width, initial-scale=1'>
<style>
#!
pp "${SHARE:?no theme directory set}"/s.uppcss
#!
</style>
#!
pp "$1"
#!
Isso funciona, mas é tosco para produção porque:
- o título vem como
My website; - o idioma depende do locale;
- o charset depende do ambiente;
- não há
<head>explícito; - não há metadados decentes;
- não há header/footer.
Uma versão melhor para o pablo.su:
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pablo Murad</title>
<meta name="description" content="Site pessoal de Pablo Murad: notas, projetos, pesquisa, programação, livros e web sem JavaScript.">
<meta name="author" content="Pablo Murad">
<meta name="generator" content="mkws">
<style>
#!
pp "${SHARE:?no theme directory set}"/s.uppcss
#!
</style>
</head>
<body>
<header class="site-header">
<a class="site-title" href="/">Pablo Murad</a>
<nav class="site-nav" aria-label="Navegação principal">
<a href="/">Início</a>
<a href="/sobre.html">Sobre</a>
</nav>
</header>
#!
pp "$1"
#!
<footer class="site-footer">
<p>
Feito em Debian 12 com mkws. Sem JavaScript.
</p>
</footer>
</body>
</html>
10. O CSS: share/s.uppcss
O CSS padrão fica em:
share/s.uppcss
No layout padrão, ele é embutido dentro de <style>.
Isso significa que o CSS final vai inline no HTML.
Vantagem:
- menos requisições HTTP;
- simples;
- ótimo para site pequeno;
- bom para no-js.club.
Desvantagem:
- se o CSS crescer muito, todas as páginas carregam o CSS inteiro;
- cache separado fica pior;
- HTML final aumenta.
Para um site pequeno, isso é aceitável. Quando crescer, podemos separar style.css.
CSS inicial sugerido:
:root {
--bg: #f7f1e3;
--fg: #181510;
--muted: #6b6257;
--line: #181510;
--link: #0645ad;
--panel: #fffaf0;
}
* {
box-sizing: border-box;
}
html {
font-family: Georgia, "Times New Roman", serif;
background: var(--bg);
color: var(--fg);
}
body {
max-width: 760px;
margin: 0 auto;
padding: 2rem 1rem;
line-height: 1.65;
font-size: 18px;
}
.site-header {
border-bottom: 2px solid var(--line);
margin-bottom: 2rem;
padding-bottom: 1rem;
}
.site-title {
display: inline-block;
color: var(--fg);
font-weight: bold;
font-size: 1.35rem;
text-decoration: none;
}
.site-nav {
margin-top: .75rem;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: .95rem;
}
.site-nav a {
margin-right: 1rem;
}
main {
background: var(--panel);
border: 2px solid var(--line);
padding: 1.25rem;
}
h1,
h2,
h3 {
line-height: 1.2;
}
h1 {
font-size: 2.2rem;
margin-top: 0;
}
a {
color: var(--link);
text-decoration-thickness: .08em;
text-underline-offset: .18em;
}
a:hover {
text-decoration-thickness: .14em;
}
p {
margin: 1rem 0;
}
.site-footer {
margin-top: 2rem;
padding-top: 1rem;
border-top: 2px solid var(--line);
color: var(--muted);
font-size: .9rem;
}
code,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
@media (max-width: 520px) {
body {
padding: 1rem;
font-size: 17px;
}
main {
padding: 1rem;
}
.site-nav a {
display: inline-block;
margin-bottom: .35rem;
}
}
11. Criando novas páginas
Para criar uma página Sobre:
cat > sobre.upphtml <<'EOF'
<main>
<h1>Sobre</h1>
<p>
Sou Pablo Murad. Este é meu espaço pessoal na web.
</p>
<p>
Aqui pretendo reunir notas, projetos, pesquisa, programação, livros,
história, experimentos e pequenos textos sem depender de plataformas
pesadas.
</p>
<p>
Este site é propositalmente simples: HTML, CSS e nenhum JavaScript.
</p>
</main>
EOF
Depois:
make
O mkws deve gerar:
sobre.html
12. Markdown: o mkws trabalha com .md?
Sim, mas não nativamente como Hugo/Jekyll.
O mkws não é um sistema “Markdown-first”. Ele é um sistema upphtml + shell.
Para usar Markdown, você chama um conversor externo, como cmark.
Instale:
sudo apt install -y cmark
Crie:
mkdir -p content
cat > content/teste.md <<'EOF'
# Página em Markdown
Isto foi escrito em **Markdown**.
- leve
- simples
- sem JavaScript
[Voltar](/)
EOF
Crie o wrapper teste.upphtml:
cat > teste.upphtml <<'EOF'
<main>
#!
cmark content/teste.md
#!
</main>
EOF
Rode:
make
Isso gera:
teste.html
Melhor prática
Use Markdown para textos longos:
content/
notas/
primeira-nota.md
posts/
primeiro-post.md
Use upphtml para páginas com estrutura específica:
index.upphtml
sobre.upphtml
links.upphtml
13. O Makefile de deploy
O comando manual funciona:
./bin/mkws https://pablo.su
rsync -av index.html sitemap.xml /home/pablosu/htdocs/pablo.su/
Mas isso não escala. Site cresce, erro humano cresce junto.
O correto é ter um Makefile.
Versão atual recomendada:
SITE_URL = https://pablo.su
PUBLIC_DIR = /home/pablosu/htdocs/pablo.su
BUILD_DIR = _site
.PHONY: all build stage check publish deploy clean distclean
all: deploy
build:
./bin/mkws $(SITE_URL)
stage: build
rm -rf $(BUILD_DIR)
mkdir -p $(BUILD_DIR)
find . -maxdepth 1 -type f \( -name '*.html' -o -name '*.xml' -o -name '*.txt' -o -name '*.css' \) -exec cp -a {} $(BUILD_DIR)/ \;
check: stage
@echo "Verificando JavaScript..."
@if grep -RniE '<script|javascript:|onload=|onclick=|onerror=|onmouseover=|onfocus=|onblur=' $(BUILD_DIR); then \
echo "ERRO: JavaScript encontrado. Publicação bloqueada."; \
exit 1; \
else \
echo "OK: nenhum JavaScript óbvio encontrado."; \
fi
publish: check
rsync -av --delete \
--exclude='/.well-known/' \
$(BUILD_DIR)/ $(PUBLIC_DIR)/
deploy: publish
clean:
rm -rf $(BUILD_DIR)
distclean: clean
rm -f ./*.html ./*.xml
Uso
cd /home/pablosu/src/pablo.su-mkws
make
Esse comando:
- gera o site;
- cria
_site; - copia os arquivos finais;
- verifica se existe JavaScript;
- publica no
htdocscomrsync.
14. Por que usar _site/?
No início, dá para publicar arquivos soltos:
rsync -av index.html sitemap.xml /home/pablosu/htdocs/pablo.su/
Mas isso fica ruim quando aparecem:
sobre.html
links.html
agora.html
robots.txt
style.css
feed.xml
img/
posts/
Então usamos _site/ como pasta de saída.
Fluxo:
fonte -> build -> _site -> htdocs
Assim, o htdocs recebe apenas o resultado final.
15. Cuidado com rsync --delete
rsync --delete é excelente, mas perigoso.
Ele remove do destino arquivos que não existem na origem. Isso é o que queremos para manter o site limpo.
Mas há um problema: no nosso caso, ele apagou a pasta:
.well-known/
Essa pasta pode ser usada por validações de certificado, especialmente Let’s Encrypt/ACME HTTP-01.
Por isso, no deploy usamos:
--exclude='/.well-known/'
E recriamos:
mkdir -p /home/pablosu/htdocs/pablo.su/.well-known/acme-challenge
Isso preserva:
/home/pablosu/htdocs/pablo.su/.well-known
/home/pablosu/htdocs/pablo.su/.well-known/acme-challenge
Fonte sobre HTTP-01 e .well-known/acme-challenge: https://letsencrypt.org/docs/challenge-types/
16. Verificação no-js
Como queremos compatibilidade com no-js.club, o build deve falhar se encontrar:
<script;javascript:;onload=;onclick=;onerror=;onmouseover=;onfocus=;onblur=.
O trecho no Makefile:
check: stage
@echo "Verificando JavaScript..."
@if grep -RniE '<script|javascript:|onload=|onclick=|onerror=|onmouseover=|onfocus=|onblur=' $(BUILD_DIR); then \
echo "ERRO: JavaScript encontrado. Publicação bloqueada."; \
exit 1; \
else \
echo "OK: nenhum JavaScript óbvio encontrado."; \
fi
Teste manual:
grep -RniE '<script|javascript:|onload=|onclick=|onerror=|onmouseover=|onfocus=|onblur=' _site || true
Se não retornar nada, ótimo.
17. Testando localmente
Se quiser testar localmente:
python3 -m http.server 8081
Se der erro:
OSError: [Errno 98] Address already in use
A porta está ocupada. Use outra:
python3 -m http.server 8082
Ou veja quem está usando:
sudo ss -ltnp | grep ':8080' || true
18. Testando online
Depois de make:
curl -I https://pablo.su
curl -s https://pablo.su | sed -n '1,80p'
curl -s https://pablo.su/sobre.html | sed -n '1,80p'
Verificar JavaScript online:
curl -s https://pablo.su | grep -iE '<script|javascript:|onload=|onclick=|onerror=|onmouseover=' || true
Se voltar vazio, bom.
19. Backup antes de mexer
Antes de qualquer mudança grande:
mkdir -p /home/pablosu/backups
cp -a /home/pablosu/htdocs/pablo.su \
/home/pablosu/backups/pablo.su-antes-mudanca-$(date +%F-%H%M%S)
Backup do layout:
cp -a share/l.upphtml share/l.upphtml.bak-$(date +%F-%H%M%S)
cp -a share/s.uppcss share/s.uppcss.bak-$(date +%F-%H%M%S)
20. Facilidade do mkws
O mkws é fácil em alguns pontos:
Instalação
Não tem Node, npm, bundle, banco nem dependência pesada.
curl -fsSL https://mkws.sh | tar -xzvf -
Geração
./bin/mkws https://pablo.su
Publicação
make
HTML final
É fácil ver o que foi gerado:
sed -n '1,120p' index.html
Auditoria
É fácil procurar JavaScript:
grep -Rni '<script' _site || true
Customização
Você edita:
share/l.upphtml
share/s.uppcss
E pronto.
21. Dificuldade do mkws
Agora a parte honesta: mkws cobra preço.
1. Pouca automação pronta
Ele não vem com:
- blog completo;
- taxonomias;
- tags;
- categorias;
- paginação;
- front matter;
- tema pronto sofisticado;
- servidor de desenvolvimento embutido;
- Markdown automático;
- busca;
- pipeline de assets.
Você monta.
2. Shell dentro de HTML pode virar inferno
Isto é poderoso:
#!
for f in content/*.md; do
echo "$f"
done
#!
Mas se abusar, fica ilegível rápido.
3. Organização é responsabilidade sua
Hugo força uma estrutura. mkws não.
Isso é liberdade, mas também é risco.
4. Segurança operacional
Um rsync --delete errado pode apagar coisas importantes.
Por isso usamos:
--exclude='/.well-known/'
e publicamos a partir de _site/.
5. Crescimento exige disciplina
Site pequeno: mkws brilha.
Site grande sem convenção: mkws vira gambiarra.
22. O que eu recomendo para o pablo.su
Agora
Manter simples:
index.upphtml
sobre.upphtml
links.upphtml
agora.upphtml
Em seguida
Adicionar Markdown para textos:
content/
notas/
2026-06-17-primeira-nota.md
E wrappers *.upphtml.
Depois
Criar:
posts.upphtml
feed.xml
robots.txt
Só depois
Pensar em:
- tags;
- categorias;
- busca sem JavaScript;
- coleção de notas;
- template para posts;
- microblog;
- RSS;
- sitemap mais elaborado.
23. Modelo mental correto
Não pense no mkws como CMS.
Pense assim:
mkws = cola Unix para gerar HTML
Ele é ótimo para:
- site pessoal;
- manifesto;
- página de links;
- zine;
- microblog;
- documentação pequena;
- site no-js;
- página editorial leve.
Ele é ruim para:
- equipe não técnica;
- conteúdo com painel administrativo;
- site comercial cheio de landing pages dinâmicas;
- blog grande sem automações próprias;
- quem quer tema pronto e plugin para tudo.
24. Comandos principais do projeto pablo.su
Entrar no projeto:
cd /home/pablosu/src/pablo.su-mkws
Gerar e publicar:
make
Limpar _site:
make clean
Limpar HTML/XML gerados na raiz:
make distclean
Ver arquivos publicados:
find /home/pablosu/htdocs/pablo.su -maxdepth 2 -type f | sort
Ver diretórios preservados:
find /home/pablosu/htdocs/pablo.su -maxdepth 3 -type d | sort
Ver JavaScript suspeito:
grep -RniE '<script|javascript:|onload=|onclick=|onerror=|onmouseover=|onfocus=|onblur=' /home/pablosu/htdocs/pablo.su || true
25. Fluxo de trabalho diário
- Editar arquivo fonte:
nano index.upphtml
- Rodar:
make
- Conferir online:
curl -I https://pablo.su
- Se der problema, olhar HTML gerado:
sed -n '1,160p' index.html
- Se for mexer em layout, fazer backup antes:
cp -a share/l.upphtml share/l.upphtml.bak-$(date +%F-%H%M%S)
26. Veredito final
O mkws é uma excelente escolha para o pablo.su se o objetivo for:
- site pessoal;
- ultraleve;
- sem JavaScript;
- fácil de hospedar;
- fácil de auditar;
- totalmente controlável;
- com estética própria;
- sem depender de framework moderno.
Mas ele exige disciplina.
A frase brutal é:
mkws é simples, mas não perdoa bagunça.
Se o projeto for organizado desde o começo com Makefile, _site, backup, validação no-js e separação entre fonte e publicação, ele pode crescer bem.
Se virar um monte de shell improvisado dentro de HTML, vai ficar pior do que usar um gerador grande.
Para o pablo.su, o caminho certo é:
pequeno primeiro
bonito depois
automatizado sempre
sem JavaScript por padrão
27. Fontes consultadas
- mkws — site oficial: https://mkws.sh/
- mkws — documentação: https://mkws.sh/docs
- mkws — fonte da documentação em upphtml: https://mkws.sh/docs.upphtml
- Jamstack generator listing: https://jamstack.org/generators/mkws/
- LinuxLinks sobre mkws: https://www.linuxlinks.com/mkws-simple-static-site-generator/
- The Linux Rain sobre mkws: https://www.thelinuxrain.org/articles/mkws-static-site-generation-with-the-shell
- cmark/CommonMark: https://github.com/commonmark/cmark
- CommonMark: https://commonmark.org/
- Let’s Encrypt challenge types: https://letsencrypt.org/docs/challenge-types/
- GNU Make — phony targets: https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
- rsync manual: https://www.man7.org/linux/man-pages/man1/rsync.1.html
Did this resonate?
Related documents
- 001
- 002
- 003
- 004
- 005