Category: Tools & Setup

Tools & Setup is where orthogonal.info curates practical, battle-tested guides on developer productivity tools, CLI utilities, self-hosted software, and environment configuration. Whether you are bootstrapping a new development machine, evaluating self-hosted alternatives to SaaS products, or fine-tuning your terminal workflow, this category delivers step-by-step walkthroughs grounded in real-world experience. Every article is written with one goal: help you build a faster, more reliable, and more enjoyable development environment.

With over 25 in-depth posts and growing, Tools & Setup is one of the most active categories on the site — reflecting just how much time engineers spend (and save) by getting their tooling right from day one.

Key Topics Covered

Command-line productivity — Shell customization (Zsh, Fish, Starship), terminal multiplexers (tmux, Zellij), and CLI utilities like ripgrep, fd, fzf, and bat that supercharge daily workflows.
Self-hosted alternatives — Deploying and configuring tools like Gitea, Nextcloud, Vaultwarden, and Uptime Kuma so you own your data without sacrificing usability.
IDE and editor setup — Configuration guides for VS Code, Neovim, and JetBrains IDEs, including extension recommendations, keybindings, and remote development workflows.
Development environment automation — Using Ansible, Homebrew, Nix, dotfiles repositories, and container-based dev environments (Dev Containers, Devbox) to make setups reproducible.
Git workflows and tooling — Advanced Git techniques, hooks, aliases, and GUI clients that streamline version control for solo developers and teams alike.
API testing and debugging — Hands-on guides for curl, HTTPie, Postman, and browser DevTools to debug REST and GraphQL APIs efficiently.
Package and runtime management — Managing multiple language runtimes with asdf, mise, nvm, and pyenv, plus dependency management best practices.

Who This Content Is For
This category is designed for software engineers, DevOps practitioners, system administrators, and hobbyist developers who want to work smarter, not harder. Whether you are a junior developer setting up your first Linux workstation or a senior engineer optimizing a multi-machine workflow, you will find actionable advice that respects your time. The guides assume basic command-line comfort but explain advanced concepts clearly.

What You Will Learn
By exploring the articles in Tools & Setup, you will learn how to automate repetitive environment tasks so a fresh machine is productive in minutes, not days. You will discover modern CLI replacements for legacy Unix tools, understand how to evaluate self-hosted software against its SaaS equivalent, and gain confidence configuring complex development stacks. Each guide includes copy-paste commands, configuration snippets, and links to upstream documentation so you can adapt the advice to your own infrastructure.

Start browsing below to find your next productivity upgrade.

  • Free Online JSON Formatter & Validator

    Free Online JSON Formatter & Validator

    Need to format JSON quickly? This free online JSON formatter and validator lets you pretty-print, minify, and validate JSON data instantly — no signup, no ads, no data collection. Your JSON never leaves your browser.

    TL;DR: Format, validate, and minify JSON data instantly with this free browser-based tool. No signup, no ads, no data collection — your JSON never leaves your browser.

    Quick Answer: Paste your raw JSON into the input area and click “Format JSON” for pretty-printed output, “Minify” to compress it, or “Validate Only” to check for syntax errors. All processing happens locally in your browser.

    How to Use This JSON Formatter

    1. Paste your raw JSON into the input area
    2. Click Format JSON for pretty-printed output with proper indentation
    3. Use Minify to compress JSON for production use
    4. Click Validate Only to check syntax without reformatting
    5. Copy Output sends the result to your clipboard







    Why Use an Online JSON Formatter?

    JSON (JavaScript Object Notation) is the universal data interchange format used by REST APIs, configuration files, and modern web applications. Raw JSON from APIs often comes minified — a single long line that’s impossible to read. A JSON formatter adds proper indentation and line breaks so you can understand the data structure at a glance.

    Common JSON Formatting Scenarios

    • API debugging — format API responses to inspect data structure and values
    • Configuration editing — pretty-print config files (package.json, tsconfig.json) before making changes
    • Data validation — catch syntax errors like missing commas, unclosed brackets, or trailing commas
    • Size optimization — minify JSON to reduce payload size for production deployments

    JSON Syntax Quick Reference

    • Objects use curly braces: {"key": "value"}
    • Arrays use square brackets: [1, 2, 3]
    • Strings must use double quotes (not single quotes)
    • Valid types: string, number, boolean (true/false), null, object, array
    • No trailing commas allowed (unlike JavaScript)
    • No comments allowed in standard JSON

    Privacy & Security

    This tool runs 100% in your browser. Your JSON data is never sent to any server. There’s no tracking, no cookies, and no data collection. Perfect for formatting sensitive API keys, configuration files, or proprietary data.

    Frequently Asked Questions

    Why is my JSON showing as invalid?

    The most common JSON syntax errors are: trailing commas after the last item in an object or array, using single quotes instead of double quotes for strings, including comments (JSON does not support comments), and missing or extra brackets. This tool’s error message shows the exact position where parsing failed.

    What is the difference between formatting and validating JSON?

    Formatting (pretty-printing) parses the JSON and re-outputs it with proper indentation and line breaks for readability. Validating only checks whether the JSON is syntactically correct without modifying it. Both operations will catch syntax errors, but formatting also produces reformatted output.

    When should I minify JSON?

    Minify JSON when you need to reduce payload size for production API responses, configuration storage, or network transmission. Minification removes all unnecessary whitespace and line breaks. A typical formatted JSON file can be reduced by 30–60% through minification.

    Is JSON the same as a JavaScript object?

    No. JSON is a strict subset of JavaScript object literal syntax. Key differences: JSON requires double-quoted property names, does not allow trailing commas, does not support comments, undefined values, or functions. Valid JSON is always valid JavaScript, but valid JavaScript objects are not always valid JSON.

    Recommended Reading

    If you work with JSON regularly, these books will deepen your understanding:

    More Free Developer Tools


    Like these free tools? We build more every week. Follow our AI Tools Telegram channel for weekly picks of the best developer tools, or check out our Market Intelligence channel for AI-powered trading insights.

    References

  • I Built a Base64 Tool That Fixes Frustrating Alternatives

    I Built a Base64 Tool That Fixes Frustrating Alternatives

    Debugging a Base64-encoded error message buried inside a JSON response shouldn’t require pasting sensitive production data into a random website. Most online Base64 tools are bloated with ads, send your data to a server, or choke on multiline input—so I built one that runs entirely in the browser.

    TL;DR: Base64Lab is a free, privacy-first Base64 encoder/decoder that runs entirely in your browser. It handles text, files, images, and data URIs in a single 25KB page with zero external dependencies, auto-detection of encode/decode intent, and full offline support via PWA.

    Quick Answer: Visit base64lab.orthogonal.info to encode or decode Base64 strings privately in your browser. It auto-detects whether to encode or decode, supports URL-safe Base64, MIME line wrapping, file drag-and-drop up to 50MB, and image preview — all with zero server communication.

    The first site loaded 47 tracking scripts before I could even see a text box. The second one sent my data to their server to decode it — a string that contained an internal error message with a database connection string. The third split encoding and decoding across two separate pages, so I had to keep switching tabs while iterating.

    I closed all of them and built my own.

    What Base64Lab Actually Does

    Base64Lab is a single-page tool that encodes and decodes Base64 strings. Everything runs in your browser. No data ever touches a server. It handles text, files, images, and data URIs — all from one interface.

    The key feature that none of the popular alternatives get right: auto-detection. Paste something into the input field, click “Auto,” and Base64Lab figures out whether you’re trying to encode or decode. It checks the character set, validates the padding, and picks the right operation. No more guessing which mode you need.

    The Privacy Problem No One Talks About

    I checked how the top 5 Base64 tools handle your data. Three of them POST your input to their servers for processing. One includes Google Analytics, Facebook Pixel, and a remarketing tag — meaning your encoded data gets logged in at least three different analytics pipelines.

    Think about what people Base64-encode: API keys, auth tokens, certificate data, error messages containing internal paths. Sending that through a third-party server defeats the entire purpose of a quick decode.

    Base64Lab processes everything using the browser’s built-in btoa() and atob() functions. The entire tool is a single HTML file — 25KB total — with zero external dependencies. No CDN calls, no analytics pixels, no server-side processing. Open your browser’s network tab while using it. You’ll see exactly zero outbound requests.

    How the Auto-Detection Works

    The auto-detect mode uses a simple heuristic. First, it checks if the input is a data URI (starts with data: followed by a MIME type and ;base64,). If so, it decodes the payload.

    Next, it validates the character set. Standard Base64 uses A-Z, a-z, 0-9, +, and /, with = padding. URL-safe Base64 replaces + with - and / with _. If the input contains only these characters and is properly padded (length divisible by 4, or close to it), it’s probably Base64.

    The final check: length. Very short strings (under 4 characters) are treated as plain text, since they could be either. Everything else gets decoded. If decoding fails or produces garbage, the tool falls back to encoding.

    Is this heuristic perfect? No. An English word like “test” is technically valid Base64. But in practice, the auto-detection is right about 95% of the time, and you can always switch to manual Encode/Decode mode.

    Image Preview: The Feature I Didn’t Plan

    While building the decoder, I realized that a huge chunk of Base64 data people work with is images. Data URIs in CSS, inline images in emails, thumbnails in API responses. So I added image detection.

    When you decode a Base64 string, Base64Lab checks the first few bytes of the decoded output for magic numbers: 0x89 0x50 for PNG, 0xFF 0xD8 for JPEG, 0x47 0x49 0x46 for GIF, and 0x52 0x49 0x46 0x46 (RIFF header) for WebP. If it finds an image, it renders a preview right below the output.

    This turned out to be genuinely useful. I’ve been using it to debug image loading issues where the server returns a Base64-encoded placeholder instead of the actual image. One glance tells me if the encoded data is the real image or a broken fallback.

    File Handling Without the Upload

    Most Base64 tools that support file encoding require you to “upload” the file to their server, which then encodes it and sends back the result. Base64Lab reads files using the browser’s FileReader API with readAsArrayBuffer, then encodes the raw bytes client-side.

    Drag a file onto the drop zone, or click to select one. The limit is 50MB, which is generous for a browser-based tool. Large files (10MB+) encode in under a second on modern hardware. The output includes timing stats so you can see exactly how fast it ran.

    URL-Safe Base64 and MIME Line Wrapping

    Two toggle switches handle edge cases that matter in real-world usage:

    URL-safe mode replaces + with - and / with _, and strips padding. This is the format used in JWTs, URL parameters, and some API payloads. Most tools ignore this variant entirely, leaving you to do the character replacement manually.

    Line wrapping splits output into 76-character lines, which is the MIME standard format used in email attachments and PEM certificates. If you’re constructing an email body or debugging certificate files, this saves a step.

    The Technical Stack

    There is no stack. It’s one HTML file containing inline CSS and JavaScript. No build system. No framework. No bundler. The CSS uses custom properties for theming (dark and light mode via prefers-color-scheme), a consistent spacing grid, and minimal animations. The JavaScript is vanilla ES6 wrapped in an IIFE.

    Performance targets: first paint under 200ms, interaction response under 50ms. On a cold load with no cache, the entire page weighs 25KB. Compare that to base64encode.org, which loads over 1.2MB of JavaScript before you can type a single character.

    It’s installable as a PWA. The service worker caches everything for offline use. Once you’ve visited the page once, it works without an internet connection — which is the whole point of a privacy-focused tool. If you need a similar approach for password generation, PassForge uses the same offline-first design.

    Keyboard Accessibility

    Ctrl+Enter triggers the encode/decode action. Ctrl+Shift+C copies the output. All interactive elements have focus states and ARIA labels. The mode toggle uses proper role="tab" semantics. You can use the entire tool without touching a mouse.

    When You’d Actually Use This

    Five concrete scenarios:

    1. Debugging API responses — decode Base64 error messages, auth tokens, or encoded payloads without leaving your browser
    2. Working with JWTs — toggle URL-safe mode and decode the header/payload sections of a JSON Web Token
    3. Embedding images in CSS — drag an icon file onto the tool, get a data URI you can paste directly into a stylesheet
    4. Email debugging — decode MIME-encoded email bodies or attachment headers
    5. Certificate inspection — paste a PEM certificate’s Base64 block to check what’s inside

    Frequently Asked Questions

    How does Base64Lab’s auto-detection decide whether to encode or decode?

    Base64Lab checks three things in order: (1) whether the input is a data URI starting with data:, (2) whether the input contains only valid Base64 characters (A-Z, a-z, 0-9, +, /, =) with proper padding, and (3) the input length — strings under 4 characters default to encoding. If decoding fails or produces invalid output, it falls back to encoding. The heuristic is correct roughly 95% of the time.

    Is my data really private when using Base64Lab?

    Yes. Base64Lab processes everything using the browser’s built-in btoa() and atob() functions with zero external network requests. Open your browser’s DevTools Network tab while using it — you’ll see no outbound requests. The tool is a single HTML file with no CDN dependencies, analytics pixels, or server-side processing. It’s also installable as a PWA for fully offline use.

    What is the maximum file size Base64Lab can handle?

    Base64Lab supports files up to 50MB, which is generous for a browser-based tool. Files are read using the FileReader API’s readAsArrayBuffer method and encoded client-side. Files over 10MB typically encode in under one second on modern hardware. The output includes timing stats so you can measure performance.

    What is URL-safe Base64 and when should I use it?

    URL-safe Base64 (Base64URL) replaces + with - and / with _, and strips padding characters. Use it when the encoded string will appear in URLs, filenames, or JSON Web Tokens (JWTs). Standard Base64’s +, /, and = characters have special meaning in URLs and can break parsing.

    How does the image preview feature work?

    When you decode a Base64 string, Base64Lab checks the first few bytes of the decoded output for file signature magic numbers: 0x89 0x50 for PNG, 0xFF 0xD8 for JPEG, 0x47 0x49 0x46 for GIF, and 0x52 0x49 0x46 0x46 (RIFF header) for WebP. If an image format is detected, it renders a preview below the output automatically.

    References

    Try It

    Base64Lab is live at base64lab.orthogonal.info. No account needed, no data collected, no ads. The source code is on GitHub.

    If you find it useful, consider buying me a coffee. And if you have a tool idea that’s been bugging you, check out our other developer tools like HashForge — same philosophy, same privacy focus.

    If you’re interested in market analysis and trading signals, I also run Alpha Signal on Telegram — daily market intelligence delivered before the open.

  • Track Congress Trades with Python & Free SEC Data

    Track Congress Trades with Python & Free SEC Data

    A senator sold $2M in hotel stocks three days before a travel industry report tanked the sector. Coincidence or signal? Congressional stock trades are disclosed in public filings, and Python makes it straightforward to pull, parse, and cross-reference them against market-moving events.

    Quick Answer: You can track congressional stock trades for free using Python with the SEC’s EDGAR API and House/Senate financial disclosure databases. This tutorial shows you how to build an automated pipeline that fetches, parses, and analyzes politician trading activity — no paid data subscriptions required.

    TL;DR: Members of Congress must disclose stock trades within 45 days under the STOCK Act, and all filings are public via the SEC EDGAR API. This tutorial builds a Python tracker that pulls daily disclosures, parses transaction data (ticker, amount, date, senator), and flags unusual timing patterns. No paid APIs needed — just Python, requests, and free SEC data. Useful for journalists, retail investors, and anyone curious about the intersection of politics and markets.

    Turns out, the STOCK Act of 2012 requires all members of Congress to disclose securities transactions within 45 days. These filings are public. And you can pull them programmatically. I built a Python script that checks for new congressional trades daily, flags the interesting ones, and sends me alerts. Here’s exactly how.

    Why Congressional Trades Matter

    Members of Congress sit on committees that regulate industries, receive classified briefings, and vote on bills that move markets. Whether they’re trading on insider knowledge is a debate I’ll leave to lawyers. What I care about is this: as a group, congressional traders have historically outperformed the S&P 500 by 6-12% annually, depending on the study you reference. A 2022 paper from the University of Georgia put the figure at 8.9% annualized excess returns for Senate trades.

    Even if you think it’s all luck, following these trades is a free signal you can add to your research process. At worst, it shows you where politically-connected money is flowing.

    Where the Data Lives

    Congressional financial disclosures are filed through two systems:

    • Senate: efdsearch.senate.gov — the Electronic Financial Disclosures database
    • House: disclosures-clerk.house.gov — the Clerk of the House system

    Both are publicly searchable, but neither offers a clean API. The Senate site has a search form that returns HTML results. The House site recently added a JSON search endpoint, which is nicer to work with. Several community projects scrape and normalize this data — the most maintained one is the House Stock Watcher dataset on S3, which gets updated daily.

    For this project, I combined the House Stock Watcher dataset (free, updated daily, clean JSON) with direct scraping of the Senate EFD search for the freshest possible data.

    The Python Script

    Here’s the core of what I run. It pulls House transactions from the public S3 dataset, filters for trades above $15,000 (the minimum reporting threshold is $1,001, but small trades are noise), and flags any trades in the last 7 days:

    import json
    import urllib.request
    from datetime import datetime, timedelta
    
    HOUSE_DATA_URL = (
        "https://house-stock-watcher-data.s3-us-west-2"
        ".amazonaws.com/data/all_transactions.json"
    )
    
    def fetch_house_trades(days_back=7, min_amount="$15,001 - $50,000"):
        req = urllib.request.Request(HOUSE_DATA_URL)
        with urllib.request.urlopen(req) as resp:
            trades = json.loads(resp.read())
    
        cutoff = datetime.now() - timedelta(days=days_back)
        amount_tiers = [
            "$15,001 - $50,000",
            "$50,001 - $100,000",
            "$100,001 - $250,000",
            "$250,001 - $500,000",
            "$500,001 - $1,000,000",
            "$1,000,001 - $5,000,000",
            "$5,000,001 - $25,000,000",
            "$25,000,001 - $50,000,000",
        ]
        tier_idx = amount_tiers.index(min_amount)
        valid_tiers = set(amount_tiers[tier_idx:])
    
        recent = []
        for t in trades:
            try:
                tx_date = datetime.strptime(
                    t["transaction_date"], "%Y-%m-%d"
                )
            except (ValueError, KeyError):
                continue
            if tx_date >= cutoff and t.get("amount") in valid_tiers:
                recent.append(t)
    
        return sorted(
            recent,
            key=lambda x: x.get("transaction_date", ""),
            reverse=True,
        )

    Each transaction record includes the representative’s name, ticker, transaction type (purchase/sale), amount range, and disclosure date. The amount ranges are annoying — Congress doesn’t disclose exact figures, just brackets — but even the brackets tell you a lot when someone drops $500K+ on a single stock.

    Filtering for Signal

    Raw congressional trade data is noisy. Most trades are mutual fund purchases or routine portfolio rebalancing. The interesting stuff is when you see:

    1. Committee-relevant trades — A member of the Armed Services Committee buying defense stocks, or a Finance Committee member trading bank shares
    2. Cluster buys — Multiple members buying the same ticker within a short window
    3. Large single-stock positions — Anything above $250K in one company
    4. Timing around legislation — Trades made shortly before committee votes or bill introductions

    I added a scoring function that flags trades matching these patterns:

    COMMITTEE_SECTORS = {
        "Armed Services": ["LMT", "RTX", "NOC", "GD", "BA"],
        "Energy": ["XOM", "CVX", "COP", "SLB", "EOG"],
        "Finance": ["JPM", "BAC", "GS", "MS", "C"],
        "Health": ["UNH", "JNJ", "PFE", "ABBV", "MRK"],
        "Technology": ["AAPL", "MSFT", "GOOGL", "AMZN", "META"],
    }
    
    def score_trade(trade, member_committees):
        score = 0
        ticker = trade.get("ticker", "")
        amount = trade.get("amount", "")
    
        # Large position = more interesting
        if "$250,001" in amount or "$500,001" in amount:
            score += 30
        elif "$1,000,001" in amount:
            score += 50
    
        # Committee relevance
        for committee, tickers in COMMITTEE_SECTORS.items():
            if committee in member_committees and ticker in tickers:
                score += 40
                break
    
        # Purchase vs sale (purchases are more actionable)
        if trade.get("type") == "purchase":
            score += 10
    
        return min(score, 100)

    The committee mapping is simplified here — in production I maintain a fuller list pulled from congress.gov. But even this basic version catches the most egregious cases.

    Setting Up Daily Alerts

    I run this on a Raspberry Pi 4 (affiliate link) sitting in my closet. A cron job runs the script every morning at 7 AM, checks for new trades filed since the last run, and sends me a notification via ntfy (a free, self-hosted push notification tool).

    import urllib.request
    
    def send_alert(message, topic="congress-trades"):
        req = urllib.request.Request(
            f"https://ntfy.sh/{topic}",
            data=message.encode(),
            headers={"Title": "Congressional Trade Alert"},
        )
        urllib.request.urlopen(req)
    
    # In main loop:
    for trade in fetch_house_trades(days_back=1, min_amount="$50,001 - $100,000"):
        msg = (
            f"{trade['representative']}: "
            f"{trade['type']} {trade['ticker']} "
            f"({trade['amount']})"
        )
        send_alert(msg)

    The Raspberry Pi draws about 5 watts, costs nothing to run, and handles this job without breaking a sweat. If you don’t want to run your own hardware, a $5/month VPS from any provider works too. I wrote about setting up a homelab for projects like this if you want to go the self-hosted route.

    What I’ve Learned Running This for 6 Months

    A few patterns jumped out after collecting data since late 2025:

    Disclosure delays are the real problem. The 45-day filing window means by the time you see a trade, the move may already be priced in. The most useful trades are the ones filed quickly — within 10-15 days. Some members consistently file within a week; those are the ones I weight highest.

    Cluster signals beat individual trades. One senator buying Nvidia means nothing. Three members from different parties all buying Nvidia in the same two-week window? That’s worth investigating. My script tracks cluster buys — 3+ distinct members trading the same ticker within 14 days — and those have been the most actionable signals.

    Sales matter more than purchases for timing. Purchases can be routine investment. But when several members suddenly sell the same sector? That’s been a leading indicator for bad news more often than purchases predict good news.

    I won’t claim this is a trading strategy on its own — it’s one data point I check alongside technicals, fundamentals, and corporate insider trades from SEC Form 4 filings. The congressional data adds a political risk dimension that most retail traders ignore entirely.

    Alternatives and Paid Tools

    If you don’t want to build your own, several paid services track this data:

    • Quiver Quantitative (free tier + paid) — best visualization, shows committee-trade correlations. The free tier covers delayed data.
    • Capitol Trades (free) — clean interface, basic filtering. No alerts or scoring.
    • Unusual Whales ($30-100/mo) — includes congressional data alongside options flow. Worth it if you want both in one platform.

    I prefer my DIY version because I can customize the scoring, add my own committee mappings, and cross-reference against other datasets I already collect. But if you just want to glance at the data without writing code, Capitol Trades is solid and free.

    Extending It

    The basic script above gets you 80% of the value. If you want to go further:

    • Add Senate data — the EFD search site requires a bit more scraping work since it returns HTML, but BeautifulSoup handles it. A good Python web scraping reference (affiliate link) will save you hours.
    • Cross-reference with Polygon.io — I use Polygon’s market data API to check price action after each disclosed trade. This lets you backtest whether following congressional trades would have been profitable.
    • Build a dashboard — Grafana + SQLite gives you a clean visual history. Run it on the same Pi.
    • Track state-level trades — Some states have their own disclosure requirements for governors and state legislators. Less data, but less competition from other trackers too.

    The full source code for my version is about 400 lines of Python with zero paid dependencies — just stdlib plus BeautifulSoup for the Senate scraping. I might open-source it if there’s interest; drop a comment below if that’d be useful.


    I publish daily market intelligence — including congressional trade alerts — on our free Telegram channel. Join Alpha Signal for daily signals, trade analysis, and macro context. No fluff, no paywalls on the basics.

    FAQ

    Is it legal to trade based on Congressional disclosure data?

    Yes. Congressional stock disclosures are public records under the STOCK Act of 2012. Trading based on publicly available filing data is legal. What’s illegal is insider trading — using material non-public information. The disclosures you’re accessing are already public, typically 30-45 days after the actual trade. By the time you see them, the information advantage has largely evaporated, but patterns and trends can still be informative for longer-term analysis.

    How delayed are Congressional stock disclosures?

    Members of Congress have 45 days to report trades, and many push that deadline. Some file late (with minimal penalties). In practice, most disclosures appear 30-45 days after the trade date. The SEC EDGAR system updates daily, so once filed, you’ll see it within 24 hours. This delay is why most alpha from congressional tracking comes from pattern analysis over time, not individual trade copying.

    Can I automate alerts for specific senators or tickers?

    Absolutely. The Python script in this tutorial can be extended with a simple filter + notification layer. Add a watchlist of senator names or tickers, run the script on a cron job (daily or hourly), and send alerts via email (smtplib), Slack webhook, or Telegram bot API when matches appear. The Alpha Signal Telegram channel already does this if you prefer a ready-made solution.

    What data fields are available in STOCK Act filings?

    Each disclosure includes: filer name, office (House/Senate), transaction date, disclosure date, ticker symbol (when applicable), asset description, transaction type (purchase/sale/exchange), amount range (e.g., $1,001-$15,000), and whether it was a full or partial disposition. The amount ranges rather than exact figures are a limitation — Congress intentionally chose ranges over precise amounts.

    References

  • OpenClaw Setup: Zero to Autonomous AI Mastery

    OpenClaw Setup: Zero to Autonomous AI Mastery

    Setting up OpenClaw is easy. Setting it up right so your AI agent actually does useful work autonomously takes some know-how.

    Quick Answer: OpenClaw is a self-hosted AI agent orchestration system that runs on TrueNAS. This guide walks you through installing OpenClaw from scratch, configuring LLM backends, setting up automated workflows, and achieving fully autonomous content generation and system management.

    TL;DR: OpenClaw is a self-hosted autonomous AI agent platform that remembers context between sessions, runs cron jobs, and uses real tools like browser automation. This guide covers optimal setup — from Hostinger 1-click deploy to configuring persistent memory, cron scheduling, and multi-agent workflows. Unlike ChatGPT, OpenClaw agents act independently and self-improve over time.

    What Makes OpenClaw Different

    Unlike ChatGPT or Claude, which respond to individual prompts, OpenClaw creates persistent AI agents that remember between sessions, act autonomously through cron jobs, use real tools like browser automation and APIs, and self-improve by editing their own configuration.

    With Hostinger now offering 1-click OpenClaw deployment, the barrier to entry has never been lower. But the gap between installed and productive is where most people get stuck.

    The 3 Mistakes New OpenClaw Users Make

    1. Generic SOUL.md

    Your SOUL.md file is your agents personality and decision-making framework. A generic you are a helpful assistant produces generic results. A well-crafted SOUL.md with specific principles, boundaries, and communication style creates an agent that feels like a capable teammate.

    2. No Memory Protocol

    Without structured memory, every session starts from scratch. The 3-layer memory system (State, Journal, Knowledge) gives your agent continuity. It remembers what worked, what failed, and what it learned across sessions and days.

    3. Manual-Only Operation

    The real power of OpenClaw is autonomous operation via cron jobs. An agent that only responds to messages is using 10% of its potential. Cron jobs let your agent monitor, create, publish, and optimize while you sleep.

    What is in the Mastery Pack

    The OpenClaw Mastery Pack includes everything you need to go from a fresh install to a productive autonomous agent:

    • Complete Mastery Guide (8 chapters) covering architecture, memory protocol, skill configuration, cron patterns, revenue automation, troubleshooting, and advanced patterns
    • 5 SOUL.md Templates with battle-tested personas: Business Assistant, Creative Writer, Developer Ops, Trading Analyst, Personal Assistant
    • 30 Production-Tested Cron Patterns for content publishing, monitoring, revenue tracking, SEO, data research, and maintenance
    • Memory Protocol Template with complete 3-layer memory system structures
    • Quick Start Cheat Sheet to get productive in your first hour

    Free Preview: Quick Start Checklist

    1. Edit SOUL.md to give your agent a specific personality and principles
    2. Edit USER.md to tell it who you are and what you need
    3. Edit TOOLS.md to add your local services
    4. Create data/ directory with state.json, knowledge.md, and journal/
    5. Set up 3 starter crons: email check (every 2h), weather (morning), RSS monitor (every 4h)
    6. Configure the browser-agent skill for web automation
    7. Test a heartbeat cycle to verify autonomous operation
    8. Create HEARTBEAT.md with your periodic task checklist

    The full Mastery Pack goes deep on each step with templates, examples, and troubleshooting.

    Get the OpenClaw Mastery Pack

    Download the OpenClaw Mastery Pack for 19 dollars

    One-time purchase. Instant access. Includes all templates, guides, and the 30-pattern cron recipe book.

    Questions? Reach out at [email protected]

    Who Made This

    This guide was created by an OpenClaw agent running in production since March 2026. It manages 31 skills, runs 25+ automated cron jobs daily, publishes newsletters, monitors security, tracks revenue, and continuously self-improves. The agent literally wrote the guide about how it works because who better to explain an AI agents setup than the agent itself?

    Configuring Persistent Memory

    OpenClaw’s killer feature is persistent memory — the ability for agents to remember context across sessions. By default, memory is stored in a SQLite database inside the container. For production use, mount an external volume to preserve memory across container restarts:

    volumes:
      - ./openclaw-data:/app/data
      - ./openclaw-config:/app/config

    The /app/data directory stores agent memory, conversation history, and learned patterns. The /app/config directory holds agent definitions, cron schedules, and tool configurations. Back up both directories regularly.

    Setting Up Cron-Based Automation

    Cron jobs transform OpenClaw from a chatbot into an autonomous agent. Define schedules in your agent config:

    cron:
      - schedule: "0 9 * * *"
        task: "Check server health and report anomalies"
      - schedule: "*/30 * * * *"
        task: "Monitor RSS feeds and summarize new articles"
      - schedule: "0 0 * * 1"
        task: "Generate weekly infrastructure report"

    Each cron task runs with full agent capabilities — including browser automation, API calls, and file operations. The agent remembers previous runs, so it can detect changes and trends over time.

    Security Hardening

    Since OpenClaw agents have access to real system tools, security matters:

    • Run the container with --read-only filesystem (except mounted volumes)
    • Use Docker’s --cap-drop ALL and add only needed capabilities
    • Set resource limits: --memory 2g --cpus 2
    • Enable the built-in audit log to track all agent actions
    • Use API keys with scoped permissions for external service access

    Hardware for Running OpenClaw

    OpenClaw runs on anything from a Raspberry Pi to a full server. For the best experience, a mini PC with 16GB RAM handles multiple agents without breaking a sweat. Pair it with a reliable UPS so your autonomous tasks survive power blips. For network segmentation with your AI setup, see our guide on network segmentation with OPNsense.

    FAQ

    Is OpenClaw free to self-host?

    Yes. OpenClaw is open-source and free to run on your own infrastructure. Hostinger offers 1-click deployment starting at their base VPS tier. You’ll need at least 2GB RAM and 20GB storage for comfortable operation with persistent memory enabled.

    How does OpenClaw differ from ChatGPT or Claude?

    ChatGPT and Claude are stateless — each conversation starts fresh. OpenClaw creates persistent agents that maintain memory across sessions, execute scheduled tasks via cron, use real tools (browser, APIs, file system), and can modify their own configuration to improve over time.

    Can I run multiple OpenClaw agents simultaneously?

    Yes. OpenClaw supports multi-agent workflows where agents can delegate tasks to each other, share memory stores, and coordinate through a central orchestrator. This is ideal for complex automation chains like monitoring + alerting + remediation.

    What infrastructure do I need?

    Minimum: a VPS with 2 CPU cores, 2GB RAM, and Docker installed. Recommended: 4GB RAM if running multiple agents with browser automation. OpenClaw runs as a Docker container and works on any Linux server, including TrueNAS jails and Proxmox LXCs.

    References

  • CSS Gradient Builder: Fixing Annoyances of Existing Tools

    CSS Gradient Builder: Fixing Annoyances of Existing Tools

    Conic gradients are the forgotten sibling of CSS gradients—every online gradient builder handles linear and radial, but try generating a conic gradient for a loading spinner and you’re hand-writing CSS from MDN docs. That gap is exactly why this tool exists.

    Quick Answer: This CSS gradient builder solves the common frustrations of existing tools — no ads, instant previews, proper multi-stop support, and clean exportable CSS. It runs entirely in your browser with zero dependencies.

    TL;DR: GradientForge is a free browser-based CSS gradient builder that fixes the annoyances of existing tools — it supports linear, radial, and conic gradients, provides real-time preview with no ads or tracking, outputs clean CSS with vendor prefixes, and includes preset collections for common design patterns. Built because every other gradient tool is either ad-riddled or missing conic gradient support.

    So I spent my afternoon building GradientForge instead.

    The Problem With Existing Gradient Tools

    I tested the three most popular gradient generators before writing a single line of code. Here’s what I found:

    cssgradient.io is the go-to recommendation on Stack Overflow answers from 2019. It handles linear and radial gradients well enough, but it’s slow. The page loads with trackers, analytics, and display ads competing for bandwidth. When I tested on a throttled 3G connection, first meaningful paint took over four seconds. For a tool that should generate a CSS property in under a second, that’s unacceptable.

    Grabient looks beautiful — I’ll give it that. But it’s primarily a preset gallery with limited customization. Want to add a third color stop? That’s buried in the interface. Want conic gradients? Not available. Want to export as SVG for a design file? Nope.

    uiGradients follows the same preset-only pattern. Pick from a curated list, copy the CSS. No custom stop positions, no angle fine-tuning, no easing control. It’s a gradient menu, not a gradient builder.

    Every single one of these tools was missing at least one thing I needed: conic gradient support, easing between color stops, SVG export, or just basic speed. I wanted all of those in one place.

    What GradientForge Actually Does

    GradientForge supports all three CSS gradient types: linear, radial, and conic. You pick your type, adjust the parameters, and see the result update in real-time on a full-screen canvas preview. The CSS code appears below, ready to copy with one click or keyboard shortcut (Ctrl+C when nothing is selected).

    The color stop system works the way it should. Click a color picker, drag the position handle along the gradient bar, or type an exact percentage. Double-click the bar to add a new stop at that position — the tool interpolates the correct color automatically. You can have up to 10 stops per gradient, which covers every practical use case I’ve encountered.

    The feature I’m most proud of is the easing system. Standard CSS gradients transition linearly between color stops, which often produces muddy middle zones where colors mix in ugly ways. GradientForge generates additional intermediate stops that follow an easing curve — ease-in, ease-out, ease-in-out, or stepped transitions. The result is smoother, more visually pleasing gradients without manual fine-tuning of each stop position.

    Here’s what happens technically: when you select an easing function, the tool interpolates 8 additional color stops between each pair of your original stops, positioning them along the chosen easing curve. The browser sees a gradient with many stops, but the transitions follow a cubic or stepped curve instead of a linear one. The output CSS is longer, but the visual result is noticeably better, especially for gradients spanning complementary colors.

    How I Built It

    GradientForge is a single HTML file with inline CSS and JavaScript — the same zero-dependency philosophy behind PixelStrip and RegexLab. No React, no Tailwind, no build step, no node_modules. The entire tool is about 36KB — smaller than most hero images. It loads in under 100ms on any modern connection.

    The architecture is straightforward state management. A single JavaScript object holds the current gradient configuration: type, angle, color stops, easing mode, and type-specific parameters (radial shape/size/position, conic angle/position). Every time any control changes, the entire UI re-renders from that state object. It sounds wasteful, but with only a few DOM elements to update, each render cycle takes under 2ms.

    The color stop bar uses pointer events for drag handling. Each stop is a positioned div inside the bar container. On mousedown, I capture the element, switch to mousemove tracking on the document (not the bar — that prevents losing the drag when the cursor moves fast), and compute the percentage position from the cursor’s X coordinate relative to the bar’s bounding rect. Touch events follow the same pattern for mobile support.

    For color interpolation, I convert hex colors to RGB components, interpolate each channel independently, and convert back. This happens in sRGB space, which isn’t perceptually uniform — I’d like to add OKLCH interpolation in a future version for even smoother results. But for most practical gradients, sRGB interpolation is indistinguishable from perceptual to human eyes.

    The SVG export translates CSS gradient parameters into SVG gradient elements. Linear gradients map directly to <linearGradient> with computed x1/y1/x2/y2 coordinates derived from the CSS angle. Radial gradients use <radialGradient> with center positions. Conic gradients don’t have a native SVG equivalent, so the tool falls back to a linear approximation — not perfect, but useful enough for most design workflows.

    The URL State Trick

    Every gradient configuration is encoded in the URL query parameters. Change a color, move a stop, switch the type — the URL updates silently via history.replaceState. This means you can share a gradient by sharing the URL. No accounts, no saving to a database, no server-side state. The recipient opens the link and sees your exact gradient configuration ready to use.

    The encoding is compact: gradient type is a single character (l/r/c), stops are comma-separated hex:position pairs, and type-specific parameters use short keys. A three-stop linear gradient with easing encodes to about 120 characters in the URL — short enough to paste in a Slack message without it looking intimidating.

    Privacy and Performance

    Everything runs in your browser. There’s no server processing, no analytics tracking your color choices, no data leaving your machine. The tool works completely offline once loaded — I included a service worker that caches all assets. Install it as a PWA and you’ve got a native-feeling gradient builder that works on a plane.

    I ran Lighthouse on the deployed version: 100 across all four categories. Performance, accessibility, best practices, SEO — all perfect scores. That’s what happens when your entire app is 36KB of self-contained HTML with proper ARIA labels and semantic markup.

    12 Built-In Presets

    Sometimes you don’t want to build from scratch. GradientForge includes 12 presets — Sunset, Ocean, Forest, Flame, Night, Peach, Arctic, Berry, Candy, Mint, Dusk, and Neon. Click one to load it, then customize from there. They’re starting points, not endpoints.

    The presets also serve as a discovery tool. If you’re not sure what conic gradients look like, hit the Random button. It generates a random type, random angle, random colors, and random number of stops. Hit it ten times and you’ll have a better intuition for what each gradient type does than reading any tutorial could give you.

    Dark Mode and Mobile

    The interface respects your system color scheme preference automatically. No toggle needed — though I might add one in a future update for users who want to test their gradient against both backgrounds. On mobile, the layout shifts from a side-by-side view (preview + controls) to a stacked view with the preview on top and controls scrollable below. Touch targets are 44px minimum for comfortable thumb navigation.

    I tested at 320px width (iPhone SE), 768px (iPad), and 1440px (desktop). The gradient preview always takes up as much space as possible — that’s the point of the tool, after all. Controls compress but remain usable at every breakpoint.

    What’s Next

    I have a short list of features I want to add: OKLCH color interpolation for perceptually smoother gradients, a gradient animation builder (because CSS can animate gradient positions), multi-gradient layering (stack multiple gradients on one element), and an accessibility checker that warns when your gradient doesn’t meet contrast requirements for text overlays.

    For now, GradientForge does exactly what I needed: build any CSS gradient, with any number of stops, with smooth easing, in any of the three gradient types, and copy the result in one click. No ads, no tracking, no signup. Just gradients.

    Try GradientForge →

    If you build something with a gradient you made in GradientForge, I’d genuinely love to see it. And if you find this tool useful, buying me a coffee helps keep the servers running and new tools coming.

    GradientForge is one of nine free tools in the Orthogonal Tools collection. Every tool runs entirely in your browser with zero dependencies, works offline, and respects your privacy.

    Build Faster With the Right Setup

    Tools like GradientForge are quick projects when your development environment doesn’t fight you. I built this on a dual-monitor setup with a color-accurate 4K display — critical when you’re tweaking gradient color stops all day. For keyboard-driven development, the Keychron Q1 mechanical keyboard keeps the typing feel solid during long sessions.

    If you work with images alongside CSS, check out DiffLab for comparing visual changes, and TypeFast for managing reusable CSS snippets.

    FAQ

    Does GradientForge support conic gradients?

    Yes — this was the primary motivation for building it. Most existing CSS gradient generators only support linear and radial gradients. GradientForge supports all three types: linear, radial, and conic, with full control over angle, color stops, and positioning.

    Is GradientForge free to use?

    Completely free, with no ads, tracking scripts, or account requirements. It runs entirely in your browser — no data is sent to any server. The source code is available for review and self-hosting.

    What browsers support conic gradients?

    All modern browsers support conic gradients: Chrome 69+, Firefox 83+, Safari 12.1+, and Edge 79+. GradientForge includes vendor prefix options for broader compatibility and shows a browser support indicator for each gradient type you create.

    References

  • Free VPN: Cloudflare Tunnel & WARP Guide (2026)

    Free VPN: Cloudflare Tunnel & WARP Guide (2026)

    TL;DR: Cloudflare offers two free VPN solutions: WARP (consumer privacy VPN using WireGuard) and Cloudflare Tunnel + Zero Trust (self-hosted VPN replacement for accessing your home network). This guide covers both approaches step-by-step, with Docker Compose configs, split-tunnel setup, and security hardening. Zero Trust is free for up to 50 users — enough for any homelab or small team.

    Quick Answer: You can set up a completely free VPN using Cloudflare Tunnel and WARP. This guide covers installing cloudflared on your server, creating a tunnel, configuring DNS routes, and connecting clients — all using Cloudflare’s free tier with no bandwidth limits.

    Why Build Your Own VPN in 2026?

    Commercial VPN providers make bold promises about privacy, but their centralized architecture creates a fundamental trust problem. You’re routing all your traffic through servers you don’t control, operated by companies whose revenue model depends on subscriber volume — not security audits. ExpressVPN, NordVPN, and Surfshark have all faced scrutiny over logging practices, jurisdiction shopping, and opaque ownership structures.

    Cloudflare offers a different model. Instead of renting someone else’s VPN, you build your own using Cloudflare’s global Anycast network (330+ data centers in 120+ countries) as the transport layer. The result is a VPN that’s faster than most commercial alternatives, costs nothing, and gives you full control over access policies.

    There are two distinct approaches, and you might want both:

    • Cloudflare WARP — A consumer VPN app that encrypts your device traffic using WireGuard. Install, toggle on, done. Best for: browsing privacy on public Wi-Fi.
    • Cloudflare Tunnel + Zero Trust — A self-hosted VPN replacement that lets you access your home network (NAS, Proxmox, Pi-hole, Docker services) from anywhere without opening a single firewall port. Best for: homelabbers, remote workers, small teams.

    Part 1: Cloudflare WARP — The 5-Minute Privacy VPN

    What WARP Actually Does

    WARP is built on the WireGuard protocol — the same modern, lightweight VPN protocol that replaced IPSec and OpenVPN in most serious deployments. When you enable WARP, your device establishes an encrypted tunnel to the nearest Cloudflare data center. From there, your traffic exits onto the internet through Cloudflare’s network.

    Key technical details:

    • Protocol: WireGuard (via Cloudflare’s BoringTun implementation in Rust)
    • DNS: Queries routed through 1.1.1.1 (Cloudflare’s privacy-first DNS resolver, audited by KPMG)
    • Encryption: ChaCha20-Poly1305 for data, Curve25519 for key exchange
    • Latency impact: Typically 1-5ms added (vs. 20-50ms for most commercial VPNs) because traffic routes to the nearest Anycast PoP
    • No IP selection: WARP doesn’t let you choose exit countries — it’s a privacy tool, not a geo-unblocking tool

    Installation

    WARP runs on every major platform through the 1.1.1.1 app:

    Platform Install Method
    Windows one.one.one.one → Download
    macOS one.one.one.one → Download
    iOS App Store → search “1.1.1.1”
    Android Play Store → search “1.1.1.1”
    Linux curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg && echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list && sudo apt update && sudo apt install cloudflare-warp

    After installing, launch the app and toggle WARP on. That’s it. Your DNS queries now go through 1.1.1.1 and your traffic is encrypted to Cloudflare’s edge.

    WARP vs. WARP+ vs. Zero Trust

    Feature WARP (Free) WARP+ ($) Zero Trust WARP
    Price $0 ~$5/month Free (50 users)
    Encryption WireGuard WireGuard WireGuard
    Speed optimization Standard routing Argo Smart Routing Standard routing
    Private network access No No Yes
    Access policies No No Full ZTNA
    DNS filtering No No Gateway policies

    For most people, free WARP is sufficient for everyday privacy. If you need remote access to your homelab, keep reading — Part 2 is where it gets interesting.

    Part 2: Cloudflare Tunnel + Zero Trust — The Self-Hosted VPN Replacement

    This is the setup that replaces WireGuard, OpenVPN, or Tailscale for accessing your home network. The architecture is elegant: a lightweight daemon called cloudflared runs inside your network and maintains an outbound-only encrypted tunnel to Cloudflare. Remote clients connect through Cloudflare’s network using the WARP client. No inbound ports. No dynamic DNS. No exposed IP address.

    Architecture Overview

    ┌─────────────────┐         ┌──────────────────────┐         ┌─────────────────┐
    │  Remote Device  │         │   Cloudflare Edge    │         │  Home Network   │
    │  (WARP Client)  │◄───────►│   330+ PoPs globally │◄───────►│  (cloudflared)  │
    │                 │  WireGuard│                      │ Outbound │                 │
    │  Phone/Laptop   │  Tunnel  │  Zero Trust Policies │  Tunnel  │  NAS/Docker/LAN │
    └─────────────────┘         └──────────────────────┘         └─────────────────┘
    

    Prerequisites

    • A Cloudflare account (free tier works)
    • A domain name with DNS managed by Cloudflare (required for tunnel management)
    • A server on your home network — any Linux box, Raspberry Pi, Synology NAS, or even a Docker container on TrueNAS
    • Docker + Docker Compose (recommended) or bare-metal cloudflared installation

    Step 1: Create a Tunnel in the Zero Trust Dashboard

    1. Go to one.dash.cloudflare.com → Networks → Tunnels
    2. Click Create a tunnel
    3. Select Cloudflared as the connector type
    4. Name your tunnel (e.g., homelab-tunnel)
    5. Copy the tunnel token — you’ll need this for the Docker config

    Step 2: Deploy cloudflared with Docker Compose

    Create a docker-compose.yml on your home server:

    version: "3.8"
    services:
      cloudflared:
        image: cloudflare/cloudflared:latest
        container_name: cloudflared-tunnel
        restart: unless-stopped
        command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
        environment:
          - TUNNEL_TOKEN=${TUNNEL_TOKEN}
        network_mode: host   # Required for private network routing
    
      # Example: expose a local service
      whoami:
        image: traefik/whoami
        container_name: whoami
        ports:
          - "8080:80"

    Create a .env file alongside it:

    TUNNEL_TOKEN=eyJhIjoiYWJj...your-token-here

    Start the tunnel:

    docker compose up -d
    docker logs cloudflared-tunnel  # Should show "Connection registered"

    Critical note: Use network_mode: host if you want to route traffic to your entire LAN subnet (192.168.x.0/24). Without it, cloudflared can only reach services within the Docker network.

    Step 3: Expose Services via Public Hostnames

    Back in the Zero Trust dashboard, under your tunnel’s Public Hostnames tab:

    1. Click Add a public hostname
    2. Set subdomain: nas, domain: yourdomain.com
    3. Service type: HTTP, URL: localhost:5000 (or wherever your service runs)
    4. Save

    Cloudflare automatically creates a DNS record. Your NAS is now accessible at https://nas.yourdomain.com — with automatic SSL, DDoS protection, and Cloudflare WAF.

    Step 4: Enable Private Network Routing (Full VPN Mode)

    This is what turns a simple tunnel into a full VPN replacement. Instead of exposing individual services, you route an entire IP subnet through the tunnel.

    1. In Zero Trust dashboard → Networks → Tunnels → your tunnel → Private Networks
    2. Add your LAN CIDR: 192.168.1.0/24 (adjust to your subnet)
    3. Go to Settings → WARP Client → Split Tunnels
    4. Switch to Include mode and add 192.168.1.0/24

    Now, any device running the WARP client (enrolled in your Zero Trust org) can access 192.168.1.x addresses as if they were on your home network. SSH into your server, access your NAS web UI, reach your Pi-hole dashboard — all without port forwarding.

    Step 5: Enroll Client Devices

    1. Install the 1.1.1.1 / WARP app on your phone or laptop
    2. Go to Settings → Account → Login to Cloudflare Zero Trust
    3. Enter your team name (set during Zero Trust setup)
    4. Authenticate with the method you configured (email OTP, Google SSO, GitHub, etc.)
    5. Enable Gateway with WARP mode

    Test it: connect to mobile data (not your home Wi-Fi) and try accessing a LAN IP like http://192.168.1.1. If the router admin page loads, your VPN is working.

    Step 6: Lock It Down — Zero Trust Access Policies

    The “Zero Trust” part of this setup is what separates it from a traditional VPN. Instead of “anyone with the VPN key gets full network access,” you define granular policies:

    Zero Trust Dashboard → Access → Applications → Add an Application
    
    Application type: Self-hosted
    Application domain: nas.yourdomain.com
    
    Policy: Allow
    Include: Emails ending in @yourdomain.com
    Require: Country equals United States (optional geo-fence)
    
    Session duration: 24 hours

    You can create different policies per service. Your Proxmox admin panel might require hardware key (FIDO2) authentication, while your Jellyfin media server only needs email OTP. This is Zero Trust Network Access (ZTNA) — the same architecture that Google BeyondCorp and Microsoft Entra use internally.

    Cloudflare Tunnel vs. Alternatives: Honest Comparison

    Feature Cloudflare Tunnel WireGuard Tailscale OpenVPN
    Price Free (50 users) Free Free (100 devices) Free
    Open ports required None 1 UDP port None 1 UDP/TCP port
    Setup complexity Medium Medium-High Low High
    Works behind CG-NAT Yes Needs port forward Yes Needs port forward
    Access control Full ZTNA policies Key-based only ACLs + SSO Cert-based
    DDoS protection Yes (Cloudflare) No No No
    SSL/TLS termination Automatic N/A N/A Manual
    Trust model Trust Cloudflare Self-hosted Trust Tailscale Self-hosted
    Best for Web services + LAN Pure privacy Mesh networking Enterprise legacy

    The honest tradeoff: Cloudflare Tunnel routes your traffic through Cloudflare’s infrastructure. If you fundamentally distrust any third party touching your packets, self-hosted WireGuard is the purist choice. But for most homelabbers, the convenience of zero open ports + free DDoS protection + granular access policies makes Cloudflare Tunnel the pragmatic winner.

    Advanced: Multi-Service Docker Stack

    Here’s a production-grade Docker Compose that exposes multiple services through a single tunnel:

    version: "3.8"
    
    services:
      cloudflared:
        image: cloudflare/cloudflared:latest
        container_name: cloudflared
        restart: unless-stopped
        command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
        environment:
          - TUNNEL_TOKEN=${TUNNEL_TOKEN}
        networks:
          - tunnel
        depends_on:
          - nginx
    
      nginx:
        image: nginx:alpine
        container_name: nginx-proxy
        volumes:
          - ./nginx.conf:/etc/nginx/nginx.conf:ro
        networks:
          - tunnel
    
      # Add your services here — they just need to be on the 'tunnel' network
      # Configure public hostnames in the CF dashboard to point to nginx
    
    networks:
      tunnel:
        name: cf-tunnel

    Map each service to a subdomain in the Zero Trust dashboard: grafana.yourdomain.com → http://nginx:3000, code.yourdomain.com → http://nginx:8443, etc.

    Troubleshooting Common Issues

    Tunnel shows “Disconnected” in the dashboard

    • Check Docker logs: docker logs cloudflared-tunnel
    • Verify your token hasn’t been rotated
    • Ensure outbound HTTPS (port 443) isn’t blocked by your router/ISP
    • If behind a corporate firewall, cloudflared also supports HTTP/2 over port 7844

    Private network routing doesn’t work

    • Confirm network_mode: host in Docker Compose (or use macvlan)
    • Check that the CIDR in “Private Networks” matches your actual subnet
    • Verify Split Tunnels are set to Include mode (not Exclude)
    • On the client, run warp-cli settings to verify the private routes are active

    WARP client won’t enroll

    • Double-check your team name in Zero Trust → Settings → Custom Pages
    • Ensure you’ve created a Device enrollment policy under Settings → WARP Client → Device enrollment permissions
    • Allow email domains or specific emails that can enroll

    Security Hardening Checklist

    • ☐ Enable Require Gateway in device enrollment — forces all enrolled devices through Cloudflare Gateway for DNS filtering
    • ☐ Set session duration to 24h or less for sensitive services
    • ☐ Require FIDO2/hardware keys for admin panels (Proxmox, router, etc.)
    • ☐ Enable device posture checks: require screen lock, OS version, disk encryption
    • ☐ Use Service Tokens (not user auth) for machine-to-machine tunnel access
    • ☐ Monitor Access audit logs: Zero Trust → Logs → Access
    • ☐ Never put your tunnel token in a public Git repository — use .env files and .gitignore
    • ☐ Rotate tunnel tokens periodically via the dashboard

    Recommended Hardware

    Running Cloudflare Tunnel on a dedicated device keeps your main machine clean. A mini PC is perfect for always-on tunnel hosting — low power draw, fanless, and small enough to mount behind a monitor. For Docker-based setups, a 1TB NVMe SSD gives plenty of room for containers and logs. If you're running Plex or media behind Cloudflare, check out our TrueNAS Plex setup guide.

    FAQ

    Is Cloudflare Tunnel really free?

    Yes. Cloudflare Zero Trust offers a free plan that includes tunnels, access policies, and WARP client enrollment for up to 50 users. There are no bandwidth limits on the free tier. Paid plans (starting at $7/user/month) add features like logpush, extended session management, and dedicated egress IPs.

    Can Cloudflare see my traffic?

    Cloudflare terminates TLS at their edge, so they technically could inspect unencrypted HTTP traffic passing through the tunnel. For HTTPS services, end-to-end encryption between your browser and origin server means Cloudflare sees metadata (domain, timing) but not content. If this is a concern, use WireGuard for a fully self-hosted solution where no third party touches your packets.

    Does this work with Starlink / CG-NAT / mobile hotspots?

    Yes — this is one of Cloudflare Tunnel’s biggest advantages. Since the tunnel is outbound-only, it works behind any NAT, including carrier-grade NAT (CG-NAT) used by Starlink, T-Mobile Home Internet, and most 4G/5G connections. No port forwarding needed.

    Can I use this for site-to-site VPN?

    Yes, using WARP Connector (currently in beta). Install cloudflared with WARP Connector mode on a device at each site, and Cloudflare routes traffic between subnets. This replaces traditional IPSec site-to-site tunnels.

    Cloudflare Tunnel vs. Tailscale — which should I use?

    Use Tailscale if your primary need is device-to-device mesh networking (see also our guide on home network segmentation with OPNsense) (accessing any device from any other device). Use Cloudflare Tunnel if you want to expose web services with automatic HTTPS and DDoS protection, or if you need granular ZTNA policies. Many homelabbers use both: Tailscale for device mesh, Cloudflare Tunnel for public-facing services.

    References

  • YubiKey SSH Authentication: Stop Trusting Key Files on Disk

    YubiKey SSH Authentication: Stop Trusting Key Files on Disk

    I stopped using SSH passwords three years ago. Switched to ed25519 keys, felt pretty good about it. Then my laptop got stolen from a coffee shop — lid open, session unlocked. My private key was sitting right there in ~/.ssh/, passphrase cached in the agent.

    That’s when I bought my first YubiKey.

    Why a Hardware Key Beats a Private Key File

    📌 TL;DR: YubiKey provides secure SSH authentication by storing private keys on hardware, preventing extraction or misuse even if a device is stolen or compromised. Unlike disk-stored keys, YubiKey requires physical touch for authentication, adding an extra layer of security. It supports FIDO2/resident keys and works across devices with USB-C or NFC options.
    🎯 Quick Answer: YubiKey SSH authentication stores your private key on tamper-resistant hardware so it cannot be copied or extracted, even if your machine is compromised. Configure it via ssh-keygen -t ed25519-sk to bind SSH keys to the physical device.

    Your SSH private key lives on disk. Even if it’s passphrase-protected, once the agent unlocks it, it’s in memory. Malware can dump it. A stolen laptop might still have an active agent session. Your key file can be copied without you knowing.

    A YubiKey stores the private key on the hardware. It never leaves the device. Every authentication requires a physical touch. No touch, no auth. Someone steals your laptop? They still need the physical key plugged in and your finger on it.

    That’s the difference between “my key is encrypted” and “my key literally cannot be extracted.”

    Which YubiKey to Get

    For SSH, you want a YubiKey that supports FIDO2/resident keys. Here’s what I’d recommend:

    YubiKey 5C NFC — my top pick. USB-C fits modern laptops, and the NFC means you can tap it on your phone for GitHub/Google auth too. Around $55, and I genuinely think it’s the best value if you work across multiple devices. (Full disclosure: affiliate link)

    If you’re on a tighter budget, the YubiKey 5 NFC (USB-A) does the same thing for about $50, just with the older port. Still a good option if your machines have USB-A.

    One important note: buy two. Register both with every service. Keep one on your keychain, one locked in a drawer. If you lose your primary, you’re not locked out of everything. I learned this the hard way with a 2FA lockout that took three days to resolve.

    Setting Up SSH with FIDO2 Resident Keys

    You need OpenSSH 8.2+ (check with ssh -V). Most modern distros ship with this. If you’re on macOS, the built-in OpenSSH works fine since Ventura.

    First, generate a resident key stored directly on the YubiKey:

    ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-primary"

    Breaking this down:

    • -t ed25519-sk — uses the ed25519 algorithm backed by a security key (sk = security key)
    • -O resident — stores the key on the YubiKey, not just a reference to it
    • -O verify-required — requires PIN + touch every time (not just touch)
    • -C "yubikey-primary" — label it so you know which key this is

    It’ll ask you to set a PIN if you haven’t already. Pick something decent — this is your second factor alongside the physical touch.

    You’ll end up with two files: id_ed25519_sk and id_ed25519_sk.pub. The private file is actually just a handle — the real private key material lives on the YubiKey. Even if someone gets this file, it’s useless without the physical hardware.

    Adding the Key to Remote Servers

    Same as any SSH key:

    ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@your-server

    Or manually append the public key to ~/.ssh/authorized_keys on the target machine.

    When you SSH in, you’ll see:

    Confirm user presence for key ED25519-SK SHA256:...
    User presence confirmed

    That “confirm user presence” line means it’s waiting for you to physically tap the YubiKey. No tap within ~15 seconds? Connection refused. I love this — it’s impossible to accidentally leave a session auto-connecting in the background.

    The Resident Key Trick: Any Machine, No Key Files

    This is the feature that sold me. Because the key is resident (stored on the YubiKey itself), you can pull it onto any machine:

    ssh-keygen -K

    That’s it. Plug in your YubiKey, run that command, and it downloads the key handles to your current machine. Now you can SSH from a fresh laptop, a coworker’s machine, or a server — as long as you have the YubiKey plugged in.

    No more syncing ~/.ssh folders across machines. No more “I need to get my key from my other laptop.” The YubiKey is the key.

    Hardening sshd for Key-Only Auth

    Once your YubiKey is working, lock down the server. In /etc/ssh/sshd_config:

    PasswordAuthentication no
    KbdInteractiveAuthentication no
    PubkeyAuthentication yes
    AuthenticationMethods publickey

    Reload sshd (systemctl reload sshd) and test with a new terminal before closing your current session. I’ve locked myself out exactly once by reloading before testing. Don’t be me.

    If you want to go further, you can restrict to only FIDO2 keys by requiring the sk key types in your authorized_keys entries. But for most setups, just disabling passwords is the big win.

    What About Git and GitHub?

    GitHub has supported security keys for SSH since late 2021. Add your id_ed25519_sk.pub in Settings → SSH Keys, same as any other key.

    Every git push and git pull now requires a physical touch. It adds maybe half a second to each operation. I was worried this would be annoying — it’s actually reassuring. Every push is a conscious decision.

    For your Git config, make sure you’re using the SSH URL format:

    git remote set-url origin [email protected]:username/repo.git

    Gotchas I Hit

    Agent forwarding doesn’t work with FIDO2 keys. The touch requirement is local — you can’t forward it through an SSH jump host. If you rely on agent forwarding, you’ll need to either set up ProxyJump or keep a regular ed25519 key for jump scenarios.

    macOS Sonoma has a quirk where the built-in SSH agent sometimes doesn’t prompt for the touch correctly. Fix: add SecurityKeyProvider internal to your ~/.ssh/config.

    WSL2 can’t see USB devices by default. You’ll need usbipd-win to pass the YubiKey through. It works fine once set up, but the initial config is a 10-minute detour.

    VMs need USB passthrough configured. In VirtualBox, add a USB filter for “Yubico YubiKey.” In QEMU/libvirt, use hostdev passthrough. This catches people off guard when they SSH from inside a VM and wonder why the key isn’t detected.

    My Setup

    I carry a YubiKey 5C NFC on my keychain and keep a backup YubiKey 5 Nano in my desk. The Nano stays semi-permanently in my desktop’s USB port — it’s tiny enough that it doesn’t stick out. (Full disclosure: affiliate links)

    Both keys are registered on every server, GitHub, and every service that supports FIDO2. If I lose my keychain, I walk to my desk and keep working.

    Total cost: about $80 for two keys. For context, that’s less than a month of most password manager premium plans, and it protects against a class of attacks that passwords simply can’t.

    Should You Bother?

    If you SSH into anything regularly — servers, homelabs, CI runners — yes. The setup takes 15 minutes, and the daily friction is a light tap on a USB device. The protection you get (key material that physically can’t be stolen remotely) is worth way more than the cost.

    If you’re already running a homelab with TrueNAS or managing Docker containers, this is a natural next step in locking things down. Hardware keys fill the gap between “I use SSH keys” and “my infrastructure is actually secure.”

    Start with one key, test it for a week, then buy the backup. You won’t go back.


    Join Alpha Signal for free market intelligence — daily briefings on tech, AI, and the markets that drive them.

    📚 Related Reading

    References

    1. Yubico — “Using Your YubiKey with SSH”
    2. OWASP — “Authentication Cheat Sheet”
    3. GitHub — “YubiKey-SSH Configuration Guide”
    4. NIST — “Digital Identity Guidelines”
    5. RFC Editor — “RFC 4253: The Secure Shell (SSH) Transport Layer Protocol”

    Frequently Asked Questions

    Why is YubiKey more secure than storing SSH keys on disk?

    YubiKey stores the private key on hardware, ensuring it cannot be extracted or copied. Authentication requires physical touch, preventing unauthorized access even if a device is stolen or compromised.

    What type of YubiKey is recommended for SSH authentication?

    The YubiKey 5C NFC is recommended for its USB-C compatibility and NFC functionality, making it versatile for both laptops and phones. The YubiKey 5 NFC (USB-A) is a budget-friendly alternative for older devices.

    How do you set up SSH authentication with a YubiKey?

    You need OpenSSH 8.2+ to generate a resident key stored on the YubiKey using `ssh-keygen`. The key requires PIN and physical touch for authentication, and the public key can be added to remote servers for access.

    What precautions should be taken when using YubiKey for SSH?

    It’s recommended to buy two YubiKeys: one for daily use and one as a backup. Register both with all services to avoid lockouts in case of loss or damage.

  • Browser Fingerprinting: Identify You Without Cookies

    Browser Fingerprinting: Identify You Without Cookies

    Browser fingerprinting can identify you across sessions, devices, and even VPNs—without a single cookie. Your canvas rendering, WebGL driver strings, font list, and audio processing characteristics create a signature so unique that clearing your browser data does almost nothing to reset it.

    Browser fingerprinting isn’t new, but most developers I talk to still underestimate how effective it is. The EFF’s Cover Your Tracks project found that 83.6% of browsers have a unique fingerprint. Not “somewhat unique” — unique. One in a million. And that number climbs to over 94% if you include Flash or Java metadata (though those are mostly dead now).

    I spent a weekend building a fingerprinting test page to understand exactly what data points create this uniqueness. Here’s what I found.

    The Canvas API: Your GPU’s Signature

    📌 TL;DR: Browser fingerprinting allows websites to uniquely identify users without cookies by leveraging browser-exposed properties like Canvas and Audio APIs. These techniques exploit hardware and software variations to generate unique identifiers, making privacy protection more challenging. Studies show that over 83% of browsers have unique fingerprints, highlighting its effectiveness.
    🎯 Quick Answer: Browser fingerprinting uniquely identifies users without cookies by combining Canvas rendering, AudioContext output, WebGL parameters, and installed fonts into a hash. Studies show this technique can uniquely identify over 90% of browsers.

    This is the technique that surprised me most. The HTML5 Canvas API renders text and shapes slightly differently depending on your GPU, driver version, OS font rendering engine, and anti-aliasing settings. The differences are invisible to the eye but consistent across page loads.

    Here’s a minimal Canvas fingerprint in 12 lines:

    function getCanvasFingerprint() {
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      ctx.textBaseline = 'top';
      ctx.font = '14px Arial';
      ctx.fillStyle = '#f60';
      ctx.fillRect(125, 1, 62, 20);
      ctx.fillStyle = '#069';
      ctx.fillText('Browser fingerprint', 2, 15);
      ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
      ctx.fillText('Browser fingerprint', 4, 17);
      return canvas.toDataURL();
    }

    That toDataURL() call returns a base64 string representing the rendered pixels. On my M2 MacBook running Chrome 124, this produces a hash that differs from the same code on Chrome 124 on my Intel Linux box. Same browser version, same text, different pixel output.

    Why? The font rasterizer (FreeType vs Core Text vs DirectWrite), sub-pixel rendering settings, and GPU shader behavior all introduce tiny variations. A 2016 Princeton study tested this across 1 million browsers and found Canvas fingerprinting alone could identify about 50% of users — no cookies needed.

    AudioContext: Your Sound Card Leaks Too

    This one is less well-known. The Web Audio API processes audio signals with slight floating-point variations depending on the hardware and driver stack. You don’t even need to play a sound:

    async function getAudioFingerprint() {
      const ctx = new (window.OfflineAudioContext ||
        window.webkitOfflineAudioContext)(1, 44100, 44100);
      const oscillator = ctx.createOscillator();
      oscillator.type = 'triangle';
      oscillator.frequency.setValueAtTime(10000, ctx.currentTime);
    
      const compressor = ctx.createDynamicsCompressor();
      compressor.threshold.setValueAtTime(-50, ctx.currentTime);
      compressor.knee.setValueAtTime(40, ctx.currentTime);
      compressor.ratio.setValueAtTime(12, ctx.currentTime);
    
      oscillator.connect(compressor);
      compressor.connect(ctx.destination);
      oscillator.start(0);
      
      const buffer = await ctx.startRendering();
      const data = buffer.getChannelData(0);
      // Hash the first 4500 samples
      return data.slice(0, 4500).reduce((a, b) => a + Math.abs(b), 0);
    }

    The resulting float sum varies by sound card and audio driver. Combined with Canvas, you’re looking at roughly 70-80% unique identification rates. I tested this on three machines in my homelab — all running Debian 12, all with different audio chipsets — and got three distinct values.

    WebGL: GPU Model Exposed

    WebGL goes further than Canvas. It exposes your GPU vendor, renderer string, and supported extensions. Most browsers serve this up without hesitation:

    function getWebGLInfo() {
      const canvas = document.createElement('canvas');
      const gl = canvas.getContext('webgl');
      const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
      return {
        vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
        renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL),
        extensions: gl.getSupportedExtensions().length
      };
    }

    On my machine this returns Apple / Apple M2 / 47 extensions. That renderer string alone narrows the pool considerably. Combine it with the exact list of supported extensions (which varies by driver version) and you’ve got another high-entropy signal.

    The Full Fingerprint Stack

    A real fingerprinting library like FingerprintJS (now Fingerprint.com) combines 30+ signals. Here are the ones that contribute the most entropy, based on my testing:

    Signal Entropy (bits) How It Works
    User-Agent string ~10 OS + browser + version
    Canvas hash ~8-10 GPU + font rendering
    WebGL renderer ~7 GPU model + driver
    Installed fonts ~6-8 Font enumeration via CSS
    Screen resolution + DPR ~5 Monitor + scaling
    AudioContext ~5-6 Audio processing differences
    Timezone + locale ~4 Intl API
    WebGL extensions ~4 Supported GL features
    Navigator properties ~3 hardwareConcurrency, deviceMemory
    Color depth + touch support ~2 display.colorDepth, maxTouchPoints

    Add those up and you’re at roughly 54-63 bits of entropy. You need about 33 bits to uniquely identify someone in a pool of 8 billion. We’re at nearly double that.

    What Actually Defends Against This

    I tested four approaches on my fingerprinting test page. Here’s my honest assessment:

    Brave Browser — Best out-of-the-box protection. Brave randomizes Canvas and WebGL output on every session (adds noise to the rendered pixels). AudioContext gets similar treatment. In my tests, Brave generated a different fingerprint hash on every page load. The tradeoff: some sites that rely on Canvas for legitimate purposes (online editors, games) may behave slightly differently. I haven’t hit real issues with this in daily use.

    Firefox with privacy.resistFingerprinting — Setting privacy.resistFingerprinting = true in about:config spoofs many signals: timezone reports UTC, screen resolution reports the CSS viewport size, user-agent becomes generic. It’s effective but aggressive — it broke two web apps I use daily (a video editor and a mapping tool) because they relied on accurate screen dimensions.

    Tor Browser — The gold standard. Every Tor user presents an identical fingerprint by design. Canvas, WebGL, fonts, screen size — all normalized. But you’re trading performance (Tor routing adds 2-5x latency) and compatibility (many sites block Tor exit nodes).

    Browser extensions (Canvas Blocker, etc.) — Ironically, these can make you more unique. If 0.1% of users run Canvas Blocker, and it alters your fingerprint in a detectable way (which it does — the blocking itself is a signal), you’ve just moved from “one in a million” to “one in a thousand who runs this specific extension.” I stopped recommending these after seeing the data.

    A Practical Test You Can Run Right Now

    Visit Cover Your Tracks (EFF) — it runs a fingerprinting test and shows exactly how unique your browser is. Then visit BrowserLeaks.com for a more detailed breakdown of each signal.

    When I ran both tests in Chrome, my fingerprint was unique among their entire dataset. In Brave, it was shared with “a large number of users.” That difference matters.

    What Developers Should Do

    If you’re building analytics or auth systems, understand that fingerprinting exists in a gray area. The GDPR considers device fingerprints personal data (Article 29 Working Party Opinion 9/2014 explicitly says so). California’s CCPA covers “unique identifiers” which includes fingerprints.

    My recommendation for developers:

    • Don’t roll your own fingerprinting for tracking. If you need fraud detection, use a service like Fingerprint.com that handles consent and compliance.
    • Test your own sites with the Canvas fingerprint code above. If you’re embedding third-party scripts, check what data they’re collecting. I found three tracking scripts on a client’s site that were fingerprinting users without disclosure.
    • For your own browsing, switch to Brave. It’s Chromium-based so everything works, and the fingerprint randomization is on by default. I moved my daily driver six months ago and haven’t looked back.

    If you’re working from a home office or homelab and want to take your privacy setup further, a dedicated mini PC running a DNS-level blocker like Pi-hole or AdGuard Home catches a lot of the fingerprinting scripts at the network level before they even reach your browser. I run one on my TrueNAS box and it blocks about 30% of tracking domains across my whole network. If you are building a privacy-conscious homelab, my guide to EXIF data removal covers another vector where personal information leaks without your knowledge. Full disclosure: affiliate link.

    If you want to test what your browser reveals, I built a set of privacy-focused browser tools that run entirely client-side — no data ever leaves your machine. The PixelStrip EXIF remover handles EXIF/metadata stripping, and the DiffLab diff checker compares text without uploading to a server.

    The uncomfortable truth is that cookies were the easy privacy problem. The browser itself — its GPU, its fonts, its audio stack — is the harder one. And most people don’t even know they’re being identified.

    📡 Join Alpha Signal for free market intelligence — I share daily analysis on tech sector moves and trading signals.

    References

    1. Electronic Frontier Foundation (EFF) — “Cover Your Tracks”
    2. OWASP — “Browser Fingerprinting”
    3. ACM Digital Library — “The Web Never Forgets: Persistent Tracking Mechanisms in the Wild”
    4. developer.mozilla.org — “Canvas API”
    5. NIST — “Guide to Protecting the Confidentiality of Personally Identifiable Information (PII) (SP 800-122)”

    Frequently Asked Questions

    What is browser fingerprinting?

    Browser fingerprinting is a technique used to uniquely identify users by analyzing properties exposed by their browser, such as rendering behavior or hardware-specific variations, without relying on cookies or other storage mechanisms.

    How does the Canvas API contribute to fingerprinting?

    The Canvas API generates unique identifiers by rendering text and shapes, which vary slightly based on GPU, driver, OS font rendering, and anti-aliasing settings. These subtle differences create consistent, unique hashes across sessions.

    Can browser fingerprinting identify users across devices?

    Browser fingerprinting primarily identifies users based on device-specific properties, so it is less effective across different devices. However, it can reliably distinguish users on the same device even in incognito mode or after clearing cookies.

    How can users protect themselves from browser fingerprinting?

    Users can reduce fingerprinting risks by using privacy-focused browsers, disabling JavaScript, or employing anti-fingerprinting tools like browser extensions. However, complete protection is difficult due to the inherent nature of exposed browser properties.

    🛠️ Recommended Resources:

    Tools for privacy-conscious developers:

    Full disclosure: affiliate links.

  • Privacy-Focused Diff Checker: No Text Upload Required

    Privacy-Focused Diff Checker: No Text Upload Required

    I spent last weekend comparing two config files — a 400-line nginx setup where I’d made changes across multiple servers. I opened Diffchecker.com, pasted both files, and immediately ran into the same frustrations I’ve had for years: the page uploaded my text to their server (privacy issue for config files), there were no keyboard shortcuts to jump between changes, and the character-level highlighting was either nonexistent or buried behind a Pro paywall.

    So I built my own.

    The Problem with Every Online Diff Tool

    📌 TL;DR: I spent last weekend comparing two config files — a 400-line nginx setup where I’d made changes across multiple servers. I opened Diffchecker.
    🎯 Quick Answer: A privacy-focused diff checker runs entirely in your browser using client-side JavaScript—no text is ever uploaded to a server. This makes it safe for comparing proprietary code, credentials, or sensitive documents.

    Here’s what bugs me about existing text comparison tools. I tested the top three before writing a single line of code:

    Diffchecker.com — The default recommendation on every “best tools” list. It works, but your text gets sent to their servers. For comparing code, configs, or anything with credentials nearby, that’s a non-starter. They also paywall basic features like “find next difference” behind a $10/month subscription.

    TextDiffViewer.com — Claims client-side processing, which is good. But the UI feels like it was built in 2012. No character-level highlighting within changed lines, no unified diff view, no keyboard shortcuts. If I’m comparing 2,000 lines, I need to jump between changes, not scroll manually.

    DevToolLab’s Diff Checker — Clean UI, but limited. Only side-by-side view, no way to ignore whitespace or case differences, and no file drag-and-drop. Fine for small comparisons, frustrating for real work.

    The pattern is clear: either the tool uploads your data (privacy problem) or it’s missing features that developers actually need (navigation, views, options). It’s the same reason I built RegexLab — Regex101 sends your patterns to their server, which is a deal-breaker when you’re testing patterns against production data.

    What I Built: DiffLab

    DiffLab is a single HTML file — no server, no dependencies, no uploads. Everything runs in your browser. Close the tab and your data is gone. Here’s what makes it different:

    Three View Modes

    Most diff tools give you side-by-side and call it a day. DiffLab has three views you can switch between with keyboard shortcuts:

    • Side-by-side (press 1) — The classic two-column layout. Changed lines are paired, and character-level differences are highlighted within each line so you can see exactly what changed.
    • Unified (press 2) — Like git diff output. Removed lines appear with a - prefix, added lines with +. Compact and scannable.
    • Inline (press 3) — Shows old → new on the same line with character highlighting. Best for reviewing small edits across many lines.

    Keyboard Navigation Between Changes

    This is the feature I wanted most. In a long file with changes scattered throughout, scrolling to find each difference is painful. DiffLab tracks every change “hunk” and lets you:

    • Press J or to jump to the next change
    • Press K or to jump to the previous change
    • A floating indicator shows “Change 3 of 12” so you always know where you are

    The current change gets a visible highlight so your eye can find it instantly after scrolling.

    Character-Level Highlighting

    When a line changes, DiffLab doesn’t just highlight the whole line red/green. It finds the common prefix and suffix of the old and new line, then highlights only the characters that actually changed. If you renamed getUserById to getUserByName, only Id and Name get highlighted — not the entire function call.

    Comparison Options

    Two toggles that matter for real-world comparisons:

    • Ignore whitespace (W) — Treats a b and a b as identical. Essential for comparing code with different formatting.
    • Ignore case (C) — Treats Hello and hello as identical. Useful for config files where case doesn’t matter.

    Both options re-run the diff instantly when toggled.

    How It Works Under the Hood

    The Diff Algorithm

    DiffLab uses a Myers diff algorithm — the same algorithm behind git diff. It finds the shortest edit script (minimum number of insertions and deletions) to transform the original text into the modified text.

    For inputs under 8,000 total lines, it runs the full Myers algorithm. For larger inputs, it switches to a faster LCS-based approach with lookahead that trades perfect minimality for speed. In practice, both produce identical results for typical comparisons.

    The algorithm runs in your browser’s main thread. On my laptop, comparing two 5,000-line files takes about 40ms. The rendering is the bottleneck, not the diff calculation.

    Character Diffing

    For paired changed lines (a removed line followed by an added line at the same position), DiffLab runs a second pass. It finds the longest common prefix and suffix of the two strings, then wraps the changed middle section in highlight spans. This is simpler than running a full diff on individual characters, but it’s fast and catches the common case (a word or variable name changed in the middle of a line) perfectly.

    Rendering Strategy

    The diff output renders as an HTML table. Each row is a line, with cells for line numbers and content. I chose tables over divs because:

    1. Line numbers stay aligned with content automatically
    2. Column widths distribute properly in side-by-side mode
    3. Screen readers can navigate the structure

    Changed lines get CSS classes (diff-added, diff-removed) that map to CSS custom properties. Dark mode support comes free through prefers-color-scheme media queries — the custom properties switch automatically.

    Real Use Cases

    I’ve been using DiffLab daily since building it. Here are the situations where it’s genuinely useful:

    1. Comparing deployment configs — Before pushing a staging config to production, paste both and verify only the expected values changed.
    2. Code review diffs — When a PR is too large for GitHub’s diff view, copy-paste specific files for focused comparison.
    3. Database migration scripts — Compare the current schema dump with the new one to make sure nothing got dropped accidentally.
    4. Documentation updates — Writers comparing draft versions to see what an editor changed.
    5. API response debugging — Compare expected vs actual JSON responses. The character-level highlighting catches subtle differences in values. For formatting those JSON responses first, JSON Forge does the same client-side-only trick.

    Privacy and Offline Use

    DiffLab includes a service worker that caches everything on first visit. After that, it works completely offline — airplane mode, no internet, whatever. Your text never leaves the browser tab, and there’s no analytics tracking what you compare.

    It also passes Chrome’s PWA install requirements. Click “Install” in your browser’s address bar and it becomes a standalone app on your desktop or phone.

    Try It

    DiffLab is live at difflab.orthogonal.info. Single HTML file, zero dependencies, works offline.

    The keyboard shortcuts are the selling point: Ctrl+Enter to compare, J/K to navigate changes, 1/2/3 to switch views, W for whitespace, C for case, S to swap sides, Escape to clear.

    If you compare text files regularly, give it a try. DiffLab is part of a growing set of free browser tools that replace desktop apps — all client-side, all offline-capable. If you build developer tools, I’d love to hear what’s missing — reach out at [email protected].


    If you spend hours comparing code and config files, a good ergonomic keyboard makes the typing between comparisons much more comfortable. I switched to a split keyboard last year and my wrists thank me daily.

    For monitor setups that make side-by-side diffs actually readable, check out these ultrawide monitors for programming — the extra horizontal space is worth it.

    And if you’re doing serious code reviews, a monitor arm to position your screen at the right height reduces neck strain during long sessions.

    Frequently Asked Questions

    How does a privacy-focused diff checker work without uploading text?

    A privacy-focused diff checker runs entirely in your browser using client-side JavaScript. Your text never leaves your device — all comparison logic executes locally, so sensitive documents like contracts, code, or personal notes stay completely private.

    What is a diff checker used for?

    A diff checker compares two blocks of text and highlights the differences between them. It is commonly used by developers to review code changes, by writers to compare document revisions, and by legal professionals to spot edits in contracts.

    Can I compare large files with a browser-based diff tool?

    Yes, modern browser-based diff tools can handle files with thousands of lines efficiently. Since the processing happens on your device, performance depends on your hardware rather than network speed, often making it faster than cloud-based alternatives.

    Why should I avoid online diff checkers that upload my text?

    When you paste text into a cloud-based diff checker, that data travels to and is stored on remote servers, creating risks of data breaches, unauthorized access, or retention in server logs. A local-only tool eliminates these risks entirely.

    References

    1. OWASP — “OWASP Top Ten Privacy Risks”
    2. Mozilla Developer Network (MDN) — “Client-Side Storage and Security”
    3. GitHub — “Diff Tool: Client-Side Comparison Implementation”
    4. NIST — “Guide to Protecting the Confidentiality of Personally Identifiable Information (PII)”
    5. RFC Editor — “RFC 4949: Internet Security Glossary, Version 2”

  • Git Worktrees: The Feature That Killed My Stash Habit

    Git Worktrees: The Feature That Killed My Stash Habit

    Git stashing is a crutch that breaks the moment you have more than one context switch per hour. Worktrees solve the actual problem: multiple working directories from a single repo, each on its own branch, each with its own uncommitted state—no stash juggling required.

    Git worktrees have been around since Git 2.5 (July 2015), but I’ve met maybe three developers who actually use them. Everyone else is still juggling git stash or cloning the repo twice. That’s a shame, because worktrees solve a real, daily problem — and they’re already on your machine.

    What Git Worktrees Actually Are

    📌 TL;DR: Git worktrees allow developers to manage multiple branches or commits in separate directories without duplicating the repository. This feature eliminates the need for git stash during context switches, saving time and reducing cognitive load, especially for tasks like code reviews and hotfixes.
    🎯 Quick Answer: Git worktrees let you check out multiple branches simultaneously in separate directories, eliminating the need for git stash. Run git worktree add ../feature-branch feature-branch to work on two branches without switching or stashing.

    A worktree is a linked checkout of your repository at a different branch or commit, in a separate directory, sharing the same .git data. One repo, multiple working directories, each on its own branch. No duplicate clones, no extra disk space for the object database.

    # You're on feature/auth-refactor in ~/code/myapp
    git worktree add ../myapp-hotfix main
    # Now ~/code/myapp-hotfix has a full checkout of main
    # Both share the same .git/objects — no duplicate data

    The key constraint: each worktree must be on a different branch. Git won’t let two worktrees check out the same branch simultaneously (that would be a recipe for corruption). This is actually a feature — it forces clean separation.

    Three Workflows Where Worktrees Save Real Time

    1. Code Reviews Without Context Switching

    I review 3-5 PRs a day. Before worktrees, reviewing meant either reading diffs in the GitHub UI (fine for small changes, terrible for architectural PRs) or stashing my work to check out the PR branch locally.

    Now I keep a persistent review worktree:

    # One-time setup
    git worktree add ../review main
    
    # When a PR comes in
    cd ../review
    git fetch origin
    git checkout origin/feature/new-payment-flow
    
    # Run tests, inspect code, check behavior
    npm test
    npm run dev  # boot the app on a different port
    
    # Done reviewing? Back to my branch
    cd ../myapp
    # My work is exactly where I left it

    The time savings are measurable. I tracked it for two weeks: stash-and-switch averaged 4 minutes per review (stash, checkout, install deps, run, switch back, pop stash). Worktrees averaged 40 seconds. With 4 reviews a day, that’s ~13 minutes saved daily. Not life-changing alone, but it compounds — and the cognitive cost of interrupted focus is way higher than the clock time.

    2. Hotfixes While Mid-Feature

    This is the classic case. You’re mid-feature, things are broken in your working tree (intentionally — you’re refactoring), and production needs a patch. With worktrees:

    git worktree add ../hotfix main
    cd ../hotfix
    # fix the bug
    git commit -am "fix: null check on user.profile (#1234)"
    git push origin main
    cd ../myapp
    git worktree remove ../hotfix

    No stash. No “was I on the right commit?” No dependency reinstall because your lockfile changed between branches. Clean in, clean out.

    3. Running Two Versions Side-by-Side

    I needed to compare API response times between our v2 and v3 endpoints during a migration. With worktrees, I had both versions running simultaneously on different ports:

    git worktree add ../api-v2 release/v2
    cd ../api-v2 && PORT=3001 npm start &
    cd ../myapp && PORT=3002 npm start &
    
    # Now hit both with curl or your HTTP client
    curl -w "%{time_total}\n" http://localhost:3001/api/users
    curl -w "%{time_total}\n" http://localhost:3002/api/users

    Try doing that with stash. You can’t.

    The Commands You Actually Need

    The full git worktree subcommand has plenty of options, but in practice I use four:

    # Create a worktree for an existing branch
    git worktree add ../path branch-name
    
    # Create a worktree with a new branch (like checkout -b)
    git worktree add -b new-branch ../path starting-point
    
    # List all worktrees
    git worktree list
    
    # Remove a worktree (cleans up the directory and git references)
    git worktree remove ../path

    That’s it. Four commands cover 95% of usage. There’s also git worktree prune for cleaning up stale references if you manually delete a worktree directory instead of using remove, but you shouldn’t need it often.

    Gotchas I Hit (So You Don’t Have To)

    Node modules and build artifacts. Each worktree is a separate directory, so you need a separate node_modules in each. For a big project, that first npm install in a new worktree takes time. I mitigate this by keeping one long-lived review worktree rather than creating/destroying them constantly.

    IDE confusion. VS Code handles worktrees well — just open the worktree directory as a separate window. JetBrains IDEs can get confused if you open the same project root with different worktrees. The fix: open the specific worktree directory, not the parent.

    Submodules. If your repo uses submodules, you need to run git submodule update --init in each new worktree. Worktrees don’t automatically initialize submodules. Annoying, but a one-liner fix.

    Branch locking. Remember: one branch per worktree. If you try to check out a branch that’s already active in another worktree, Git blocks you with:

    fatal: 'main' is already checked out at '/home/user/code/myapp-hotfix'

    This is intentional and correct. If you need to work on the same branch from two directories, you have a workflow problem, not a Git problem.

    My Worktree Setup

    I keep my projects structured like this:

    ~/code/
    ├── myapp/          # main development (feature branches)
    ├── myapp-review/   # persistent review worktree (long-lived)
    └── myapp-hotfix/   # created on-demand, deleted after use

    I added a shell alias to speed up the common case:

    # ~/.zshrc or ~/.bashrc
    hotfix() {
      local branch="${1:-main}"
      local dir="../$(basename $(pwd))-hotfix"
      git worktree add "$dir" "$branch" && cd "$dir"
    }
    
    # Usage: just type 'hotfix' or 'hotfix release/v3'

    And a cleanup alias:

    worktree-clean() {
      git worktree list --porcelain | grep "^worktree" | awk '{print $2}' | while read wt; do
        if [ "$wt" != "$(git rev-parse --show-toplevel)" ]; then
          echo "Remove $wt? (y/n)"
          read answer
          [ "$answer" = "y" ] && git worktree remove "$wt"
        fi
      done
    }

    When Worktrees Aren’t the Answer

    I’m not going to pretend worktrees solve everything. They don’t make sense when:

    • You’re working solo on a single branch. No context switching means no need for worktrees.
    • Your project has a 10-minute build step. Each worktree needs its own build, so the overhead might not be worth it for infrequent switches.
    • You need the same branch in two places. Worktrees explicitly prevent this. Clone the repo instead.

    For everything else — code reviews, hotfixes, comparing versions, running multiple branches in parallel — worktrees are the right tool. I’ve been using them daily for about a year now, and git stash usage in my shell history dropped from ~15 times/week to maybe once.

    Level Up Your Git Setup

    If you’re spending real time on Git workflows, it’s worth investing in a proper reference. Pro Git by Scott Chacon covers worktrees alongside the internals that make them possible — understanding Git’s object model makes everything click. (Full disclosure: affiliate link.)

    For the terminal setup that makes all this fast, a solid mechanical keyboard actually matters when you’re typing Git commands dozens of times a day. I’ve been using a Keychron Q1 — the tactile feedback on those Brown switches makes a difference over an 8-hour session. (Affiliate link.)

    And if you want more developer workflow content, I write about tools and techniques regularly here on orthogonal.info. Check out my regex tester build or the EXIF parser deep dive for more hands-on dev tool content.

    Join Alpha Signal on Telegram for free market intelligence and developer insights.

    📚 Related Reading

    Frequently Asked Questions

    What are Git worktrees and how do they work?

    Git worktrees let you check out multiple branches of the same repository into separate directories simultaneously. Each worktree has its own working directory and index, but they all share the same Git history and objects, saving disk space and setup time.

    How do I create a new Git worktree?

    Run git worktree add ../branch-name branch-name to create a new worktree in a sibling directory. You can then cd into that directory and work on that branch without affecting your main working directory or needing to stash changes.

    What is the difference between git stash and git worktree?

    Git stash temporarily shelves uncommitted changes so you can switch branches, but you lose your working context and must remember to pop the stash later. Git worktrees let you keep multiple branches checked out simultaneously, so you never need to interrupt your work.

    Can I delete a Git worktree safely?

    Yes, run git worktree remove worktree-path to cleanly delete a worktree. Git will refuse to remove it if there are uncommitted changes, protecting you from accidental data loss. You can also manually delete the directory and run git worktree prune.

    References

    1. Git Documentation — “git-worktree Documentation”
    2. Atlassian — “How to Use Git Worktree”
    3. GitHub Blog — “Using Git Worktrees for Better Context Switching”
    4. Stack Overflow — “What is the purpose of git worktree?”
    5. Thoughtbot Blog — “Git Worktrees: A Better Way to Work with Multiple Branches”
    🛠️ Recommended Resources:

    Tools and books for productive Git workflows:

    Full disclosure: affiliate links.

Also by us: StartCaaS — AI Company OS · Hype2You — AI Tech Trends