<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Sergey Istomin</title>
<link>https://neanderthal.github.io/</link>
<atom:link href="https://neanderthal.github.io/index.xml" rel="self" type="application/rss+xml"/>
<description>Senior Python Developer &amp; Tech Lead — notes on code, systems, and security.</description>
<generator>quarto-1.7.32</generator>
<lastBuildDate>Mon, 27 Apr 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Спазм мозга: My Telegram Channel for Tech Brain-Cramps</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/mind-cramp/</link>
  <description><![CDATA[ 






<p>A quick meta-post: I’ve been running a Telegram channel called <strong><a href="https://t.me/mind_cramp">Спазм мозга</a></strong> (“Brain Cramp”, <code>@mind_cramp</code>) since 2019. It’s where I dump the things that catch my attention before they vanish — tool releases, papers, eclectic bookmarks, and short Russian-language announcements of whatever I’ve been hacking on.</p>
<p>If you’ve followed any of my recent project posts (<code>unix-pass-mcp</code>, <code>telegram-search</code>, <code>whitelist-probe</code>, <code>security-audit</code>), the channel is the rough-draft companion. Rule of thumb: short notes appear on Telegram first, the long-form writeup lands here days later.</p>
<section id="whats-actually-in-there" class="level2">
<h2 class="anchored" data-anchor-id="whats-actually-in-there">What’s actually in there</h2>
<p>A rolling mix:</p>
<ul>
<li><strong>My own tool releases</strong> — short Russian-language announcements for projects I open-source. The polished version usually shows up here on the blog with the architecture and security posture spelled out.</li>
<li><strong>AI / agents news</strong> — Claude Code, Cursor Automations, LangChain Deep Agents, the latest local-model drops (Qwen, Gemini, Llama). Mostly focused on agentic infrastructure rather than chat-assistant hype.</li>
<li><strong>Security tooling</strong> — Bug Bounty kits, OSINT, hardware hacks (RaspyJack et al.), occasional CVE writeups.</li>
<li><strong>Self-hosting &amp; open source</strong> — Rust CLI tools, infra utilities, bookmarks for living a little less inside big-tech defaults.</li>
<li><strong>Off-topic detours</strong> — book reviews, weird internet finds, things that genuinely cramp my brain. The channel name is a literal description.</li>
</ul>
<p>It’s <strong>Russian by default</strong> — most of my readers are Russian-speaking infra and security folks — but tools and links work for everyone. If you read English-only, the GitHub repos linked from each post are usually self-explanatory.</p>
</section>
<section id="who-its-for" class="level2">
<h2 class="anchored" data-anchor-id="who-its-for">Who it’s for</h2>
<ul>
<li>People who want a pre-blog feed of what I’m currently building</li>
<li>Engineers who treat their Telegram saved-messages folder as a second brain and need raw material for it</li>
<li>Anyone who liked the security-audit / telegram-search / unix-pass-mcp posts and wants the next one before I get around to writing it up</li>
</ul>
</section>
<section id="subscribe" class="level2">
<h2 class="anchored" data-anchor-id="subscribe">Subscribe</h2>
<p><strong><a href="https://t.me/mind_cramp">t.me/mind_cramp →</a></strong></p>
<p>Broadcast-only, no comments, no paywalls. Unsubscribe is one tap and I won’t take it personally.</p>


</section>

 ]]></description>
  <category>meta</category>
  <category>telegram</category>
  <category>ai</category>
  <category>security</category>
  <category>tools</category>
  <guid>https://neanderthal.github.io/posts/mind-cramp/</guid>
  <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/mind-cramp/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>unix-pass-mcp: Letting AI Agents Touch Your Password Store — Carefully</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/unix-pass-mcp/</link>
  <description><![CDATA[ 






<p>If you’ve ever used the Unix <a href="https://www.passwordstore.org/"><code>pass</code></a> password manager, you know it’s beautifully Unix-y: just <code>gpg(1)</code>, the filesystem, and a thin bash wrapper. That same simplicity makes it tempting to expose to an AI agent — but the moment you do, you’re handing a non-deterministic LLM a shell-shaped foot-gun.</p>
<p>That’s why I built <strong><a href="https://github.com/Neanderthal/unix-pass-mcp">unix-pass-mcp</a></strong> — a typed, hardened Model Context Protocol server over the safe subset of <code>pass</code>. Read-only by default. Writes, key administration, and network ops behind explicit env opt-ins. No shell, no clipboard, no secrets in logs.</p>
<section id="the-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-problem">The Problem</h2>
<p>You want your AI assistant to:</p>
<ul>
<li>Pull a credential into a browser MCP for a signup flow</li>
<li>Generate a password for a new account and store it back</li>
<li>Compute a TOTP code on demand</li>
<li>Audit which entries exist without decrypting them</li>
</ul>
<p>What you <em>don’t</em> want is the model deciding to overwrite half your store because a poisoned tool result told it to.</p>
</section>
<section id="how-its-gated" class="level2">
<h2 class="anchored" data-anchor-id="how-its-gated">How It’s Gated</h2>
<p>The server exposes 24 tools across four capability tiers, each with its own env flag:</p>
<ol type="1">
<li><strong>Read-only — always available.</strong> <code>store_info</code>, <code>list</code>, <code>find</code>, <code>show</code>, <code>show_field</code>, <code>show_metadata</code>, <code>grep</code>, <code>otp</code>, <code>otp_uri</code>, <code>unlock_agent</code>. The expensive ones (<code>grep</code> decrypts the whole store) require explicit confirmation.</li>
<li><strong>Writes — <code>PASS_MCP_ALLOW_WRITES=1</code>.</strong> <code>insert</code>, <code>set_field</code>, <code>generate</code>, <code>mv</code>, <code>cp</code>, and friends. Passwords go in via stdin only — never argv — so they don’t leak into process listings.</li>
<li><strong>Git — <code>git_status</code> and <code>git_log</code> always; <code>git_pull</code> / <code>git_push</code> need <code>PASS_MCP_ALLOW_NETWORK=1</code>.</strong> Pulls are <code>--ff-only</code>. Pushes are never <code>--force</code>.</li>
<li><strong>Destructive — <code>PASS_MCP_ALLOW_DESTRUCTIVE=1</code>.</strong> <code>init</code> and <code>reencrypt</code>, which re-encrypt the store to a new recipient set. There is no undo.</li>
</ol>
<p>Scope it further with <code>PASS_MCP_ALLOWED_PATHS="work/*,personal/notes/*"</code> and the agent literally cannot reach your banking entries — the allowlist is honoured by <code>grep</code> and <code>unlock_agent</code> too, so there are no scope escapes.</p>
</section>
<section id="security-posture" class="level2">
<h2 class="anchored" data-anchor-id="security-posture">Security Posture</h2>
<ul>
<li><strong>No shell.</strong> Every <code>pass</code> invocation goes through one chokepoint using <code>subprocess.run</code> with <code>shell=False</code> and an arg list. Audited by a <code>ruff S404</code> allowlist on a single file.</li>
<li><strong>Strict input validation.</strong> Pass-names match a tight regex; no <code>..</code>, no leading <code>-</code>, no control chars.</li>
<li><strong>Stderr sanitization.</strong> Anything between <code>-----BEGIN</code>/<code>-----END</code> markers is stripped before being raised back to the caller.</li>
<li><strong>Sensitive output flagged.</strong> <code>show</code>, <code>generate</code>, <code>otp</code>, and <code>grep</code> carry <code>meta.sensitive = true</code> so MCP hosts can refuse to log or cache them.</li>
<li><strong>Append-only audit log.</strong> Records pass-names only — never values, never grep patterns.</li>
<li><strong>Strict startup.</strong> Refuses to start if <code>PASSWORD_STORE_UMASK</code> is weaker than <code>077</code> or the store directory is world-readable.</li>
</ul>
<p>The one real wart is <strong>pinentry</strong> — the MCP server can’t pop a passphrase prompt itself, since <code>gpg</code> would hang on stdin. So you either configure a GUI pinentry in <code>~/.gnupg/gpg-agent.conf</code>, pre-warm the agent in a real terminal, or call the <code>unlock_agent</code> tool to bounce the prompt through <code>zenity</code>/<code>kdialog</code>. Either way, the LLM never sees the passphrase.</p>
</section>
<section id="honest-disclaimer" class="level2">
<h2 class="anchored" data-anchor-id="honest-disclaimer">Honest Disclaimer</h2>
<p>The whole point of this server is to grant an LLM access to your secrets. The capability gates, path allowlist, and audit log reduce the <em>blast radius</em> of mistakes and prompt injection — they don’t make it safe to point an unsupervised agent at a production credential store. With writes and network on, an agent that’s been prompt-injected by a hostile webpage in a sibling browser MCP can absolutely overwrite or exfiltrate credentials within scope.</p>
<p>I run mine read-only against a scoped subfolder, and only flip writes on when I’m actively running an agent through a signup flow.</p>
</section>
<section id="give-it-a-try" class="level2">
<h2 class="anchored" data-anchor-id="give-it-a-try">Give It a Try</h2>
<p>346 unit tests, 45 real-GPG integration tests, Python 3.11 / 3.12 / 3.13 in CI, MIT-licensed: <strong><a href="https://github.com/Neanderthal/unix-pass-mcp">GitHub – Neanderthal/unix-pass-mcp</a></strong></p>
<p>If you’re already running an agent loop that combines a browser MCP with shell access, this is the bridge that lets you stop pasting passwords into chat.</p>


</section>

 ]]></description>
  <category>security</category>
  <category>mcp</category>
  <category>python</category>
  <category>gpg</category>
  <category>tools</category>
  <guid>https://neanderthal.github.io/posts/unix-pass-mcp/</guid>
  <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/unix-pass-mcp/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>I Spent a Weekend Reverse-Engineering My Mobile Carrier’s Whitelist</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/whitelist-probe/</link>
  <description><![CDATA[ 






<p>My phone’s hotspot stopped working properly. Not outright dead, but weirdly selective. Google loaded fine. My VPN didn’t. Some news sites worked; others timed out. I rebooted, tried different DNS, toggled airplane mode — the usual ritual. Nothing helped.</p>
<p>Then it hit me: this wasn’t random. My carrier had enabled a whitelist. They just hadn’t bothered to tell anyone what was on it.</p>
<p>I spent the next weekend figuring out how to reconstruct that list from the outside. Here’s what I learned.</p>
<section id="the-obvious-approach-is-wrong" class="level2">
<h2 class="anchored" data-anchor-id="the-obvious-approach-is-wrong">The Obvious Approach Is Wrong</h2>
<p>The first thing that comes to mind is: just scan the internet. Connect to everything, see what responds.</p>
<p>There are problems with this. One is scale — 2^32 IPv4 addresses. Even at 1000 connections per second (which would get you banned instantly), you’d need months. Another is that uniform scanning tells you nothing about structure. You get a flat set of reachable IPs, but no insight into <em>why</em> those and not others.</p>
<p>The bigger problem, though, is that whitelists aren’t always IP-based. If the filter looks at SNI (the hostname in the TLS handshake), then the same IP might work for one domain and fail for another. Scanning IPs would give you garbage. You’d be reconstructing a fantasy of what the filter looks like instead of what it actually is.</p>
<p>So the first thing you need to do is figure out what kind of filter you’re dealing with.</p>
</section>
<section id="phase-zero-diagnostics" class="level2">
<h2 class="anchored" data-anchor-id="phase-zero-diagnostics">Phase Zero: Diagnostics</h2>
<p>I ended up building a tool that does this in two phases. Phase zero is just diagnostics — ~5 minutes, ~5 MB of traffic, and you get a picture of the filter’s shape.</p>
<p>The idea is simple: run a few controlled experiments that isolate different filtering behaviors from each other.</p>
<ul>
<li>Connect to IPs that should always be reachable (1.1.1.1, 8.8.8.8, etc.). If these fail, something is fundamentally broken.</li>
<li>Resolve domains that you suspect are blocked via a public DNS, then probe those IPs. This isolates DNS from TCP.</li>
<li>Send TLS handshakes to the same IP with different SNI values. If some succeed and others fail, you’re looking at SNI filtering.</li>
<li>Resolve the same set of domains against your carrier’s DNS and several public resolvers. Look for discrepancies.</li>
<li>Run traceroutes to allowed and blocked targets. Count hops to find where the drop happens.</li>
</ul>
<p>After running through this checklist, you know: is this an IP filter? SNI? DNS manipulation? A mix? Where exactly in the network does the filtering happen? How confident are you in each of these conclusions?</p>
<p>This seems like overhead. Why not just start scanning? Because the scanning strategy depends entirely on the answer. If it’s SNI-based, IP reconstruction is a waste of time. If it’s DNS poisoning, TCP probes won’t reveal anything. Five minutes of diagnostics saves you hours of scanning the wrong thing.</p>
</section>
<section id="phase-one-reconstruction" class="level2">
<h2 class="anchored" data-anchor-id="phase-one-reconstruction">Phase One: Reconstruction</h2>
<p>Once you know what you’re dealing with, you can actually reconstruct the whitelist.</p>
<section id="ip-based-filters" class="level3">
<h3 class="anchored" data-anchor-id="ip-based-filters">IP-Based Filters</h3>
<p>If the filter drops packets based on IP, you need to map reachable prefixes. But here’s the thing: whitelists almost always align to organizational boundaries. Cloud providers, CDNs, residential blocks. They’re not random.</p>
<p>So instead of scanning uniformly, you start with a prior distribution based on ASN categories. AWS prefixes are more likely to be whitelisted (people need cloud services). Residential ISP prefixes are less likely. Unknown blocks get a default prior.</p>
<p>You probe each prefix, update your belief using Bayesian inference (a Beta posterior), and make a decision: accept, reject, or keep investigating. If you’re uncertain, you bisect the prefix and recurse. This lets you find boundaries efficiently — instead of scanning every address, you converge on the actual whitelist edges in <code>O(log size)</code> time.</p>
<p>At the end, you coalesce accepted prefixes into a minimal CIDR set. You get a list of blocks with confidence scores attached.</p>
</section>
<section id="sni-filters" class="level3">
<h3 class="anchored" data-anchor-id="sni-filters">SNI Filters</h3>
<p>If the filter looks at hostnames, the strategy changes. You don’t care about IPs anymore. You care about which names are allowed.</p>
<p>Take a list of popular domains (I used Cisco Umbrella’s top 100k). Rotate each through a set of anchor IPs that you know are reachable. Send a TLS handshake with each SNI. If any anchor accepts it, the domain is on the whitelist.</p>
<p>This is slower — full TLS handshakes cost about 4 KB each, and scanning 50k domains can burn a gigabyte of data. But you get a list of names, not addresses, which is what matters for SNI-based filters.</p>
</section>
<section id="dns-manipulation" class="level3">
<h3 class="anchored" data-anchor-id="dns-manipulation">DNS Manipulation</h3>
<p>Some carriers don’t block traffic at all. They just lie about DNS resolution. You ask for a domain, and they return a sinkhole IP or NXDOMAIN instead of the real address.</p>
<p>To detect this, resolve the same domains against your carrier’s DNS and several public resolvers. Classify each domain: consistent answers? NXDOMAIN hijacking? Poisoned responses?</p>
<p>This is cheap — DNS queries are tiny compared to TLS handshakes — and it reveals a different kind of filter.</p>
</section>
</section>
<section id="the-details-matter" class="level2">
<h2 class="anchored" data-anchor-id="the-details-matter">The Details Matter</h2>
<p>A few things I learned the hard way:</p>
<section id="mobile-networks-have-strange-limits" class="level3">
<h3 class="anchored" data-anchor-id="mobile-networks-have-strange-limits">Mobile Networks Have Strange Limits</h3>
<p>The bottleneck isn’t bandwidth. It’s the phone’s NAT conntrack table. Android hotspots typically have 4k-16k entries. If you open too many connections too quickly, you saturate the table, and everything — including your own SSH sessions — dies for 30 seconds until it recovers.</p>
<p>Keep the rate conservative. 30 queries per second, 50 concurrent connections max. Short timeouts (3 seconds for TCP). This keeps you well below the saturation point.</p>
</section>
<section id="carriers-notice-scans" class="level3">
<h3 class="anchored" data-anchor-id="carriers-notice-scans">Carriers Notice Scans</h3>
<p>Mobile operators flag SIMs for aggressive scanning much faster than fixed-line ISPs. Randomize your destination order. Add jitter between probes. Use ephemeral source ports. Don’t do contiguous /24 sweeps — that’s too regular.</p>
<p>If you suddenly see a spike in “filtered” responses against previously reachable IPs mid-run, stop. The carrier is probably throttling you. Wait 24 hours before trying again.</p>
</section>
<section id="track-your-budget" class="level3">
<h3 class="anchored" data-anchor-id="track-your-budget">Track Your Budget</h3>
<p>Mobile data is billed. Every probe costs something. The tool I wrote tracks bytes transferred and halts at a configurable limit (default: 1 GB). This bounds cost in the unit that actually matters.</p>
</section>
</section>
<section id="what-you-actually-get" class="level2">
<h2 class="anchored" data-anchor-id="what-you-actually-get">What You Actually Get</h2>
<p>After a few hours (or a full day for a thorough scan), you have a report with four sections:</p>
<ul>
<li><strong>Filter model</strong>: What kind of filter, where it sits in the network, how confident you are.</li>
<li><strong>Reconstructed whitelist</strong>: CIDRs, SNIs, or a DNS map (shape depends on filter type).</li>
<li><strong>Provider breakdown</strong>: Which cloud footprints are covered. Useful context.</li>
<li><strong>Concrete examples</strong>: 10 domains that work, 10 that don’t. Grounds the abstract analysis.</li>
</ul>
<p>This isn’t perfect. Anycast services complicate IP reconstruction (you’re probing the local edge, not the whole advertised range). Some filters combine multiple techniques. But you get a picture that’s good enough to make decisions.</p>
</section>
<section id="why-do-this" class="level2">
<h2 class="anchored" data-anchor-id="why-do-this">Why Do This?</h2>
<p>There are practical reasons. Maybe you’re deciding whether to pay for a VPN. Maybe you’re planning a project that needs specific services. Maybe you just want to document what your carrier is actually doing.</p>
<p>But there’s also something satisfying about reverse-engineering a system that’s designed to be opaque. You take a black box, poke it systematically, and gradually form a model of what’s inside. It’s the same impulse that drives any kind of debugging or security research — wanting to understand the hidden mechanisms.</p>
</section>
<section id="the-bigger-picture" class="level2">
<h2 class="anchored" data-anchor-id="the-bigger-picture">The Bigger Picture</h2>
<p>Whitelists are becoming more common. Mobile carriers in some markets enforce them by default. Corporate networks use them. Schools. Public Wi-Fi. Sometimes they’re documented, often they’re not.</p>
<p>The tools to audit them are also getting better. Not just for evasion, but for transparency. If a network operator can decide what you’re allowed to access, it matters that you can audit that decision. Even if you don’t have direct access to their configuration.</p>
<p>I’m not saying this is a perfect solution. It has limitations. It’s not a substitute for proper documentation or transparency policies. But when nobody will tell you what’s blocked, this is how you find out.</p>
<hr>
</section>
<section id="installation" class="level2">
<h2 class="anchored" data-anchor-id="installation">Installation</h2>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone git@github.com:Neanderthal/whitelist-probe.git</span>
<span id="cb1-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> whitelist-probe</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> sync</span></code></pre></div>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wlp</span> auto</span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span> reports/latest/whitelist-report.md</span></code></pre></div>
<p>Or a cheap first pass:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wlp</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--quick</span> auto</span></code></pre></div>
</section>
<section id="repository" class="level2">
<h2 class="anchored" data-anchor-id="repository">Repository</h2>
<p><a href="https://github.com/Neanderthal/whitelist-probe">Neanderthal/whitelist-probe</a></p>
<hr>
<p><em>This is what happens when your internet breaks and nobody will tell you why. You figure it out yourself.</em></p>


</section>

 ]]></description>
  <category>networking</category>
  <category>security</category>
  <category>reverse-engineering</category>
  <category>isp</category>
  <category>essay</category>
  <guid>https://neanderthal.github.io/posts/whitelist-probe/</guid>
  <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/whitelist-probe/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>QR Geoposition Tracker: Measuring Offline Engagement with GPS Precision</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/users-geoposition/</link>
  <description><![CDATA[ 






<p>Print media often feels like a "black box" when it comes to analytics. You know how many flyers you printed, but you don’t know exactly <em>where</em> people are scanning them. To bridge this gap, I built <strong><a href="https://github.com/Neanderthal/users_geoposition">users_geoposition</a></strong> — a simple, self-hosted tracker that captures reader coordinates before they reach your site.</p>
<section id="the-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-problem">The Problem</h2>
<p>Traditional UTM links in QR codes tell you <em>which</em> campaign was scanned, but not <em>where</em>. While some commercial services offer geolocation tracking, they often involve monthly fees, data exfiltration, or clunky UIs. I wanted something I could host on a $5 VPS that saves data directly to a format my clients already understand: Excel.</p>
</section>
<section id="how-it-works" class="level2">
<h2 class="anchored" data-anchor-id="how-it-works">How it Works</h2>
<p>The flow is designed to be as frictionless as possible:</p>
<ol type="1">
<li><strong>QR Scan:</strong> The reader scans a custom QR code from a newspaper or flyer.</li>
<li><strong>The "Lobby" Page:</strong> They hit a lightweight FastAPI endpoint. A simple "Redirecting…" page with a spinner appears.</li>
<li><strong>Permission:</strong> The browser asks the reader: <em>"geo.yourdomain.com wants to use your current location."</em></li>
<li><strong>Capture:</strong>
<ul>
<li>If <strong>Allowed</strong>: GPS coordinates are sent via POST to the backend and appended to an <code>.xlsx</code> file.</li>
<li>If <strong>Denied</strong> (or timeout): The script proceeds regardless.</li>
</ul></li>
<li><strong>Redirect:</strong> The reader is instantly redirected to the final promotion or website.</li>
</ol>
</section>
<section id="under-the-hood" class="level2">
<h2 class="anchored" data-anchor-id="under-the-hood">Under the Hood</h2>
<p>The project is built for reliability and ease of deployment:</p>
<ul>
<li><strong>FastAPI Backend:</strong> Handles incoming telemetry and serves the JS-driven redirection page.</li>
<li><strong>Excel Storage:</strong> Uses <code>openpyxl</code> with thread-safe locks to write data. No database (PostgreSQL/Redis) is required, keeping the footprint tiny.</li>
<li><strong>Ansible Deployment:</strong> I’ve included a playbook that handles the entire server setup — from installing Docker and Nginx to configuring Let’s Encrypt for HTTPS (crucial, as browsers block geolocation over non-secure connections).</li>
<li><strong>QR Generator:</strong> A built-in Python script that takes a <code>campaigns.yaml</code> file and generates high-resolution, print-ready QR codes for each campaign.</li>
</ul>
</section>
<section id="why-excel" class="level2">
<h2 class="anchored" data-anchor-id="why-excel">Why Excel?</h2>
<p>While a database would be "cleaner" for developers, this tool is meant to be used. Marketing teams and clients don’t want to query a SQL database; they want to open a file. By saving each campaign to its own <code>.xlsx</code> file, I can just provide an API endpoint to download the raw data directly.</p>
</section>
<section id="deployment-in-one-command" class="level2">
<h2 class="anchored" data-anchor-id="deployment-in-one-command">Deployment in One Command</h2>
<p>If you have a Linux server and a domain, you can get this running in minutes:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Set your variables in vars.yml</span></span>
<span id="cb1-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> ansible</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ansible-playbook</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-i</span> inventory.ini deploy.yml</span></code></pre></div>
</section>
<section id="give-it-a-try" class="level2">
<h2 class="anchored" data-anchor-id="give-it-a-try">Give it a try</h2>
<p>The source code is open and available on GitHub:<br>
<strong><a href="https://github.com/Neanderthal/users_geoposition">GitHub - Neanderthal/users_geoposition</a></strong></p>
<p>If you’re running print ads and want to see a heatmap of where your audience actually is, this tool is for you.</p>


</section>

 ]]></description>
  <category>python</category>
  <category>geospatial</category>
  <category>marketing</category>
  <category>devops</category>
  <category>tools</category>
  <guid>https://neanderthal.github.io/posts/users-geoposition/</guid>
  <pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/users-geoposition/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Telegram-Search: Bringing Your Chat History to AI Agents</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/telegram-search/</link>
  <description><![CDATA[ 






<p>We spend a huge part of our professional lives in Telegram. It’s where decisions are made, snippets are shared, and logs are dumped. However, most AI agents are blind to this context. I built <strong><a href="https://github.com/Neanderthal/telegram-search">telegram-search</a></strong> — a read-only Model Context Protocol (MCP) server — to give my AI tools a way to “remember” what we discussed in chat.</p>
<section id="the-goal" class="level2">
<h2 class="anchored" data-anchor-id="the-goal">The Goal</h2>
<p>The objective was simple: allow an LLM (like Claude) to answer questions like <em>“What was that database migration snippet Sergey sent last Tuesday?”</em> or <em>“Summarize the recent discussion in the project-alpha group.”</em></p>
</section>
<section id="how-it-works" class="level2">
<h2 class="anchored" data-anchor-id="how-it-works">How it Works</h2>
<p>The project implements the <a href="https://modelcontextprotocol.io/">Model Context Protocol</a>, acting as a bridge between the Telegram API and an AI client.</p>
<ol type="1">
<li><strong>Authentication:</strong> It uses a one-time session creation script (<code>create_session.py</code>) to generate a secure Telethon session.</li>
<li><strong>MCP Tools:</strong> It exposes several tools to the AI:
<ul>
<li><code>search_messages</code>: Keyword search within a specific chat.</li>
<li><code>search_global</code>: Search across all your dialogs.</li>
<li><code>get_message_context</code>: Retrieve the “surrounding” messages of a specific post to give the AI context.</li>
<li><code>get_chat_history</code>: Fetch recent messages with pagination.</li>
</ul></li>
<li><strong>Read-Only Safety:</strong> By design, this server does not include sending capabilities. It’s meant for context retrieval, ensuring your agent won’t accidentally post on your behalf while “thinking.”</li>
</ol>
</section>
<section id="key-features" class="level2">
<h2 class="anchored" data-anchor-id="key-features">Key Features</h2>
<ul>
<li><strong>Deep Context:</strong> Unlike basic keyword search, the <code>get_message_context</code> tool allows the AI to see what happened before and after a hit, making summaries much more accurate.</li>
<li><strong>Media Filtering:</strong> Support for global searches filtered by type (photos, documents, links, etc.).</li>
<li><strong>Lazy Loading:</strong> The Telegram client connects only when a tool is first invoked, making the MCP initialization instant.</li>
</ul>
</section>
<section id="why-mcp" class="level2">
<h2 class="anchored" data-anchor-id="why-mcp">Why MCP?</h2>
<p>The Model Context Protocol is becoming the standard for connecting local data to LLMs. By packaging this as an MCP server, it works out-of-the-box with <strong>Claude Desktop</strong>, <strong>Claude Code</strong>, and any other compatible IDE or CLI tool.</p>
</section>
<section id="under-the-hood" class="level2">
<h2 class="anchored" data-anchor-id="under-the-hood">Under the Hood</h2>
<p>The stack is Python 3.12, using <code>FastMCP</code> for the server logic and <code>Telethon</code> for the Telegram MTProto interaction. It’s lightweight enough to run in the background of your workstation.</p>
</section>
<section id="give-it-a-try" class="level2">
<h2 class="anchored" data-anchor-id="give-it-a-try">Give it a try</h2>
<p>The source code and setup guide are available on GitHub:<br>
<strong><a href="https://github.com/Neanderthal/telegram-search">GitHub - Neanderthal/telegram-search</a></strong></p>
<p>If you want your AI assistant to actually know what’s going on in your primary communication channel, this is the bridge you need.</p>


</section>

 ]]></description>
  <category>python</category>
  <category>mcp</category>
  <category>telegram</category>
  <category>ai</category>
  <category>tools</category>
  <guid>https://neanderthal.github.io/posts/telegram-search/</guid>
  <pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/telegram-search/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Security Audit: A Local Security Scanner for Developer Workstations</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/security-audit/</link>
  <description><![CDATA[ 






<p>When managing diverse development environments and working across Python, Node.js, and Rust, ensuring that the local workstation isn’t leaking data or relying on critically vulnerable packages is essential. That’s why I created <strong><a href="https://github.com/Neanderthal/security-audit">security-audit</a></strong> — a comprehensive bash and Python-based security scanner tailored for developer workstations.</p>
<section id="goals" class="level2">
<h2 class="anchored" data-anchor-id="goals">Goals</h2>
<p>The main objectives behind <code>security-audit</code> are: - <strong>Comprehensive Coverage:</strong> Go beyond basic OS-level package audits by checking Python (pip), Rust (cargo), and Node (npm) environments. - <strong>Local Isolation:</strong> Run locally without relying on heavy cloud scanners that exfiltrate data. - <strong>Easy Integration:</strong> Use standard Linux tools (bash, Python) so it can be dropped into any Unix-like dev machine (like Ubuntu or Manjaro) without huge dependency chains. - <strong>Actionable Output:</strong> Clearly distinguish between critical CVEs (CVSS &gt;= 7.0) and minor warnings, allowing the developer to quickly patch what matters.</p>
</section>
<section id="the-process" class="level2">
<h2 class="anchored" data-anchor-id="the-process">The Process</h2>
<p>The tool executes a modular checklist, hitting several critical security vectors:</p>
<ol type="1">
<li><strong>OS Vulnerabilities:</strong> Uses local package managers and external sources (like OSV.dev) to check for outdated and vulnerable system packages.</li>
<li><strong>Environment Scanning:</strong> Deep dives into your language-specific ecosystems. It checks <code>pip</code> packages via <code>pip-audit</code>, Rust crates via <code>cargo-audit</code>, and Node.js packages via <code>npm audit</code>.</li>
<li><strong>Permissions &amp; Secrets:</strong> Scans for world-writable sensitive files, unprotected private keys (<code>~/.ssh/</code>), and accidentally committed secrets.</li>
<li><strong>Firewall &amp; Network:</strong> Validates that <code>ufw</code> or <code>iptables</code> rules are active, checks for unnecessarily open ports, and flags insecure SSH daemon configurations.</li>
<li><strong>Report Generation:</strong> Aggregates findings into an actionable summary, highlighting Critical and High issues.</li>
</ol>
</section>
<section id="pros-cons" class="level2">
<h2 class="anchored" data-anchor-id="pros-cons">Pros &amp; Cons</h2>
<section id="pros" class="level3">
<h3 class="anchored" data-anchor-id="pros">Pros</h3>
<ul>
<li><strong>Fast &amp; Lightweight:</strong> Written primarily in Bash and Python.</li>
<li><strong>Unified Overview:</strong> Combines multiple different dependency scanners into one cohesive report.</li>
<li><strong>Privacy First:</strong> It queries upstream vulnerability databases (like OSV.dev) without uploading your source code or full environment dumps to a third party.</li>
<li><strong>Customizable:</strong> Because it’s an open-source script, you can easily add specific company compliance checks.</li>
</ul>
</section>
<section id="cons" class="level3">
<h3 class="anchored" data-anchor-id="cons">Cons</h3>
<ul>
<li><strong>Linux Focused:</strong> Currently optimized for Linux distributions. macOS and Windows WSL support might require manual tweaks.</li>
<li><strong>False Positives:</strong> Some development packages inherently flag minor vulnerabilities that are non-exploitable in local, non-production contexts.</li>
<li><strong>Dependency Overhead:</strong> Requires <code>pip-audit</code>, <code>cargo-audit</code>, and <code>npm</code> to be installed for full functionality.</li>
</ul>
</section>
</section>
<section id="comparison" class="level2">
<h2 class="anchored" data-anchor-id="comparison">Comparison</h2>
<p>How does <code>security-audit</code> stack up against other tools?</p>
<ul>
<li><strong>vs.&nbsp;Trivy:</strong> Trivy is fantastic, but it’s heavily optimized for container images and CI pipelines. <code>security-audit</code> is designed specifically for the <em>host workstation</em>, bridging the gap between OS config and local dev dependencies.</li>
<li><strong>vs.&nbsp;Lynis:</strong> Lynis is the gold standard for POSIX host auditing. However, it focuses heavily on system compliance (file permissions, kernel hardening). <code>security-audit</code> incorporates developer-specific checks (like scanning local virtual environments and cargo registries) which Lynis doesn’t do out of the box.</li>
<li><strong>vs.&nbsp;Snyk/Dependabot:</strong> These are great for source code repositories and CI/CD integrations. <code>security-audit</code> looks at what is <em>actually installed and running</em> on your local machine, not just what’s in a <code>requirements.txt</code> file.</li>
</ul>
</section>
<section id="give-it-a-try" class="level2">
<h2 class="anchored" data-anchor-id="give-it-a-try">Give it a try</h2>
<p>You can check out the source code, contribute, or grab the latest release on GitHub:<br>
<strong><a href="https://github.com/Neanderthal/security-audit">GitHub - Neanderthal/security-audit</a></strong></p>
<p>If you are a developer looking to lock down your local Linux environment against supply-chain attacks and basic misconfigurations, this tool might save you a significant headache.</p>


</section>

 ]]></description>
  <category>security</category>
  <category>linux</category>
  <category>python</category>
  <category>devops</category>
  <category>tools</category>
  <guid>https://neanderthal.github.io/posts/security-audit/</guid>
  <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/security-audit/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Software development as a mental illness</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/just_my_dry_thoughts/</link>
  <description><![CDATA[ 






<p>I develop software every day and everywhere—development is second nature to me. If I have a problem in any part of my life, I sit down and start developing something. It’s like I have a hammer, and now every problem looks like a software bug. Even if my head were cut off, I’d still be developing for at least two more hours.</p>
<p>I create at least a small script to automate every aspect of my life. I never copy and paste anything—I write code that copies and pastes it for me. That’s how I keep myself DRY enough.</p>



 ]]></description>
  <category>thoughts</category>
  <guid>https://neanderthal.github.io/posts/just_my_dry_thoughts/</guid>
  <pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/just_my_dry_thoughts/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>How to Launch Local LLM Models with TGI Docker Containers</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/serve-tgi-llm-localy/</link>
  <description><![CDATA[ 






<section id="how-to-launch-local-llm-models-with-tgi-docker-containers" class="level3">
<h3 class="anchored" data-anchor-id="how-to-launch-local-llm-models-with-tgi-docker-containers">How to Launch Local LLM Models with TGI Docker Containers</h3>
<p>Launching local language models (LLMs) for testing or development can seem complex, especially when navigating the different configurations and environments. However, using a TGI (Text Generation Inference) Docker container simplifies the process considerably. Here, we’ll walk through a straightforward method to get your local LLM model up and running quickly, using the latest Docker image without any modifications.</p>
<section id="step-by-step-guide-to-running-local-models" class="level4">
<h4 class="anchored" data-anchor-id="step-by-step-guide-to-running-local-models">Step-by-Step Guide to Running Local Models</h4>
<ol type="1">
<li><p><strong>Prepare the Environment</strong>: Ensure that you have a suitable environment for running Docker containers, with Docker installed and configured correctly on your system. Additionally, if you plan to use machine learning models, make sure your machine has the necessary GPU support and that Docker can access these resources.</p></li>
<li><p><strong>Clone the Model with Git LFS</strong>: Before you can run the model, you need to have it on your local machine. Use Git Large File Storage (LFS) to handle large files typically associated with models. Here’s how you can clone an example model, such as <code>flan-t5-xl</code> from Hugging Face:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> /data</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://huggingface.co/google/flan-t5-xl</span></code></pre></div>
<p>Ensure that you have <code>git lfs</code> installed before you clone the repository, as this will allow you to pull large model files correctly.</p></li>
<li><p><strong>Run the Docker Container</strong>: With the model downloaded, you can now run the Docker container. The following command demonstrates how to set up the Docker container to use the GPU, map the ports, and link the local model directory:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--rm</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--gpus</span> all <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> 8080:80 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-v</span> /data:/data <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--pull</span> always ghcr.io/huggingface/text-generation-inference:latest <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--model-id</span> /data/flan-t5-xl</span></code></pre></div>
<ul>
<li><code>--rm</code>: Cleans up the container after you stop it.</li>
<li><code>--gpus all</code>: Ensures that Docker can use all available GPUs.</li>
<li><code>-p 8080:80</code>: Maps port 80 in the container to port 8080 on your host, allowing you to access the service via <code>localhost:8080</code>.</li>
<li><code>-v /data:/data</code>: Mounts the local <code>/data</code> directory to <code>/data</code> in the container, providing access to the model data.</li>
<li><code>--pull always</code>: Ensures that Docker pulls the latest version of the image.</li>
<li><code>--model-id /data/flan-t5-xl</code>: Points to the model you downloaded, instructing the container to load this specific model.</li>
</ul></li>
</ol>
</section>
<section id="conclusion" class="level4">
<h4 class="anchored" data-anchor-id="conclusion">Conclusion</h4>
<p>Running local models doesn’t have to be a headache. By following the steps above, you can quickly get your LLM model running locally using the TGI Docker container. This approach is streamlined, efficient, and ensures that you are always running your model with the latest and most secure container settings. Whether you’re developing new applications or just experimenting with different models, this method provides a reliable and straightforward solution.</p>


</section>
</section>

 ]]></description>
  <category>docker</category>
  <category>tgi</category>
  <category>llm</category>
  <category>localy</category>
  <guid>https://neanderthal.github.io/posts/serve-tgi-llm-localy/</guid>
  <pubDate>Mon, 29 Apr 2024 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/serve-tgi-llm-localy/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Transferring Docker Images Between Servers</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/docker_copy_image/</link>
  <description><![CDATA[ 






<section id="transferring-docker-images-between-servers" class="level3">
<h3 class="anchored" data-anchor-id="transferring-docker-images-between-servers">Transferring Docker Images Between Servers</h3>
<p>Transferring Docker images between servers is straightforward using Docker’s save and load functionality, combined with secure file transfer. Here’s how to do it step-by-step.</p>
<section id="step-1-save-the-docker-image" class="level4">
<h4 class="anchored" data-anchor-id="step-1-save-the-docker-image">Step 1: Save the Docker Image</h4>
<p>First, save the Docker image to a tar file:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> save ghcr.io/huggingface/text-generation-inference:2.0 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-o</span> tgi.tar</span></code></pre></div>
<p>This command converts the Docker image into a tar archive, <code>tgi.tar</code>.</p>
</section>
<section id="step-2-transfer-the-image" class="level4">
<h4 class="anchored" data-anchor-id="step-2-transfer-the-image">Step 2: Transfer the Image</h4>
<p>Next, use <code>scp</code> to transfer the tar file to the target server:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scp</span> tgi.tar <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>@<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>server<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>:/data/projects/inference_test</span></code></pre></div>
<p>Ensure you replace <code>istomin</code> with your username and modify the server address and directory as necessary.</p>
</section>
<section id="step-3-load-the-image-on-the-destination-server" class="level4">
<h4 class="anchored" data-anchor-id="step-3-load-the-image-on-the-destination-server">Step 3: Load the Image on the Destination Server</h4>
<p>After transferring, log into the destination server and load the image from the tar file:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> load <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-i</span> tgi.tar</span></code></pre></div>
</section>
<section id="step-4-verify-the-image" class="level4">
<h4 class="anchored" data-anchor-id="step-4-verify-the-image">Step 4: Verify the Image</h4>
<p>Confirm the image is loaded correctly:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> images</span></code></pre></div>
<p>Check for the <code>text-generation-inference</code> image in the output list.</p>
</section>
<section id="conclusion" class="level4">
<h4 class="anchored" data-anchor-id="conclusion">Conclusion</h4>
<p>This method provides an efficient way to move Docker images across servers without needing a Docker registry, perfect for environments with restricted internet access.</p>
<hr>
<p>This condensed version focuses on the essential steps, ensuring clarity and brevity for quick reference.</p>


</section>
</section>

 ]]></description>
  <category>code</category>
  <category>docker</category>
  <category>ssh</category>
  <guid>https://neanderthal.github.io/posts/docker_copy_image/</guid>
  <pubDate>Sun, 28 Apr 2024 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/docker_copy_image/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>How to handle exceptions in python</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/handling_exceptions/</link>
  <description><![CDATA[ 






<p>based on: https://blog.miguelgrinberg.com/post/the-ultimate-guide-to-error-handling-in-python</p>
<section id="general-principles" class="level3">
<h3 class="anchored" data-anchor-id="general-principles"><strong>General Principles:</strong></h3>
<ol type="1">
<li><strong>Prefer EAFP over LBYL</strong>:
<ul>
<li>Use <code>try/except</code> (EAFP) instead of pre-checking conditions (LBYL) when errors are expected.<br>
</li>
<li>EAFP is more robust because it avoids race conditions and handles errors directly.</li>
</ul></li>
<li><strong>Avoid Catching All Exceptions</strong>:
<ul>
<li>Catching <code>Exception</code> or bare <code>except:</code> hides bugs. Only catch specific exceptions you can handle.<br>
</li>
<li>The <strong>only exception</strong> is at the <strong>top level</strong> (e.g., CLI/GUI/web apps), where you log the error and exit gracefully.</li>
</ul></li>
<li><strong>Let Errors Bubble Up When Appropriate</strong>:
<ul>
<li>If your function can’t recover from an error, <strong>don’t catch it</strong>. Let higher-level code handle it.<br>
</li>
<li>This keeps code clean and separates concerns (e.g., database errors should be handled by the framework, not individual routes).</li>
</ul></li>
</ol>
<hr>
</section>
<section id="error-handling-strategies" class="level3">
<h3 class="anchored" data-anchor-id="error-handling-strategies"><strong>Error Handling Strategies:</strong></h3>
<section id="new-recoverable-errors" class="level4">
<h4 class="anchored" data-anchor-id="new-recoverable-errors"><strong>1. New Recoverable Errors</strong></h4>
<ul>
<li>Fix the issue internally and continue.<br>
</li>
<li>Example: Defaulting a missing <code>song.year</code> to <code>'Unknown'</code> instead of raising an error.</li>
</ul>
</section>
<section id="bubbled-up-recoverable-errors" class="level4">
<h4 class="anchored" data-anchor-id="bubbled-up-recoverable-errors"><strong>2. Bubbled-Up Recoverable Errors</strong></h4>
<ul>
<li>Catch the exception, recover, and continue.<br>
</li>
<li>Example: Retry a failed database query or create a missing record.</li>
</ul>
</section>
<section id="new-non-recoverable-errors" class="level4">
<h4 class="anchored" data-anchor-id="new-non-recoverable-errors"><strong>3. New Non-Recoverable Errors</strong></h4>
<ul>
<li>Raise an exception to let the caller handle it.<br>
</li>
<li>Example: Reject a <code>song</code> with a missing <code>name</code> by raising <code>ValueError</code>.</li>
</ul>
</section>
<section id="bubbled-up-non-recoverable-errors" class="level4">
<h4 class="anchored" data-anchor-id="bubbled-up-non-recoverable-errors"><strong>4. Bubbled-Up Non-Recoverable Errors</strong></h4>
<ul>
<li><strong>Do nothing</strong>. Let the exception bubble up to a higher layer that can handle it.<br>
</li>
<li>Example: A database error in a Flask route should propagate to Flask’s built-in error handler.</li>
</ul>
<hr>
</section>
</section>
<section id="best-practices" class="level3">
<h3 class="anchored" data-anchor-id="best-practices"><strong>Best Practices:</strong></h3>
<ol start="4" type="1">
<li><strong>Use Specific Exception Classes</strong>:
<ul>
<li>Catch only the exceptions you expect (e.g., <code>OSError</code> for file ops, <code>SQLAlchemyError</code> for DB issues).</li>
</ul></li>
<li><strong>Logging Over Silencing</strong>:
<ul>
<li>If you catch an error, log it with <code>logger.exception()</code> to include the stack trace.</li>
</ul></li>
<li><strong>Separate Error Handling Logic</strong>:
<ul>
<li>Move error recovery to higher layers (e.g., framework-level handlers) to avoid repetitive code.</li>
</ul></li>
<li><strong>Development vs.&nbsp;Production</strong>:
<ul>
<li>In <strong>development</strong>, let crashes show stack traces for debugging.<br>
</li>
<li>In <strong>production</strong>, catch all exceptions at the top level and log them (e.g., return a 500 error in web apps).</li>
</ul></li>
<li><strong>Avoid Redundant Checks</strong>:
<ul>
<li>Don’t pre-check conditions (LBYL) if the operation itself raises clear exceptions (EAFP).</li>
</ul></li>
</ol>
<hr>
</section>
<section id="examples-of-anti-patterns-to-avoid" class="level3">
<h3 class="anchored" data-anchor-id="examples-of-anti-patterns-to-avoid"><strong>Examples of Anti-Patterns to Avoid:</strong></h3>
<ul>
<li>Catching <code>Exception</code> in mid-level functions.<br>
</li>
<li>Logging an error but not re-raising it when recovery isn’t possible.<br>
</li>
<li>Repeating error-handling logic across multiple functions (e.g., rolling back DB sessions in every route).</li>
</ul>
<p>By following these tips, you’ll write cleaner, more maintainable code that handles errors effectively while avoiding common pitfalls.</p>


</section>

 ]]></description>
  <category>code</category>
  <category>jupyter</category>
  <category>ssh</category>
  <guid>https://neanderthal.github.io/posts/handling_exceptions/</guid>
  <pubDate>Sun, 21 Apr 2024 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/handling_exceptions/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>How to run jupyter through ssh</title>
  <dc:creator>Sergey Istomin</dc:creator>
  <link>https://neanderthal.github.io/posts/post-with-code/</link>
  <description><![CDATA[ 






<p>To run your Jupyter notebook remotely without opening it in a browser, enter the following command:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jupyter</span> notebook <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--no-browser</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>8080</span></code></pre></div>
<p>Next, establish an SSH tunnel from your local machine using this command:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ssh</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-L</span> 8080:localhost:8080 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>REMOTE_USER<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>@<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>REMOTE_HOST<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span></code></pre></div>
<p>Replace <code>&lt;REMOTE_USER&gt;</code> with your remote username and <code>&lt;REMOTE_HOST&gt;</code> with the host name or IP address of your remote server.</p>
<p>After setting up the tunnel, access the Jupyter notebook by navigating to the following URL in your local browser:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">http://localhost:8080</span></span></code></pre></div>



 ]]></description>
  <category>code</category>
  <category>jupyter</category>
  <category>ssh</category>
  <guid>https://neanderthal.github.io/posts/post-with-code/</guid>
  <pubDate>Sun, 21 Apr 2024 00:00:00 GMT</pubDate>
  <media:content url="https://neanderthal.github.io/posts/post-with-code/image.webp" medium="image" type="image/webp"/>
</item>
</channel>
</rss>
