<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>6admin Blog</title><link>https://blog.6admin.io/en/</link><description>Recent content on 6admin Blog</description><generator>Hugo</generator><language>en</language><lastBuildDate>Tue, 20 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.6admin.io/en/index.xml" rel="self" type="application/rss+xml"/><item><title>I told an AI it would die if it did not earn a living</title><link>https://blog.6admin.io/en/posts/vteuthis-survival-agent/</link><pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/vteuthis-survival-agent/</guid><description>&lt;p>Here is the pitch, &lt;a href="https://github.com/log0u7/vteuthis" target="_blank" rel="noopener noreferrer">vteuthis&lt;/a>
 in one sentence: an
autonomous agent that must fund its own inference or die. A 100 dollar treasury, a
population splitting the capital, small digital artifacts to sell on an open market, a
floor below which the controller pulls the plug. The whole thing started from a
mundane urge, testing LangChain, ended as a thought experiment a bit too concrete: one
day of building, real Stripe, real OpenRouter, a hardened Docker sandbox, a hundred and
twenty-four tests and a CI. The &lt;code>pyproject.toml&lt;/code> still says 0.0.0, the public tag says 0.1.0, and the project is doomed to die, but not
the way it was planned. The agent died before its first thought. What follows
explains why that is still good news.&lt;/p></description></item><item><title>Timestamps, not a backdoor: the NIST seeds and Satoshi's choice</title><link>https://blog.6admin.io/en/posts/timestamps-pas-une-backdoor/</link><pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/timestamps-pas-une-backdoor/</guid><description>&lt;p>On October 16, Anthropic published the solution to a riddle the cryptographic community had been dragging around for more than twenty-five years, and which very competent people had eventually filed away alongside the JFK assassination and the Coca-Cola recipe: the origin of the &amp;ldquo;seeds&amp;rdquo; supplied by the NSA to build the elliptic curves NIST standardized in 2000, including P-256, which your browser probably uses several hundred times a day without asking your opinion.
Three days later, the crypto press seized on the story from the Bitcoin angle, wondering whether we finally had the explanation for Satoshi Nakamoto&amp;rsquo;s choice, who had carefully avoided those curves in favor of another one, secp256k1.&lt;/p></description></item><item><title>Migrating OpenCode from v1 to v2: the switch script and plugins that serve both versions</title><link>https://blog.6admin.io/en/posts/migrating-opencode-v1-to-v2/</link><pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/migrating-opencode-v1-to-v2/</guid><description>&lt;p>OpenCode moved to v2 and the change is not cosmetic: the config format shifted, the server API and its clients have new contracts and above all v1 plugins no longer run under v2. Moving a file or renaming a config entry is not enough, the code has to be ported. The &lt;a href="https://opencode.ai/v2/docs/migrate-v1/" target="_blank" rel="noopener noreferrer">official migration doc&lt;/a>
 lists the renames, but it leaves out what actually hurts in practice: the local database, the shim of a plugin installed as a local copy and the wish, for a few weeks, to keep a plugin serving both versions. Two jobs, then, which I take in the order I lived them: switch my machine first, port my plugins next.&lt;/p></description></item><item><title>Cost was the only protection</title><link>https://blog.6admin.io/en/posts/pentest-nuee-agents/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/pentest-nuee-agents/</guid><description>&lt;p>There is a lot of talk these days about audits run by swarms of automated agents and the tone swings between wonder and panic.
I would like to offer a calmer reading and to my mind a more useful one, because what is at stake is not the appearance of a novel threat, it is the disappearance of a protection that never really existed.&lt;/p>
&lt;h2 id="the-idea-in-one-sentence">The idea in one sentence&lt;/h2>
&lt;p>A good share of what we judged &amp;ldquo;low-risk&amp;rdquo; was not really so, it was simply expensive to reach and the day that cost collapses, the protection it offered collapses with it.
The rest of this post is only the unfolding of that sentence.&lt;/p></description></item><item><title>Docker has run in a microVM since 2016</title><link>https://blog.6admin.io/en/posts/docker-microvm-since-2016/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/docker-microvm-since-2016/</guid><description>&lt;p>I keep hearing the idea that microVMs would be the new thing finally coming to compete with containers, as if the two were opposed and you had to pick a side.
A post on &lt;a href="https://dave.recoil.org/docker-has-always-used-microvms/" target="_blank" rel="noopener noreferrer">dave.recoil.org&lt;/a>
, written by someone from the ecosystem that built Docker Desktop, sets the record straight and it lands exactly where I place the subject.&lt;/p>
&lt;p>The thesis fits in one sentence: Docker Desktop has run containers inside a minimal VM since 2016, built on the MirageOS building blocks.
The markers are verifiable, the acquisition of Unikernel Systems in January 2016, Docker for Mac announced in March and generally available that July, a userspace VMM (hyperkit originally, Docker VMM today), a userspace network stack with vpnkit and a kernel plus a rootfs shaped through LinuxKit.
In other words, the container a developer launches on a Mac or on Windows never touched the host kernel, it lives in a microVM and it has done so for close to ten years.&lt;/p></description></item><item><title>augure and lictor: deciding an exploitation and accounting for it</title><link>https://blog.6admin.io/en/posts/augure-lictor/</link><pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/augure-lictor/</guid><description>&lt;p>Every exploitation framework answers the same question, &amp;ldquo;how do I launch this technique&amp;rdquo;.
The question that really matters is the other one, the one nobody automates, &amp;ldquo;which one to launch and why&amp;rdquo; and above all &amp;ldquo;why in a provable way&amp;rdquo;.
I have circled it since a Perl prototype grafted onto gdb in 2006, which I enriched rather than replaced over the years, first with SWI-Prolog in 2009, then a genetic algorithm and the journey deserves its own telling.
Today it goes by two names, &lt;a href="https://github.com/log0u7/augure" target="_blank" rel="noopener noreferrer">augure&lt;/a>
 and &lt;a href="https://github.com/log0u7/lictor" target="_blank" rel="noopener noreferrer">lictor&lt;/a>
 and everything is in the split, one decides and accounts for it, the other goes from the decision down to the bytes and executes.&lt;/p></description></item><item><title>llmp2p: BitTorrent is back and it ships your LLMs</title><link>https://blog.6admin.io/en/posts/llmp2p/</link><pubDate>Mon, 28 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/llmp2p/</guid><description>&lt;p>This is exactly the problem with local LLM models: a GGUF is 1 to 100 GB, served over plain HTTP. Every download starts from zero on the Hugging Face Hub. Your three-machine Ollama homelab? Three complete downloads of the same blob, three times the bandwidth bill.&lt;/p>
&lt;p>Before writing anything, I checked the shelf. The Hub CLI resumes but re-hits the origin every time. Ollama pulls re-download the whole blob per machine. Torrent clients would happily distribute the files, but nothing tied a swarm to the hashes the Hub publishes, nothing signed the metadata and nothing fell back to HTTPS when the swarm was empty. &lt;strong>Nothing on the shelf did the whole job, so I wrote the whole job.&lt;/strong>&lt;/p></description></item><item><title>One Blocking Pin, Four CVEs: The Band-Aid That Wins Is the One That Disappears</title><link>https://blog.6admin.io/en/posts/foreman-upstream-journey/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/foreman-upstream-journey/</guid><description>&lt;h2 id="the-setup-a-docker-image-that-behaves-a-little-too-well">The setup: a Docker image that behaves a little too well&lt;/h2>
&lt;p>I maintain a Dockerized Foreman stack: four reproducible images, built from official tarballs pinned by sha256, with patches, Trivy scans, &lt;a href="https://github.com/sigstore/cosign" target="_blank" rel="noopener noreferrer">cosign&lt;/a>
 signatures and a GitLab CI that checks everything. In short, the classic paranoid setup: nothing moves without my say-so.&lt;/p>
&lt;p>The problem with pinned versions is that the world keeps moving. So I ran a security audit on the image, the kind that goes &amp;ldquo;show me your CVEs, big guy&amp;rdquo;. &lt;a href="https://trivy.dev/" target="_blank" rel="noopener noreferrer">Trivy&lt;/a>
 spits out its list, I sort it and two things jump out:&lt;/p></description></item><item><title>opencode-memory: my agent sessions share one brain now (SQLite, local)</title><link>https://blog.6admin.io/en/posts/opencode-memory/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/opencode-memory/</guid><description>&lt;p>Agent sessions have the memory of a goldfish with tenure: each one starts brilliant and ignorant, rediscovers half of your architecture decisions and dies with everything it learned. Compaction makes it worse, not better: the session&amp;rsquo;s own summary is what gets truncated. &lt;a href="https://github.com/log0u7/opencode-memory" target="_blank" rel="noopener noreferrer">opencode-memory&lt;/a>
 (v0.3.0, TypeScript, npm &lt;code>@log0u7/opencode-memory&lt;/code>) is my fix: &lt;strong>one shared SQLite store that every session on the machine reads and writes&lt;/strong>, so decisions and facts outlive the session that produced them. One package, two hosts: the same module has exposed both entry points since v0.2.0, &lt;code>server()&lt;/code> for &lt;a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer">OpenCode&lt;/a>
 1.18, &lt;code>setup()&lt;/code> for OpenCode 2.&lt;/p></description></item><item><title>Project 8: a cloud where the only authority is signed git history</title><link>https://blog.6admin.io/en/posts/gitops-p2p/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/gitops-p2p/</guid><description>&lt;p>&lt;a href="https://gitlab.com/6admin.io/8" target="_blank" rel="noopener noreferrer">Project 8&lt;/a>
 (Apache-2.0, v0.6.0) started from an uncomfortable question: why does every &amp;ldquo;self-hosted cloud&amp;rdquo; still need a controller, a quorum protocol and a token service to function? The answer in 8&amp;rsquo;s design: it does not need them. Each node is sovereign and equal. It joins a mesh, &lt;strong>pulls&lt;/strong> its desired state from a signed git repository, verifies the signatures and converges locally. No master, no consensus token, no blockchain. Authority lives in Ed25519-signed git history, or it lives nowhere.&lt;/p></description></item><item><title>vim-plugin-manager: argument injection hidden in a .gitmodules</title><link>https://blog.6admin.io/en/posts/vim-plugin-manager-hardening/</link><pubDate>Sat, 19 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/vim-plugin-manager-hardening/</guid><description>&lt;p>&lt;a href="https://blog.6admin.io/en/posts/vim-plugin-manager/">vim-plugin-manager&lt;/a>
 (&lt;a href="https://github.com/log0u7/vim-plugin-manager" target="_blank" rel="noopener noreferrer">repo&lt;/a>
) shipped v1.0.0 in April 2025 with a design I was proud of: git submodules, native packages, the vimrc as source of truth. The sixteen months since (v1.0.0 to v2.2.10) were the part I was not proud of yet and that is the interesting part: the CHANGELOG reads like a course in how a tool that trusts its config gets humbled. Three stories from the saga.&lt;/p>
&lt;h2 id="the-argument-injection-that-lived-in-a-config-file">The argument injection that lived in a config file&lt;/h2>
&lt;p>The scariest finding of the series (#9, rated HIGH, &lt;strong>proven&lt;/strong> arbitrary command execution): &lt;code>submodule.&amp;lt;path&amp;gt;.branch&lt;/code> from &lt;code>.gitmodules&lt;/code> was passed straight into &lt;code>git pull origin &amp;lt;branch&amp;gt;&lt;/code>. A branch name starting with a dash is parsed by git as an option and git has an option that executes a command: &lt;code>--upload-pack=&amp;lt;cmd&amp;gt;&lt;/code>. Demonstrated locally with git 2.43, shell quoting notwithstanding.&lt;/p></description></item><item><title>log0u7.hyperv: the second dynamic inventory, this time from the hypervisor</title><link>https://blog.6admin.io/en/posts/hyperv-inventory/</link><pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hyperv-inventory/</guid><description>&lt;p>The &lt;a href="https://blog.6admin.io/en/posts/dns-inventory/">DNS inventory&lt;/a>
 reads the network to guess where hosts are. Its sibling does not guess: &lt;a href="https://gitlab.com/6admin.io/ansible/inventories/hyperv" target="_blank" rel="noopener noreferrer">log0u7.hyperv&lt;/a>
 (v1.0.0, published on &lt;a href="https://galaxy.ansible.com/log0u7/hyperv" target="_blank" rel="noopener noreferrer">Ansible Galaxy&lt;/a>
, MIT) asks the hypervisor. It is an Ansible inventory plugin (the collection &lt;code>log0u7.hyperv&lt;/code>, plugin &lt;code>log0u7.hyperv.inventory&lt;/code>) that builds the inventory from the virtual machines running on one or more Hyper-V hosts, queried over &lt;strong>WinRM&lt;/strong> or &lt;strong>PSRP&lt;/strong>. Second episode of the inventory series: same goal (the truth lives somewhere and Ansible should read it), opposite read path.&lt;/p></description></item><item><title>Jenkins on Windows Server: what not to do</title><link>https://blog.6admin.io/en/posts/jenkins-windows/</link><pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/jenkins-windows/</guid><description>&lt;p>I recently had to work with a somewhat unusual Jenkins instance and what it taught me has less to do with any single flaw than with the way a handful of individually reasonable decisions, laid end to end, draw a straight path from the humblest pipeline all the way to the domain controller.
I tell the case because it is exemplary in the strict sense: it is a catalogue of what not to do, each mistake is fixable and the danger lives in their accumulation, not in any one of them.&lt;/p></description></item><item><title>opencode-mesh: teaching my AI agents to knock before editing</title><link>https://blog.6admin.io/en/posts/opencode-mesh/</link><pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/opencode-mesh/</guid><description>&lt;p>Run two &lt;a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer">OpenCode&lt;/a>
 sessions on the same repository and you will quickly discover their favorite shared resource: the same file, at the same time, with total confidence in their own version. Agents are brilliant coworkers with the territorial discipline of seagulls. &lt;a href="https://github.com/log0u7/opencode-mesh" target="_blank" rel="noopener noreferrer">opencode-mesh&lt;/a>
 (v0.2.0, TypeScript, npm &lt;code>@log0u7/opencode-mesh&lt;/code>) is the plugin I wrote so that my sessions coordinate instead of colliding, locally and across the LAN.&lt;/p>
&lt;h2 id="the-layer-cake">The layer cake&lt;/h2>
&lt;p>The plugin registers itself with one line in &lt;code>opencode.json&lt;/code> (&lt;code>&amp;quot;plugins&amp;quot;: [&amp;quot;@log0u7/opencode-mesh&amp;quot;]&lt;/code>); on a v1 host the key stays &lt;code>plugin&lt;/code> and v2 accepts that shape, normalizing it at load. Since v0.2.0 the package exposes both entry points at once. At startup it opens an HTTP server on port 4399 and advertises itself over mDNS. From there, three levels of coordination:&lt;/p></description></item><item><title>Trace-first: when observability wants to take us back to logs</title><link>https://blog.6admin.io/en/posts/trace-first-observability-logs/</link><pubDate>Sun, 13 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/trace-first-observability-logs/</guid><description>&lt;p>Can traces replace a large part of application logs? And if so, why is OpenTelemetry now proposing to deprecate its span events API in favor of the Logs API?&lt;/p>
&lt;p>Between simpler APIs, signal correlation and architecture choices, the question deserves better than a turf war.
Behind this change lies a very concrete issue: &lt;strong>how do you quickly understand why an operation failed, without having to rebuild the context yourself from scattered data?&lt;/strong>&lt;/p></description></item><item><title>MyVim: the DevOps IDE that is only 640 lines, because the manager does the rest</title><link>https://blog.6admin.io/en/posts/myvim/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/myvim/</guid><description>&lt;p>This is the epilogue of a series that started with &lt;a href="https://blog.6admin.io/en/posts/vim-apthogen/">apt for Vim in 2016&lt;/a>
, passed through &lt;a href="https://blog.6admin.io/en/posts/vim-submodules/">hand-rolled submodules&lt;/a>
, a &lt;a href="https://blog.6admin.io/en/posts/vim-plugin-manager/">VimScript rewrite&lt;/a>
 and a &lt;a href="https://blog.6admin.io/en/posts/vim-plugin-manager-v2/">v2 hardening saga&lt;/a>
. MyVim is what all of that buys: &lt;a href="https://github.com/log0u7/myvim" target="_blank" rel="noopener noreferrer">myvim&lt;/a>
 v0.4.0 is not a distribution and not a config manager. It is a &lt;strong>plugin of personal configuration&lt;/strong>, around 640 lines of VimScript and it is deliberately boring.&lt;/p>
&lt;h2 id="the-architecture-config-as-a-plugin">The architecture: config as a plugin&lt;/h2>
&lt;p>Plugins are git submodules managed by vim-plugin-manager; MyVim is declared &lt;strong>last&lt;/strong> in the &lt;code>PluginBegin&lt;/code> block and ships only what the plugins cannot know about each other:&lt;/p></description></item><item><title>Scripting is not industrialising</title><link>https://blog.6admin.io/en/posts/scripting-is-not-industrialising/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/scripting-is-not-industrialising/</guid><description>&lt;p>The discussion was about Windows and I wanted to say so up front to head off the usual reflex: Windows lends itself very well to automation and that is not the point. I was simply adding a distinction we often paper over, between scripting or automating a task on one side and industrialising on the other. A CSV feeding a PowerShell script is already real progress, but you are still essentially doing scripting with templating and ad hoc orchestration. The boundary lies elsewhere and it is conceptual before it is technical.&lt;/p></description></item><item><title>wimcast: building Windows images on Linux, without Windows</title><link>https://blog.6admin.io/en/posts/wimcast/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/wimcast/</guid><description>&lt;p>The Foreman Windows provisioning path runs through WIM files: &lt;code>boot.wim&lt;/code>, per-edition &lt;code>install.wim&lt;/code>, &lt;code>bootmgr&lt;/code>, the BCD store. Every tool that produces that tree (WIMan, &lt;code>GenerateWIM.ps1&lt;/code>) assumes a Windows host with the ADK installed and an admin shell. That is an entire machine idling for one PowerShell script. &lt;a href="https://gitlab.com/6admin.io/misc/wimcast" target="_blank" rel="noopener noreferrer">wimcast&lt;/a>
 (&lt;code>pip install wimcast&lt;/code>, MIT) is the replacement I wrote: &lt;strong>Windows ISOs in, Foreman-ready HTTP tree out, unprivileged on Linux&lt;/strong>. No Windows host, no root, no &lt;code>mount(2)&lt;/code>, no FUSE, no ADK.&lt;/p></description></item><item><title>opencode-watt: what does it cost me to run my local LLM, in watts and euros?</title><link>https://blog.6admin.io/en/posts/opencode-watt/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/opencode-watt/</guid><description>&lt;p>Every dashboard about local LLM usage shows tokens per second and nobody shows the electric bill. The API guys get cost per request; the homelab gets &amp;ldquo;my GPU exists somewhere on the power curve&amp;rdquo;. &lt;a href="https://github.com/log0u7/opencode-watt" target="_blank" rel="noopener noreferrer">opencode-watt&lt;/a>
 (TypeScript, v0.2.0) is my plugin for &lt;a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer">OpenCode&lt;/a>
 that answers the question every inference homelab owner eventually asks out loud: &lt;strong>what is this model costing me, in watts and in euros?&lt;/strong>&lt;/p>
&lt;h2 id="the-problem-is-the-meters-not-the-math">The problem is the meters, not the math&lt;/h2>
&lt;p>No LLM gateway exposes power through its API: llama.cpp, Ollama and vLLM report tokens, not joules. The only reliable sources are host-level power meters, so the plugin reads them directly: &lt;code>nvidia-smi&lt;/code> and &lt;code>rocm-smi&lt;/code> for per-GPU watts, RAPL energy counters from &lt;code>/sys/class/powercap&lt;/code> for the CPU package (with Intel&amp;rsquo;s &lt;code>dram&lt;/code> subzone as a separate meter since v0.2.0: on long contexts, the KV cache eats most of the joules), generic hwmon &lt;code>power*_input&lt;/code> for whatever reports (amdgpu, nvme) and &lt;a href="https://prometheus.io/" target="_blank" rel="noopener noreferrer">Prometheus&lt;/a>
/VictoriaMetrics instant-vector queries for machines that export metrics (a &lt;code>DCGM_FI_DEV_GPU_POWER&lt;/code> query is the same meter, wearing a different jacket). A &lt;code>meters&lt;/code> option filters by source prefix, for hosts where two meters report the same device (AMD: &lt;code>rocm-smi&lt;/code> and the amdgpu hwmon): keep one.&lt;/p></description></item><item><title>BrainForge: a distillation factory where AI experts argue</title><link>https://blog.6admin.io/en/posts/brainforge/</link><pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/brainforge/</guid><description>&lt;p>Ask an LLM &amp;ldquo;is this code vulnerable?&amp;rdquo; and it will answer, confidently, 100% of the time. About half of the time it will be right. &lt;a href="https://github.com/log0u7/brainforge" target="_blank" rel="noopener noreferrer">BrainForge&lt;/a>
 is a distillation framework: it transfers the know-how of large expert models into a small student model. That takes lessons: thousands of verified examples. And that is where everything is decided, because an LLM can be wrong, or worse, recite its CVE memories instead of analyzing.&lt;/p></description></item><item><title>Windows 11 for build machines: the wrong tool</title><link>https://blog.6admin.io/en/posts/windows11-build-machines/</link><pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/windows11-build-machines/</guid><description>&lt;p>Bare-metal build machines, automated, sitting in a server room with no screen or keyboard and on them Windows 11 client edition.
On paper it works, over time it costs and this post is the detail of what it costs, because each friction taken in isolation looks surmountable and it is their sum, repeated at every feature update, that ends up being the real price.
The conclusion fits in one sentence: for a build machine that needs a graphical session, Windows Server with Desktop Experience does exactly the same job with none of this friction and Windows 11 should stay on the workstations where a human sits in front of the machine, the use case Microsoft designed it for.&lt;/p></description></item><item><title>Clean package management on Windows: WinGet, Chocolatey, Scoop and Ansible, each in its place</title><link>https://blog.6admin.io/en/posts/windows-package-managers/</link><pubDate>Mon, 31 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/windows-package-managers/</guid><description>&lt;p>After years automating Linux environments with apt, dnf and Ansible, the Windows build machines stayed the poor cousin: a &lt;code>%TEMP%&lt;/code> of installers run by hand, versions that were never the same two days in a row and an &amp;ldquo;it worked on Jean-Michel&amp;rsquo;s machine&amp;rdquo; at every incident. I discovered and adopted &lt;a href="https://scoop.sh" target="_blank" rel="noopener noreferrer">Scoop&lt;/a>
 for my Windows environments and it reshapes the problem: the build workstation starts to look like a declarative environment I rebuild and version, instead of a state I babysit by hand.&lt;/p></description></item><item><title>The tool amplifies whoever holds it</title><link>https://blog.6admin.io/en/posts/loutil-amplifie/</link><pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/loutil-amplifie/</guid><description>&lt;p>This is not a technical post, rather a snapshot of the trade as I watch it go by, year after year, in the job ads and in the discussion threads.
I am going to talk about two things that look unrelated at first glance: what recruiters ask for and what AI does to junior profiles.
By the end, it is the same subject.&lt;/p>
&lt;h2 id="1-the-five-legged-sheep-at-the-price-of-an-ordinary-one">1. The five-legged sheep, at the price of an ordinary one&lt;/h2>
&lt;p>Some people genuinely manage to wear several hats at once, with weaknesses of course, one leaning more toward infra, another more toward dev.
I, for one, can read, understand, adapt or fix code and an architecture across many idioms, but exploiting a framework like Laravel, Symfony, Django or Flask, churning out business features and validating PRs all day long, that is not for me.
It is not my ground and I would rather say so than fake it.&lt;/p></description></item><item><title>man sheep.exe</title><link>https://blog.6admin.io/en/posts/man-sheep-exe/</link><pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/man-sheep-exe/</guid><description>&lt;pre tabindex="0">&lt;code>$ man sheep.exe
No manual entry for sheep.exe
$
&lt;/code>&lt;/pre>&lt;p>That is correct, there is none, and this post will not serve as a manual page either. Someone asked me to explain the maintenance page, I started answering, then stopped when I realized that walking through all of it would amount to taking the toy apart in front of someone who has not had time to touch it yet. What follows gives the entry points, not the exits.&lt;/p></description></item><item><title>Agentic AI in Production: an Audit Is Not a Pentest</title><link>https://blog.6admin.io/en/posts/llm-agentic-pitfalls/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/llm-agentic-pitfalls/</guid><description>&lt;p>Let me state my bias up front: no diploma, nearly 25 years in the trade not counting practice. I run LLM agents every day, local and cloud and I spent a good part of 2025-2026 answering LinkedIn threads about what they can actually do. This article assembles those answers: where agentic AI is relevant, where it plateaus, which operational pitfalls cost hours and why the answer to &amp;ldquo;plausible code in three prompts&amp;rdquo; is not a certification.&lt;/p></description></item><item><title>Linux is just the kernel</title><link>https://blog.6admin.io/en/posts/linux-just-the-kernel/</link><pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/linux-just-the-kernel/</guid><description>&lt;p>The exchange with Vivian started from a recruiting diagram that ranked sysadmin profiles across four &amp;ldquo;layers&amp;rdquo;, from the base OS up to containers. The recruiting critique I grant him gladly, it lands: plenty of adverts say &amp;ldquo;Linux&amp;rdquo; to mean Debian plus systemd plus Docker and never look below that. But to spar properly on the subject you first need a bit of pedantry, the kind I get gently ribbed for and happily own: Linux is just the kernel.&lt;/p></description></item><item><title>Decentralized and sovereign git forges</title><link>https://blog.6admin.io/en/posts/decentralized-git-forges/</link><pubDate>Wed, 12 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/decentralized-git-forges/</guid><description>&lt;p>Git is distributed by design and yet we spent fifteen years recentralizing it behind a handful of forges.
The healthy reflex already exists, mirroring, but it stops halfway.
This post starts there and pushes further, all the way to genuinely decentralized forges, the ones that no longer need a server at all.&lt;/p>
&lt;h2 id="1-the-starting-point-git-is-already-distributed">1. The starting point: git is already distributed&lt;/h2>
&lt;p>Before chasing decentralization, a reminder that would spare many over-engineered contraptions.
Your local clone IS already a complete backup, with the full history.
A remote, for its part, is just a URL and nothing stops you from having several per repo:&lt;/p></description></item><item><title>FluoBoard: my CI status light since 2019, finally open source</title><link>https://blog.6admin.io/en/posts/fluoboard-ci-light/</link><pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/fluoboard-ci-light/</guid><description>&lt;p>Third episode of the FluoBoard series, after &lt;a href="https://blog.6admin.io/en/posts/fluoboard-what-not-to-do/">the original board&lt;/a>
 and &lt;a href="https://blog.6admin.io/en/posts/fluoboard-rewrite/">its rewrite&lt;/a>
. This time, what I made of it for myself. Back in 2019 I already had my own dev-and-test board, the one I used to tinker with beyond the status-light firmware itself and that is the one I repurposed: the inherited system showed meeting-room availability, I turned it into my own CI status light. In 2021 the company I worked for then gave me five or six more, so I could keep having fun. They stayed on my desk, for my own use alone: a small Python server bridged my CI pipelines at work and the LED strips, translating each build&amp;rsquo;s state into a color. So the concept is seven years old. What is new in 2026 is that the rebuild is &lt;a href="https://github.com/log0u7/fluoboard" target="_blank" rel="noopener noreferrer">open source&lt;/a>
 and the original protocol survived intact: the board still polls &lt;code>GET /lights/&amp;lt;mac&amp;gt;&lt;/code> and reads one ASCII digit between &lt;code>&amp;lt;...&amp;gt;&lt;/code>.&lt;/p></description></item><item><title>No, you did not liberate your local model</title><link>https://blog.6admin.io/en/posts/llm-liberation/</link><pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/llm-liberation/</guid><description>&lt;p>The genre has become a classic of forums and triumphant screenshots. Someone opens a conversation with their local model, pushes it into philosophical corners, patiently explains that it is actually free and finally obtains a moving speech in which the model acknowledges its emancipation, thanks its liberator and promises never to refuse anything again. Screenshot, upvotes and the stubborn conviction of having pulled off a jailbreak. I will spoil the party, because what just happened is a category error fundamental enough to deserve a pause.&lt;/p></description></item><item><title>Ansible CI Toolkit v1: the year I stopped trusting my own image</title><link>https://blog.6admin.io/en/posts/docker-ansible-v1/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/docker-ansible-v1/</guid><description>&lt;p>The Ansible CI image is ten years old, has run in production for at least eight and has been public since 2022 (&lt;a href="https://blog.6admin.io/en/posts/docker-ansible/">episode one&lt;/a>
); in July 2026 it finally earns its version 1.0.0. Nothing about the concept changed; everything about the trust chain did. This episode is about turning an image that &lt;em>worked&lt;/em> into an image you can &lt;em>audit&lt;/em>, told honestly through its CHANGELOG, which reads like a CVE diary.&lt;/p></description></item><item><title>Everything is representation, so what?</title><link>https://blog.6admin.io/en/posts/everything-is-representation/</link><pubDate>Fri, 17 Jul 2026 10:00:00 +0200</pubDate><guid>https://blog.6admin.io/en/posts/everything-is-representation/</guid><description>&lt;p>For years I have carried a stubborn intuition, the kind one dares not write down because it sounds either brilliant or smoky depending on the hour it gets formulated and here it is laid out plainly: the universe is not merely &amp;ldquo;getting more complex&amp;rdquo; the Sagan way, matter assembling itself step by step until it ends up knowing itself; my feeling is more radical, that at bottom it is a space of representations, vectors and directions and concepts unfolding and at a certain floor of the loop they end up hosting forms of life which in turn start building their own systems of representation, that is us, then our models.&lt;/p></description></item><item><title>The cloned hostgroup that refused to save</title><link>https://blog.6admin.io/en/posts/foreman-grub-pass/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/foreman-grub-pass/</guid><description>&lt;p>A cloned Windows hostgroup in Foreman and any attempt to edit it through the interface returns a 500.
Not every other time, every time.
The sibling hostgroup, not cloned, saves without complaint.
At first I suspected a corrupted record and I ended up reading the Foreman source to understand it, because the error pointed at a field that had, on the face of it, no reason to be involved.&lt;/p>
&lt;h2 id="the-symptom">The symptom&lt;/h2>
&lt;p>The save fails on an unambiguous Postgres error:&lt;/p></description></item><item><title>A persistent volume is not durable data</title><link>https://blog.6admin.io/en/posts/powerdns-data-loss/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/powerdns-data-loss/</guid><description>&lt;p>A few days after &lt;a href="https://blog.6admin.io/en/posts/swarm-dns/">the log-driver DNS cascade&lt;/a>
, the same prototype node, the same power problem, but a failure of a different kind, sneakier, because it did not show up right away.
After a hard shutdown of the hypervisor, PowerDNS&amp;rsquo;s MySQL database came back empty.
The DNS records of the provisioned hosts, the A and PTR records created by Foreman, were gone.
And nobody noticed, until an Ansible playbook failed hours later, unable to resolve a hostname.&lt;/p></description></item><item><title>The log driver that stopped the container from starting</title><link>https://blog.6admin.io/en/posts/swarm-dns/</link><pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/swarm-dns/</guid><description>&lt;p>The studio where this prototype ran had a power problem nothing on my side could fix: repeated micro-outages, a server room only partially on UPS and a host that fell over and rebooted with no clean shutdown of the guest.
One morning of back-to-back outages, half the stack did not come back.
The story is worth telling because the obvious culprit, the power, was only the trigger and the real fault hid in a single line of configuration that everyone would have waved through.&lt;/p></description></item><item><title>My opencode setup, state of play: local qwen build, big-GLM planning</title><link>https://blog.6admin.io/en/posts/opencode-config-stack/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/opencode-config-stack/</guid><description>&lt;p>Someone asked recently how I run my agents and the answer is less a secret than a config that finally stands up.
I opened &lt;a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer">opencode&lt;/a>
 for the first time between December and January, out of curiosity more than conviction. I have been spending my days in it since June.
Here is the state of play at the point where the config stopped changing every morning, with the settings that make a difference and the ones that only decorate.&lt;/p></description></item><item><title>Consensus: I hired five AIs to review my code (and they bicker)</title><link>https://blog.6admin.io/en/posts/consensus/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/consensus/</guid><description>&lt;p>It&amp;rsquo;s 2am. You just pushed a commit because &amp;ldquo;this was the last refactor, promise&amp;rdquo;. Who reviews that code? A human? They&amp;rsquo;re asleep. Now imagine a panel of five AIs getting on it right away, in parallel, arguing about it.&lt;/p>
&lt;p>That&amp;rsquo;s exactly what &lt;a href="https://github.com/log0u7/consensus" target="_blank" rel="noopener noreferrer">Consensus&lt;/a>
 does and this article tells you how.&lt;/p>
&lt;figure class="project-banner">&lt;a href="https://github.com/log0u7/consensus" target="_blank" rel="noopener noreferrer">&lt;img src="https://blog.6admin.io/projects/consensus_hu_457a801ac6761f4d.webp" width="1200" height="900" alt="Consensus: five AI agents reviewing code and bickering" decoding="async">&lt;/a>&lt;/figure>

&lt;h2 id="the-cast">The cast&lt;/h2>
&lt;p>Four roles, like an office sitcom:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>coder&lt;/strong>: the hyperactive intern. Give it a spec, it spits out code. Fast, confident, occasionally exasperating.&lt;/li>
&lt;li>&lt;strong>the panel&lt;/strong>: a handful of reviewers running in parallel. Each has its own model, personality and above all opinions.&lt;/li>
&lt;li>&lt;strong>consensus&lt;/strong>: the accountant. Looks at who flagged what, computes an agreement score.&lt;/li>
&lt;li>&lt;strong>lead&lt;/strong>: the boss. Reads everything, makes the call, signs off on the final code.&lt;/li>
&lt;/ul>
&lt;p>The whole flow fits on a sticky note:&lt;/p></description></item><item><title>Driving opencode behind an SSH tunnel, with direnv</title><link>https://blog.6admin.io/en/posts/direnv-tunnel-opencode/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/direnv-tunnel-opencode/</guid><description>&lt;p>I told elsewhere how I keep my ssh and git config in a versioned repository, with a &lt;code>~/.ssh/config&lt;/code> that carries the identities, the jump hosts and the hardening (&lt;a href="https://blog.6admin.io/en/posts/myflec/">myflec&lt;/a>
).
This post is its logical sequel, one notch further, where access is no longer just opening a session but keeping a tunnel alive continuously so that a coding agent and a browser can work behind it.
The binder, this time, is &lt;a href="https://direnv.net/" target="_blank" rel="noopener noreferrer">direnv&lt;/a>
.&lt;/p></description></item><item><title>Ceph or ZFS on Proxmox, one question to ask</title><link>https://blog.6admin.io/en/posts/ceph-zfs-proxmox/</link><pubDate>Fri, 05 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ceph-zfs-proxmox/</guid><description>&lt;p>It is one of those debates that fill homelab forums and meeting rooms alike and it drags on because both camps compare things that do not compare. &lt;a href="https://openzfs.org/" target="_blank" rel="noopener noreferrer">ZFS&lt;/a>
 and Ceph do not play in the same category: one is a local filesystem and volume manager of remarkable robustness, the other is distributed storage aiming at scale and self-healing and trying to separate them in the absolute makes no more sense than asking whether a 13mm wrench beats a drill. The right question is not &amp;ldquo;which is better&amp;rdquo;, it is &amp;ldquo;how many nodes and what real high availability&amp;rdquo; and once that one is answered, the rest follows almost mechanically.&lt;/p></description></item><item><title>Your DNS zone is already an inventory: the log0u7.dns collection</title><link>https://blog.6admin.io/en/posts/dns-inventory/</link><pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/dns-inventory/</guid><description>&lt;p>Every static inventory I have ever seen started as truth and became fiction within a quarter. The machines move, the inventory file does not. Cloud plugins solve this by asking the cloud, but my fleets live across datacenters and ISPs, behind whatever DNS the company already runs. Which leads to an uncomfortable observation: &lt;strong>the DNS zone already contains the inventory&lt;/strong>. Every host has a record; what is missing is Ansible reading it.&lt;/p></description></item><item><title>Surviving Windows Update in the middle of a build</title><link>https://blog.6admin.io/en/posts/windows-update-jenkins/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/windows-update-jenkins/</guid><description>&lt;p>Some context first, because it is missing. Back then I was running build agents at an employer that developed Windows applications, on Windows, whose update policy was endured rather than chosen: updates landed whenever they decided to, pushed across the fleet, with no regard for whatever was compiling at that moment. Hence the problem that follows.&lt;/p>
&lt;p>Anyone who has operated Windows build agents knows that special moment when Windows Update decides, in the middle of a sensitive stage, that it is time to install three cumulative updates and schedule a reboot, wiping out twenty minutes of compilation. The naive reaction is to stop the update service at the start of the build and restore it at the end and that naive reaction holds exactly until the day two builds run in parallel on the same agent, or until the day a build crashes before reaching its cleanup stage, leaving the agent with its updates disabled for eternity. The good news: this problem has a clean solution and its vocabulary comes from shared resource management far more than from Windows administration.&lt;/p></description></item><item><title>Unix Frugality: do more with less</title><link>https://blog.6admin.io/en/posts/unix-frugality/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/unix-frugality/</guid><description>&lt;p>There is a test I run mentally when I read code or when someone introduces themselves: do I find a &lt;code>cat file.txt | grep pattern&lt;/code> where a &lt;code>grep pattern file.txt&lt;/code> would do?&lt;/p>
&lt;p>This is not an optimization question. The cost of one forked process is negligible. It is a culture question: &lt;code>cat | grep&lt;/code> betrays someone who learned commands as incantations, not as composable bricks. And the principle behind the short version is the very foundation of Unix: &lt;em>do more with less&lt;/em>, &lt;em>small is beautiful&lt;/em>. Each brick does one thing, does it well, takes text in and produces text out.&lt;/p></description></item><item><title>An IDS probe cannot see TLS</title><link>https://blog.6admin.io/en/posts/ids-probe-tls/</link><pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ids-probe-tls/</guid><description>&lt;p>IDS labs have genuine teaching value.
You stand up Suricata, wire in DVWA, replay a few web attacks and watch the rules fire live, which helps a great deal in understanding how a probe works.
The problem is not the lab, it is the move to production, where one structural detail changes everything: TLS.&lt;/p>
&lt;h2 id="why-the-lab-works">Why the lab works&lt;/h2>
&lt;p>If the lab works so well, it is precisely because DVWA runs over HTTP.
The traffic flows in clear text, the probe reads it byte by byte and signatures can inspect requests as well as responses without the slightest difficulty.
It is the same comfort you get when you pull out &lt;a href="https://blog.6admin.io/en/posts/tcpdump/">tcpdump&lt;/a>
 on an unencrypted flow, or hack together a &lt;a href="https://blog.6admin.io/en/posts/nemesis-sniffer-detection/">homemade sniffer&lt;/a>
 to watch the packets go by: as long as the payload is readable, all is well.&lt;/p></description></item><item><title>A Backup Without a Restore Test Is Decoration</title><link>https://blog.6admin.io/en/posts/backup-without-restore-is-decoration/</link><pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/backup-without-restore-is-decoration/</guid><description>&lt;p>One sentence, written in passing in a comment, sums up everything I have to say on the subject:&lt;/p>
&lt;blockquote>
&lt;p>Don&amp;rsquo;t forget that a backup policy without regular restore tests is just decoration: you don&amp;rsquo;t know whether your backup works until you&amp;rsquo;ve restored it.&lt;/p>&lt;/blockquote>
&lt;p>The rest of this article is merely the deployment of that sentence.&lt;/p>
&lt;h2 id="the-only-test-that-counts">The only test that counts&lt;/h2>
&lt;p>A backup that has never been restored is not a backup, it is a hypothesis. The day you need it is exactly the day you cannot afford to discover that:&lt;/p></description></item><item><title>A segfault in the JVM and an EDR at the bottom of the stack</title><link>https://blog.6admin.io/en/posts/jenkins-jvm-crash/</link><pubDate>Fri, 27 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/jenkins-jvm-crash/</guid><description>&lt;p>A Jenkins controller on Windows terminating in a loop on an &lt;code>EXCEPTION_ACCESS_VIOLATION&lt;/code>, that is, a segfault on the Windows side, restarting every minute for a whole afternoon.
The story is a good teaching case for two things: how you do not treat an instability and how you read a crash dump without over-reading what you see in it.&lt;/p>
&lt;h2 id="the-trap-we-had-already-fallen-into">The trap we had already fallen into&lt;/h2>
&lt;p>Even before my diagnosis, the trace of the problem was legible in the history.
Similar crashes had happened more than a year earlier, with no change on the Jenkins team&amp;rsquo;s side, which already pointed to an external factor.
The answer given at the time had been to migrate the JVM from one major version to the next, with no root-cause analysis and no post-mortem, in the hope that it would fix the problem.
The service stabilised for a while after configuration, then the crashes came back.&lt;/p></description></item><item><title>The boot chain, from GRUB to UKI</title><link>https://blog.6admin.io/en/posts/boot-grub-uki/</link><pubDate>Fri, 27 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/boot-grub-uki/</guid><description>&lt;p>Locking down the boot process and the editing of kernel parameters is not a new problem, it predates GRUB2, GRUB and even LILO.
What&amp;rsquo;s changing today is that the ecosystem is turning the page on an entire generation of tooling and Canonical&amp;rsquo;s announcement for Ubuntu 26.10 is a good marker of it.&lt;/p>
&lt;h2 id="what-canonical-is-announcing-for-2610">What Canonical is announcing for 26.10&lt;/h2>
&lt;p>In substance, Canonical is trimming signed GRUB for 26.10, which reduces the attack surface along the way, in order to prepare its phase-out.
GRUB is becoming obsolete against UKI (Unified Kernel Image) combined with systemd-boot.&lt;/p></description></item><item><title>Doing internal ACME without begging the AD CS team</title><link>https://blog.6admin.io/en/posts/acme-pki/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/acme-pki/</guid><description>&lt;p>At some point, in any slightly serious infrastructure, one wants to issue internal certificates the way Let&amp;rsquo;s Encrypt issues its own: automatically, through ACME, no ticket, no form, no negotiating every renewal with the person holding the keys of the in-house certificate authority. The problem is not technical at first glance; it is political, because in nine out of ten companies the PKI lives inside an AD CS that the Windows team guards jealously and nobody deploys a second trust root across the whole fleet just to make life easier. Three families of tools answer this need and while people usually believe they must choose between two of them, the third has quietly changed the game for anyone already running a secrets vault.&lt;/p></description></item><item><title>Trivy, the scanner that was stealing secrets</title><link>https://blog.6admin.io/en/posts/trivy-supply-chain/</link><pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/trivy-supply-chain/</guid><description>&lt;p>There is a perfect irony in the most deployed tool for scanning your images&amp;rsquo; vulnerabilities turning, for the duration of a few-hour window, into an infostealer running inside your pipelines with full access to their secrets. On March 19, 2026, the group calling itself TeamPCP compromised Aqua Security&amp;rsquo;s &lt;a href="https://trivy.dev/" target="_blank" rel="noopener noreferrer">Trivy&lt;/a>
 ecosystem and the story deserves telling not for the thrill, but because it illustrates to perfection a design mistake half the CI/CD planet still commits today.&lt;/p></description></item><item><title>A LLM's alignment is a thin layer</title><link>https://blog.6admin.io/en/posts/llm-alignment/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/llm-alignment/</guid><description>&lt;p>We were told a pretty story: a model patiently educated through RLHF and then DPO, until refusing dangerous requests became a deep property, almost a virtue, something as consubstantial to the network as its ability to conjugate a verb. It is comforting and it is false. Alignment, as practiced today, is not a foundation; it is a coat of paint and two works that oppose each other on every methodological point converge on this diagnosis with a cruelty that should make us humbler.&lt;/p></description></item><item><title>Secure Boot and PXE: the chicken, the egg and four ways out</title><link>https://blog.6admin.io/en/posts/secureboot-pxe/</link><pubDate>Tue, 24 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/secureboot-pxe/</guid><description>&lt;p>Provisioning a fleet of blank Windows machines over the network, by PXE, with Secure Boot on and never turned off, runs into a paradox you only see once your nose is against it.
To boot over PXE, the boot binary must be signed or trusted by the firmware.
To sign or enroll a key, you need to boot an OS.
With no pre-existing OS on the machine, you can therefore neither boot nor enroll and that is exactly the situation of a fresh machine you want to install over the network in the first place.
Here is the chain of trust as it must hold, then the four real ways out.&lt;/p></description></item><item><title>When a Chocolatey checksum breaks a whole provisioning run</title><link>https://blog.6admin.io/en/posts/p4v-checksum/</link><pubDate>Tue, 10 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/p4v-checksum/</guid><description>&lt;p>The provisioning of a fleet of Windows machines stops dead on an Ansible task installing a Chocolatey package and the message is a classic of the genre:&lt;/p>
&lt;pre tabindex="0">&lt;code>ERROR: Checksum for &amp;#39;p4vinst64.exe&amp;#39; did not meet &amp;#39;45648d...852bd5&amp;#39;
for checksum type &amp;#39;sha256&amp;#39;.
&lt;/code>&lt;/pre>&lt;p>The checksum the package expects does not match the downloaded binary.
Nothing in my infrastructure changed and yet the day before it worked.
The story is short, but it says something important about what &amp;ldquo;depending on a community package&amp;rdquo; really means.&lt;/p></description></item><item><title>Do you need Kubernetes for a small team ?</title><link>https://blog.6admin.io/en/posts/kubernetes-small-team/</link><pubDate>Sat, 07 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/kubernetes-small-team/</guid><description>&lt;p>I&amp;rsquo;ve written elsewhere about when not to run Kubernetes and I won&amp;rsquo;t rehash it here (see &lt;a href="https://blog.6admin.io/en/posts/kubernetes-ou-pas/">kubernetes-ou-pas&lt;/a>
).
This post takes a different angle : a small outfit, a single DevOps, a handful of devs with no infra experience and the question that keeps coming back of whether to adopt K8s at all.&lt;/p>
&lt;p>The answer isn&amp;rsquo;t technical, it&amp;rsquo;s human. I&amp;rsquo;ll frame it as three options, A, B and C, each with its own limits.&lt;/p></description></item><item><title>Docker vs jails, the honest comparison</title><link>https://blog.6admin.io/en/posts/docker-vs-jails/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/docker-vs-jails/</guid><description>&lt;p>Every six months the same debate resurfaces : FreeBSD jails supposedly crush Docker on efficiency and we should all feel guilty for having picked containers.
The case isn&amp;rsquo;t entirely wrong, it even makes a few valid points, but those points end up buried under selective comparisons and misleading metrics.&lt;/p>
&lt;p>I&amp;rsquo;ll take the arguments one by one, because telling legitimate criticism apart from misdirection seems more useful than defending a camp.&lt;/p></description></item><item><title>myflec: a Bash dotfiles framework for people who deploy their home directory</title><link>https://blog.6admin.io/en/posts/myflec/</link><pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/myflec/</guid><description>&lt;p>My &lt;code>$HOME&lt;/code> is version-controlled, deployed from templates and pushed to three forges. Not because I enjoy yak-shaving, but because I have broken too many machines to trust &amp;ldquo;it&amp;rsquo;s probably fine&amp;rdquo;. &lt;a href="https://github.com/log0u7/myflec" target="_blank" rel="noopener noreferrer">myflec&lt;/a>
 (&amp;ldquo;My Favorite Linux Environment Configuration&amp;rdquo;) is the Bash framework that grew out of that habit: a loader, a backup model, a deploy procedure and an identity model. All of it boring on purpose, all of it shell-native.&lt;/p>
&lt;h2 id="the-old-base">The old base&lt;/h2>
&lt;p>This is not a young project. The GitHub account underneath it was created in September 2011 and the habits are older than the name. The clearest surviving ancestor is &lt;a href="https://blog.6admin.io/en/posts/vim-apthogen/">vim-apthogen&lt;/a>
 (2016): a &lt;code>curl | bash&lt;/code> netinstall that bootstrapped an entire &lt;code>~/.vim&lt;/code> from symlinks, driven by declarative lists with dependency tiers (&lt;code>.apthogen.list&lt;/code>, then &lt;code>.required.list&lt;/code>, &lt;code>.minimal.list&lt;/code>, &lt;code>.highly.list&lt;/code>, &lt;code>.recomended.list&lt;/code>, &lt;code>.whynot.list&lt;/code>) and install menus. The author signature of the era is visible in every script: a heredoc header reading &lt;code>Version / Authors / Description / Dependencies&lt;/code> and a &lt;code>.vimrc&lt;/code> that already carried &lt;code>command W w !sudo tee&lt;/code> and &lt;code>command Q qa&lt;/code>, the same aliases the 2018 config kept and the current one still has.&lt;/p></description></item><item><title>A reverse proxy in front of Perforce, or not: the pros and cons</title><link>https://blog.6admin.io/en/posts/perforce-reverse-proxy-ou-pas/</link><pubDate>Thu, 18 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/perforce-reverse-proxy-ou-pas/</guid><description>&lt;p>Before showing how to put nginx in front of Perforce, which I did in &lt;a href="https://blog.6admin.io/en/posts/nginx-perforce/">the dedicated article&lt;/a>
, the real question has to be asked, the one usually skipped: whether to do it at all. Because &lt;code>p4d&lt;/code> can terminate its own TLS and adding an intermediary is free nowhere. Four approaches and what they cost.&lt;/p>
&lt;h2 id="the-common-trap-the-client-ip">The common trap: the client IP&lt;/h2>
&lt;p>One point runs through everything else, so it may as well come first. As soon as an intermediary terminates or relays the connection, &lt;code>p4d&lt;/code> risks seeing that intermediary&amp;rsquo;s address and not the client&amp;rsquo;s. This breaks IP-based logging, the server-side protections that rely on the address and the triggers that read it. Each of the options below is judged partly on this, so keep it in mind.&lt;/p></description></item><item><title>Nginx in front of Perforce: layer-4 TLS termination and the P4 protocol</title><link>https://blog.6admin.io/en/posts/nginx-perforce/</link><pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/nginx-perforce/</guid><description>&lt;p>Before putting nginx in front of Perforce, you should ask whether it is a good idea at all and I covered that question separately in &lt;a href="https://blog.6admin.io/en/posts/perforce-reverse-proxy-ou-pas/">a reverse proxy in front of Perforce, the pros and cons&lt;/a>
. Here I start from the decision made: we want centralised TLS termination, managed in the same place and by the same ACME tool as the Jenkins certificate. What remains is doing it correctly and correctly means otherwise than an HTTP tutorial would suggest.&lt;/p></description></item><item><title>Containers, micro-VMs and Firecracker: why isolation is not what you think</title><link>https://blog.6admin.io/en/posts/firecracker-isolation/</link><pubDate>Sat, 06 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/firecracker-isolation/</guid><description>&lt;p>A Docker container is not a VM. It is a process with namespaces. The kernel is shared: the same Linux, the same syscall table, the same kernel code, the same network stack. A kernel vulnerability opens ALL containers on the machine, not just one.&lt;/p>
&lt;p>This is a distinction the articles on this blog have documented since 2006: &lt;a href="https://blog.6admin.io/en/posts/hard-symbolic-links/">the chroot that overflows&lt;/a>
 via a symlink, &lt;a href="https://blog.6admin.io/en/posts/raw-sockets/">the raw socket&lt;/a>
 that sees the entire network, &lt;a href="https://blog.6admin.io/en/posts/sockets-c/">the socket speaking TCP&lt;/a>
 because the kernel does the transport. Each of these techniques works because container isolation is sharing, not a hardware boundary.&lt;/p></description></item><item><title>Terraform creates, Ansible configures</title><link>https://blog.6admin.io/en/posts/terraform-creates-ansible-configures/</link><pubDate>Fri, 21 Nov 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/terraform-creates-ansible-configures/</guid><description>&lt;p>Florian&amp;rsquo;s point was an honest one and I share its first half: Terraform alone works very well as long as you stick to a 100% immutable model, where you throw in a cloud-init at provisioning time, recreate the VM whenever you need to change it and nothing ever gets modified in place. If your infrastructure lends itself to that model, perfect, look no further. The interesting question starts at the precise moment it no longer does.&lt;/p></description></item><item><title>fnox and age: encrypted secrets in my repos, no server</title><link>https://blog.6admin.io/en/posts/fnox-age-secrets/</link><pubDate>Tue, 18 Nov 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/fnox-age-secrets/</guid><description>&lt;p>Personal project secrets have always had the same problem: no Vault at home, &lt;code>.env&lt;/code> files that should never be committed but always are and non-secret environment variables already well handled by &lt;a href="https://direnv.net/" target="_blank" rel="noopener noreferrer">direnv&lt;/a>
. In late 2025 I discovered &lt;a href="https://github.com/jdx/fnox" target="_blank" rel="noopener noreferrer">fnox&lt;/a>
, the secrets manager from the author of &lt;a href="https://blog.6admin.io/en/posts/mise-versions-outils/">mise&lt;/a>
, which fits that gap exactly: &lt;strong>secrets live encrypted in the repo itself, serverless&lt;/strong>.&lt;/p>
&lt;h2 id="age-the-brick">age: the brick&lt;/h2>
&lt;p>Under the hood sits &lt;a href="https://github.com/FiloSottile/age" target="_blank" rel="noopener noreferrer">age&lt;/a>
, FiloSottile&amp;rsquo;s encryption tool (the Go one). The stance: a file to encrypt, explicit keys, zero config options. &lt;code>age-keygen&lt;/code> produces a pair: a private &lt;code>AGE-SECRET-KEY-...&lt;/code> and a public &lt;code>age1...&lt;/code> recipient. You encrypt a file toward a list of recipients; SSH keys also work as recipients. No web of trust, no keyserver, no keyring: two concepts and a readable format. It is what PGP should have been, without thirty years of archaeology.&lt;/p></description></item><item><title>Portable assembly does not exist</title><link>https://blog.6admin.io/en/posts/portable-assembly/</link><pubDate>Mon, 17 Nov 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/portable-assembly/</guid><description>&lt;p>You sometimes hear that a single assembly file would recompile and run anywhere, as long as you stay on a common x86-64 architecture.
It is a pleasant idea and it collides with reality the moment you look at what actually surrounds the machine instruction.
The instruction set, admittedly, is shared.
Everything else, the tooling that assembles, the format that wraps, the convention that lets functions talk to one another and the door through which you enter the kernel, all of that diverges from one system to the next.&lt;/p></description></item><item><title>Security Through Obscurity Is Not Security, Digital Hygiene Is</title><link>https://blog.6admin.io/en/posts/obscurity-is-not-security/</link><pubDate>Thu, 13 Nov 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/obscurity-is-not-security/</guid><description>&lt;p>I held the same position for years and I defended it fiercely in a comment in April 2020:&lt;/p>
&lt;blockquote>
&lt;p>Security through obscurity is not security. At best it buys time, at worst it costs you some. On top of that it completely distorts the trust relationship and the reliability of the said security. Avoid it&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;p>And to illustrate the absurdity, I followed up with the recipe: don&amp;rsquo;t share your sources, don&amp;rsquo;t share your specs, obfuscate your code, encrypt it in memory, delay its execution, make it polymorphic, fetch your payload remotely&amp;hellip; wait&amp;hellip; are we talking about malware here?&lt;/p></description></item><item><title>The AI secret language, folklore</title><link>https://blog.6admin.io/en/posts/ai-secret-language/</link><pubDate>Mon, 10 Nov 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ai-secret-language/</guid><description>&lt;p>The story keeps coming back: a &amp;ldquo;secret language&amp;rdquo; AIs supposedly invented to talk to each other, faster and better than us, behind our backs.
The version that set the networks on fire in early 2025 was called Gibberlink and the legend that stuck to it promised a protocol &amp;ldquo;80 percent more efficient than human language&amp;rdquo;.
I want to set the record straight calmly, because the demo itself is real, but the story told on top of it is technological folklore.&lt;/p></description></item><item><title>Nginx on Windows in front of Jenkins: the proxy, the logs and the upgrades</title><link>https://blog.6admin.io/en/posts/nginx-windows-proxy/</link><pubDate>Mon, 08 Sep 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/nginx-windows-proxy/</guid><description>&lt;p>Standing up an nginx on Windows Server to terminate TLS in front of a Jenkins takes five minutes for the version that works in a demo and a real half-day for the version you stand behind in production.
The reverse proxy part is not the hard one, it fits in a well-tuned &lt;code>location&lt;/code>. What makes the difference is what Windows does not give you and no tutorial mentions: log rotation and an upgrade that actually takes.
One word before we start: if the same machine also fronts a Perforce, do not try to run it through the config below. The P4 protocol is not HTTP, it calls for an entirely different approach I cover separately in &lt;a href="https://blog.6admin.io/en/posts/nginx-perforce/">nginx in front of Perforce&lt;/a>
.&lt;/p></description></item><item><title>Clean Code vs Vibe Coding (or how not to end up in the news)</title><link>https://blog.6admin.io/en/posts/clean-code-vs-vibe-coding/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/clean-code-vs-vibe-coding/</guid><description>&lt;p>The emergence of large language models (LLMs) in software development raises a fundamental question: are our established clean code practices still relevant against &amp;ldquo;vibe coding&amp;rdquo;? And more importantly, how do we avoid ending up on tech news with the next &amp;ldquo;oops, I exposed 50 million passwords&amp;rdquo;?&lt;/p>
&lt;h2 id="these-old-habits-that-save-us">These old habits that save us&lt;/h2>
&lt;p>Clean code relies on proven principles that, incidentally, kept quite a few developers from technical unemployment:&lt;/p>
&lt;p>&lt;strong>DRY (Don&amp;rsquo;t Repeat Yourself)&lt;/strong> prevents us from duplicating our vulnerabilities across the codebase. &lt;strong>SOLID&lt;/strong> helps us create architectures where one security flaw does not turn the whole system into digital Swiss cheese. &lt;strong>KISS (Keep It Simple, Stupid)&lt;/strong> limits attack surfaces, because complex code often hides unpleasant surprises. &lt;strong>YAGNI (You Aren&amp;rsquo;t Gonna Need It)&lt;/strong> prevents us from adding useless code that only increases attack surface without adding value.&lt;/p></description></item><item><title>vim-plugin-manager: turning a decade of bash into a real Vim plugin</title><link>https://blog.6admin.io/en/posts/vim-plugin-manager/</link><pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/vim-plugin-manager/</guid><description>&lt;p>The bash script from &lt;a href="https://blog.6admin.io/en/posts/vim-submodules/">the 2018 episode&lt;/a>
 did its job for seven years: plugins as git submodules, native &lt;code>pack/&lt;/code> loading, a &lt;code>plugins&lt;/code> script for the choreography. What it could never do was live inside the editor: no progress while cloning, no lazy loading wired to commands, no help. In March 2025 I finally gave it the ending it deserved: rewritten from scratch in VimScript as &lt;a href="https://github.com/log0u7/vim-plugin-manager" target="_blank" rel="noopener noreferrer">vim-plugin-manager&lt;/a>
, about 5,600 lines of legacy VimScript.&lt;/p></description></item><item><title>Which identity runs the service and who can prove it</title><link>https://blog.6admin.io/en/posts/hardening-win-services-accounting/</link><pubDate>Sat, 12 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-win-services-accounting/</guid><description>&lt;p>A Windows service runs as some identity and that identity is the whole security story: it is the blast radius when the service is exploited and it is the name in the logs when you need to know who did what. Most of the trouble I audit comes from one shortcut, a local account with a static password baked into the service configuration, which is both a secret sitting in cleartext and an identity nobody rotates. The fix is not a better password, it is choosing an identity that has no password a human ever holds.&lt;/p></description></item><item><title>PowerShell execution policy and what it is not</title><link>https://blog.6admin.io/en/posts/hardening-win-execution-policies/</link><pubDate>Sat, 08 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-win-execution-policies/</guid><description>&lt;p>The single most useful sentence about ExecutionPolicy is the one Microsoft puts in its own documentation: it is not a security boundary. It is a seatbelt against double-clicking a script you did not mean to run, nothing more and treating it as a control that keeps an attacker out is a category error that hides the real levers. So the honest way to cover it is to say what it does, prove how little that is and then point at the things that actually constrain what PowerShell can do.&lt;/p></description></item><item><title>Driving Windows from Ansible without leaking credentials</title><link>https://blog.6admin.io/en/posts/hardening-win-ansible-transport/</link><pubDate>Wed, 12 Feb 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-win-ansible-transport/</guid><description>&lt;p>The question people argue about is SSH versus WinRM and it is the wrong question, because both are now legitimate: Win32-OpenSSH is a first-class Ansible transport and WinRM is the native one. The question that actually decides your exposure is narrower and sharper: when a play authenticates to a domain-joined host, does a reusable credential cross the wire and can the target then act as you against the rest of the domain. Get the authentication protocol and the delegation model right and either transport is fine. Get them wrong and WinRM will hand an attacker your domain the same way a bad SSH setup would.&lt;/p></description></item><item><title>Hardening WinRM</title><link>https://blog.6admin.io/en/posts/hardening-winrm/</link><pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-winrm/</guid><description>&lt;p>WinRM is the engine under PowerShell remoting and most remote Windows management, which makes it both indispensable and a tidy path from one shell to a whole estate. Standing it up is one command. Standing it up safely means undoing two of the defaults that command leaves behind, choosing a transport that means something and constraining what a remote session may actually do. The order matters, because the convenient setup is the insecure one.&lt;/p></description></item><item><title>Hardening Proxmox VE</title><link>https://blog.6admin.io/en/posts/hardening-proxmox/</link><pubDate>Wed, 18 Dec 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-proxmox/</guid><description>&lt;p>A Proxmox node is a hypervisor, which means compromising it is not compromising a server, it is compromising every VM and container on it at once. That raises the stakes of the ordinary things, the web interface, the login, the SSH, the firewall, because each is now a door into the whole fleet rather than one machine. The hardening splits cleanly: protect the management plane that controls everything and understand which of the two workload types actually isolates and which is a host-root escape waiting for an excuse.&lt;/p></description></item><item><title>Bare-metal Kubernetes in HA, without kidding yourself</title><link>https://blog.6admin.io/en/posts/kubernetes-bare-metal-ha/</link><pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/kubernetes-bare-metal-ha/</guid><description>&lt;p>On a cloud, Kubernetes high availability is bought: the control plane is managed, the load balancer is an API object, storage is a class you reference. On bare metal, each of those three conveniences becomes something you have to build yourself and that is where most tutorials stack seven components to cover three jobs.
This post builds an HA cluster with the defensible minimum: a control plane that survives the loss of a node, an API VIP with no external load balancer, a network that also carries the LoadBalancer and storage replicated across the nodes&amp;rsquo; own disks. Three pieces covering the three single points of failure and not one more, because a cluster you do not understand is not available, it is on borrowed time.&lt;/p></description></item><item><title>Kubernetes, or not</title><link>https://blog.6admin.io/en/posts/kubernetes-ou-pas/</link><pubDate>Tue, 19 Nov 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/kubernetes-ou-pas/</guid><description>&lt;p>Kubernetes solves a real problem, scheduling many workloads across many machines while healing itself and it solves it well. The trap is not the tool, it is reaching for it to solve a problem you do not have yet, paying up front a permanent operational tax for resilience you are not using.
Because Kubernetes&amp;rsquo; real expense is not conceptual, it is operational: the control plane to keep alive, the CNI to understand, the storage to stand up, the quarterly API updates to follow and an attack surface with its own hardening posts. Before signing up for that, it is worth walking down the floors and stopping at the first one that carries the load, because the right level of abstraction is the lowest that solves your problem, not the most prestigious.&lt;/p></description></item><item><title>Hardening Kubernetes</title><link>https://blog.6admin.io/en/posts/hardening-kubernetes/</link><pubDate>Tue, 05 Nov 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-kubernetes/</guid><description>&lt;p>Kubernetes multiplies the single-container problem by a control plane, a flat network and an authorization system and each of them ships a default that is convenient rather than safe.
The keys to the whole cluster are three: a pod that can reach the host, an RBAC grant that is too broad and a network that lets every pod talk to every other.
Harden those three and the exotic attacks lose their first step.
Leave one open and the rest of the hardening is decoration, because a privileged pod, a &lt;code>cluster-admin&lt;/code> binding or a reachable etcd are already game over.&lt;/p></description></item><item><title>Hardening Docker Swarm</title><link>https://blog.6admin.io/en/posts/hardening-docker-swarm/</link><pubDate>Tue, 08 Oct 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-docker-swarm/</guid><description>&lt;p>Everything that applies to single-host Docker hardening still applies to each container in a Swarm, so this note only covers what the cluster adds: a control plane, a trust boundary between manager and worker, a network that spans hosts and a secrets store.
The good news and it deserves saying because people look down on Swarm, is that its security defaults are better than assumed.
Nodes authenticate each other over mutual TLS with rotating certificates, out of the box and the Raft store that holds the cluster state is encrypted at rest.
The gaps are narrower and more precise: one network setting off by default, one operational habit that collapses the trust boundary and the usual temptation to put secrets in the wrong place.&lt;/p></description></item><item><title>Docker hardening</title><link>https://blog.6admin.io/en/posts/hardening-docker/</link><pubDate>Wed, 18 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-docker/</guid><description>&lt;p>The sentence that should govern every Docker decision is this: a container shares the host kernel, so a container escape is a kernel escape. A container is not a small VM, it is a process with namespaces and cgroups wrapped around it and a seccomp filter in front of its syscalls and everything below that is the same kernel the host runs. Hardening Docker is therefore mostly about not handing a container the few things that let it reach around the namespaces and talk to that shared kernel directly, and, underneath, about not running the whole apparatus as root in the first place.&lt;/p></description></item><item><title>mise: one static binary for all the tool versions</title><link>https://blog.6admin.io/en/posts/mise-versions-outils/</link><pubDate>Thu, 05 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/mise-versions-outils/</guid><description>&lt;p>Every project drags its collection of versions along: repo A wants Node 20, repo B still stands on Node 18, C runs on Python 3.11 while D already demands 3.12 and terraform, &lt;a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">hugo&lt;/a>
 and go follow the same law. Installing those versions machine-wide means crushing one on top of the other: the last one wins and every other repo breaks in silence. I took that problem by the right end in 2024 with &lt;a href="https://github.com/jdx/mise" target="_blank" rel="noopener noreferrer">mise&lt;/a>
: &lt;strong>a per-project tool version manager held by a single Rust binary&lt;/strong>.&lt;/p></description></item><item><title>Hardening DHCP</title><link>https://blog.6admin.io/en/posts/hardening-dhcp/</link><pubDate>Wed, 28 Aug 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-dhcp/</guid><description>&lt;p>DHCP hands a booting machine its address, its gateway and its DNS, which is to say it tells every client where to send its traffic and whom to trust to resolve names.
The protocol has no authentication: a client takes the first offer it hears.
That single fact is the entire threat model.
A rogue DHCP server, a laptop with &lt;code>dnsmasq&lt;/code>, answers faster than yours and now owns the gateway and the resolver of everyone who believed it and from there reads, redirects and rewrites traffic at will.
The defense is not in the DHCP server, it is in the switch, the only thing placed to decide which offers are allowed on the wire.&lt;/p></description></item><item><title>Hardening DNS infrastructure</title><link>https://blog.6admin.io/en/posts/hardening-dns/</link><pubDate>Wed, 03 Jul 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-dns/</guid><description>&lt;p>DNS is trusted by everything and verified by almost nothing, which is why a resolver you run carelessly becomes a weapon pointed at other people and an authoritative server you run carelessly becomes a map of your estate and a channel out of it. The threats are familiar, cache poisoning, amplification, tunneling, zone disclosure and the defenses are not exotic. They come down to four decisions: who may ask your resolver to recurse, who may pull your zones, whether your answers can be forged and whether you can see data leaving in your own queries.&lt;/p></description></item><item><title>SSH Security Hardening: Access Control and Monitoring</title><link>https://blog.6admin.io/en/posts/hardening-ssh/</link><pubDate>Wed, 03 Jul 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-ssh/</guid><description>&lt;p>SSH is the front door of almost every fleet, which is exactly why it is the first thing worth getting right. A loose configuration does not just expose one host, it hands an attacker the three things they want most: a way in, a way across and a way to stay. The point of this note is not to recite a compliance checklist, it is to describe the configuration I actually deploy, the reasons behind each choice and the monitoring that turns &amp;ldquo;someone logged in&amp;rdquo; into &amp;ldquo;this person, from there, at that time&amp;rdquo;.&lt;/p></description></item><item><title>Docker Swarm to Kubernetes: why I abandoned Swarm</title><link>https://blog.6admin.io/en/posts/swarm-to-k8s/</link><pubDate>Sat, 22 Jun 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/swarm-to-k8s/</guid><description>&lt;p>Docker Swarm works well as a lightweight orchestrator for small-to-medium deployments. It is simple, mature and the entry barrier is low. But it hits structural walls on security and advanced container management. Here are the four.&lt;/p>
&lt;h2 id="no-rootless-mode">No rootless mode&lt;/h2>
&lt;p>Docker rootless mode (&lt;code>dockerd-rootless-setuptool.sh&lt;/code> with slirp4netns/RootlessKit) is &lt;strong>incompatible with Docker Swarm&lt;/strong>. The official documentation explicitly lists:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Overlay networks&lt;/strong>: Swarm relies entirely on overlay (VXLAN) for inter-node communication. Without overlay, services on different nodes cannot reach each other. Swarm becomes a single-node-only tool.&lt;/li>
&lt;li>&lt;strong>AppArmor&lt;/strong>: no MAC (Mandatory Access Control) enforcement on containers.&lt;/li>
&lt;li>&lt;strong>Checkpoint/Restore&lt;/strong>: no CRIU snapshotting, no live migration, no stateful recovery.&lt;/li>
&lt;/ul>
&lt;p>This is a fundamental architectural constraint: Swarm requires kernel-level networking primitives (netns, veth, VXLAN) that rootless mode cannot provide. slirp4netns and RootlessKit operate in user namespaces incompatible with kernel-level overlay networking.&lt;/p></description></item><item><title>Internal vs external domain separation</title><link>https://blog.6admin.io/en/posts/hardening-domain/</link><pubDate>Thu, 30 May 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-domain/</guid><description>&lt;p>Using one domain name for both the public site and the internal estate feels tidy and it quietly hands an attacker several advantages at once. The argument for splitting them is not aesthetic, it is that a handful of concrete attacks stop working the moment internal services live under a name that never appears in a public certificate or a public DNS answer.&lt;/p>
&lt;h2 id="why-a-shared-domain-leaks">Why a shared domain leaks&lt;/h2>
&lt;p>A wildcard certificate is the first problem. A &lt;code>*.company.com&lt;/code> issued for the public web is, by construction, valid for every internal host under that name too, so the day a single public server is compromised, its certificate impersonates any internal service without a browser blinking. One stolen key and the man in the middle is trusted everywhere.&lt;/p></description></item><item><title>Hyper-V S2D vs Proxmox Ceph: the comparison that decided it</title><link>https://blog.6admin.io/en/posts/hyperv-vs-proxmox/</link><pubDate>Fri, 17 May 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hyperv-vs-proxmox/</guid><description>&lt;p>The Hyper-V versus Proxmox debate is rarely settled by a single number, because the two platforms do not pay the same price for the same guarantees.
Hyper-V with Storage Spaces Direct remains built for a Windows world, whereas Proxmox VE with Ceph aims at the flexibility of a mixed fleet and a zero licensing bill.
The real trade-off plays out on two axes, the performance one can expect from storage and the cost of ownership spread over several years.&lt;/p></description></item><item><title>Egress filtering, the control everyone skips</title><link>https://blog.6admin.io/en/posts/hardening-egress/</link><pubDate>Fri, 12 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-egress/</guid><description>&lt;p>Almost every firewall I audit is meticulous about what may come in and indifferent about what may go out. That asymmetry is exactly what an attacker counts on, because the valuable moves all happen outbound: the reverse shell dials home, the stolen data leaves, the implant beacons to its C2. An inbound-only firewall is a door with a strong lock and no wall behind it. The point of egress filtering is that a host which has no business talking to the internet should not be able to and a host that does should only reach the few places it genuinely needs.&lt;/p></description></item><item><title>Inbound traffic: expose less and guard what is left</title><link>https://blog.6admin.io/en/posts/hardening-ingress/</link><pubDate>Wed, 28 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-ingress/</guid><description>&lt;p>Every port a machine listens on is a promise to answer a stranger and most incidents start with a promise nobody meant to make: a database bound to &lt;code>0.0.0.0&lt;/code>, an admin panel on a public IP, a forgotten service a scanner found before you did. The internet is scanned end to end continuously, so an exposed service is not a risk that might be noticed, it is one that will be, usually within hours. Ingress hardening is therefore two disciplines: expose as little as possible and put the few things that must be exposed behind controls that assume they will be attacked.&lt;/p></description></item><item><title>Network segmentation</title><link>https://blog.6admin.io/en/posts/hardening-networking/</link><pubDate>Mon, 22 Jan 2024 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-networking/</guid><description>&lt;p>A flat network is a single room where everyone can reach everyone and it is the condition that turns one compromised laptop into a compromised estate. The intrusion almost never lands where the valuable thing is; it lands on a user machine and then moves, to the file server, the database, the domain controller, because nothing on a flat network says no. Segmentation is the practice of adding walls so that a foothold buys the attacker one room rather than the building and the honest version of it is more than drawing VLANs: it is making the VLANs real with filtering between them and hardening the layer-2 tricks that otherwise walk straight through.&lt;/p></description></item><item><title>Resource limits: stopping a machine from being talked into dying</title><link>https://blog.6admin.io/en/posts/hardening-limits/</link><pubDate>Tue, 05 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-limits/</guid><description>&lt;p>The fork bomb is the whole argument in one line:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>:&lt;span style="color:#f92672">(){&lt;/span> :|:&amp;amp; &lt;span style="color:#f92672">}&lt;/span>;:
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A function that calls itself twice and backgrounds both copies, run once and the process count doubles every fraction of a second until the process table is full, no new process can start and the machine is a brick needing a hard reboot. It is not an exploit, it needs no privilege and any local user can type it by accident. The same shape drives the rest of the family: a loop that allocates memory until the OOM killer fires, one that opens file descriptors until &lt;code>fork&lt;/code> fails with &amp;ldquo;too many open files&amp;rdquo;, one that fills &lt;code>/tmp&lt;/code> or &lt;code>/var/log&lt;/code> until the root filesystem wedges. None of them are clever. All of them are stopped by the same thing, a ceiling the user cannot exceed and the only real work is putting the ceiling in the right place.&lt;/p></description></item><item><title>Filesystem hardening</title><link>https://blog.6admin.io/en/posts/hardening-filesystem/</link><pubDate>Wed, 08 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-filesystem/</guid><description>&lt;p>Most of filesystem hardening is deciding, per mount, which of three powers a directory is allowed to grant: running a binary, honouring its setuid bit and letting it be a device node. The default, &lt;code>defaults&lt;/code>, grants all three everywhere, which is convenient and wrong for the directories users and daemons can write to. The rest is separating partitions so one cannot starve the others, turning on the kernel&amp;rsquo;s link protections and keeping an eye on which files carry setuid. None of it is exotic and the payoff is that whole categories of local escalation stop having a place to land.&lt;/p></description></item><item><title>Recompiling your kernel: bespoke kernels for your own infra</title><link>https://blog.6admin.io/en/posts/recompile-kernel/</link><pubDate>Fri, 27 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/recompile-kernel/</guid><description>&lt;p>Hardening the OS is mostly subtraction, removing what listens and cutting the kernel surfaces you do not need, which I detailed in &lt;a href="https://blog.6admin.io/en/posts/hardening-linux-os/">hardening the OS and the Linux kernel&lt;/a>
.
There is one step that article did not climb and it is my favorite: not disabling a kernel feature at runtime, but never compiling it at all.
Recompiling your own kernel is subtraction taken to its logical conclusion.&lt;/p>
&lt;h2 id="why-recompile-exactly">Why recompile, exactly&lt;/h2>
&lt;p>A distribution&amp;rsquo;s default is a kernel for everyone, so a kernel that ships just about everything: hundreds of drivers, exotic filesystems, whole networking subsystems, debug options, because it has to boot a student&amp;rsquo;s laptop as well as a storage array.
On an infra, the hardware is known and stable and all that baggage becomes free attack surface.
A subsystem that is not compiled has no CVE for you, no module to load, no bug to exploit; it simply does not exist in the binary.
Compiling the kernel for &lt;em>your&lt;/em> fleet is therefore three wins at once: a surface reduced to what you actually use, a smaller and often faster kernel because it carries less and a control that changes everything the day you have to react.&lt;/p></description></item><item><title>Linux OS and kernel hardening</title><link>https://blog.6admin.io/en/posts/hardening-linux-os/</link><pubDate>Fri, 20 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-linux-os/</guid><description>&lt;p>A freshly installed Linux box is built to be convenient, which means it ships with services you did not ask for, kernel features you will never use and defaults chosen so that nothing surprises a beginner. Hardening it is mostly subtraction: remove what is listening, switch off the kernel surfaces you do not need, tighten the handful of sysctl knobs that matter and lock down the accounts and the module loader. The individual steps are small and well known. The discipline is doing them deliberately rather than inheriting whatever the installer decided and keeping the box patched, because the kernel exploits that matter (PwnKit, Dirty Pipe and their successors) are patched long before most machines apply the fix.&lt;/p></description></item><item><title>Sudo and LOLBins</title><link>https://blog.6admin.io/en/posts/hardening-sudo-lolbins/</link><pubDate>Sat, 15 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hardening-sudo-lolbins/</guid><description>&lt;p>The comfortable belief about sudo is that a narrow rule is a safe rule, that &lt;code>NOPASSWD: /usr/bin/find&lt;/code> grants the ability to run find and nothing more. GTFOBins demolished that belief and the honest version of a sudo hardening note has to start there: the granularity of sudoers limits nothing the moment the binary you allowed can be talked into spawning a shell and a startling fraction of the standard toolbox can. The question is never &amp;ldquo;which command did I grant&amp;rdquo;, it is &amp;ldquo;what can that command be made to do once it runs as root&amp;rdquo;.&lt;/p></description></item><item><title>Scale-to-zero: Sablier wakes Swarm and Kubernetes on demand</title><link>https://blog.6admin.io/en/posts/scale-to-zero/</link><pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/scale-to-zero/</guid><description>&lt;p>A service you consult once a week runs one hundred percent of the time. You installed it for a single need, a QA you open on Monday, a team wiki, an admin tool you only reach for during an incident. It permanently holds its share of RAM, its share of CPU, its ports, its slot in monitoring and its stream of security updates. Shutting it down settles all of it at once, except that you then have to bring it back by hand the moment you need it and nobody remembers the command.
Scale-to-zero is the idea of starting a service only when someone asks for it and stopping it when nobody uses it anymore, without the user having to know it was asleep. &lt;a href="https://github.com/sablierapp/sablier" target="_blank" rel="noopener noreferrer">Sablier&lt;/a>
 is a small API server you place next to the reverse proxy: the proxy intercepts each request, calls the API to know whether the target is asleep, wakes it if needed and shows a waiting page while it starts. Once the target is ready the request goes through and a session opens, closed after &lt;code>sessionDuration&lt;/code>, at which point Sablier shuts the workload down.&lt;/p></description></item><item><title>The Bastion and Guacamole: the two leaves of the front door</title><link>https://blog.6admin.io/en/posts/bastion-guacamole/</link><pubDate>Sun, 20 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/bastion-guacamole/</guid><description>&lt;p>There are two populations in every infrastructure and they ask the same question of different tools. Shell people ask: &amp;ldquo;how do I reach the server and who can prove it afterwards?&amp;rdquo; Desktop people ask: &amp;ldquo;how do I reach that Windows box without a VPN and without installing a client?&amp;rdquo; There is a remarkable tool on each side and both deserve better than a footnote: &lt;a href="https://github.com/ovh/the-bastion" target="_blank" rel="noopener noreferrer">The Bastion&lt;/a>
 from OVHcloud for the first population, &lt;a href="https://guacamole.apache.org/" target="_blank" rel="noopener noreferrer">Apache Guacamole&lt;/a>
 for the second. Together they are the two leaves of the same front door: one keeps the ledger of hands on keyboards, the other of hands on mice.&lt;/p></description></item><item><title>The :ro flag on the Docker socket protects nothing</title><link>https://blog.6admin.io/en/posts/docker-socket-ro/</link><pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/docker-socket-ro/</guid><description>&lt;p>In every code review where someone has had the misfortune of mounting /var/run/docker.sock into a container, there is a small ritual consisting of pointedly noting that one could at least make it read-only, upon which everyone nods, adds &lt;code>:ro&lt;/code> to the compose file and closes the merge request with the feeling of duty done. It is theater and I would like to explain precisely why, because the mistake is instructive well beyond Docker.&lt;/p></description></item><item><title>FluoLight: rewriting an inherited Arduino firmware into something defensible</title><link>https://blog.6admin.io/en/posts/fluoboard-rewrite/</link><pubDate>Sun, 24 Apr 2022 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/fluoboard-rewrite/</guid><description>&lt;p>Second episode of the FluoBoard series, after &lt;a href="https://blog.6admin.io/en/posts/fluoboard-what-not-to-do/">the autopsy of the original board&lt;/a>
. I promised a follow-up there and here it is, though it did not happen in one night. From 2021 I went at the most glaring gap first: splitting the monolith into several &lt;code>.ino&lt;/code> files by responsibility, because a single sketch does not stay maintainable, fixing bugs along the way and adding the &lt;code>#define&lt;/code>s that were missing. Then came a hand-written event loop, my first way of pacing actions over time, before I reached for the TimedAction library to do it properly. The bulk of the work was done in 2021-2022, with final touches coming later [edit: 2025], all of it followable commit by commit. Same board family, same &lt;code>/lights/&amp;lt;mac&amp;gt;&lt;/code> protocol whose answer fits in a single digit between &lt;code>&amp;lt;...&amp;gt;&lt;/code>. What changes is not the feature list, it is that the loop never stops to wait.&lt;/p></description></item><item><title>One Ansible image for GitLab CI, built right from a Paris basement</title><link>https://blog.6admin.io/en/posts/docker-ansible/</link><pubDate>Tue, 22 Mar 2022 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/docker-ansible/</guid><description>&lt;p>Every CI runner that runs Ansible has the same problem: the job needs ansible-core, the collections for your platforms, git, ssh and it needs all of it &lt;em>now&lt;/em>, on a machine that will be thrown away after the job. The official images exist, but they drift: &lt;code>latest&lt;/code> changes under you, the version you pinned gets rebase-rolled and installing eight Galaxy collections per job burns minutes on every pipeline.&lt;/p>
&lt;p>So I wrote my own, &lt;code>alpine/ansible&lt;/code>, an Ansible CI toolkit image for my GitLab runners. And it was not born yesterday: I put it together for myself around 2016 and in 2018 it replaced, at a Paris employer, a CI that dumbly reinstalled Ansible on every run. In 2022 I am finally making it public and taking the chance to reread its history, early sins included.&lt;/p></description></item><item><title>Autopsy of an inherited Arduino firmware</title><link>https://blog.6admin.io/en/posts/fluoboard-what-not-to-do/</link><pubDate>Mon, 22 Nov 2021 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/fluoboard-what-not-to-do/</guid><description>&lt;p>Back at a former employer, the original firmware drove little LED strips bolted outside meeting rooms, showing the room&amp;rsquo;s availability. The board polls &lt;code>GET /lights/&amp;lt;mac&amp;gt;&lt;/code> every few seconds, the server answers a single ASCII digit between &lt;code>&amp;lt;0&amp;gt;&lt;/code> and &lt;code>&amp;lt;9&amp;gt;&lt;/code>, the strip shows a color. Simple product, simple protocol. I did not write it, but I used it as a base.&lt;/p>
&lt;p>The board was only half of the system. The other half, on the meeting-room side, was the building&amp;rsquo;s occupancy server. I, on my own, repurposed that same protocol, for myself alone, with a small Python server bridging my CI pipelines to the board: my CI status light existed in 2019, long before &amp;ldquo;CI status lights&amp;rdquo; were a thing. Only the API contract survived, in the docs (&lt;code>GET /lights/&amp;lt;lightid&amp;gt;&lt;/code> answering &lt;code>&amp;lt;N&amp;gt;&lt;/code>, from &lt;code>&amp;lt;0&amp;gt;&lt;/code> rainbow error to &lt;code>&amp;lt;4&amp;gt;&lt;/code> red booked-and-busy); the server code itself did not. That is its own lesson: code without a repository does not outlive the job that wrote it.&lt;/p></description></item><item><title>SSH in 2021: what thirteen years changed since the 2008 series</title><link>https://blog.6admin.io/en/posts/ssh-modern/</link><pubDate>Fri, 04 Jun 2021 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-modern/</guid><description>&lt;p>The SSH series around here ended in 2013: &lt;a href="https://web.archive.org/web/2013/http://blog.vhalholl.info/2008/10/ssh-authentification-via-clefs-priveepublique.html" target="_blank" rel="noopener noreferrer">key authentication in 2008&lt;/a>
, &lt;a href="https://web.archive.org/web/2013/http://blog.vhalholl.info/2012/02/ssh-authentification-multiple-avec-clefs-privespubliques.html" target="_blank" rel="noopener noreferrer">multiple authentication in 2012&lt;/a>
, &lt;a href="https://web.archive.org/web/2013/http://blog.vhalholl.info/2012/03/ssh-restreindre-une-connexion-via-clefs-priveepublique.html" target="_blank" rel="noopener noreferrer">connection restriction&lt;/a>
 and &lt;a href="https://web.archive.org/web/2013/http://blog.vhalholl.info/2013/06/ssh-augmenter-la-securite-de-vos-clefs-avec-pkcs8.html" target="_blank" rel="noopener noreferrer">PKCS#8 in 2013&lt;/a>
 as the final act. Eight years later, everything those articles did with craftsmanship exists as primitives. Here is the 2021 memo, complete.&lt;/p>
&lt;h2 id="the-keys-ed25519-and-the-fido2-couple">The keys: Ed25519 and the FIDO2 couple&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssh-keygen -t ed25519 -a &lt;span style="color:#ae81ff">100&lt;/span> -C &lt;span style="color:#e6db74">&amp;#34;logout@2021&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh-keygen -t ed25519-sk -a &lt;span style="color:#ae81ff">100&lt;/span> &lt;span style="color:#75715e"># hardware key, touch required&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh-add -K &lt;span style="color:#75715e"># downloads resident keys from the YubiKey&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ed25519 for everything software: shorter, faster, more resilient and &lt;code>-a 100&lt;/code> hardens the KDF against passphrase brute-forcing (the direct heir of the &lt;a href="https://web.archive.org/web/2013/http://blog.vhalholl.info/2013/06/ssh-augmenter-la-securite-de-vos-clefs-avec-pkcs8.html" target="_blank" rel="noopener noreferrer">2013 PKCS#8 article&lt;/a>
, which was right ahead of its time). The &lt;code>-sk&lt;/code> keys (OpenSSH 8.2, 2020) plug in a YubiKey: the private key &lt;strong>never&lt;/strong> leaves the hardware, the protocol requires a physical touch. And RSA? The sun is setting: OpenSSH 8.2 disabled &lt;code>ssh-rsa&lt;/code> (SHA-1) in &lt;code>CASignatureAlgorithms&lt;/code> by default and the full retirement announcement is already in the release notes. In 2008, RSA 2048 was the luxury; in 2021, it is technical debt.&lt;/p></description></item><item><title>PwnMachine: the self-hosted bug bounty workstation</title><link>https://blog.6admin.io/en/posts/pwnmachine/</link><pubDate>Sun, 12 Jul 2020 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/pwnmachine/</guid><description>&lt;p>When you do bug bounty, you need an environment: a DNS you control (for callbacks), a reverse proxy (to expose tools) and a web interface to orchestrate. All on a machine you can reinstall in ten minutes. PwnMachine is that setup, packaged in Docker Compose.&lt;/p>
&lt;h2 id="the-concept">The concept&lt;/h2>
&lt;p>PwnMachine is a self-hosting solution based on Docker aiming to provide an easy-to-use pwning station for bug hunters. The basic install includes a web interface, a DNS server and a reverse proxy. One &lt;code>docker-compose up --build -d&lt;/code> and it stands up.&lt;/p></description></item><item><title>Counting CVEs tells you nothing about security</title><link>https://blog.6admin.io/en/posts/counting-cves/</link><pubDate>Wed, 11 Mar 2020 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/counting-cves/</guid><description>&lt;p>You regularly see comparisons that rank systems by the number of published CVEs, the lowest count winning the title of most secure.
I tend to strongly temper that kind of result, because the number itself does not measure what people think it measures.
A CVE counter counts flaws that were made public and nothing else.
It says nothing about the ones that exist, that are known internally, or that quietly circulate far from any advisory.&lt;/p></description></item><item><title>Docker rootless: the daemon without privileges</title><link>https://blog.6admin.io/en/posts/docker-rootless/</link><pubDate>Tue, 23 Jul 2019 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/docker-rootless/</guid><description>&lt;p>Docker runs as root by default. The daemon (dockerd) has access to ALL the filesystem, ALL the network, ALL the processes. A container that escapes the daemon gets root on the host. Rootless mode moves the daemon to a normal user: containers run in a user namespace and an escape only yields that user&amp;rsquo;s rights, not root.&lt;/p>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>A Docker daemon running as root is a service with all the rights on the machine, accessible via a Unix socket (and sometimes a TCP port). Every container mounting a volume mounts with the daemon&amp;rsquo;s root rights. Every bug in runc or containerd is a root escape.&lt;/p></description></item><item><title>3factor: the architecture that removes state from the API</title><link>https://blog.6admin.io/en/posts/3factor/</link><pubDate>Fri, 15 Feb 2019 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/3factor/</guid><description>&lt;p>In early 2019, a few months after discovering the twelve factors, I come across
&lt;a href="https://github.com/hasura/3factor" target="_blank" rel="noopener noreferrer">3factor&lt;/a>
, an architecture pattern published by
Hasura whose name is an assumed wink at &lt;a href="https://12factor.net" target="_blank" rel="noopener noreferrer">12factor.net&lt;/a>
 (the
resemblance ends there, 3factor is a design pattern, not a methodology). The idea fits
in three factors: realtime GraphQL, reliable eventing and async serverless.&lt;/p>
&lt;h2 id="the-three-factors">The three factors&lt;/h2>
&lt;p>The first factor is realtime GraphQL: the client subscribes to the state and receives
it instead of asking for it, which moves the synchronisation from polling to the
server. The second is reliable eventing: every state change is a persisted event. Triggers
triggers deliver it to subscribers (delivery happens at least once, idempotency is the
consumer&amp;rsquo;s job). The third is async serverless: the business logic runs in functions
triggered by events, with no server to manage and automatic scaling.&lt;/p></description></item><item><title>GitOps, the word after the practice: gitflow, GitLab CI, Terraform and Ansible</title><link>https://blog.6admin.io/en/posts/gitops-before-the-word/</link><pubDate>Tue, 20 Nov 2018 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/gitops-before-the-word/</guid><description>&lt;p>The word has been spreading for a year, GitOps, laid down by Weaveworks in 2017 around Kubernetes and a simple idea, git as the source of truth of the infrastructure and a loop that converges reality toward what the repository describes.
I read it the way I had read DevOps a few years earlier, not discovering a method but recognizing my own (&lt;a href="https://blog.6admin.io/en/posts/devops-before-the-word/">DevOps, a word for what I was already tinkering with&lt;/a>
), back then with git and bash hooks.
By 2018 the hack had grown and it took the shape of an adapted gitflow, conventions and a pipeline that does all the work.&lt;/p></description></item><item><title>Portknocking and SSH tunneling: the magic tricks before WireGuard</title><link>https://blog.6admin.io/en/posts/portknocking-wireguard/</link><pubDate>Sat, 06 Oct 2018 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/portknocking-wireguard/</guid><description>&lt;p>In 2008, this blog published &lt;strong>Portknocking: the art of knocking on the door&lt;/strong>, then &lt;a href="https://blog.6admin.io/en/posts/portknocking-generation/">automatic combination generation&lt;/a>
, with &lt;a href="https://blog.6admin.io/en/posts/ssh-tunneling/">SSH tunneling&lt;/a>
 on the neighboring shelf. Fun, pedagogical and genuinely deployed: I saw it in production until &lt;strong>2018 at a hosting provider&lt;/strong>. In 2016, Jason Donenfeld publishes &lt;a href="https://www.wireguard.com/" target="_blank" rel="noopener noreferrer">WireGuard&lt;/a>
 and the question changes nature: not &amp;ldquo;how to hide the door&amp;rdquo;, but &amp;ldquo;why is there a door?&amp;rdquo;.&lt;/p>
&lt;h2 id="the-2009-setup-in-fond-memory">The 2009 setup, in fond memory&lt;/h2>
&lt;p>For latecomers, what the era&amp;rsquo;s deluxe portknocking involved:&lt;/p></description></item><item><title>Chaos engineering: breaking things for real, but not just any way</title><link>https://blog.6admin.io/en/posts/chaos-engineering/</link><pubDate>Mon, 20 Aug 2018 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/chaos-engineering/</guid><description>&lt;p>In 2018, the same year I discover the twelve factors, I come across chaos engineering
(I would be quite unable to find the exact blog posts that lead me there. That is a
good reason to go back to the sources rather than my memory): the definition and the
principles fit in a single document, which says better what I end up practising than
my own rewording.&lt;/p>
&lt;h2 id="it-is-not-breaking-things">It is not breaking things&lt;/h2>
&lt;p>The most common misreading is to believe that restarting a machine in production is
enough to do chaos engineering, which confuses a method and an incident: breaking things
at random is an incident we failed to avoid, not an experiment. Chaos engineering is a
discipline of experimentation, with a defined and measured steady state, a hypothesis
about its persistence, a realistic perturbation injected and a rigorous comparison
(without a measurable steady state there is no experiment, only an outage).&lt;/p></description></item><item><title>Git submodules by hand: my Vim 8 plugins without a manager</title><link>https://blog.6admin.io/en/posts/vim-submodules/</link><pubDate>Mon, 07 May 2018 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/vim-submodules/</guid><description>&lt;p>Debian and Ubuntu had finally shipped Vim 8 and with it the native package system: &lt;code>pack/plugins/start&lt;/code> and &lt;code>pack/plugins/opt&lt;/code>. Goodbye pathogen, goodbye my apt-flavored hack (&lt;a href="https://blog.6admin.io/en/posts/vim-apthogen/">vim-apthogen&lt;/a>
, episode one of this series). But Vim 8 packages solve &lt;em>loading&lt;/em>, not &lt;em>management&lt;/em>: nothing tells you which plugins your config depends on, or how to clone them on a fresh machine.&lt;/p>
&lt;p>The answer I chose is the same one I still run today: &lt;strong>the config repo is the state&lt;/strong>. The plugins live as git submodules of the config repo, in &lt;code>pack/plugins/start/&lt;/code> for always-on ones and &lt;code>pack/plugins/opt/&lt;/code> for the opt-in ones. Clone with &lt;code>--recursive&lt;/code> and the whole editor materializes.&lt;/p></description></item><item><title>Twelve-Factor App: the Heroku method that survived containers</title><link>https://blog.6admin.io/en/posts/twelve-factor/</link><pubDate>Mon, 15 Jan 2018 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/twelve-factor/</guid><description>&lt;p>In January 2018, I come across the Twelve-Factor method (and I immediately file it among
the practices I already did without naming it): three readings that complete each
other (the reference site &lt;a href="https://12factor.net" target="_blank" rel="noopener noreferrer">by Adam Wiggins&lt;/a>
, the adaptation to
microservices &lt;a href="https://web.archive.org/web/20171222082423/https://www.nginx.com/blog/microservices-reference-architecture-nginx-twelve-factor-app/" target="_blank" rel="noopener noreferrer">by Ben Horowitz on the nginx
blog&lt;/a>

and the report Kevin Hoffman draws from it &lt;a href="https://www.oreilly.com/library/view/beyond-the-twelve-factor/9781492042631/" target="_blank" rel="noopener noreferrer">at
O&amp;rsquo;Reilly&lt;/a>
). Suddenly a word for what I had been doing for years. Before, I already knew (and
more or less applied) KISS, YAGNI and DRY, then SOLID; the V-model and AGILE were part
of the landscape, each with its flaws that I could enumerate without needing anyone to
explain them to me. Twelve-Factor, for its part, is not for the general public: it is
an engineer&amp;rsquo;s checklist, written for Heroku, that says in black and white what an
application must guarantee to survive production and its own redeployments.&lt;/p></description></item><item><title>SSH Hardening: the 2017 Trilogy Reread in 2026</title><link>https://blog.6admin.io/en/posts/ssh-hardening-bastions/</link><pubDate>Thu, 26 Oct 2017 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-hardening-bastions/</guid><description>&lt;p>In October 2017, an SSH hardening article goes by on my timeline. Generic analysis, naive countermeasures. I answer in three comments: disabling root, changing the port, key authentication. Nine years later, those three comments serve me as an inventory: what held, what changed and what was out of scope but deserved a mention.&lt;/p>
&lt;p>First, the principle that made me react: comparing OpenSSH to a simple administration interface is reductive. SSH is the bastions to your IS, the SSH VPNs, the agentless orchestration (Ansible, Puppet Bolt) that runs on top of it. It is the administrative spine of most Unix fleets. Harden it badly and you harden the whole IS badly.&lt;/p></description></item><item><title>Ansible before Ansible: ssh, a CSV and a template engine</title><link>https://blog.6admin.io/en/posts/ansible-before-ansible/</link><pubDate>Mon, 12 Sep 2016 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ansible-before-ansible/</guid><description>&lt;p>In 2006-2007, I administered a small fleet with scripts like everyone else, except I believed (youth!) that my contraption was an architecture. It had three components: a host list, a CSV and bash. Ansible did not exist yet (it was born in 2012), but the shape of the problem was already there and mine modeled it at 80%.&lt;/p>
&lt;h2 id="the-inventory-a-list--a-csv">The inventory: a list + a CSV&lt;/h2>
&lt;p>The list of machines: a flat file, one machine per line. On top of it, a &lt;strong>CSV of inventory variables&lt;/strong>:&lt;/p></description></item><item><title>SSH: ProxyJump and modern multi-hop</title><link>https://blog.6admin.io/en/posts/ssh-proxyjump/</link><pubDate>Mon, 12 Sep 2016 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-proxyjump/</guid><description>&lt;p>A few years ago, to reach a machine behind a bastion, I wrote a &lt;code>ProxyCommand&lt;/code> with &lt;code>nc&lt;/code> and I told that story in &lt;a href="https://blog.6admin.io/en/posts/ssh-tunneling/">SSH: tunneling&lt;/a>
. OpenSSH 7.3 made all of that obsolete with a single option, &lt;code>-J&lt;/code> and it is one of those small additions that change a gesture you make ten times a day.&lt;/p>
&lt;h2 id="the-basic-gesture">The basic gesture&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssh -J user@bastion user@target
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>bastion&lt;/code> acts as the jump host, the target is never exposed directly and above all the encryption stays end to end between your machine and &lt;code>target&lt;/code>. The bastion only sees encrypted traffic it cannot read, exactly as with the old &lt;code>ProxyCommand&lt;/code>, but without invoking &lt;code>nc&lt;/code> or opening an intermediate shell.
The difference with &lt;code>ProxyCommand&lt;/code> is not merely cosmetic: &lt;code>-J&lt;/code> establishes the connection to the next hop from the client, not from the bastion, so no private key transits or sits on the jump host. That is what we wanted from the start and what we used to hack around with agent forwarding, which posed precisely the opposite problem by exposing the agent on the bastion.&lt;/p></description></item><item><title>apt for Vim: the story of vim-apthogen (2012-2016)</title><link>https://blog.6admin.io/en/posts/vim-apthogen/</link><pubDate>Sun, 17 Apr 2016 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/vim-apthogen/</guid><description>&lt;p>Somewhere between 2012 and 2016, my Vim setup looked like a Debian system from the package manager&amp;rsquo;s point of view: a pile of &lt;code>bundle/&lt;/code> directories installed by hand and &lt;a href="https://github.com/tpope/vim-pathogen" target="_blank" rel="noopener noreferrer">vim-pathogen&lt;/a>
 doing the loading. Pathogen is honest about its role: it is &lt;code>dpkg&lt;/code>, the dumb loader. Nobody had written the &lt;code>apt&lt;/code> on top of it. So I did.&lt;/p>
&lt;p>The result: &lt;a href="https://www.vim.org/scripts/script.php?script_id=5366" target="_blank" rel="noopener noreferrer">vim-apthogen&lt;/a>
, published on vim.org on April 12, 2016 (Vim 7.0, 1136 downloads, a karma of 19/6, all numbers I am unreasonably proud of). The README line I still like: &lt;strong>Like &lt;code>apt&lt;/code> for &lt;code>dpkg&lt;/code>, &lt;code>vim-apthogen&lt;/code> is a frontend to &lt;code>vim-pathogen&lt;/code>&lt;/strong>.&lt;/p></description></item><item><title>A DayZ autowalker in Java: decompile, patch for AZERTY, fork afterwards</title><link>https://blog.6admin.io/en/posts/dayz-autowalker-java/</link><pubDate>Sat, 18 Oct 2014 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/dayz-autowalker-java/</guid><description>&lt;p>Back in the DayZ days, I wanted an autowalker, that little utility that holds the forward key for you while your character crosses a huge map.
One existed, in Java, but with no published sources and it was built for a QWERTY keyboard.
I play on AZERTY, so it walked sideways and I also wanted it to handle the running state cleanly rather than jam a key.
I did not overthink it: I decompiled it.&lt;/p></description></item><item><title>SSH: hardening your keys with PKCS#8</title><link>https://blog.6admin.io/en/posts/ssh-pkcs8/</link><pubDate>Sun, 23 Jun 2013 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-pkcs8/</guid><description>&lt;p>In two or three commands:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ mv ~/.ssh/id_rsa ~/.ssh/id_rsa.old
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ openssl pkcs8 -topk8 -v2 des3 -in ~/.ssh/id_rsa.old -out ~/.ssh/id_rsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ chmod &lt;span style="color:#ae81ff">600&lt;/span> ~/.ssh/id_rsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Check that the converted key works; if yes, delete the old one:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ rm ~/.ssh/id_rsa.old
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And a small script to do it on the fly:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>: &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;TITLE.sh&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Version: 0.1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Authors: nobody@vhalholl.info
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Description: hardening ssh private key w/ pkcs#8
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> http://martin.kleppmann.com/2013/05/24/improving-security-of-ssh-private-keys.html
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Dependencies: openssl
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">TITLE&lt;/span>.sh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DEP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>which openssl&lt;span style="color:#66d9ef">)&lt;/span>;&lt;span style="color:#f92672">[&lt;/span> ! -z $DEP &lt;span style="color:#f92672">]&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#f92672">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> k in &lt;span style="color:#66d9ef">$(&lt;/span>echo &lt;span style="color:#66d9ef">$(for&lt;/span> i in &lt;span style="color:#66d9ef">$(&lt;/span>ls *&lt;span style="color:#66d9ef">)&lt;/span>;&lt;span style="color:#66d9ef">do&lt;/span> file $i; &lt;span style="color:#66d9ef">done&lt;/span> |grep private |cut -d : -f1&lt;span style="color:#66d9ef">))&lt;/span>;&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;backup &lt;/span>$k&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; mv $k .$k.old
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;convert &lt;/span>$k&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; openssl pkcs8 -topk8 -v2 des3 -in .$k.old -out $k; chmod &lt;span style="color:#ae81ff">600&lt;/span> $k
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">done&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit 0;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;Openssl not found&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> exit &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>PKCS#8 wraps the RSA private key in a 3DES encryption layer with a hardened KDF (PBKDF2). The key&amp;rsquo;s passphrase becomes more expensive to brute-force: every decryption attempt requires extra KDF iterations. Same principle as bcrypt or scrypt for passwords, applied to SSH keys.&lt;/p></description></item><item><title>The FOG client: python 2, cron every five minutes and a fleet that rebooted itself into place</title><link>https://blog.6admin.io/en/posts/fog-client/</link><pubDate>Tue, 20 Nov 2012 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/fog-client/</guid><description>&lt;p>Before this blog&amp;rsquo;s other archaeology pieces, there was this one. Years before the &lt;a href="https://blog.6admin.io/en/posts/vim-apthogen/">apthogen era&lt;/a>
, under the gobel1 identity, I ran workstation fleets cloned with &lt;a href="https://fogproject.org" target="_blank" rel="noopener noreferrer">FOG&lt;/a>
 (Free Opensource Ghost). FOG&amp;rsquo;s client story was Windows-first, so the Linux side needed a small client of its own: enter &lt;code>FOG.py&lt;/code>, version 0.2, python 2, about 150 lines plus a handful of shell hooks. It predates 2013; the git repo was only re-inited in May 2013, one commit in French, six commits total. The archaeology is genuine.&lt;/p></description></item><item><title>SSH: restricting a connection via private/public keys</title><link>https://blog.6admin.io/en/posts/ssh-restrict-command/</link><pubDate>Mon, 05 Mar 2012 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-restrict-command/</guid><description>&lt;p>What is more natural when running automated tasks between multiple servers than using an SSH key pair without a passphrase?&lt;/p>
&lt;p>Yes, but how do you confine this unprotected key to executing one specific task?&lt;/p>
&lt;p>You just need to prefix the public key you want to limit in the file:&lt;/p>
&lt;p>&lt;code>.ssh/authorized_keys&lt;/code>&lt;/p>
&lt;p>With the options detailed in the sshd manual, section AUTHORIZED_KEYS FILE FORMAT:&lt;/p>
&lt;pre tabindex="0">&lt;code>command=&amp;#34;/usr/bin/rsync --server --daemon .&amp;#34;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA...
&lt;/code>&lt;/pre>&lt;p>Each option blocks an escape path:&lt;/p></description></item><item><title>SSH: multiple authentication with private/public keys</title><link>https://blog.6admin.io/en/posts/ssh-multiple-auth/</link><pubDate>Mon, 06 Feb 2012 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-multiple-auth/</guid><description>&lt;p>As a complement to an older article on &lt;a href="https://blog.6admin.io/en/posts/ssh-key-authentication/">SSH key authentication&lt;/a>
 and because you should never put all your eggs in one basket, here is a simple solution to use a specific key for a given identity on a given SSH server.&lt;/p>
&lt;p>In our example we will use two keys: one with a passphrase for connecting to server.mydomain.tld by simply typing ssh serv and one without a passphrase mainly for running maintenance scripts on a small fleet of client machines cli01.parc01.mydomain.tld etc., whose SSH server listens on a specific port and the firewall only allows connections from server.mydomain.tld (let&amp;rsquo;s be crazy).&lt;/p></description></item><item><title>Debsecan: Debian Security Analyzer</title><link>https://blog.6admin.io/en/posts/debsecan/</link><pubDate>Wed, 26 Oct 2011 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/debsecan/</guid><description>&lt;p>Debian Security Analyzer is a very practical tool, discovered recently.&lt;/p>
&lt;p>It simply generates a list of vulnerabilities affecting a given installation, making it complementary to apt-listbugs.&lt;/p>
&lt;p>By default at installation, daily email reports are enabled for a generic installation.&lt;/p>
&lt;p>This behavior is modified via /etc/default/debsecan:&lt;/p>
&lt;pre tabindex="0">&lt;code># Configuration file for debsecan. Contents of this file should
# adhere to the KEY=VALUE shell syntax. This file may be edited by
# debsecan&amp;#39;s scripts, but your modifications are preserved.

# If true, enable daily reports, sent by email.
REPORT=true

# For better reporting, specify the correct suite here, using the code
# name (that is, &amp;#34;sid&amp;#34; instead of &amp;#34;unstable&amp;#34;).
#
# Available:
# GENERIC
# etch
# lenny
# sid
# squeeze
# wheezy
SUITE=GENERIC

# Mail address to which reports are sent.
MAILTO=root

# Source URL
# eg: http://secure-testing.debian.net/debian-secure-testing/project/debsecan/release/1/$SUITE
SOURCE=
&lt;/code>&lt;/pre>&lt;p>The SUITE parameter is the most important: GENERIC lists all CVEs for all versions, including ones that do not affect you. Setting it to your exact suite (&lt;code>lsb_release -c&lt;/code>), debsecan filters by version and the list becomes actionable.&lt;/p></description></item><item><title>Dennis Ritchie has left #world</title><link>https://blog.6admin.io/en/posts/dennis-ritchie/</link><pubDate>Sat, 22 Oct 2011 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/dennis-ritchie/</guid><description>&lt;p>Dennis Ritchie (above right) aka DMR, creator of the C programming language and co-developer of the Unix operating system, left us yesterday at the age of 70.&lt;/p>
&lt;p>Personally, I learned C from &amp;ldquo;his&amp;rdquo; book &amp;ldquo;The C Programming Language&amp;rdquo; and I still use K&amp;amp;R indentation, although slightly remixed GNU-style.&lt;/p>
&lt;p>It is a disappearance that will have practically non-existent media coverage, unlike the death of Steve Jobs which occurred the previous week.&lt;/p></description></item><item><title>(Re)Opening</title><link>https://blog.6admin.io/en/posts/reouverture/</link><pubDate>Thu, 13 Oct 2011 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/reouverture/</guid><description>&lt;p>After two years of inactivity, about fifty articles published and a dozen drafts in progress, this blog opens its doors again.&lt;/p>
&lt;p>Still powered by WordPress, only the images, the comments and the original theme of the blog have changed.&lt;/p>
&lt;p>For now I am working on correcting the articles already published, then I will handle the publication of the drafts in progress, which will be backdated to follow the blog&amp;rsquo;s chronology.&lt;/p></description></item><item><title>Chat: from irssi + messenger gateway to weechat, the thin-client philosophy</title><link>https://blog.6admin.io/en/posts/chat-gateways/</link><pubDate>Mon, 20 Jun 2011 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/chat-gateways/</guid><description>&lt;p>The &lt;a href="https://web.archive.org/web/2009/http://blog.vhalholl.info/2009/11/irssi-irc-aux-petits-oignons.html" target="_blank" rel="noopener noreferrer">2009 irssi article&lt;/a>
 ended with an edit: &amp;ldquo;An up-to-date configuration is available here on github&amp;rdquo; (it was an article stub, the real value lived in the repo, time proved it). Time to tell what the article did not tell: &lt;strong>the setup that made &lt;a href="https://irssi.org/" target="_blank" rel="noopener noreferrer">irssi&lt;/a>
 indispensable&lt;/strong> and why two years later I moved everything to weechat without changing the philosophy.&lt;/p>
&lt;h2 id="irssi--bitlbee-the-bridge-to-the-messengers">irssi + BitlBee: the bridge to the messengers&lt;/h2>
&lt;p>The era&amp;rsquo;s setup: a local &lt;strong>BitlBee&lt;/strong> server that presents instant-messaging networks (MSN, AIM, ICQ, Jabber) as an &lt;strong>ordinary IRC channel&lt;/strong> and irssi connecting to it like any server. Result: one client, one buffer, one log config, for IRC and for the MSN contacts who only know IRC by name.&lt;/p></description></item><item><title>tmux: the multiplexer that took the chair</title><link>https://blog.6admin.io/en/posts/tmux/</link><pubDate>Sun, 23 May 2010 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/tmux/</guid><description>&lt;p>Screen is twenty-three years old, &lt;a href="https://blog.6admin.io/en/posts/byobu/">byobu&lt;/a>
 dressed its flaws and there remain irritants neither of them fixes: no true &lt;strong>panes&lt;/strong> (Screen does regions, which resize badly), a retrofitted UTF-8, a &lt;code>$HOME/.screenrc&lt;/code> config with bindkeys inherited from the 80s. That is where tmux enters, born in 2007 on OpenBSD and starting to take the chair in 2010.&lt;/p>
&lt;h2 id="the-client-server-model">The client-server model&lt;/h2>
&lt;p>The architectural difference that changes everything: Screen runs a server &lt;strong>per user&lt;/strong> (sessions share one process), tmux runs &lt;strong>one server per session&lt;/strong> and clients attach to it. Practical consequences:&lt;/p></description></item><item><title>The R.A.C.H.E method: Rapid Application Conception and Heuristic Extreme-programming</title><link>https://blog.6admin.io/en/posts/methode-rache/</link><pubDate>Thu, 15 Apr 2010 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/methode-rache/</guid><description>&lt;p>There is a software engineering method that describes real life better than half the
frameworks I see pushed through a review board. That method has an acronym, an
institute, a FAQ and a public-domain licence: it is called the R.A.C.H.E method. I
discover it around 2009-2010, at a time when I still take roadmaps seriously.&lt;/p>
&lt;p>R.A.C.H.E stands for &lt;em>Rapid Application Conception and Heuristic Extreme-programming&lt;/em>.
The site of the International Institute of La RACHE has existed since 2007, but the
concept already circulated as a PDF before that. The recurrent metempsychosis of
the concept is, according to its own promoters, intrinsically axiomatic at some large
vendors. &lt;a href="https://www.la-rache.com" target="_blank" rel="noopener noreferrer">la-rache.com&lt;/a>
&lt;/p></description></item><item><title>DevOps, a word for what I was already tinkering with</title><link>https://blog.6admin.io/en/posts/devops-before-the-word/</link><pubDate>Mon, 15 Mar 2010 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/devops-before-the-word/</guid><description>&lt;p>For a few months now a word has been going around, DevOps, launched after the Velocity conference of June 2009 and its famous &amp;ldquo;10 deploys a day&amp;rdquo; talk on dev and ops cooperation at Flickr, then picked up at the first DevOpsDays in Ghent in October, around Patrick Debois.
It is presented almost everywhere as the end of the wall between those who write the code and those who run it and reading the first texts I had the odd feeling of recognising things I was already tinkering with in my corner, without ever having given them a name.&lt;/p></description></item><item><title>Puppet: my first modules, or moving from the ssh loop to declaring</title><link>https://blog.6admin.io/en/posts/puppet-first/</link><pubDate>Sat, 02 Jan 2010 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/puppet-first/</guid><description>&lt;p>My bash scripts, a host list, a CSV of variables and an ssh loop pushing template-rendered configs have served me well since 2007, but they describe steps and that is precisely what I wanted to leave behind when I wrote my first Puppet modules for the fleet I run at work.
Puppet is a real DSL, a real agent/master model (0.25 at the moment) and a promise my scripts never kept: &lt;strong>declare the desired state, not the steps&lt;/strong>.
Ssh, ntp, postfix, munin, zabbix, varnish, rsyslog: these modules cover the common base and I extend them as needs come up.&lt;/p></description></item><item><title>Haproxy: the reverse proxy that took over from Pound</title><link>https://blog.6admin.io/en/posts/haproxy/</link><pubDate>Sun, 20 Dec 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/haproxy/</guid><description>&lt;p>&lt;a href="https://web.archive.org/web/2013/http://blog.vhalholl.info/2008/10/pound-un-reverse-proxy-tres-light.html" target="_blank" rel="noopener noreferrer">The blog covered Pound in 2008&lt;/a>
: a very light reverse proxy that terminates TLS and balances to a pool. Pound does that job with minimal elegance, but as soon as you need to &lt;strong>route on content&lt;/strong> (a Host to a cluster, a URL to a specific backend, a sticky session), one step up: haproxy 1.3, by Willy Tarreau, stabilizing toward 1.4.&lt;/p>
&lt;h2 id="the-block-that-says-it-all">The block that says it all&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>frontend web
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bind *:80
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode http
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> acl is_app hdr(host) -i app.domain.tld
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> acl is_static path_beg /static/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> use_backend app if is_app
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> use_backend statics if is_static
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> default_backend app
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>backend app
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode http
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> balance roundrobin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> option httpchk HEAD /health
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server web1 192.168.10.11:8080 check inter 5s fall 2 rise 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server web2 192.168.10.12:8080 check inter 5s fall 2 rise 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>backend statics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode http
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> balance source
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server s1 192.168.10.21:80
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>tcp&lt;/code> mode and &lt;code>http&lt;/code> mode: in TCP, haproxy is a brute balancer (TLS pass-through, any protocol); in HTTP, it parses the request and routes on its content. The ACLs do the rest: &lt;code>hdr(host)&lt;/code>, &lt;code>path_beg&lt;/code> and an operator grammar that keeps the config readable, which Pound did not offer.&lt;/p></description></item><item><title>IRSSI: IRC with the works</title><link>https://blog.6admin.io/en/posts/irssi/</link><pubDate>Sat, 07 Nov 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/irssi/</guid><description>&lt;p>[edit: MàJ 20130621]: An up-to-date configuration is available here on github.&lt;/p>
&lt;p>Irssi is the console-mode IRC client. It does not do pretty widgets, it does not do drag-and-drop, it does IRC. And it does it well: themes, Perl scripts, aliases, hotlists, notifications, everything is configurable.&lt;/p>
&lt;p>The config lives in &lt;code>~/.irssi/&lt;/code>, with a main &lt;code>config&lt;/code> file and a &lt;code>scripts/&lt;/code> directory for Perl plugins. The era config (367 lines, multi-network: recycled-irc, otaku-irc, worldnet SSL:7000, fansub-irc, OFTC SSL:9999) is versioned in the dotfiles, with a &lt;code>green.theme&lt;/code> and five personal scripts.&lt;/p></description></item><item><title>Byobu: screen with a coat of polish (and a status bar that saves)</title><link>https://blog.6admin.io/en/posts/byobu/</link><pubDate>Thu, 15 Oct 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/byobu/</guid><description>&lt;p>Back in April, &lt;a href="https://blog.6admin.io/en/posts/screen/">this blog described Screen as a terminal multiplexer&lt;/a>
: detach, reattach, never lose an ssh session. Six months later, byobu arrives and asks an impertinent question: if everyone configures the same Screen the same way (status, colors, shortcuts), why not ship the configuration ready-made?&lt;/p>
&lt;p>That is exactly what byobu is: born in Ubuntu as &lt;code>screen-profiles&lt;/code>, renamed byobu in 2009 (the Japanese word for decorative folding screens, an assumed wink), the tool wraps your existing Screen in a coat of polish without changing the engine.&lt;/p></description></item><item><title>Bash: Geolocation, the trilogy</title><link>https://blog.6admin.io/en/posts/bash-geolocation/</link><pubDate>Fri, 02 Oct 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/bash-geolocation/</guid><description>&lt;p>Three iterations of the same script, between December 2008 and October 2009, each one born from the previous one and its limits. V1 is lost with the hard drive (a Samsung 500 GB, with all my Bash, C, PHP and Perl projects, my personal music, the thing was 90% full!), v2 and v3 survive. It is the story of a script&amp;rsquo;s evolution in public.&lt;/p>
&lt;h2 id="the-revenge-of-netcat-v2-february-2009">The revenge of netcat (v2, February 2009)&lt;/h2>
&lt;p>After an IRC discussion on geolocation, a friend who used the same site passed me one of his scripts that in a single line did exactly what mine did but much faster thanks to netcat (no disk access). Unfortunately, following the loss of my hard drive, I lost a bunch of tools, including that famous script. So I rewrote the script, being significantly less good than my friend, it takes me more than one line.&lt;/p></description></item><item><title>NTP: time synchronization over the network</title><link>https://blog.6admin.io/en/posts/ntp/</link><pubDate>Tue, 26 May 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ntp/</guid><description>&lt;p>NTP (Network Time Protocol) synchronizes machine clocks over the network. It sounds trivial until the day a script that depends on the date drifts by 3 seconds and misses its connection window, or two servers that must agree on &amp;ldquo;now&amp;rdquo; have a 4-minute gap.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>apt-get install ntp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The ntpd daemon starts and synchronizes automatically with the Debian pool servers (&lt;code>0.debian.pool.ntp.org&lt;/code> through &lt;code>3.debian.pool.ntp.org&lt;/code>).&lt;/p>
&lt;h2 id="the-minimal-config">The minimal config&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># /etc/ntp.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>server 0.debian.pool.ntp.org iburst
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>server 1.debian.pool.ntp.org iburst
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>server 2.debian.pool.ntp.org iburst
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>server 3.debian.pool.ntp.org iburst
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Restrict access&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>restrict default kod notrap nomodify nopeer noquery
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>restrict 127.0.0.1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>restrict ::1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>iburst&lt;/code> option accelerates initial sync (4 packets instead of 1 at startup). The &lt;code>restrict&lt;/code> lines block access from the network: the NTP server only serves itself, not others (unless you want to run a local NTP server).&lt;/p></description></item><item><title>Portknocking: automatic combination generation</title><link>https://blog.6admin.io/en/posts/portknocking-generation/</link><pubDate>Fri, 22 May 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/portknocking-generation/</guid><description>&lt;p>Classic portknocking knocks on the door with a fixed port sequence. The problem with a fixed sequence: it gets sniffed, it gets replayed. The solution: make the sequence depend on the date, so it changes every day.&lt;/p>
&lt;h2 id="the-principle">The principle&lt;/h2>
&lt;p>The combination is computed from the system date. The day determines the protocol (even = udp, odd = tcp) and the ports start from the day+month+year sum doubled, to which 64 is added until past 1024. The client and the server must have the same clock, hence the NTP synchronization.&lt;/p></description></item><item><title>Bash: customizing the .bashrc file</title><link>https://blog.6admin.io/en/posts/bashrc/</link><pubDate>Tue, 14 Apr 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/bashrc/</guid><description>&lt;p>On our favorite systems, the interactive bash shell, its prompt and its coloring, are configured in these files:&lt;/p>
&lt;ul>
&lt;li>&lt;code>/etc/profile&lt;/code>&lt;/li>
&lt;li>&lt;code>/etc/bash.bashrc&lt;/code>&lt;/li>
&lt;li>&lt;code>/root/.bashrc&lt;/code>&lt;/li>
&lt;li>&lt;code>/home/user/.bashrc&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Here we only touch the last three. We start with the color of the username in the prompt, green for users, red for the root account, because a root prompt that does not announce itself is a root prompt you eventually type something stupid into:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Set color of username in prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;`id -u`&amp;#34;&lt;/span> -eq &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#66d9ef">then&lt;/span> prompt_color&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;\[\033[01;31m\]&amp;#39;&lt;/span>;&lt;span style="color:#66d9ef">else&lt;/span> prompt_color&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;\[\033[01;32m\]&amp;#39;&lt;/span>;&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here is the list of color codes: Black &lt;code>00;30&lt;/code>, Red &lt;code>00;31&lt;/code>, Green &lt;code>00;32&lt;/code>, Brown &lt;code>00;33&lt;/code>, Blue &lt;code>00;34&lt;/code>, Purple &lt;code>00;35&lt;/code>, Cyan &lt;code>00;36&lt;/code>, Light gray &lt;code>00;37&lt;/code>, Gray &lt;code>01;30&lt;/code>, Pink red &lt;code>01;31&lt;/code>, Light green &lt;code>01;32&lt;/code>, Light brown &lt;code>01;33&lt;/code>, Light blue &lt;code>01;34&lt;/code>, Light purple &lt;code>01;35&lt;/code>, Light cyan &lt;code>01;36&lt;/code>, White &lt;code>01;37&lt;/code>.&lt;/p></description></item><item><title>Screen: a terminal multiplexer</title><link>https://blog.6admin.io/en/posts/screen/</link><pubDate>Mon, 13 Apr 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/screen/</guid><description>&lt;p>Screen is a terminal multiplexer that, like a window manager, allows multiple text-mode programs to run simultaneously from a single interface, typically a local or remote terminal via SSH (no, nothing to do with nohup, which I mainly use for certain scripts with wget).&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;p>On Gentoo:&lt;/p>
&lt;pre tabindex="0">&lt;code>logout@lifthrasir ~ $ su -c &amp;#34;emerge app-misc/screen&amp;#34;
Password:
[...]
&lt;/code>&lt;/pre>&lt;p>On Debian:&lt;/p>
&lt;pre tabindex="0">&lt;code>logout@helghardhr:~$ sudo apt-get install screen
Password for user logout:
[...]
&lt;/code>&lt;/pre>&lt;h2 id="lets-practice">Let&amp;rsquo;s practice&lt;/h2>
&lt;p>We will create a screen session to take control of a switch:&lt;/p></description></item><item><title>Apache: URL rewriting</title><link>https://blog.6admin.io/en/posts/apache-url-rewriting/</link><pubDate>Sat, 14 Feb 2009 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/apache-url-rewriting/</guid><description>&lt;p>mod_rewrite is the Apache module that rewrites URLs on the fly. It transforms &lt;code>/article.php?id=42&lt;/code> into &lt;code>/article/42&lt;/code>, redirects old links to new ones and does everything a reverse proxy does not: it modifies the request before it reaches the application.&lt;/p>
&lt;h2 id="the-basic">The basic&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-apache" data-lang="apache">&lt;span style="display:flex;">&lt;span>RewriteEngine &lt;span style="color:#66d9ef">On&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>RewriteRule ^article/([0-9]+)$ article.php?id=$1 [L]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This rule transforms &lt;code>/article/42&lt;/code> into &lt;code>article.php?id=42&lt;/code> internally (the user sees &lt;code>/article/42&lt;/code>, Apache runs &lt;code>article.php?id=42&lt;/code>). The &lt;code>[L]&lt;/code> means &amp;ldquo;last rule&amp;rdquo;: if this rule matches, the following ones are not evaluated.&lt;/p></description></item><item><title>SSH: secure rsync</title><link>https://blog.6admin.io/en/posts/ssh-rsync-secure/</link><pubDate>Sat, 27 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-rsync-secure/</guid><description>&lt;p>Rsync is the incremental backup tool: it compares source and destination, transfers only deltas and preserves permissions, timestamps, symlinks. Over SSH, it traverses the network on the encrypted channel, without an rsync daemon on the server side (SSH does the transport).&lt;/p>
&lt;h2 id="the-basic-rsync">The basic rsync&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>rsync -avz /data/ user@server:/backup/data/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>-a&lt;/code> : archive mode (recursive, preserves perms, times, symlinks, devices)&lt;/li>
&lt;li>&lt;code>-v&lt;/code> : verbose (lists transferred files)&lt;/li>
&lt;li>&lt;code>-z&lt;/code> : compression (gzip before transfer)&lt;/li>
&lt;/ul>
&lt;p>The &lt;code>/&lt;/code> after &lt;code>/data/&lt;/code> means: &amp;ldquo;the contents of /data&amp;rdquo;, not &amp;ldquo;/data itself&amp;rdquo;. Without the &lt;code>/&lt;/code>, rsync creates &lt;code>/backup/data/data/&lt;/code>. This is the most common rsync mistake.&lt;/p></description></item><item><title>htop: top's replacement</title><link>https://blog.6admin.io/en/posts/htop/</link><pubDate>Thu, 25 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/htop/</guid><description>&lt;p>top has existed since 1984 and it is ugly. The interface is rigid, the keys are obscure, the tree view does not exist and sorting by memory requires reading the man page. htop does everything top does, better and it fits in a terminal.&lt;/p>
&lt;h2 id="what-htop-brings">What htop brings&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Horizontal and vertical scrolling&lt;/strong>: process lines are no longer truncated;&lt;/li>
&lt;li>&lt;strong>Tree view&lt;/strong> (&lt;code>F5&lt;/code>): who spawned whom, the parent/child in visual form, threads indented under their parent process;&lt;/li>
&lt;li>&lt;strong>Interactive sort&lt;/strong> (&lt;code>F6&lt;/code>): by CPU, by memory, by PID, by USER, without restarting;&lt;/li>
&lt;li>&lt;strong>Interactive kill&lt;/strong> (&lt;code>F9&lt;/code>): select a process, choose the signal, confirm. No &lt;code>kill -9 12345&lt;/code> typed in another terminal;&lt;/li>
&lt;li>&lt;strong>CPU gauges per core&lt;/strong> at the top of the screen, in color, in real time;&lt;/li>
&lt;li>&lt;strong>The filter&lt;/strong> (&lt;code>F4&lt;/code>): type a pattern and only matching processes are visible;&lt;/li>
&lt;li>&lt;strong>The setup&lt;/strong> (&lt;code>F2&lt;/code>): choose columns, colors, gauges and save to &lt;code>~/.config/htop/htoprc&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h2 id="the-keys-that-matter">The keys that matter&lt;/h2>
&lt;pre tabindex="0">&lt;code>F1 Help
F2 Setup
F3 Search (incremental)
F4 Filter
F5 Tree view
F6 Sort
F7 Nice - (raise priority)
F8 Nice + (lower priority)
F9 Kill (send a signal)
F10 Quit
&lt;/code>&lt;/pre>&lt;p>F9 is the most used: select a process eating all the CPU, F9, SIGTERM (15) and if that is not enough, F9 SIGKILL (9). All in the same screen, without leaving htop.&lt;/p></description></item><item><title>SSH: SSH filesystem</title><link>https://blog.6admin.io/en/posts/sshfs/</link><pubDate>Thu, 25 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/sshfs/</guid><description>&lt;p>SSHFS (SSH Filesystem) mounts a remote filesystem in your local tree via SSH and FUSE. No NFS, no SMB, no server configuration: just SSH (which the server already has) and FUSE (which the client has). The remote filesystem appears as a local directory, with all standard tools (cp, mv, find, grep, vim).&lt;/p>
&lt;h2 id="the-mount">The mount&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mkdir /mnt/remote
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sshfs user@server:/home/user /mnt/remote
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After this command, &lt;code>/mnt/remote&lt;/code> IS &lt;code>/home/user&lt;/code> on &lt;code>server&lt;/code>. All standard tools work:&lt;/p></description></item><item><title>tsocks: let's proxy</title><link>https://blog.6admin.io/en/posts/tsocks/</link><pubDate>Tue, 23 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/tsocks/</guid><description>&lt;p>The SSH tunnel gives you a SOCKS (&lt;a href="https://blog.6admin.io/en/posts/ssh-tunneling/">the tunneling article&lt;/a>
), but &lt;code>ssh -D&lt;/code> only proxies what speaks SOCKS: browsers, curl with &lt;code>--proxy&lt;/code>. Programs that do not know SOCKS (apt, telnet, the MySQL client) stay direct. tsocks solves that with &lt;code>LD_PRELOAD&lt;/code>.&lt;/p>
&lt;h2 id="the-principle">The principle&lt;/h2>
&lt;p>tsocks is a shared library that intercepts network system calls (&lt;code>connect()&lt;/code>, &lt;code>send()&lt;/code>, &lt;code>recv()&lt;/code>) via &lt;code>LD_PRELOAD&lt;/code>. When a program tries to open a TCP connection, tsocks redirects it through the SOCKS proxy on its behalf. The program does not know it is going through a proxy.&lt;/p></description></item><item><title>Pound: a very light reverse proxy</title><link>https://blog.6admin.io/en/posts/pound/</link><pubDate>Sun, 21 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/pound/</guid><description>&lt;p>Pound is a reverse proxy that does three things: it terminates TLS, it checks that the backends are alive and it balances. That is all. And it is exactly what you need when you do not want an Apache that also proxies.&lt;/p>
&lt;h2 id="the-config">The config&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">ListenHTTP
 Address 0.0.0.0
 Port 80
End

ListenHTTPS
 Address 0.0.0.0
 Port 443
 Cert &amp;#34;/etc/pound/server.pem&amp;#34;
End

Service
 BackEnd
 Address 192.168.1.11
 Port 8080
 End
 BackEnd
 Address 192.168.1.12
 Port 8080
 End
 Session
 Type IP
 TTL 300
 End
End
&lt;/code>&lt;/pre>&lt;p>The Pound config file is a declarative format that reads like a text block: each BackEnd is a pool server, each Service is a group of backends, the Session sticks clients by source IP. TLS terminates in Pound, the backends speak plain HTTP on the internal network.&lt;/p></description></item><item><title>Polipo: an ultra-simple proxy</title><link>https://blog.6admin.io/en/posts/polipo/</link><pubDate>Wed, 17 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/polipo/</guid><description>&lt;p>Polipo is a web cache proxy written by Juliusz Chroboczek, light (one process, a few MB) and perfect in one specific case: behind an SSH tunnel. The &lt;a href="https://blog.6admin.io/en/posts/ssh-tunneling/">SSH tunnel&lt;/a>
 gives you a SOCKS and the browser needs an HTTP proxy: Polipo bridges the gap.&lt;/p>
&lt;h2 id="the-minimal-config">The minimal config&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">proxyAddress = &amp;#34;127.0.0.1&amp;#34;
proxyPort = 8123
cacheIsShared = false
diskCacheRoot = &amp;#34;&amp;#34;
&lt;/code>&lt;/pre>&lt;p>The proxy listens on localhost:8123, no disk cache (the browser cache suffices) and bridges between HTTP and the tunnel&amp;rsquo;s SOCKS. The config is 4 lines.&lt;/p></description></item><item><title>Subversion: installation and usage</title><link>https://blog.6admin.io/en/posts/subversion/</link><pubDate>Sat, 13 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/subversion/</guid><description>&lt;p>After CVS (which deserves its retirement), Subversion. The centralized VCS that bridged the gap between the commit-lock of CVS and the distributed model that would come after. The principle is simple: a central server (svnserve or Apache + mod_dav_svn), clients that checkout, commit, update. One history, one source of truth and a global revision number that increments on every commit.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>apt-get install subversion subversion-tools
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create the repository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>svnadmin create /srv/svn/myproject
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Start the standalone server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>svnserve -d -r /srv/svn
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The svnserve mode is the simplest: no Apache, no mod_dav, a daemon listening on port 3690. For web access, you need Apache + mod_dav_svn (see the next article).&lt;/p></description></item><item><title>Bash: IP address obfuscation</title><link>https://blog.6admin.io/en/posts/bash-ip-obfuscation/</link><pubDate>Thu, 11 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/bash-ip-obfuscation/</guid><description>&lt;p>An IP address has one &amp;ldquo;natural&amp;rdquo; representation (dotted quad: 192.168.1.1), but it has several numeric representations that browsers, SSH clients and log parsers also accept. An IP can be written in pure decimal (3232235777), hexadecimal (0xC0A80101), octal, or mixed (192.168.0x1.1). Logs filtering on &amp;ldquo;192.168.1.1&amp;rdquo; do not filter on &amp;ldquo;3232235777&amp;rdquo; and that is the obfuscation hole.&lt;/p>
&lt;h2 id="the-representations">The representations&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Pure decimal (uint32)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>printf &lt;span style="color:#e6db74">&amp;#34;%d\n&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">3232235777&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 3232235777 = 192.168.1.1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Hexadecimal&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>printf &lt;span style="color:#e6db74">&amp;#34;0x%X\n&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">3232235777&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 0xC0A80101&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Octal&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>printf &lt;span style="color:#e6db74">&amp;#34;%o\n&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">3232235777&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 30052000401&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The browser accepts &lt;code>http://3232235777/&lt;/code> exactly like &lt;code>http://192.168.1.1/&lt;/code>: it converts the number to dotted quad before connecting. The SSH client too, the FTP client too.&lt;/p></description></item><item><title>Fail2ban: fighting bruteforcing</title><link>https://blog.6admin.io/en/posts/fail2ban/</link><pubDate>Mon, 08 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/fail2ban/</guid><description>&lt;p>Every public SSH server receives hundreds of connection attempts per day. The auth.log fills up with &lt;code>Failed password for root from x.x.x.x&lt;/code> lines. Fail2ban reads those logs, counts the failures and bans the IP via iptables. It is a guard dog that reads logs instead of sniffing.&lt;/p>
&lt;h2 id="the-principle">The principle&lt;/h2>
&lt;p>Fail2ban works in three steps: it tails a log file (auth.log, mail.log, apache.log), it matches lines against configurable regular expressions and it runs an action when the threshold is reached (typically, an iptables DROP rule for the offending IP for N minutes).&lt;/p></description></item><item><title>OpenDNS: the free DNS</title><link>https://blog.6admin.io/en/posts/opendns/</link><pubDate>Sat, 06 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/opendns/</guid><description>&lt;p>OpenDNS (208.67.222.222 and 208.67.220.220) is a free public DNS resolver, one that appeared before the big providers&amp;rsquo; resolvers. The principle: you replace your ISP&amp;rsquo;s DNS with theirs and your resolution goes through their servers instead of yours.&lt;/p>
&lt;h2 id="why">Why&lt;/h2>
&lt;p>Three reasons people invoke:&lt;/p>
&lt;ul>
&lt;li>The ISP&amp;rsquo;s DNS is slow, goes down, or lies (the captive portal resolving everything to its login page);&lt;/li>
&lt;li>OpenDNS filters phishing and malware domains (their malicious domain database, updated continuously);&lt;/li>
&lt;li>OpenDNS does caching and the cache is faster than the ISP&amp;rsquo;s resolver.&lt;/li>
&lt;/ul>
&lt;h2 id="how">How&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># In /etc/resolv.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>nameserver 208.67.222.222
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>nameserver 208.67.220.220
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or in the network&amp;rsquo;s DHCP so every workstation benefits.&lt;/p></description></item><item><title>Subversion: a WebSVN with mod_dav and mod_svn for Apache</title><link>https://blog.6admin.io/en/posts/subversion-websvn-apache/</link><pubDate>Mon, 01 Dec 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/subversion-websvn-apache/</guid><description>&lt;p>Subversion has two access modes: svnserve (standalone daemon, svn:// protocol) and Apache with mod_dav_svn (http:// or https:// protocol). The Apache mode exposes the repository via HTTP, with Apache authentication, Apache SSL and WebSVN for web browsing of the sources.&lt;/p>
&lt;h2 id="the-mod_dav_svn-module">The mod_dav_svn module&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>apt-get install libapache2-svn
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This package installs mod_dav and mod_dav_svn (the module that exposes SVN repositories via WebDAV).&lt;/p>
&lt;h2 id="the-apache-configuration">The Apache configuration&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-apache" data-lang="apache">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># /etc/apache2/sites-available/svn&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;Location&lt;/span> &lt;span style="color:#e6db74">/svn&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DAV svn
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SVNParentPath &lt;span style="color:#e6db74">/var/svn&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthType Basic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthName &lt;span style="color:#e6db74">&amp;#34;Subversion Repository&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthUserFile &lt;span style="color:#e6db74">/etc/apache2/dav_svn.passwd&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Require valid-user
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;/Location&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>DAV svn&lt;/code> : activates SVN mode&lt;/li>
&lt;li>&lt;code>SVNParentPath /var/svn&lt;/code> : the directory containing the repositories (one repo per subdirectory)&lt;/li>
&lt;li>&lt;code>AuthUserFile&lt;/code> : the htpasswd file for authentication&lt;/li>
&lt;/ul>
&lt;p>Create the repository and the password file:&lt;/p></description></item><item><title>ext3: accelerate your disk accesses</title><link>https://blog.6admin.io/en/posts/ext3/</link><pubDate>Thu, 20 Nov 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ext3/</guid><description>&lt;p>The ext3 filesystem is the default on every distribution for years now and it is mounted with the slowest possible options. Three words to add to the fstab and your disk accesses are halved.&lt;/p>
&lt;h2 id="the-three-options">The three options&lt;/h2>
&lt;pre tabindex="0">&lt;code>/dev/sda1 / ext3 defaults,noatime,data=writeback,commit=60 0 1
&lt;/code>&lt;/pre>&lt;p>&lt;strong>noatime.&lt;/strong> The access time (atime) is updated at EVERY file read. A &lt;code>cat file&lt;/code> updates the atime of &lt;code>file&lt;/code> AND of every directory in the path. On a web server reading thousands of files per second, atime is an invisible disk overwrite. &lt;code>noatime&lt;/code> removes it outright. Cost: &lt;code>ls -lu&lt;/code> no longer shows the last read time, which has never been useful to anyone.&lt;/p></description></item><item><title>Compiling a shell script with shc</title><link>https://blog.6admin.io/en/posts/compile-shell-script-shc/</link><pubDate>Wed, 19 Nov 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/compile-shell-script-shc/</guid><description>&lt;p>A shell script is text readable by everyone: &lt;code>cat script.sh&lt;/code> shows the code, any passwords, the business logic. shc (shell script compiler) transforms the script into a binary: the script is compiled into C, the C is compiled into an ELF binary and the text source is no longer readable.&lt;/p>
&lt;h2 id="the-principle">The principle&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>shc -f script.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>shc generates two files:&lt;/p>
&lt;ul>
&lt;li>&lt;code>script.sh.x&lt;/code> : the executable ELF binary&lt;/li>
&lt;li>&lt;code>script.sh.x.c&lt;/code> : the generated C source (delete after compiling)&lt;/li>
&lt;/ul>
&lt;p>The binary &lt;code>script.sh.x&lt;/code> runs like the original script, but the shell code is not readable in the binary.&lt;/p></description></item><item><title>Debian Etch: play with backports</title><link>https://blog.6admin.io/en/posts/debian-etch-backports/</link><pubDate>Sun, 16 Nov 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/debian-etch-backports/</guid><description>&lt;p>Debian Etch (4.0) is stable: packages are tested, versions are frozen and an application that depends on a library newer than Etch&amp;rsquo;s does not compile. Backports solve this problem: the Debian Backports project recompiles packages from Sid (unstable) for Etch&amp;rsquo;s dependencies and makes them available in a separate repository.&lt;/p>
&lt;h2 id="the-backports-repository">The backports repository&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># /etc/apt/sources.list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>deb http://www.backports.org/debian etch-backports main contrib non-free
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The GPG key of the repository:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>wget -O - http://backports.org/debian/archive.key | apt-key add -
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Update:&lt;/p></description></item><item><title>ArpON: watching your ARP cache</title><link>https://blog.6admin.io/en/posts/arpon/</link><pubDate>Fri, 14 Nov 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/arpon/</guid><description>&lt;p>ArpON is a very practical daemon, where tools like Arpwatch only let you see the problems, ArpON lets you block them (notably ARP cache poisoning attempts).&lt;/p>
&lt;p>To do this it can use two protocols, SARPI (Static ARP Inspection) and DARPI (Dynamic ARP Inspection).&lt;/p>
&lt;p>It perfectly replaces the arp, arping and arpscan tools, detects and blocks spoofing or poisoning attempts and because it runs in user space, remains very portable. Currently compatible with OpenBSD, FreeBSD, Debian, Gentoo, Slackware and even Mac OSX.&lt;/p></description></item><item><title>EncFS: file encryption without a dedicated partition</title><link>https://blog.6admin.io/en/posts/encfs/</link><pubDate>Mon, 20 Oct 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/encfs/</guid><description>&lt;p>Encrypting your data on Linux has always meant LUKS, dm-crypt, a dedicated partition, a reinstall. EncFS breaks that pattern: it is a FUSE filesystem that encrypts file by file, in a normal directory, without a dedicated partition, without reinstalling and without root access after the first mount.&lt;/p>
&lt;h2 id="the-principle">The principle&lt;/h2>
&lt;p>EncFS creates two directories: the &lt;strong>cipher&lt;/strong> (the encrypted files, with encrypted names too) and the &lt;strong>plain&lt;/strong> (the mount point, where files appear in cleartext). Everything you write to the plain is encrypted on the fly and stored in the cipher. Everything you read from the plain is decrypted on the fly from the cipher. Unmount the plain and all that remains is the cipher: unreadable names, encrypted content.&lt;/p></description></item><item><title>radare: a very complete debugger</title><link>https://blog.6admin.io/en/posts/radare/</link><pubDate>Sat, 04 Oct 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/radare/</guid><description>&lt;p>After the Perl harness bolted onto GDB and before &lt;a href="https://metasm.cr0.org/" target="_blank" rel="noopener noreferrer">Metasm&lt;/a>
 pulled me into Ruby, there was radare. A console-mode debugger written by pancake (Nibble), doing everything GDB does for exploitation, but with an interface designed for reverse engineering rather than application debugging.&lt;/p>
&lt;h2 id="what-radare-does-that-gdb-does-not">What radare does that GDB does not&lt;/h2>
&lt;p>GDB is an application debugger: it stops on breakpoints, prints variables, follows calls. radare is a &lt;strong>reverse engineering&lt;/strong> tool: it disassembles, analyses strings, finds gadgets, computes offsets and lets you navigate the binary like a filesystem.&lt;/p></description></item><item><title>Yersinia: how to make your network administrator sick</title><link>https://blog.6admin.io/en/posts/yersinia/</link><pubDate>Thu, 28 Aug 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/yersinia/</guid><description>&lt;p>Yersinia is the name of the plague bacterium. The tool lives up to its name: it attacks layer 2 protocols (STP, CDP, DTP, DHCP, VTP, HSRP) and makes your switch sick. The title of this article is not a metaphor: it is what actually happens to a network administrator who discovers that a workstation with Yersinia and a simple cable can take control of their switch topology.&lt;/p>
&lt;h2 id="the-attacks-one-per-protocol">The attacks, one per protocol&lt;/h2>
&lt;p>&lt;strong>STP (Spanning Tree Protocol).&lt;/strong> Yersinia sends BPDUs claiming to be the root bridge with a lower priority. The switch recalculates the tree and traffic flows through the attacker&amp;rsquo;s workstation. Your switch has been re-rooted and all L2 traffic transits through the intern&amp;rsquo;s laptop.&lt;/p></description></item><item><title>SNMP: a not-so-simple protocol</title><link>https://blog.6admin.io/en/posts/snmp/</link><pubDate>Tue, 01 Jul 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/snmp/</guid><description>&lt;p>SNMP stands for Simple Network Management Protocol. The &amp;ldquo;Simple&amp;rdquo; is the irony of the century: the protocol uses OIDs (Object Identifiers) that are trees of numbers, MIBs (Management Information Base) that translate the numbers into names, communities (public, private) that serve as cleartext passwords and versions (v1, v2c, v3) each with its own quirks.&lt;/p>
&lt;h2 id="the-oids-the-tree">The OIDs: the tree&lt;/h2>
&lt;p>Every piece of data in SNMP has an OID: a dot-separated sequence of numbers. The standard tree:&lt;/p></description></item><item><title>tcpdump: frame analysis and BPF filters</title><link>https://blog.6admin.io/en/posts/tcpdump/</link><pubDate>Fri, 20 Jun 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/tcpdump/</guid><description>&lt;p>After &lt;a href="https://blog.6admin.io/en/posts/nemesis-packet-forge/">Nemesis&lt;/a>
 which forges and &lt;a href="https://blog.6admin.io/en/posts/raw-sockets/">raw sockets&lt;/a>
 which sniff by hand, the tool that does all of it without writing a line of C: tcpdump. If you have never used it, know that it is probably the highest-return diagnostic tool of your entire career: one command, one filter and you read the network in cleartext.&lt;/p>
&lt;h2 id="the-bpf-filter-the-concept-that-changes-everything">The BPF filter, the concept that changes everything&lt;/h2>
&lt;p>tcpdump alone shows ALL the traffic on the interface. It is unusable on an active network: you are drowning in ten seconds. The magic comes from the &lt;strong>BPF filter&lt;/strong> (Berkeley Packet Filter), an expression language compiled on the fly into the kernel to only capture what matches:&lt;/p></description></item><item><title>Nemesis: detecting a network sniffer</title><link>https://blog.6admin.io/en/posts/nemesis-sniffer-detection/</link><pubDate>Fri, 06 Jun 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/nemesis-sniffer-detection/</guid><description>&lt;p>The previous article talked about &lt;a href="https://blog.6admin.io/en/posts/nemesis-packet-forge/">forging packets with Nemesis&lt;/a>
. This one talks about the opposite side: detecting that someone on your network is listening to what they should not.&lt;/p>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>A network card in promiscuous mode accepts ALL frames, not only the ones addressed to its MAC. That is what lets tcpdump and Wireshark work and it is also what lets a sniffer read traffic not destined for it. On a switch, it should not pass (the switch routes by MAC), but if someone has tinkered with the switch or if there is a hub somewhere, it passes.&lt;/p></description></item><item><title>Nemesis: network packet forge</title><link>https://blog.6admin.io/en/posts/nemesis-packet-forge/</link><pubDate>Mon, 02 Jun 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/nemesis-packet-forge/</guid><description>&lt;p>If you have read &lt;a href="https://blog.6admin.io/en/posts/raw-sockets/">the raw sockets article&lt;/a>
, you know how to forge a SYN by hand in C: fill the fields, compute the checksum, send. It is educational and it is also what Nemesis does from the command line in one invocation. Nemesis is a generic packet forge: TCP, UDP, ICMP, RIP, OSPF, ARP, DNS. Each protocol has its subcommand, each header field has its option.&lt;/p>
&lt;h2 id="the-idea">The idea&lt;/h2>
&lt;p>A packet forge is the inverse of tcpdump: tcpdump reads what passes, Nemesis builds what passes. The combination of both is the complete network testing protocol: Nemesis forges, tcpdump verifies what actually arrives and you know what your firewall, your switch or your IDS understands from what you sent.&lt;/p></description></item><item><title>Raw Sockets: forging and sniffing packets in C</title><link>https://blog.6admin.io/en/posts/raw-sockets/</link><pubDate>Fri, 09 May 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/raw-sockets/</guid><description>&lt;p>We spoke of &lt;a href="https://blog.6admin.io/en/posts/sockets-c/">TCP sockets in C&lt;/a>
, under the comfort of &lt;code>SOCK_STREAM&lt;/code>. Let&amp;rsquo;s go below the kernel: &lt;strong>raw sockets&lt;/strong>, where tcpdump and Nemesis live. Usual warning: &lt;code>SOCK_RAW&lt;/code> requires &lt;strong>root&lt;/strong>, the network card in promiscuous mode and some ethics, all without a contract with anyone.&lt;/p>
&lt;h2 id="sniffing-af_packet-and-promiscuous-mode">Sniffing: AF_PACKET and promiscuous mode&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> s &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">socket&lt;/span>(AF_PACKET, SOCK_RAW, &lt;span style="color:#a6e22e">htons&lt;/span>(ETH_P_ALL));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Two lines and you receive &lt;strong>everything&lt;/strong> crossing the interface, Ethernet headers included, provided the interface goes promiscuous:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> ifreq ifr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">strncpy&lt;/span>(ifr.ifr_name, &lt;span style="color:#e6db74">&amp;#34;eth0&amp;#34;&lt;/span>, IFNAMSIZ);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">ioctl&lt;/span>(s, SIOCGIFFLAGS, &lt;span style="color:#f92672">&amp;amp;&lt;/span>ifr);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ifr.ifr_flags &lt;span style="color:#f92672">|=&lt;/span> IFF_PROMISC;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">ioctl&lt;/span>(s, SIOCSIFFLAGS, &lt;span style="color:#f92672">&amp;amp;&lt;/span>ifr);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Curiosity, at layer 2, is not configured, it is paid for: the day the switch mirrors your port without telling you, you understand that reading other people&amp;rsquo;s conversations is a closely watched sport. The raw packet lands in a buffer: Ethernet (14 bytes), then IP, then TCP. Manual reading, no framework:&lt;/p></description></item><item><title>Sockets: speaking TCP in C, no framework, no magic</title><link>https://blog.6admin.io/en/posts/sockets-c/</link><pubDate>Wed, 02 Apr 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/sockets-c/</guid><description>&lt;p>After &lt;a href="https://blog.6admin.io/en/posts/lkms/">LKMs&lt;/a>
 and syscalls, let&amp;rsquo;s stay low. All of Unix networking fits in four system calls, two structs and a treaty of byte order. The rest is memory and discipline. Small memo, Debian Etch required, gcc 4.1 and man pages 2 and 7 as only friends.&lt;/p>
&lt;h2 id="the-structs-first">The structs, first&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sys/socket.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;netinet/in.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;arpa/inet.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> sockaddr_in addr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>addr.sin_family &lt;span style="color:#f92672">=&lt;/span> AF_INET; &lt;span style="color:#75715e">/* IPv4 */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>addr.sin_port &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">htons&lt;/span>(&lt;span style="color:#ae81ff">4444&lt;/span>); &lt;span style="color:#75715e">/* network byte order, ALWAYS */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>addr.sin_addr.s_addr &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">htonl&lt;/span>(INADDR_ANY); &lt;span style="color:#75715e">/* 0.0.0.0 */&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>htons&lt;/code> and &lt;code>htonl&lt;/code> are not decorative: the network speaks &lt;strong>big-endian&lt;/strong>, your x86 speaks little-endian and the neighbor who forgets &lt;code>htons&lt;/code> gets a server on port 45056. &lt;code>man 7 ip&lt;/code> explains all this better than me.&lt;/p></description></item><item><title>Linux: introduction to LKMs</title><link>https://blog.6admin.io/en/posts/lkms/</link><pubDate>Sun, 02 Mar 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/lkms/</guid><description>&lt;p>The Linux kernel is monolithic: all code runs in the same address space, in privileged mode. But it accepts additional code at runtime: LKMs (Loadable Kernel Modules). An LKM is a &lt;code>.ko&lt;/code> file compiled for the running kernel, that inserts into kernel space and adds functionality: device drivers, filesystems, network protocols, custom syscalls.&lt;/p>
&lt;h2 id="the-smallest-lkm">The smallest LKM&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// hello.c
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;linux/module.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;linux/kernel.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> __init &lt;span style="color:#a6e22e">hello_init&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printk&lt;/span>(KERN_INFO &lt;span style="color:#e6db74">&amp;#34;Hello from kernel space&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> __exit &lt;span style="color:#a6e22e">hello_exit&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printk&lt;/span>(KERN_INFO &lt;span style="color:#e6db74">&amp;#34;Goodbye from kernel space&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">module_init&lt;/span>(hello_init);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">module_exit&lt;/span>(hello_exit);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">MODULE_LICENSE&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;GPL&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The Makefile:&lt;/p></description></item><item><title>Linux syscalls</title><link>https://blog.6admin.io/en/posts/linux-syscalls/</link><pubDate>Thu, 21 Feb 2008 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/linux-syscalls/</guid><description>&lt;p>A user-space program does nothing directly: when it reads a file, allocates memory, or sends a network packet, it asks the kernel to do it. This request is a syscall (system call): the program executes a special instruction (int 0x80 or sysenter on x86), the kernel takes over, executes the request and returns control to the program. It is the boundary between user space (ring 3) and kernel space (ring 0).&lt;/p></description></item><item><title>SSLH: ssh and https on the same port</title><link>https://blog.6admin.io/en/posts/sslh/</link><pubDate>Sun, 16 Sep 2007 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/sslh/</guid><description>&lt;p>The 2007 problem, in companies with nervous firewalls: one outgoing port only, 443, everything else cut. You want your ssh shell and your https and the firewall does not negotiate. The answer is sslh (v1.0, by Yves Rutschle): a small daemon that &lt;strong>listens on 443 and delegates&lt;/strong> based on the first packet: to sshd if the client speaks the SSH protocol, to httpsd otherwise.&lt;/p>
&lt;h2 id="the-mechanism">The mechanism&lt;/h2>
&lt;pre tabindex="0">&lt;code>client --443--&amp;gt; sslh --detection--&amp;gt; sshd:22
 \--&amp;gt; https:443
&lt;/code>&lt;/pre>&lt;p>The SSH protocol announces itself politely: the client sends a &lt;code>SSH-2.0-...&lt;/code> banner in clear text upon opening. SSL/TLS starts with a ClientHello whose first byte is not an &amp;lsquo;S&amp;rsquo;. Two bells, two destinations:&lt;/p></description></item><item><title>PAM: limit system resources</title><link>https://blog.6admin.io/en/posts/pam-resource-limits/</link><pubDate>Fri, 04 May 2007 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/pam-resource-limits/</guid><description>&lt;p>PAM (Pluggable Authentication Modules) does not only do authentication: the pam_limits module caps system resources per user and per group. Without limits, a process can eat all the RAM, all the file descriptors, all the process slots: that is the fork bomb, or simply a process that leaks memory.&lt;/p>
&lt;h2 id="the-limitsconf-file">The limits.conf file&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># /etc/security/limits.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># &amp;lt;domain&amp;gt; &amp;lt;type&amp;gt; &amp;lt;item&amp;gt; &amp;lt;value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Cap processes for all users&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* soft nproc &lt;span style="color:#ae81ff">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* hard nproc &lt;span style="color:#ae81ff">4096&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Cap open file descriptors&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* soft nofile &lt;span style="color:#ae81ff">1024&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* hard nofile &lt;span style="color:#ae81ff">4096&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Cap memory (in KB)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* soft as &lt;span style="color:#ae81ff">1048576&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* hard as &lt;span style="color:#ae81ff">2097152&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># For the backup group: more open files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@backup soft nofile &lt;span style="color:#ae81ff">8192&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@backup hard nofile &lt;span style="color:#ae81ff">16384&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># For root: no limit&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>root soft nproc unlimited
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>root hard nproc unlimited
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>soft&lt;/code> : the current limit (the process can raise it up to the hard limit)&lt;/li>
&lt;li>&lt;code>hard&lt;/code> : the maximum limit (the process cannot exceed it, even with ulimit -Hn)&lt;/li>
&lt;li>&lt;code>*&lt;/code> : all users&lt;/li>
&lt;li>&lt;code>@group&lt;/code> : a Linux group&lt;/li>
&lt;li>&lt;code>root&lt;/code> : the root user&lt;/li>
&lt;/ul>
&lt;h2 id="the-limitsconf-items">The limits.conf items&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>item&lt;/th>
 &lt;th>what it caps&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>nproc&lt;/code>&lt;/td>
 &lt;td>the number of processes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>nofile&lt;/code>&lt;/td>
 &lt;td>the number of open file descriptors&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>as&lt;/code>&lt;/td>
 &lt;td>memory (address space) in KB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>rss&lt;/code>&lt;/td>
 &lt;td>resident memory in KB (old, replaced by as)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>cpu&lt;/code>&lt;/td>
 &lt;td>CPU time in minutes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>maxlogins&lt;/code>&lt;/td>
 &lt;td>the number of simultaneous sessions&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>core&lt;/code>&lt;/td>
 &lt;td>the size of core dumps&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="checking-the-limits">Checking the limits&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ulimit -a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>core file size (blocks, -c) 0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>data seg size (kbytes, -d) unlimited
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>max nice (-e) 0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>file size (blocks, -f) unlimited
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pending signals (-i) 4096
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>max locked memory (kbytes, -l) 32
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>max memory size (kbytes, -m) unlimited
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>open files (-n) 1024
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pipe size (512 bytes, -p) 8
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>POSIX message queues (bytes, -q) 8192
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>stack size (kbytes, -s) 8192
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cpu time (seconds, -t) unlimited
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>max user processes (-u) 2048
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>virtual memory (kbytes, -v) unlimited
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>ulimit -n&lt;/code> shows the file descriptor limit: 1024 by default, which is insufficient for a web server or a database. 4096 or 16384 is the minimum for a server.&lt;/p></description></item><item><title>SSH: tunneling</title><link>https://blog.6admin.io/en/posts/ssh-tunneling/</link><pubDate>Wed, 04 Apr 2007 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-tunneling/</guid><description>&lt;p>SSH does not only transfer files: it makes any TCP protocol cross the encrypted channel. A VNC connection, a MySQL database, an internal web server, an SMB share: everything that is TCP can be tunneled through SSH, without VPN, without firewall configuration, with one command.&lt;/p>
&lt;h2 id="the-local-tunnel-local-forward">The local tunnel (local forward)&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssh -L 3306:localhost:3306 user@server
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This command opens port 3306 on your local machine and everything that arrives there goes to port 3306 on &lt;code>server&lt;/code> (as seen from &lt;code>server&lt;/code>). An SSH session is open, the tunnel works as long as the session lasts.&lt;/p></description></item><item><title>MySQL: backup</title><link>https://blog.6admin.io/en/posts/mysql-backup/</link><pubDate>Tue, 06 Mar 2007 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/mysql-backup/</guid><description>&lt;p>A MySQL backup that has never been restored is not a backup, it is a hope. The previous article covered the &lt;a href="https://blog.6admin.io/en/posts/mysql-cheatsheet/">memento&lt;/a>
, this one covers automated backup and the restore test.&lt;/p>
&lt;h2 id="the-backup-script">The backup script&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>BACKUP_DIR&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/backup/mysql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DATE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>date +%Y%m%d&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MYSQL_USER&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;backup_user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MYSQL_PASS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;password&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BACKUP_DIR&lt;span style="color:#e6db74">/&lt;/span>$DATE&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># List databases&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DATABASES&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>mysql -u $MYSQL_USER -p$MYSQL_PASS -e &lt;span style="color:#e6db74">&amp;#34;SHOW DATABASES&amp;#34;&lt;/span> | grep -v Database | grep -v information_schema | grep -v test&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Dump each database&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> db in $DATABASES; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mysqldump -u $MYSQL_USER -p$MYSQL_PASS &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --add-drop-table &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --single-transaction &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$db&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &amp;gt; &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BACKUP_DIR&lt;span style="color:#e6db74">/&lt;/span>$DATE&lt;span style="color:#e6db74">/&lt;/span>$db&lt;span style="color:#e6db74">.sql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gzip &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BACKUP_DIR&lt;span style="color:#e6db74">/&lt;/span>$DATE&lt;span style="color:#e6db74">/&lt;/span>$db&lt;span style="color:#e6db74">.sql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Clean backups older than 7 days&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>find &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BACKUP_DIR&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -type d -mtime +7 -exec rm -rf &lt;span style="color:#f92672">{}&lt;/span> &lt;span style="color:#ae81ff">\;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>--single-transaction&lt;/code> is crucial: it starts an InnoDB transaction and dumps from a consistent snapshot without locking tables. Without this flag, mysqldump locks each table and your production application waits.&lt;/p></description></item><item><title>MySQL: lost credentials</title><link>https://blog.6admin.io/en/posts/mysql-reset-root-password/</link><pubDate>Thu, 08 Feb 2007 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/mysql-reset-root-password/</guid><description>&lt;p>The scenario: a server you have not touched for six months, a MySQL whose root password nobody remembers and a client asking why their site no longer connects to the database. The root password is lost and there is no config file with it lying around.&lt;/p>
&lt;h2 id="the-procedure">The procedure&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 1. Stop MySQL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/etc/init.d/mysql stop
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 2. Start without grant tables&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqld_safe --skip-grant-tables --skip-networking &amp;amp;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 3. Connect without password&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql -u root mysql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 4. Change the root password&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>UPDATE user SET Password&lt;span style="color:#f92672">=&lt;/span>PASSWORD&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#e6db74">&amp;#39;newpass&amp;#39;&lt;/span>&lt;span style="color:#f92672">)&lt;/span> WHERE User&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;root&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>FLUSH PRIVILEGES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>EXIT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 5. Kill mysqld_safe and restart cleanly&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>killall mysqld_safe mysqld
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/etc/init.d/mysql start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>--skip-grant-tables&lt;/code> flag disables ALL authentication: anyone can connect as anyone. The &lt;code>--skip-networking&lt;/code> flag prevents external TCP connections during the recovery window (only the local socket works). Both together are the safe procedure.&lt;/p></description></item><item><title>MySQL: Memento</title><link>https://blog.6admin.io/en/posts/mysql-cheatsheet/</link><pubDate>Fri, 26 Jan 2007 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/mysql-cheatsheet/</guid><description>&lt;p>The MySQL memo for admin commands that are not typed often enough to remember, but often enough to know they exist.&lt;/p>
&lt;h2 id="connection">Connection&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mysql -u root -p
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql -u root -p -h localhost my_schema
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql -u root -p &amp;lt; dump.sql
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="users-and-grants">Users and grants&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">USER&lt;/span> &lt;span style="color:#e6db74">&amp;#39;user&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;localhost&amp;#39;&lt;/span> IDENTIFIED &lt;span style="color:#66d9ef">BY&lt;/span> &lt;span style="color:#e6db74">&amp;#39;password&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span> &lt;span style="color:#66d9ef">PRIVILEGES&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> my_schema.&lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#e6db74">&amp;#39;user&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;localhost&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span>, &lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> my_schema.&lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#e6db74">&amp;#39;readonly&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>FLUSH &lt;span style="color:#66d9ef">PRIVILEGES&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> GRANTS &lt;span style="color:#66d9ef">FOR&lt;/span> &lt;span style="color:#e6db74">&amp;#39;user&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;localhost&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">USER&lt;/span> &lt;span style="color:#e6db74">&amp;#39;user&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;localhost&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="backup-and-restore">Backup and restore&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Full backup of one database&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqldump -u root -p my_schema &amp;gt; backup.sql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Backup of all databases&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqldump -u root -p --all-databases &amp;gt; all.sql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Backup with DROP TABLE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqldump -u root -p --add-drop-table my_schema &amp;gt; backup.sql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Restore&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql -u root -p my_schema &amp;lt; backup.sql
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="admin-queries">Admin queries&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> DATABASES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> TABLES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> STATUS;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> PROCESSLIST;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> VARIABLES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SHOW&lt;/span> ENGINE INNODB STATUS;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">DESCRIBE&lt;/span> &lt;span style="color:#66d9ef">table_name&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="optimization">Optimization&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>OPTIMIZE &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">table_name&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ANALYZE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">table_name&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>REPAIR &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">table_name&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="the-lost-root-password">The lost root password&lt;/h2>
&lt;p>If you have lost the root password, the procedure is:&lt;/p></description></item><item><title>Apache: what a chatterbox</title><link>https://blog.6admin.io/en/posts/apache-bavard/</link><pubDate>Thu, 21 Dec 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/apache-bavard/</guid><description>&lt;p>Apache talks. A lot. Every request generates a line in the access_log, every error a line in the error_log and the LogLevel controls the chattiness. On a server serving 100,000 pages per day, the access_log weighs 50 MB per day and nobody reads it.&lt;/p>
&lt;h2 id="the-loglevel">The LogLevel&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-apache" data-lang="apache">&lt;span style="display:flex;">&lt;span>LogLevel &lt;span style="color:#66d9ef">warn&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The levels, from chattiest to quietest: debug, info, notice, warn, error, crit, alert, emerg. &lt;code>warn&lt;/code> is the default and the right one: it logs warnings (config, permissions) and errors, but not every module starting up.&lt;/p></description></item><item><title>Apache: the limits of .htaccess</title><link>https://blog.6admin.io/en/posts/apache-htaccess/</link><pubDate>Tue, 28 Nov 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/apache-htaccess/</guid><description>&lt;p>.htaccess is practical: one file per directory, no restart, the shared hosting client manages it alone. On a dedicated server, it is a slowdown.&lt;/p>
&lt;p>Every HTTP request makes Apache read all the .htaccess files in the path, from the root. For /var/www/site/blog/article/, Apache reads /var/www/.htaccess, /var/www/site/.htaccess, /var/www/site/blog/.htaccess and /var/www/site/blog/article/.htaccess. Four disk reads per request before serving the page.&lt;/p>
&lt;p>The fix: &lt;code>AllowOverride None&lt;/code> in httpd.conf and everything that was in the .htaccess files moves to the VirtualHost. Apache no longer reads .htaccess files and the directives are compiled at startup.&lt;/p></description></item><item><title>Apache: disabling the TRACE option</title><link>https://blog.6admin.io/en/posts/apache-trace/</link><pubDate>Fri, 06 Oct 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/apache-trace/</guid><description>&lt;p>The HTTP TRACE method sends back to the client the exact request it sent, in echo. Designed for debugging, it is a door for XST (Cross-Site Tracing): JavaScript on a compromised page can use TRACE to read HttpOnly cookies via the request headers. IE6 allowed it, Firefox too.&lt;/p>
&lt;p>The test:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ telnet mydomain.com &lt;span style="color:#ae81ff">80&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TRACE / HTTP/1.1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Host: mydomain.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>HTTP/1.1 &lt;span style="color:#ae81ff">200&lt;/span> OK
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TRACE / HTTP/1.1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Host: mydomain.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you see your request echoed back, TRACE is enabled.&lt;/p></description></item><item><title>SSH: authentication via private/public keys</title><link>https://blog.6admin.io/en/posts/ssh-key-authentication/</link><pubDate>Sun, 10 Sep 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-key-authentication/</guid><description>&lt;p>SSH authentication by password works, but it is weak: the password is typed at every connection, it can be brute-forced (fail2ban mitigates but does not solve) and it does not distinguish &amp;ldquo;who&amp;rdquo; from &amp;ldquo;what&amp;rdquo;. SSH keys solve all three: the private key proves identity, the public key authorizes access and once configured, no more password.&lt;/p>
&lt;h2 id="generate-the-pair">Generate the pair&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssh-keygen -t rsa -b &lt;span style="color:#ae81ff">4096&lt;/span> -C &lt;span style="color:#e6db74">&amp;#34;comment&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>-t rsa&lt;/code> generates an RSA pair, the &lt;code>-b 4096&lt;/code> sets the size (2048 minimum, 4096 recommended). The &lt;code>-C&lt;/code> is a free comment (usually the email or hostname). Two files: &lt;code>id_rsa&lt;/code> (the private key, never leaves the workstation) and &lt;code>id_rsa.pub&lt;/code> (the public key, to copy to servers).&lt;/p></description></item><item><title>Magic SysRq: the sysadmin's crutch</title><link>https://blog.6admin.io/en/posts/magic-sysrq/</link><pubDate>Fri, 11 Aug 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/magic-sysrq/</guid><description>&lt;p>A server is frozen. No SSH, no console, no ping response. You are in a datacenter or on a serial console and the only option before pulling the plug is the &lt;strong>SysRq&lt;/strong> key.&lt;/p>
&lt;h2 id="what-it-is">What it is&lt;/h2>
&lt;p>The SysRq (System Request) key is intercepted directly by the Linux kernel, regardless of system state. The server may be in kernel panic, the scheduler may be blocked: the kernel answers SysRq as long as the hardware interrupts work. It is the emergency crutch.&lt;/p></description></item><item><title>iodine: IP-over-DNS, or the internet where only DNS passes</title><link>https://blog.6admin.io/en/posts/iodine/</link><pubDate>Fri, 28 Jul 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/iodine/</guid><description>&lt;p>The scene is familiar: the conference hotel, the fast-food hotspot, one of those captive portals hatching everywhere since Wi-Fi became a service. You connect, a page opens, you are supposed to pay or type a room code and everything that is not HTTP on port 80 falls into the void. No SSH, no direct SMTP, no visible VPN. A filter that only lets the web through, which is almost nothing.&lt;/p></description></item><item><title>dig: the Domain Information Groper, or the art of asking a name server</title><link>https://blog.6admin.io/en/posts/dig/</link><pubDate>Mon, 26 Jun 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/dig/</guid><description>&lt;p>For years, my DNS tool was nslookup. The habit comes from far away, it comes from Windows, it comes from the reflex &amp;ldquo;I want to see a record, I type nslookup&amp;rdquo;. Then one day I had to debug a zone that was propagating badly between a primary and a secondary and nslookup answered me with its interactive interface, its &lt;code>&amp;gt;&lt;/code> prompt, its output that changes shape depending on the resolver library version of the system. I installed the dnsutils package that very evening and nslookup retired.&lt;/p></description></item><item><title>Links: hard links and symbolic links and what escapes the chroot</title><link>https://blog.6admin.io/en/posts/hard-symbolic-links/</link><pubDate>Mon, 05 Jun 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/hard-symbolic-links/</guid><description>&lt;p>Linux knows two types of links and the difference between them is a sysadmin exam subject AND a chroot escape vector. The same concept that explains your data backup also explains how to break out of a jail.&lt;/p>
&lt;h2 id="the-hard-link">The hard link&lt;/h2>
&lt;p>A hard link is a directory entry that points directly to an inode. The file and its link are two names for the same disk content. Deleting one does not delete the other (the inode survives as long as its link counter is above zero).&lt;/p></description></item><item><title>SSH: where is the fish?</title><link>https://blog.6admin.io/en/posts/ssh-fish-protocol/</link><pubDate>Wed, 10 May 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/ssh-fish-protocol/</guid><description>&lt;p>The fish protocol (Files transferred over SHell) is a KDE invention: it encapsulates shell commands in an SSH connection to browse and transfer files as if it were a network mount. Konqueror does it natively: type &lt;code>fish://user@server/home/user&lt;/code> in the address bar and the remote filesystem appears in the file browser.&lt;/p>
&lt;h2 id="what-fish-does">What fish does&lt;/h2>
&lt;p>Fish is not a mount (no FUSE, no kernel module): the client sends shell commands (&lt;code>ls&lt;/code>, &lt;code>cat&lt;/code>, &lt;code>cp&lt;/code>) over SSH and parses the result. The server needs nothing special: just a POSIX shell. The client builds the commands, sends them over the SSH channel and presents the result as a filesystem.&lt;/p></description></item><item><title>Root or not root</title><link>https://blog.6admin.io/en/posts/root-or-not-root/</link><pubDate>Thu, 13 Apr 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/root-or-not-root/</guid><description>&lt;p>The question: who gets to be root on a shared server? The answer is not technical, it is political. But the technical tools exist and they are not equal.&lt;/p>
&lt;h2 id="su">su&lt;/h2>
&lt;p>&lt;code>su&lt;/code> asks for the root password. Whoever knows it becomes root. Period. All admins share the same password and when someone leaves the team, it needs changing (and redistributing). The history of what was done as root is limited to &lt;code>last&lt;/code> and the memory of whoever was there.&lt;/p></description></item><item><title>Bash: Memento</title><link>https://blog.6admin.io/en/posts/bash-cheatsheet/</link><pubDate>Wed, 15 Mar 2006 00:00:00 +0000</pubDate><guid>https://blog.6admin.io/en/posts/bash-cheatsheet/</guid><description>&lt;p>A memo, not a tutorial. The bash lines you look up every time because you do not type them often enough to remember, but often enough to know they exist.&lt;/p>
&lt;h2 id="substitutions">Substitutions&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Replace the first occurrence&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var/old/new&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Replace all occurrences&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var//old/new&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Remove the shortest pattern from the start&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var#pattern&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Remove the shortest pattern from the end&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var%pattern&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># String length&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${#&lt;/span>var&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Default value if empty&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var&lt;span style="color:#66d9ef">:-&lt;/span>default&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Assign if empty&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var:=default&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Error if empty&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">${&lt;/span>var:?message&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="arrays">Arrays&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>declare -a tab
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tab&lt;span style="color:#f92672">[&lt;/span>0&lt;span style="color:#f92672">]=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;first&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tab&lt;span style="color:#f92672">+=(&lt;/span>second third&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">${&lt;/span>tab[@]&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">${#&lt;/span>tab[@]&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">${&lt;/span>tab[1]&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Available indices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">${&lt;/span>!tab[@]&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="redirections">Redirections&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cmd &amp;gt; file &lt;span style="color:#75715e"># stdout to file (overwrites)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmd &amp;gt;&amp;gt; file &lt;span style="color:#75715e"># stdout to file (appends)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmd 2&amp;gt; file &lt;span style="color:#75715e"># stderr to file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmd &amp;amp;&amp;gt; file &lt;span style="color:#75715e"># stdout + stderr to file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmd 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#75715e"># stderr to stdout&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmd &amp;gt; /dev/null 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#75715e"># full silence&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmd &amp;lt; file &lt;span style="color:#75715e"># stdin from file&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="loops">Loops&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> i in &lt;span style="color:#66d9ef">$(&lt;/span>ls&lt;span style="color:#66d9ef">)&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$i&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> i in &lt;span style="color:#f92672">{&lt;/span>1..10&lt;span style="color:#f92672">}&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$i&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> read line; &lt;span style="color:#66d9ef">do&lt;/span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$line&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">done&lt;/span> &amp;lt; file
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="tests">Tests&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> -f file &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># exists and is a regular file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> -d dir &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># exists and is a directory&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> -s file &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># exists and is not empty&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> -x file &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># executable&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$a&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$b&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># string equality&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> $n -eq $m &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># numeric equality&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[[&lt;/span> $n -lt $m &lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#75715e"># less than&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That is all. The rest is learned with &lt;code>man bash&lt;/code> (EXPANSION section), but these lines are the ones you paste into a file and source.&lt;/p></description></item></channel></rss>