Compare commits

...

10 Commits

41 changed files with 1024 additions and 152 deletions

133
.cspell.config.yaml Normal file
View File

@ -0,0 +1,133 @@
# https://cspell.org/configuration/
version: "0.2"
language: en,pt
dictionaries:
- en-us
- pt-br
- softwareTerms
- bash
- networking-terms
import:
- "@cspell/dict-pt-br/cspell-ext.json"
words:
- alice
- anubis
- asciidoctor
- bioinformaticians
- bioinformatics
- Clonezilla
- Configless
- cpus
- colortheme
- ctstate
- colorthemeswitcher
- comentario
- conntrack
- chacha
- cspell
- dport
- docker
- ESXi
- fail2ban
- findtime
- filesystems
- frontmatter
- GParted
- GSSAPI
- HELO
- hreflang
- icmpv6
- iptables
- ignoreip
- ip6tables
- ipset
- jails
- jsonld
- lastmod
- linkcheck
- linkinator
- lychee
- markdownlint
- markdownlintrc
- maxretry
- mathjax
- mermaid
- nerdalert
- netcat
- noindex
- nvme
- opencode
- oque
- pagination
- PagerSize
- QUIC
- rsync
- sshaudit
- sbatch
- scancel
- scontrol
- sinfo
- slurmd
- slurmctld
- slurmdbd
- squeue
- srun
- shortcodes
- Sysadmin
- sysadmin
- textlint
- trafego
- Thanwer
- TOML
- umami
- Umami
- VMDK
- VMX
- webserver
- yamllint
- ZgotmplZ
- arancibia
- thanwer
- bantime
- bootable
- configless
- dropadas
- dropar
- dropamos
- enablecolorthemeswitcher
- Gparted
- hugo
- icds
- JOBID
- nmap
- Nmap
- NODELIST
- nodelist
- ntasks
- readnextposts
- shinobi
- Slurm
- slurm
- slurmctl
- socialicons
- TIMELIMIT
- unbootable
- Universität
- pietro
flagWords: []
ignorePaths:
- node_modules
- public
- resources
- .git
- "*.gif"
- "*.png"
- "*.jpg"
- "*.webp"
- "*.svg"
- "*.ico"
- themes
- "package.json"
- "package-lock.json"
enableFiletypes:
- markdown

14
.lychee.toml Normal file
View File

@ -0,0 +1,14 @@
# https://github.com/lycheeverse/lychee
exclude = [
"linkedin\\.com",
"facebook\\.com",
"instagram\\.com",
"x\\.com",
"t\\.me",
"telegram\\.me",
"youtube\\.com",
"sshaudit\\.com",
]
exclude_private = true
timeout = 20
max_retries = 2

7
.lycheeignore Normal file
View File

@ -0,0 +1,7 @@
linkedin\.com
facebook\.com
instagram\.com
x\.com
t\.me
telegram\.me
youtube\.com

63
.markdownlint-cli2.yaml Normal file
View File

@ -0,0 +1,63 @@
# https://github.com/DavidAnson/markdownlint-cli2
frontMatter: (^\+\+\+$[^]*?^\+\+\+$)
globs:
- "content/**/*.md"
config:
MD001: true
MD003:
style: atx
MD004:
style: dash
MD005: true
MD007: false
MD009: true
MD010: true
MD011: true
MD012: true
MD013: false
MD014: true
MD018: true
MD019: true
MD020: true
MD021: true
MD022: true
MD023: true
MD024: false
MD025:
front_matter_title: "\\s*title\\s*="
level: 1
MD026: true
MD027: true
MD028: false
MD029:
style: one
MD030: true
MD031: true
MD032: true
MD033: false
MD034: false
MD035:
style: ---
MD036: true
MD037: true
MD038: true
MD039: true
MD040: true
MD041: false
MD042: true
MD043: false
MD044: false
MD045: true
MD046: false
MD047: true
MD048:
style: backtick
MD049:
style: underscore
MD050:
style: asterisk
MD051: false
MD052: true
MD053: true

56
.markdownlintrc Normal file
View File

@ -0,0 +1,56 @@
MD001: true
MD003:
style: atx
MD004:
style: dash
MD005: true
MD007: false
MD009: true
MD010: true
MD011: true
MD012: true
MD013: false
MD014: true
MD018: true
MD019: true
MD020: true
MD021: true
MD022: true
MD023: true
MD024: false
MD025:
front_matter_title: "\\s*title\\s*="
level: 1
MD026: true
MD027: true
MD028: false
MD029:
style: one
MD030: true
MD031: true
MD032: true
MD033: false
MD034: false
MD035:
style: ---
MD036: true
MD037: true
MD038: true
MD039: true
MD040: true
MD041: false
MD042: true
MD043: false
MD044: false
MD045: true
MD046: false
MD047: true
MD048:
style: backtick
MD049:
style: underscore
MD050:
style: asterisk
MD051: false
MD052: true
MD053: true

39
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,39 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-merge-conflict
- id: check-yaml
- id: check-toml
- id: check-json
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=auto]
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: check-added-large-files
args: [--maxkb=1024]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.48.0
hooks:
- id: markdownlint
args: [--config, .markdownlintrc]
- repo: https://github.com/streetsidesoftware/cspell-cli
rev: v10.0.0
hooks:
- id: cspell
args: [--no-summary, --no-progress, --config, .cspell.config.yaml]
additional_dependencies:
- "@cspell/dict-pt-br"
types: [markdown]
- repo: local
hooks:
- id: lychee
name: lychee
entry: lychee
args: [--config, .lychee.toml, --root-dir, static, --no-progress]
language: system
types: [markdown]

62
AGENTS.md Normal file
View File

@ -0,0 +1,62 @@
# AGENTS.md — Thanwer's Blog
## Project
Hugo blog (multilingual EN + PT-BR) using the **anubis2** theme as a git submodule (`themes/anubis2`, v1.6.0). Deployed via rsync to a VPS.
## Commands
```bash
hugo server # dev server at localhost:1313
hugo # build to ./public/ (no minify)
./deploy.sh # pre-commit → hugo --minify → rsync
pre-commit run --all-files # quality check (lint, spell, links)
```
## Quality toolchain
- **pre-commit** — runs on `./deploy.sh` or manually via `pre-commit run --all-files`. Includes: YAML/TOML/JSON check, EOF fixer, trailing whitespace, large file check, markdownlint, cspell (EN + PT-BR), lychee (link checker).
- **markdownlint** — config: `.markdownlintrc`. MD013 (line-length) disabled.
- **cspell** — config: `.cspell.config.yaml`. Adds `@cspell/dict-pt-br` via `additional_dependencies`. Custom word list for technical terms.
- **lychee** — config: `.lychee.toml`. Run locally with `--root-dir static` to resolve `/images/...` links. Excludes social media domains and `sshaudit.com`.
All quality tooling must pass before deploy. No separate test framework.
## Content conventions
- **Posts**: `content/posts/<slug>.md` (EN), `content/posts/<slug>.pt.md` (PT-BR)
- **Images**: `static/images/<filename>` — referenced as `/images/<filename>` in markdown
- **PT translations** set `canonical = "/posts/<en-slug>"` in frontmatter
- **`lastmod`** must use bare TOML datetime (no quotes): `lastmod = 2026-05-09T00:00:00Z`.
- **Archetype**: `hugo new posts/<slug>.md``archetypes/default.md`
- **Tags** only (no categories): `tags = ["GNU/Linux", "Tools"]`
## Configuration quirks
- Theme requires **all lowercase keys** under `params.anubis2`: `colortheme`, `enablecolorthemeswitcher`, `readnextposts`, `socialicons`, etc. See `themes/anubis2/PARAMS.md`.
- `[params.anubis2.services.umami]` keys are lowercase: `id`, `url`.
- Analytics inject only in production (`hugo.IsProduction`). Test locally: `hugo server --environment production`.
- Config uses modern Hugo keys: `locale` (not `languageCode`), `label` (not `languageName`).
- `enableGitInfo = true``.Lastmod` derives from git; template prefers `.Params.lastmod` when set.
## Frontmatter reference
```toml
date = 2024-10-26T16:59:23-03:00
lastmod = 2026-05-09T00:00:00Z
title = 'Post Title'
description = "One-line summary for SEO and RSS"
tags = ["tag1", "tag2"]
canonical = "/posts/original-post" # PT translations only
```
## Deprecated keys (avoid)
- `languageCode``locale`; `languageName``label`; `.Site.LanguageCode``.Site.Language.Locale`
## Theme overrides (`layouts/` shadows theme)
- `layouts/partials/head.html` — canonical from `Params.canonical`, Umami analytics (prod only)
- `layouts/partials/post-info.html` — prefers `.Params.lastmod`, shows "[Last modified]" when different from date
- `layouts/partials/footer-extra.html` — Ko-fi widget
- `layouts/robots.txt` — custom sitemap for both languages

13
content/about.md Normal file
View File

@ -0,0 +1,13 @@
+++
date = '2026-05-09T00:00:00Z'
title = 'About'
description = "About Pietro Arancibia \"Thanwer\""
+++
Hey, I'm **Pietro Arancibia** (a.k.a. **Thanwer**), a sysadmin and GNU/Linux enthusiast currently based in Germany.
I created this blog to share practical knowledge about server administration, security, and tools I use in my day-to-day work. Most posts come from real problems I've encountered — things I wished someone had told me earlier.
When I'm not breaking or fixing servers, I enjoy contributing to open-source projects and exploring new technologies.
Feel free to reach out via any of the social links on this site!

14
content/about.pt.md Normal file
View File

@ -0,0 +1,14 @@
+++
date = '2026-05-09T00:00:00Z'
title = 'Sobre'
description = "Sobre Pietro Arancibia \"Thanwer\""
canonical = "/about"
+++
Olá, sou **Pietro Arancibia** (conhecido como **Thanwer**), um sysadmin e entusiasta de GNU/Linux atualmente baseado na Alemanha.
Criei este blog para compartilhar conhecimento prático sobre administração de servidores, segurança e ferramentas que uso no meu dia a dia. A maioria dos posts vem de problemas reais que encontrei - coisas que gostaria que alguém tivesse me dito antes.
Quando não estou quebrando ou consertando servidores, gosto de contribuir com projetos open-source e explorar novas tecnologias.
Sinta-se à vontade para entrar em contato através dos links sociais deste site!

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-23T20:43:44-03:00'
title = 'Securing Your Linux Server with Fail2Ban for SSH Protection'
description = "Thanwer's Blog Post about how to use Fail2ban"
description = "Learn how to configure Fail2ban SSH jails on GNU/Linux with custom ban times, multipliers, and trusted IPs to protect your server from brute-force attacks."
tags = [
"webserver",
"GNU/Linux",
@ -10,7 +10,7 @@ tags = [
+++
[Fail2ban](https://github.com/fail2ban/fail2ban) is a software which scans log files like `/var/log/auth.log` and bans IP addresses which have done too many failed login attempts.
This is just one layer of security and should be used together with other tools and techniques such as iptables and SSH hardening.
I always configure a simple SSH jail using fail2ban on my GNU/Linux servers.
@ -42,9 +42,7 @@ Next, we have the `jail.conf` file, which is where we configure our "jails" as t
Here we are going to make some changes, so first let's create a `jail.local` file with the following content:
```
```text
[DEFAULT]
bantime.multipliers = 5 15 30 60 300 720 1440 2880
bantime = 86400
@ -65,7 +63,8 @@ Take your time to fine tune those parameters.
## Enabling and testing Fail2ban
Enable and start the fail2ban service
Enable and start the fail2ban service
```bash
systemctl enable --now fail2ban
```
@ -73,6 +72,7 @@ systemctl enable --now fail2ban
For SSH jails the test is very simple, you can test by simply failing to log on a couple times.
You can check the status with the command:
```bash
fail2ban-client status
fail2ban-client status sshd
@ -82,11 +82,11 @@ fail2ban-client status sshd
As you can see, the SSH service on a cloud VPS is constantly being attacked, my server has almost 400 currently banned IPs.
## More jails!
## More jails
Today I showed how to protect your GNU/Linux SSH service, but is is not over, you can use fail2ban to protect any service that you run!
You can even integrate fail2ban protection with [Cloudflare](https://github.com/nesrual/fail2ban-cloudflare!) if you use their services!
You can even integrate fail2ban protection with [Cloudflare](https://github.com/nesrual/fail2ban-cloudflare) if you use their services!
I suggest you to check the `/etc/fail2ban/filter.d` folder, which contains the default filters for popular services, like Apache, Asterisk, Mail server services, etc...

View File

@ -1,20 +1,20 @@
+++
date = '2024-10-23T20:43:44-03:00'
title = 'Melhorando a segurança do seu servidor com o Fail2ban'
description = "Post no blog do Thanwer sobre como usar o Fail2ban"
description = "Aprenda a configurar jails SSH no Fail2ban em GNU/Linux com tempos de banimento customizados, multiplicadores e IPs confiáveis para proteger seu servidor contra ataques de força bruta."
tags = [
"webserver",
"GNU/Linux",
"Basics Series",
]
canonical = "posts/fail2ban-basics"
canonical = "/posts/fail2ban-basics"
+++
[Fail2ban](https://github.com/fail2ban/fail2ban) é um software que escaneia arquivos de logs como o `/var/log/auth.log` e bane IPs com muitas falhas de login.
Isso é apenas uma camada de segurança e deve ser utilizada em conjunto com outras ferramentas como o iptables e melhorias na configuração do SSH.
Eu sempre configuro uma jail para SSH usando o fail2ban em meus servidoresGNU/Linux.
Eu sempre configuro uma jail para SSH usando o fail2ban em meus servidores GNU/Linux.
Hoje, vou mostrar como configurar uma jail simples para o SSH.
@ -43,7 +43,7 @@ Depois, temos o arquivo `jail.conf` , que é onde configuramos as nossas "jails"
Aqui vamos fazer algumas alterações, primeiro vamos criar um arquivo `jail.local` com o seguinte conteúdo:
```
```text
[DEFAULT]
bantime.multipliers = 5 15 30 60 300 720 1440 2880
bantime = 86400
@ -65,6 +65,7 @@ Invista seu tempo para estudar e melhorar esses parâmetros.
## Ativando e testando o fail2ban
Ative e inicie o serviço fail2ban:
```bash
systemctl enable --now fail2ban
```
@ -72,6 +73,7 @@ systemctl enable --now fail2ban
Para testar as jails SSH, basta tentar o login com a senha incorreta por algumas vezes.
Voce pode ver o status com os seguintes comandos:
```bash
fail2ban-client status
fail2ban-client status sshd
@ -81,11 +83,11 @@ fail2ban-client status sshd
Como voce pode ver, o serviço SSH em uma VPS em cloud é constantemente atacada, meu servidor já tem mais de 400 IPs banidos.
## Mais jails!
## Mais jails
Hoje eu mostrei como proteger o seu serviço SSH em uma maquina GNU/Linux, mas não acabou, voce pode usar o fail2ban para qualquer serviço!
Voce pode até mesmo integrar a proteção do fail2ban com o [Cloudflare](https://github.com/nesrual/fail2ban-cloudflare!) se voce usa os serviços deles!
Voce pode até mesmo integrar a proteção do fail2ban com o [Cloudflare](https://github.com/nesrual/fail2ban-cloudflare) se voce usa os serviços deles!
Eu sugiro que voce veja o diretório `/etc/fail2ban/filter.d`, que contem os filtros default para vários serviços populares, como Apache, Asterisk, serviços de servidor de e-mail, etc...

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-19T23:00:41-03:00'
title = 'First Post'
description = "A simple blog post about using Hugo as a framework for Thanwer's Blog"
description = "Starting a sysadmin blog with Hugo: why I chose a static site generator, how I deployed it on Hetzner Cloud via rsync, and what to expect from this blog."
tags = [
"webserver",
"Hugo",
@ -28,9 +28,10 @@ You can also use [Github Pages](https://pages.github.com/) or [Cloudflare Pages]
## The deploy
I followed Hugo guide to [deploy by rsync](https://gohugo.io/hosting-and-deployment/deployment-with-rsync/) and I worked very well for me.
I followed Hugo guide to [deploy by rsync](https://gohugo.io/hosting-and-deployment/deployment-with-rsync/) and it worked very well for me.
A simple bash script to generate the website and deploy, all in one line!
```bash
#!/usr/bin/env bash
hugo && rsync -avz --delete public/ shinobi.thanwer.com:/srv/www/blog/
@ -38,4 +39,4 @@ hugo && rsync -avz --delete public/ shinobi.thanwer.com:/srv/www/blog/
## The Conclusion
From my experience until now (which is only the contents of this post btw...), this platform seems to meet my expectations I will continue to bring move topics and keep you guys posted!
From my experience until now (which is only the contents of this post btw...), this platform seems to meet my expectations I will continue to bring more topics and keep you guys posted!

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-19T23:00:41-03:00'
title = 'First Post'
description = "A simple bolog post about using Hugo as a framework for Thanwer's Blog"
description = "Iniciando um blog de sysadmin com Hugo: por que escolhi um gerador de sites estáticos, como fiz deploy na Hetzner Cloud via rsync, e o que esperar deste blog."
tags = [
"webserver",
"Hugo",
@ -32,6 +32,7 @@ Você também pode usar o [Github Pages](https://pages.github.com/) ou o [Cloudf
Eu segui o guia oficial do Hugo para fazer o [deploy via rsync](https://gohugo.io/hosting-and-deployment/deployment-with-rsync/) e ele funcionou bem para mim
Um simples script em bash gera o site e faz o deploy, tudo em uma linha!
```bash
#!/usr/bin/env bash
hugo && rsync -avz --delete public/ shinobi.thanwer.com:/srv/www/blog/
@ -39,4 +40,4 @@ hugo && rsync -avz --delete public/ shinobi.thanwer.com:/srv/www/blog/
## Conclusões
Pela minha experiência até agora (que foi apenas o conteúdo dessa pagina a propósito...), essa plataforma parece atender minhas expectativas, vou continuar trazendo mais conteúdo e mantenho vocês informados.
Pela minha experiência até agora (que foi apenas o conteúdo dessa página a propósito...), essa plataforma parece atender minhas expectativas, vou continuar trazendo mais conteúdo e mantenho vocês informados.

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-20T14:22:33-03:00'
title = 'Mastering iptables: Basic Firewall Configurations for a Newly Deployed GNU/Linux Server'
description = "The basic usage of iptables for a new GNU/Linux webserver"
description = "A step-by-step guide to configuring iptables firewall rules for a newly deployed GNU/Linux server, covering SSH, HTTP/HTTPS, IPv6, ICMP, and security best practices."
tags = [
"webserver",
"GNU/Linux",
@ -34,12 +34,14 @@ You can check those well made diagrams by [nerdalert on Github](https://gist.git
Lets start with a quick overview of essential iptables commands:
### Show current rules:
### Show current rules
```bash
iptables -L
```
### Flush (delete) all existing rules:
### Flush (delete) all existing rules
```bash
iptables -F
```
@ -87,6 +89,7 @@ iptables -P OUTPUT ACCEPT
```
This means any incoming connection will be dropped unless you explicitly allow it. Outgoing traffic, on the other hand, is allowed by default.
### Step 3: Allow SSH Traffic (Port 22)
Youll need SSH access to manage your server, so well allow traffic on port 22. Its important to only allow connections from trusted IP addresses, but for simplicity, well start by allowing all connections on SSH:
@ -151,8 +154,7 @@ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
This ensures your server can accept responses to any requests it makes.
### Step 8: Don't forget IPv6 rules!
### Step 8: Don't forget IPv6 rules
For IPv6, the syntax is the same, you mostly need to change the command from `iptables` to `ip6tables`

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-20T14:22:33-03:00'
title = 'Dominando o iptables: Configurações básicas de firewall para um servidor GNU/Linux recém-implantado'
description = "O uso básico do iptables para um novo servidor web GNU/Linux"
description = "Um guia passo a passo para configurar regras de firewall iptables em um servidor GNU/Linux recém-implantado, cobrindo SSH, HTTP/HTTPS, IPv6, ICMP e melhores práticas de segurança."
tags = [
"webserver",
"GNU/Linux",
@ -14,7 +14,7 @@ Ao configurar um novo servidor Linux, uma das primeiras coisas que você deve fa
Nesta publicação, vou mostrar algumas configurações básicas do iptables para um servidor recém-implantado. No final, você terá um firewall sólido para proteger seus serviços e, ao mesmo tempo, manter as coisas simples.
## Oque é iptables?
## O que é iptables?
iptables é um utilitário de firewall por linha de comando que utiliza "chains" para gerenciar o tráfego. As "chains" são basicamente uma lista de regras que aplicam para pacotes de entrada ou saída. Voce pode fazer as regras baseadas em IPs de origem, portas e protocolos, por exemplo.
@ -36,11 +36,13 @@ Recomendo que voce inicie por esses diagramas muito bem feitos nesse gist do [ne
Vamos começar com uma overview dos comandos essenciais do iptables:
### Mostrar regras atuais
```bash
iptables -L
```
### Deletar regras atuais
```bash
iptables -F
```
@ -153,7 +155,7 @@ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Isso garante que o servidor aceite respostas para requisições que ele mesmo fez.
### Passo 8: Não esqueça o IPv6!
### Passo 8: Não esqueça o IPv6
Para IPv6, a sintaxe é a mesma, voce apenas precisa mudar o comando de `iptables` para `ip6tables`

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-31T22:00:53-03:00'
title = 'Netcat'
description = "Thanwer's Blog Post about netcat tool"
description = "Learn how to use netcat (nc) for network troubleshooting, file transfers, port scanning, and directly communicating with services like SMTP on GNU/Linux."
tags = [
"GNU/Linux",
"Tools",
@ -12,8 +12,8 @@ I heard too many times a work colleague, or a third-party vendor telling me "the
You can use websites to try to test for open ports, a quick Google search should yield you a couple good results, but sometimes that is not enough.
## The problem
Let's suppose you followed my other post about [restricting source address with iptables]({{< ref "/posts/iptables-basics" >}} "Iptables basics"), in this case those random web tools won't help you, so you use nmap!
I will make a post about nmap in the future, for now let's use it in a very simple way to check for an open port:
@ -47,8 +47,9 @@ Netcat is a very simple, but very powerful tool for those cases. With that, you
### Installing
The installation is very simple! On Debian systems you can do it with the command:
```bash
$ sudo apt install netcat
sudo apt install netcat
```
### Using the tool
@ -57,24 +58,27 @@ I invite you to read the [nc man page](https://linux.die.net/man/1/nc).
The main options we need to use are:
* -p : This specifies which port should we listen to.
* -k : This makes netcat to keep listening after a client disconnects.
* -u : Use UDP instead of TCP
- -p : This specifies which port should we listen to.
- -k : This makes netcat to keep listening after a client disconnects.
- -u : Use UDP instead of TCP
### Use cases
#### Check for network communication
Listen on port 2000:
```bash
$ nc -l 2000
nc -l 2000
```
This starts a server which listens on port 2000.
Now let's open another terminal and connect a client to this port:
```bash
$ nc localhost 2000
nc localhost 2000
```
@ -91,12 +95,13 @@ Netcat can be used to send and receive files between two hosts over a network. T
On the receivers end:
```bash
$ nc -l -p 1234 > received_file.txt
nc -l -p 1234 > received_file.txt
```
On the senders end, assuming that 192.0.0.2 is the receiving server:
```bash
$ nc 192.0.0.2 -p 1234 < file_to_send.txt
nc 192.0.0.2 1234 < file_to_send.txt
```
This can be useful for transferring a log file for example.
@ -104,6 +109,7 @@ This can be useful for transferring a log file for example.
#### Port scan
nc can be used as a simpler alternative to nmap:
```bash
$ nc -z host.example.com 20-30
Connection to host.example.com 22 port [tcp/ssh] succeeded!

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-31T22:00:53-03:00'
title = 'Netcat'
description = "Post no blog do Thanwer sobre a ferramenta netcat."
description = "Aprenda a usar o netcat (nc) para troubleshooting de rede, transferência de arquivos, varredura de portas e comunicação direta com serviços como SMTP no GNU/Linux."
tags = [
"GNU/Linux",
"Tools",
@ -14,6 +14,7 @@ Já ouvi muitas vezes de algum colega de trabalho, ou de algum prestador de serv
Voce pode usar testes online para verificar as portas abertas, uma rápida pesquisa no Google vai te trazer resultados bons, porem nem sempre isso é o suficiente.
## O problema
Vamos supor que voce seguiu meu outro post sobre [restringir por endereço de origem no iptables]({{< ref "/posts/iptables-basics" >}} "Iptables basics"), nesse caso essas ferramentas aleatórias não vão lhe ajudar, então voce pode usar o nmap!
Farei um post sobre o nmap no futuro, por enquanto vamos usa-lo de forma bem simples para verificar se uma porta está aberta:
@ -47,8 +48,9 @@ O netcat é uma ferramenta super simples, porem muito poderosa para esses casos.
### Instalação
A instalação é muito simples! Em um sistema Debian basta executar o seguinte comando:
```bash
$ sudo apt install netcat
sudo apt install netcat
```
### Usando a ferramenta
@ -57,24 +59,27 @@ Lhes convido a ler a [man page do nc](https://linux.die.net/man/1/nc).
Os principais argumentos que precisamos são:
* -p : Isso especifica a porta que queremos escutar.
* -k : Isso mantem o netcat aberto mesmo depois do cliente desconectar.
* -u : Isso faz o netcat escutar UDP em vez de TCP.
- -p : Isso especifica a porta que queremos escutar.
- -k : Isso mantém o netcat aberto mesmo depois do cliente desconectar.
- -u : Isso faz o netcat escutar UDP em vez de TCP.
### Casos de uso
#### Testar comunicação de rede
Escutar na porta 2000:
```bash
$ nc -l 2000
nc -l 2000
```
Isso inicia um serviço na porta 2000.
Agora vamos abrir outro terminal e conectar um cliente a essa porta:
```bash
$ nc localhost 2000
nc localhost 2000
```
@ -84,26 +89,28 @@ Veja um exemplo disso funcionando:
![Exemplo de uso do netcat](/images/nc-example1.gif)
#### Transferencia de arquivos
#### Transferência de arquivos
O netcat tambem pode ser usado para transferir arquivos entre dois hosts, isso é util em casos de ambientes sem acessos FTP ou SCP.
O netcat também pode ser usado para transferir arquivos entre dois hosts, isso é util em casos de ambientes sem acessos FTP ou SCP.
No receptor:
```bash
$ nc -l -p 1234 > arquivo_recebido.txt
nc -l -p 1234 > arquivo_recebido.txt
```
No transmissor, assumindo que o receptor seja o IP 192.0.0.2::
```bash
$ nc 192.0.0.2 -p 1234 < arquivo_para_enviar.txt
nc 192.0.0.2 1234 < arquivo_para_enviar.txt
```
Isso pode ser util para transferir um arquivo de log, por exemplo.
#### Port scac
#### Port scan
nc pode ser usado como uma alternativa "mais barata" para o nmap:
```bash
$ nc -z host.example.com 20-30
Connection to host.example.com 22 port [tcp/ssh] succeeded!

View File

@ -0,0 +1,82 @@
+++
date = '2025-10-12T13:41:47+02:00'
title = 'Shrink VMDK safely'
description = "A safe method to shrink VMDK disk files in ESXi using Clonezilla, without manually editing VMX files — perfect for physical-to-virtual conversions."
tags = [
"GNU/Linux",
"Tools",
"ESXi",
]
+++
I recently was tasked to convert a couple physical servers to virtual machines.
The physical servers basically had 2x 500GB SSDs in a RAID1 configuration, and the total used space was around 40GB.
To optimize the storage for the virtual machines, I needed to resize the VMDK files accordingly. This process involves several steps to ensure data integrity and minimize downtime.
Here I document the steps I took to safely resize a VMDK file, without modifying the virtual machine description file (VMX).
## Step 1: Prepare the physical server
Before starting, I did the basic preparation on the physical server:
- As always, create a full backup of the server, just in case something goes wrong
- Clean up unnecessary files to reduce the used space
- Resize the partitions and filesystems to reduce the used space to the smallest possible size (in my case, I resized the partitions to 64GB to have some buffer space)
![Gparted result](/images/vmdk-gparted-1.jpg)
## Step 2: Convert the physical server to a virtual machine
I used Clonezilla to convert the physical server to a virtual machine.
This process was simply:
- Boot the physical server with the Clonezilla live CD
- Follow the wizard and choose the mode "remote-source" to send the image to a remote server
- On the target virtual machine, boot with the Clonezilla live CD and choose the mode "remote-dest" to receive the image from the physical server
- Inform the IP address of the physical server and start the process
> Important: In this first step, I created a VMDK file with the same size as the physical disk (in my case, 500GB).
![ESXi Disks](/images/vmdk-disks.jpg)
## Step 3: Resize the VMDK file
Now here is the tricky part, resizing the VMDK file without modifying the VMX file.
Many tutorials suggest to access the ESXi host via SSH and directly modify the VMX file using a text editor.
I found it to be very risky, as a small mistake can lead to a non-bootable virtual machine. I lost many attempts this way.
So instead, I found a method that works without modifying the VMX file:
Use Clonezilla again, to clone the virtual machine to a new virtual machine with a smaller disk size.
This time we are going to use local disks, so we need to create a new virtual machine with a smaller disk size (in my case, 64GB).
![Clonezilla local source](/images/clonezilla-1.jpg)
When asked about the partition table, choose the option "-k1" to keep the partition table as is.
![Clonezilla partition table](/images/clonezilla-2.jpg)
When asked about the mode, choose "Expert"
![Clonezilla expert mode](/images/clonezilla-3.jpg)
Then, enable the option "-icds" to ignore the disk size.
![Clonezilla ignore disk size](/images/clonezilla-4.jpg)
After following the rest of the wizard, the cloning process will start.
![Clonezilla cloning](/images/clonezilla-5.jpg)
After the process is complete, you will have a new virtual machine with a smaller disk size, and the same data as the original virtual machine.
This will leave you with the original VMDK file, and a smaller one. After verifying that the new virtual machine works correctly, you can delete the original one to free up space.
## Conclusion
Using Clonezilla to clone to a smaller disk is a safe and reliable method to shrink VMDK files without touching VMX files directly. While it requires more steps than the manual approach, it reduces the risk of configuration errors that could leave your VM unbootable. As always, keep backups before attempting any disk resizing operations.

150
content/posts/slurm.md Normal file
View File

@ -0,0 +1,150 @@
+++
date = '2025-09-14T10:19:39+02:00'
title = 'Slurm'
description = "A practical guide to setting up a Slurm cluster for HPC and bioinformatics workloads, covering installation, configuration, job submission, and useful tips."
tags = [
"GNU/Linux",
"Tools",
]
+++
So recently I was tasked to set up a Slurm cluster for bioinformatics workloads, and I thought it would be a good idea to share some notes about the installation and configuration process.
So in short, before we start, what is Slurm?
> Slurm is an open-source, fault-tolerant, and highly scalable cluster management and job scheduling system for large and small Linux clusters. It is used by many of the world's supercomputers and computer clusters.
Before we have Slurm, the bioinformaticians used to access the servers directly via SSH, open a screen/tmux session, and run their jobs directly on the servers. This approach has several drawbacks:
- Resource management: Without a job scheduler, it is difficult to manage and allocate resources efficiently. Suppose you have a server with 128Gb of RAM, and your job requires 32Gb of RAM to run, so you think "I can run 4 jobs in parallel", but if another user is running a job that requires 64Gb of RAM, your job will fail due to insufficient resources.
- Job prioritization: Without a job scheduler, it is difficult to prioritize jobs based on their importance or urgency. This can lead to situations where less important jobs are running while more important jobs are waiting in the queue.
- Queue management: Without a job scheduler, the users have to manually monitor and send new jobs when the previous one finishes. Now imagine that your job takes 16 hours to run, will you SSH into the server at 2AM to send a new job? Probably not, so your jobs will be delayed, and the server will just sit idle while you sleep. This is inefficient.
With that in mind, it is very clear that we need a job scheduler, and Slurm is a great choice for that.
## Slurm operation
### The main workflow
The main workflow of Slurm is quite simple:
1. The user creates a special bash script with the job parameters and the commands to run. Here is an example of such a script:
```bash
#!/usr/bin/env bash
#SBATCH --job-name=my_job_name # Job name
#SBATCH --output=output.txt # Standard output file
#SBATCH --error=error.txt # Standard error file
#SBATCH --partition=partition_name # Partition or queue name
#SBATCH --nodes=1 # Number of nodes
#SBATCH --ntasks-per-node=1 # Number of tasks per node
#SBATCH --cpus-per-task=1 # Number of CPU cores per task
#SBATCH --time=1:00:00 # Maximum runtime (D-HH:MM:SS)
#SBATCH --mail-type=END # Send email at job completion
#SBATCH --mail-user=your@email.com # Email address for notifications
# Your commands go here
module load python/3.8
python my_script.py
```
1. The user submits a job to the Slurm controller using the `sbatch` command
1. The Slurm controller adds the job to the queue and assigns it a job ID
1. The Slurm controller schedules the job to run on a compute node when resources are available
1. The compute node executes the job and sends the output back to the user
The user can monitor the job status using the `squeue` command.
The administrator can manage the jobs using the `scancel` command to cancel a job, or the `scontrol` command to modify a job.
### Basic commands
Check the cluster status:
```bash
sinfo
PARTITION AVAIL TIMELIMIT NODES STATE NODELIST
debug up 30:00 2 idle node[01-02]
batch up 2-00:00:00 4 alloc node[03-06]
```
List running and pending jobs:
```bash
squeue
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
1001 batch test.sh alice R 0:15 2 node[03-04]
1002 debug script bob PD 0:00 1 (Priority)
```
Run a interactive job session:
```bash
srun --partition=debug --nodes=1 --ntasks=1 --time=00:30:00 --pty bash
```
Cancel a job:
```bash
scancel <job_id>
```
## Slurm components
The main components of Slurm are:
- **slurmctld**: The Slurm controller daemon, which is responsible for managing the cluster and scheduling jobs. It runs on the controller node.
- **slurmd**: The Slurm daemon, which is responsible for executing jobs on the compute nodes. It runs on each compute node.
- **slurmdbd**: The Slurm database daemon, which is responsible for storing job and user information in a database. It is optional and can run on a separate node.
## Installation
The installation process is quite straightforward, and you can find the [official documentation here](https://slurm.schedmd.com/quickstart_admin.html).
In my case, I used a Debian 13 VM as a all-in-one controller and multiple physical Debian 13 servers as compute nodes. In a more robust setup, you would probably want to have a separate database server, multiple controllers for redundancy, but for this example, since the cluster is relatively small, we will use the simplest configuration.
Since the configuration is the same for all nodes, this is a perfect scenario to be automated by [Ansible](https://www.ansible.com/), I will not cover the Ansible part here, but I will show you the main gotchas that I encountered and would save a lot of time.
## Tips and tricks
### Find real user documentation
Since Slurm is very popular in the academic world, there are many universities that have their own documentation, it is a very good starting point to find real user documentation, here are some examples that were very helpful to me:
[Advanced Research Computing at Hopkins](https://docs.arch.jhu.edu/en/latest/)
[Freie Universität Berlin](https://wiki.physik.fu-berlin.de/it/services:cluster:slurm)
[HPC Wiki](https://hpc-wiki.info/hpc/HPC_Wiki)
### Configless mode
Since Slurm 20.02, it supports a configless mode, which means that you don't need to create a `/etc/slurm/slurm.conf` file on each node, instead, you can use the `slurmctld` to distribute the configuration to all nodes.
This is a great feature, and it simplifies the installation process a lot. [The official documentation is here](https://slurm.schedmd.com/configless_slurm.html).
It just depends on a very simple DNS SRV record, so make sure you have that set up correctly. Take this example, and adapt it to your setup:
```bind
_slurmctld._tcp 3600 IN SRV 10 0 6817 slurmctl-backup
_slurmctld._tcp 3600 IN SRV 0 0 6817 slurmctl-primary
```
### Slurm-web
Slurm-web is a web interface for Slurm, which allows you (and your users!) to monitor and manage the cluster from a web browser. You can find the [official documentation here](https://slurm-web.com/).
![Slurm-web dashboard](/images/slurm-web.webp)
### slurm-mail
Slurm-Mail is a drop in replacement for Slurm's e-mails to give users much more information about their jobs compared to the standard Slurm e-mails. You can find it [on Github](https://github.com/neilmunday/slurm-mail).
### Power saving
Now this is a feature that I was not aware of, but Slurm has a built-in power saving feature, which allows you to automatically power on/off compute nodes based on demand.
It relies on scripts that you need to provide, one for powering on a node, and another for powering off a node. You can find the [Slurm power saving documentation](https://slurm.schedmd.com/power_save.html).
In my case I adapted [the scripts from here](https://github.com/OleHolmNielsen/Slurm_tools/tree/master/power_save) to use IPMI to power on/off the nodes.

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-26T16:59:23-03:00'
title = 'Advanced SSH Hardening Best Practices'
description = "Thanwer's Blog Post about best practices for hardening SSH servers"
description = "Strengthen your SSH server security with key-based authentication, 2FA, encryption hardening, access restrictions, and disabling unused auth methods."
tags = [
"webserver",
"GNU/Linux",
@ -55,6 +55,7 @@ When you finish those tunings, check with the [ssh-audit tool](https://github.co
On top of iptables rules, the `/etc/ssh/sshd_config` file can apply restrictions based on users, groups and/or source IPs.
For example, to whitelist by IP, you can add the following:
```bash
# Restrict to a specific IP address
AllowUsers *@192.0.0.2
@ -63,4 +64,8 @@ AllowUsers *@192.0.0.0/24
# Restrict a specific user to a specific IP address
Match User alice
AllowUsers alice@192.0.0.2
```
```
## Conclusion
SSH hardening is a continuous process, not a one-time task. Start with key-based authentication and disable password login, then layer on 2FA, encryption hardening, and access restrictions. Combined with iptables rules and fail2ban jails, you can significantly reduce the attack surface of your server. Review your SSH configuration periodically and keep up with the latest security recommendations.

View File

@ -1,7 +1,7 @@
+++
date = '2024-10-26T16:59:23-03:00'
title = 'Boas praticas para configuração do SSH'
description = "Post no blog do Thanwer sobre melhores praticas para configurar servidores SSH"
title = 'Boas práticas para configuração do SSH'
description = "Fortaleça a segurança do seu servidor SSH com autenticação por chaves, 2FA, hardening de criptografia, restrições de acesso e desativação de métodos não utilizados."
tags = [
"webserver",
"GNU/Linux",
@ -14,7 +14,7 @@ Como voce pode ver no meu ultimo post sobre [fail2ban]({{< ref "/posts/fail2ban-
Além de regras do iptables e jails do fail2ban, temos alguns parâmetros que podemos ajustar nos nossos servidores para termos mais uma medida de proteção.
## Métodos de autenticação SSG
## Métodos de autenticação SSH
Por padrão, o serviço SSH permite autenticação por senha para todos os usuários exceto o root, oque é um bom começo, mas podemos fazer mais.
@ -33,7 +33,7 @@ PermitRootLogin no
### Autenticação por dois fatores (2FA)
Se voce quiser mais um nivel de segurança, voce pode implementar [autenticação de dois fatores com o Google Autenticator](https://docs.vultr.com/how-to-use-two-factor-authentication-with-sudo-and-ssh-on-linux-with-google-authenticator).
Se voce quiser mais um nível de segurança, voce pode implementar [autenticação de dois fatores com o Google Authenticator](https://docs.vultr.com/how-to-use-two-factor-authentication-with-sudo-and-ssh-on-linux-with-google-authenticator).
### Desativar métodos de autenticação não utilizados
@ -47,7 +47,7 @@ GSSAPIAuthentication no
## Melhorar algoritmos de criptografia do SSH
Seguindo [esses guias](https://www.sshaudit.com/hardening_guides.html) voce pode desabilitar algoritmos de autenticação e criptografia vulneraveis e priorizar os mais fortes (AES-256-GCM, chacha20-poly1305).
Seguindo [esses guias](https://www.sshaudit.com/hardening_guides.html) voce pode desabilitar algoritmos de autenticação e criptografia vulneráveis e priorizar os mais fortes (AES-256-GCM, chacha20-poly1305).
Quando voce terminar, verifique com a ferramenta [ssh-audit](https://github.com/jtesta/ssh-audit).
@ -56,6 +56,7 @@ Quando voce terminar, verifique com a ferramenta [ssh-audit](https://github.com/
Além das regras do iptables, o arquivo `/etc/ssh/sshd_config` pode aplicar restrições baseadas em usuário, grupo e/ou IP de origem.
Por exemplo, para permitir conexões por IP, voce pode adicionar conforme os seguintes exemplos:
```bash
# Restrict to a specific IP address
AllowUsers *@192.0.0.2
@ -64,4 +65,8 @@ AllowUsers *@192.0.0.0/24
# Restrict a specific user to a specific IP address
Match User alice
AllowUsers alice@192.0.0.2
```
```
## Conclusão
O hardening do SSH é um processo contínuo, não uma tarefa única. Comece com autenticação por chaves e desative o login por senha, depois adicione camadas como 2FA, hardening de criptografia e restrições de acesso. Combinado com regras de iptables e jails do fail2ban, você pode reduzir significativamente a superfície de ataque do seu servidor. Revise sua configuração SSH periodicamente e mantenha-se atualizado com as recomendações de segurança mais recentes.

View File

@ -1,2 +1,16 @@
#!/usr/bin/env bash
hugo && rsync -avz --delete public/ shinobi.thanwer.com:/srv/www/blog/
set -euo pipefail
echo "=== Running pre-commit hooks ==="
pre-commit run --all-files
echo ""
echo "=== Building site ==="
hugo --minify
echo ""
echo "=== Deploying to server ==="
rsync -avz --delete public/ shinobi.thanwer.com:/srv/www/blog/
echo ""
echo "=== Done ==="

View File

@ -1,28 +1,87 @@
baseURL = 'https://blog.thanwer.com/'
languageCode = 'en-us'
locale = 'en-US'
theme = "anubis2"
title = "Thanwer's Blog"
enableRobotsTXT = true
enableGitInfo = true
defaultContentLanguage = 'en'
[pagination]
pagerSize = 5
[taxonomies]
tag = "tags"
[languages]
[languages.en]
languageName = 'EN'
label = 'EN'
weight = 1
[languages.pt]
languageName = 'BR'
languageCode = 'pt-BR'
[[languages.en.menu.main]]
identifier = "home"
name = "Home"
url = "/"
weight = 1
[[languages.en.menu.main]]
identifier = "tags"
name = "Tags"
url = "/tags/"
weight = 2
[[languages.en.menu.main]]
identifier = "archive"
name = "Archive"
url = "/posts/"
weight = 3
[[languages.en.menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 4
[languages.pt]
label = 'BR'
locale = 'pt-BR'
weight = 2
[[languages.pt.menu.main]]
identifier = "home"
name = "Início"
url = "/"
weight = 1
[[languages.pt.menu.main]]
identifier = "tags"
name = "Tags"
url = "/tags/"
weight = 2
[[languages.pt.menu.main]]
identifier = "archive"
name = "Arquivo"
url = "/posts/"
weight = 3
[[languages.pt.menu.main]]
identifier = "about"
name = "Sobre"
url = "/about/"
weight = 4
[params]
author.name = "Pietro Arancibia"
author.email = "admin@thanwer.com"
author.location = "Germany"
colorTheme = "auto"
colorThemeSwitcher = true
description = "A blog written by Pietro Arancibia \"Thanwer\""
social = [
dateFormat = "2006-01-02"
[params.anubis2]
colorTheme = "auto"
enableColorThemeSwitcher = true
readNextPosts = 3
socialIcons = [
{ id = "github", url = "https://github.com/thanwer" },
{ id = "linkedin", url = "https://www.linkedin.com/in/thanwer/" },
{ id = "email", url = "mailto:admin@thanwer.com" },
@ -30,10 +89,15 @@ social = [
{ id = "instagram", url = "https://www.instagram.com/thanwer/" },
{ id = "telegram", url = "https://t.me/thanwer" },
{ id = "x", url = "https://x.com/thanwer" },
{ id = "youtube", url = "https://youtube.com/lordthanwer" }
{ id = "youtube", url = "https://youtube.com/lordthanwer" },
]
[params.anubis2.services.umami]
id = "eded536d-a598-471a-945b-faaebc4837a9"
url = "https://umami.thanwer.com/script.js"
[markup.goldmark.renderer]
unsafe = true # true | false; Enable unsafe mode to have a better experience
unsafe = true
[markup.highlight]
style = 'base16-snazzy' # Highlight.js style
style = 'base16-snazzy'

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
{{/* Cache params once for all partials */}}
{{ $params := partial "params-helper.html" . }}
{{ $dataTheme := "" }}
{{ if and (ne $params.colorTheme "auto") (eq $params.enableColorThemeSwitcher false) }}
{{ $dataTheme = $params.colorTheme }}
{{ end }}
<html lang="{{ site.Params.locale | default site.Language.Locale }}" data-theme="{{ $dataTheme }}">
<head>
{{ block "head" . }}
{{ partial "head/head.html" . }}
{{ end }}
</head>
<body>
<a class="skip-main" href="#main">{{ i18n "skipToContent" }}</a>
<div class="container">
<header class="common-header">
{{ block "header" . }}
{{ partial "head/header.html" . }}
{{ end }}
</header>
<main id="main" tabindex="-1">
{{ block "main" . }}{{ end }}
</main>
{{ block "footer" . }}
{{ partial "footer.html" . }}
{{ end }}
</div>
</body>
</html>

59
layouts/_default/rss.xml Normal file
View File

@ -0,0 +1,59 @@
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = site }}{{- end -}}
{{- $pages := where (where $pctx.RegularPages ".Params.disable_feed" "!=" true) "Params.private" "!=" true -}}
{{- $limit := site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title>{{ with .Site.Params.feedTitle }}{{ . }}{{ else }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Site.BaseURL }}</link>
<description>Recent blog posts on {{ .Site.Title }}</description>
{{ with .Site.Params.copyright }}
<copyright>{{ . }}</copyright>{{ end }}
<generator>Hugo (https://gohugo.io)</generator>
{{ with .Site.Language.Locale }}
<language>{{.}}</language>{{end}}
{{ with .Site.Params.author.name }}
<managingEditor>{{ with $.Site.Params.author.email }}{{ . }}{{ with $.Site.Params.author.name }} ({{ . }}){{ end }}{{ end }}</managingEditor>{{ end }}
{{ with .Site.Params.author.name }}
<webMaster>{{ with $.Site.Params.author.email }}{{ . }}{{ with $.Site.Params.author.name }} ({{ . }}){{ end }}{{ end }}</webMaster>{{ end }}
{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 Z0700" | safeHTML }}</lastBuildDate>{{ end }}
{{ with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}{{ end }}
{{ range $index, $page := $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 Z0700" | safeHTML }}</pubDate>
{{ with .Site.Params.author.name }}
<author>{{ with $.Site.Params.author.email }}{{ . }}{{ with $.Site.Params.author.name }} ({{ . }}){{ end }}{{ end }}</author>{{ end }}
{{ $cover := "" }}
{{ with $page.Params.cover }}
{{ $res := $page.Resources.GetMatch . }}
{{ if $res }}
{{ $cover = $res.Permalink }}
{{ else }}
{{ $cover = . | absURL }}
{{ end }}
{{ end }}
{{ if $cover }}
<media:content url="{{ $cover }}" medium="image" />
<media:title>Cover Image</media:title>
{{ end }}
<description>
{{ .Summary | plainify | htmlEscape }}
</description>
<guid isPermaLink="true">{{ .Permalink }}</guid>
</item>
{{ end }}
</channel>
</rss>

View File

@ -0,0 +1,24 @@
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
{{ range where .Data.Pages "Params.private" "!=" true }}
{{- if .Permalink -}}
<url>
<loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
<changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
<priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
<xhtml:link
rel="alternate"
hreflang="{{ .Language.Label }}"
href="{{ .Permalink }}"
/>{{ end }}
<xhtml:link
rel="alternate"
hreflang="{{ .Language.Label }}"
href="{{ .Permalink }}"
/>{{ end }}
</url>
{{- end -}}
{{ end }}
</urlset>

View File

@ -6,4 +6,4 @@
'floating-chat.donateButton.background-color': '#794bc4',
'floating-chat.donateButton.text-color': '#fff'
});
</script>
</script>

View File

@ -1,47 +1,37 @@
<!-- Basic stuff -->
<meta charset="utf-8">
<meta name="HandheldFriendly" content="True">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer-when-downgrade">
<meta name="theme-name" content="Anubis2">
{{ partial "title.html" . }}
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}">
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}">
<meta name="author" content="{{ with .Params.author }}{{ . }}{{ else }}{{ with site.Params.author }}{{ . }}{{ end }}{{ end -}}">
<meta name="copyright" content="{{ with site.Params.copyright }}{{ . }}{{ else }}{{ with site.Params.author }}{{ . }}{{ end }}{{ end -}}">
{{ with .Keywords }}
<meta name="keywords" content="{{ range $i, $e := . }}{{ if $i }},{{ end }}{{ $e }}{{ end }}">
{{ end }}
{{ if and (.IsPage) (eq .Params.hidden true)}}
{{ if and (.IsPage) (eq .Params.private true)}}
<meta name="robots" content="noindex" />
{{ end }}
{{ with .OutputFormats.Get "rss" -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .RelPermalink $.Site.Title | safeHTML }}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .RelPermalink site.Title | safeHTML }}
{{ end -}}
{{ partial "favicons.html" . }}
{{ partial "head/favicons.html" . }}
{{ $colorTheme := "light" }}
{{ $colorThemeSwitcher := true }}
{{ $params := partial "params-helper.html" . }}
{{ if and (isset site.Params "colortheme") (ne site.Params.colortheme "") }}
{{ $colorTheme = site.Params.colortheme | lower }}
{{ end }}
{{ if isset site.Params "colorthemeswitcher" }}
{{ $colorThemeSwitcher = site.Params.colorthemeswitcher }}
{{ else }}
{{ $colorThemeSwitcher = true }}
{{ end }}
{{ if or (eq $colorThemeSwitcher true) (eq $colorTheme "auto") }}
{{ if or (eq $params.enableColorThemeSwitcher true) (eq $params.colorTheme "auto") }}
<style>
body {
visibility: hidden;
opacity: 0;
}
</style>
<noscript>
<style>
body {
@ -50,80 +40,33 @@
}
</style>
</noscript>
{{end}}
{{ end }}
{{ partial "resource.html" (dict "context" . "type" "css" "filename" "css/main.css") }}
{{ partial "resource.html" (dict "context" . "type" "css" "filename" "css/light.css") }}
{{ partial "resource.html" (dict "context" . "type" "css" "filename" "css/dark.css") }}
{{ partial "features/custom-css.html" . }}
{{ partial "features/custom-js.html" . }}
{{ $opts := dict "transpiler" "libsass" "targetPath" "css/style.css" }}
{{ range site.Params.customCSS }}
{{ $r := "" }}
{{ with resources.Get . }}
{{ $r = . }}
{{ else }}
{{ errorf "error: You defined customCSS, but %s was not found, please check your config." . }}
{{ end }}
{{ if $r}}
{{ with $r | toCSS $opts | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{ end }}
{{ end }}
{{ end }}
{{ if site.Params.isso.enabled }} {{/* TODO: maybe we can remove it */}}
<style>
#isso-thread .textarea {
color: #000;
}
#isso-thread .isso-feedlink {
position: relative;
z-index: 1;
}
</style>
{{ end }}
{{ if .Site.Params.copyCodeButton | default true }}
{{ if $params.enableCopyCodeButton }}
{{ partial "resource.html" (dict "context" . "type" "js" "filename" "js/copy-code.js") }}
{{ end }}
{{ range .Site.Params.customJS -}}
{{ range $params.customJS -}}
{{ partial "resource.html" (dict "context" $ "type" "js" "filename" . ) }}
{{- end }}
{{ if isset .Site.Params "webmentions" }}
{{ if isset .Site.Params.webmentions "login" }}
<link rel="webmention" href="https://webmention.io/{{.Site.Params.webmentions.login}}/webmention" />
{{ if eq .Site.Params.webmentions.pingback true }}
<link rel="pingback" href="https://webmention.io/{{.Site.Params.webmentions.login}}/xmlrpc" />
{{ end }}
{{ end }}
{{ if isset .Site.Params.webmentions "url" }}
<link rel="webmention" href="{{.Site.Params.webmentions.url}}" />
{{ end }}
{{ end }}
{{- if isset .Params "canonical" -}}
<link rel="canonical" href="{{ .Params.canonical }}" />
{{- else -}}
<link rel="canonical" href="{{ .Permalink }}" />
{{- end }}
<!-- Article tags -->
<!-- <meta property="article:published_time" content="">
<meta property="article:modified_time" content="">
<meta property="article:tag" content="">
<meta property="article:publisher" content="https://www.facebook.com/XXX"> -->
{{ if (.Site.Params.GoogleAnalytics.enabled) }}
{{ partial "google_analytics.html" . }}
{{ end }}
{{ if (.Site.Params.UmamiAnalytics.enabled ) }}
{{ partial "umami_analytics.html" . }}
{{ end }}
{{ partial "head-extra.html" . }}
<script defer src="https://umami.thanwer.com/script.js" data-website-id="eded536d-a598-471a-945b-faaebc4837a9"></script>
{{ if hugo.IsProduction }}
{{ if isset $params.services "umami" }}
{{ partial "analytics/umami_analytics.html" . }}
{{ end }}
{{ end }}

View File

@ -0,0 +1,15 @@
<ul class="language-select">
{{ $pageLang := .Page.Lang}}
{{ $translations := .AllTranslations }}
{{ if not .IsTranslated }}
{{ $translations = site.Home.AllTranslations }}
{{ end }}
{{ range $translations }}
{{ if eq .Lang $pageLang }}
<li>{{ .Language.Label }}</li>
{{ else }}
<li><a href="{{ .RelPermalink }}">{{ .Language.Label }}</a></li>
{{ end }}
{{ end }}
</ul>

View File

@ -0,0 +1,56 @@
{{ $dateFormat := "2006-01-02" }}
{{ if site.Params.dateFormat }}
{{ $dateFormat = site.Params.dateFormat }}
{{ end }}
{{ $machineDateFormat := "2006-01-02" }}
{{ $lastmod := .Lastmod }}
{{ if .Params.lastmod }}
{{ $lastmod = .Params.lastmod }}
{{ end }}
<div class="post-info noselect">
{{ if .Params.date }}
<div class="post-date dt-published">
<time datetime="{{ .Params.date.Format $machineDateFormat }}">
{{- .Params.date.Format $dateFormat -}}
</time>
{{ if and $lastmod (ne $lastmod .Date) -}}
[{{ i18n "lastMod" }} <time datetime="{{ $lastmod.Format $machineDateFormat }}">
{{- $lastmod.Format $dateFormat -}}
</time>]
{{- end }}
</div>
{{ end }}
<a class="post-hidden-url u-url" href="{{ .RelPermalink }}">{{ .RelPermalink }}</a>
<a href="{{ site.BaseURL }}" class="p-name p-author post-hidden-author h-card" rel="me">{{ .Params.author | default site.Params.author }}</a>
<div class="post-taxonomies">
{{ if .Params.categories }}
<ul class="post-categories">
{{ range $cat_name := .Params.categories }}
{{ $cat_url := (printf "/categories/%s" $cat_name | urlize ) }}
<li><a href="{{ $cat_url }}">{{ $cat_name }}</a></li>
{{ end }}
</ul>
{{ end }}
{{ if .Params.tags }}
<ul class="post-tags">
{{ range $tag_name := .Params.tags }}
{{ $tag_url := (printf "/tags/%s" ($tag_name | urlize)) }}
<li><a href="{{ $tag_url }}">#{{ $tag_name }}</a></li>
{{ end }}
</ul>
{{ end }}
{{ if .Params.authors }}
<ul class="post-authors">
{{ range $author_name := .Params.authors }}
{{ $author := (site.GetPage (printf "/authors/%s" $author_name | urlize )) }}
<li><a href="{{ $author.RelPermalink }}">@{{ $author_name }}</a></li>
{{ end }}
</ul>
{{ end }}
</div>
</div>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 419 KiB

After

Width:  |  Height:  |  Size: 419 KiB

View File

@ -1,6 +1,6 @@
_ _
| | | |_ _ _ __ ___ __ _ _ __ ___
_ _
| | | |_ _ _ __ ___ __ _ _ __ ___
| |_| | | | | '_ ` _ \ / _` | '_ \/ __|
| _ | |_| | | | | | | (_| | | | \__ \
|_| |_|\__,_|_| |_| |_|\__,_|_| |_|___/

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@ -1 +1 @@
Subproject commit 95844a6f7a416d79ecc9e49034192e45fe0381aa
Subproject commit 9c4e5b169d285c4c008a754bbd361181407323ed