Murad Library
Murad LibraryREF-0337MD

Tutorial completo: mkws no Debian 12 para o pablo.su

Catalogued
Reading
14 min read

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 sh como 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:

  1. Não injeta JavaScript automaticamente.
  2. Gera HTML simples.
  3. É fácil de auditar.
  4. Não depende de ecossistema pesado.
  5. É customizável em nível brutal.
  6. Funciona muito bem em Debian 12.
  7. 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:

  1. gera o site;
  2. cria _site;
  3. copia os arquivos finais;
  4. verifica se existe JavaScript;
  5. publica no htdocs com rsync.

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

  1. Editar arquivo fonte:
nano index.upphtml
  1. Rodar:
make
  1. Conferir online:
curl -I https://pablo.su
  1. Se der problema, olhar HTML gerado:
sed -n '1,160p' index.html
  1. 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

Did this resonate?

Related documents