<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>jonathan tsai</title>
 <link href="http://www.jonathantsai.com/feed/" rel="self"/>
 <link href="http://www.jonathantsai.com/"/>
 <updated>2026-02-12T23:18:08+00:00</updated>
 <id>http://www.jonathantsai.com/</id>
 <author>
   <name>Jonathan Tsai</name>
   <email>akajontsai-devel@yahoo.com</email>
 </author>

 
 <entry>
   <title>Building Mission Control for My AI Workforce: Introducing OpenClaw Command Center</title>
   <link href="http://www.jonathantsai.com/2026/02/12/building-mission-control-for-my-ai-workforce-introducing-openclaw-command-center"/>
   <updated>2026-02-12T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2026/02/12/building-mission-control-for-my-ai-workforce-introducing-openclaw-command-center</id>
   <content type="html">
&lt;h2 id=&quot;the-backstory&quot;&gt;The Backstory&lt;/h2&gt;

&lt;p&gt;I’m not a “script kiddie” or weekend hobbyist. I’m a UC Berkeley-trained Computer Scientist with over two decades of professional experience in Silicon Valley. I joined Iterable and EasyPost after their Series A rounds — both are now unicorns. At EasyPost, I managed 4 teams totaling ~20 engineers and delivered 8 figures of revenue.&lt;/p&gt;

&lt;p&gt;I know what production systems look like at scale.&lt;/p&gt;

&lt;p&gt;A few months ago, I read &lt;a href=&quot;https://steipete.me/posts/2025/shipping-at-inference-speed&quot;&gt;Peter Steinberger’s seminal post&lt;/a&gt; about shipping at inference speed. steipete is possibly one of the greatest programmers of this generation, and ClawdBot (now &lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;OpenClaw&lt;/a&gt;) was immediately on my radar. I was already racing to build my own AI Agent Swarm orchestrator — but I thought, “He’s good, but can I trust him?”&lt;/p&gt;

&lt;p&gt;Then, three weeks ago, OpenClaw went viral. I went all-in. I’ve been hacking until 4am, 5am every night building out what I call the &lt;strong&gt;OpenClaw Command Center&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This year alone:&lt;/strong&gt; After switching to Claude Code, I got a ~20x productivity boost. After adding OpenClaw, I got another 50x on top of that.&lt;/p&gt;

&lt;p&gt;The math: &lt;strong&gt;1000x productivity multiplier.&lt;/strong&gt; That’s not hyperbole. That’s my lived experience.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-im-running-right-now&quot;&gt;What I’m Running Right Now&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;5 OpenClaw master instances&lt;/strong&gt; — one for each domain of my life&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;10 satellite agents&lt;/strong&gt; — specialized workers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;1 “Godfather” orchestrator&lt;/strong&gt; — coordinates everything&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;20+ scheduled tasks per instance&lt;/strong&gt; — running 24/7&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Hardware:&lt;/strong&gt; Mac Studio M2 Ultra + Mac Minis + MacBook Pro + VirtualBox VMs on top of old Windows host&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each OpenClaw instance is a “GM” (General Manager) that oversees one aspect of my personal or professional life. They advance my goals and keep me locked in — even when I’m sleeping.&lt;/p&gt;

&lt;p&gt;I’m literally coding at the gym on my phone… via Slack… in between bench pressing 315 lbs.&lt;/p&gt;

&lt;p&gt;The possibilities are endless. &lt;strong&gt;AGI is here.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;see-it-in-action&quot;&gt;See It In Action&lt;/h2&gt;

&lt;div style=&quot;position: relative; padding-bottom: 56.25%; height: 0;&quot;&gt;&lt;iframe src=&quot;https://www.loom.com/embed/453cafab9dd142abb21559dee37785c7&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-vision-bring-the-work-to-where-humans-are&quot;&gt;The Vision: Bring the Work to Where Humans Are&lt;/h2&gt;

&lt;p&gt;I’ve seen the mockups and prototypes online — “the future of work” dashboards, agent orchestration UIs, yet-another-SaaS-tool. That’s the wrong direction.&lt;/p&gt;

&lt;p&gt;Here’s the thing: &lt;strong&gt;humans are already in Slack.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve worked at companies with dozens, hundreds, even thousands of Slack channels. That’s where work happens. That’s where context lives. That’s where people communicate.&lt;/p&gt;

&lt;p&gt;So instead of building another tool that forces context-switching, I asked: &lt;strong&gt;what if I brought the visibility to where I already am?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agents live in Slack threads — that’s their native habitat. Command Center doesn’t replace Slack; it gives you the bird’s-eye view you’re missing. It’s the air traffic control tower for your AI workforce.&lt;/p&gt;

&lt;p&gt;Think of it like a &lt;strong&gt;Starcraft command center&lt;/strong&gt; (yes, I’m dating myself):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;High APMs (actions per minute)&lt;/li&gt;
  &lt;li&gt;Lots of AI workers running in parallel&lt;/li&gt;
  &lt;li&gt;Ensure all agents are unblocked&lt;/li&gt;
  &lt;li&gt;No idle workers sitting around&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need to see everything at once to coordinate effectively.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-i-built&quot;&gt;What I Built&lt;/h2&gt;

&lt;h3 id=&quot;real-time-visibility&quot;&gt;Real-Time Visibility&lt;/h3&gt;

&lt;p&gt;The dashboard shows everything that matters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Session monitoring&lt;/strong&gt; — Every active AI session, with model, tokens, cost, and context&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LLM Fuel Gauges&lt;/strong&gt; — Never get surprised by quota limits (we’ve all been there)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;System Vitals&lt;/strong&gt; — CPU, memory, disk — is your machine the bottleneck?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cost Intelligence&lt;/strong&gt; — Know exactly what your AI workforce costs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;topic-tracking-cerebro&quot;&gt;Topic Tracking (Cerebro)&lt;/h3&gt;

&lt;p&gt;One of the most powerful features is automatic conversation organization. I call it &lt;strong&gt;Cerebro&lt;/strong&gt; — inspired by the machine that augments Professor X’s innate telepathic abilities.&lt;/p&gt;

&lt;p&gt;My setup: multiple Slack channels, one per project. Within each channel, one thread per feature. Cerebro auto-detects topics from threads and organizes them.&lt;/p&gt;

&lt;p&gt;Each thread becomes a trackable unit of work:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;All topics across your workspace&lt;/li&gt;
  &lt;li&gt;Thread counts per topic&lt;/li&gt;
  &lt;li&gt;Jump directly into any conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is possible because OpenClaw integrates deeply with Slack threading. Every message goes into the right thread, every thread has a topic, every topic is visible in the dashboard.&lt;/p&gt;

&lt;p&gt;I worked really hard to allow OpenClaw to “stay focused” on topic. That discipline pays dividends.&lt;/p&gt;

&lt;h3 id=&quot;scheduled-tasks-cron-jobs&quot;&gt;Scheduled Tasks (Cron Jobs)&lt;/h3&gt;

&lt;p&gt;AI agents shouldn’t just react — they should proactively check on things, generate reports, clean up stale work. The cron dashboard shows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;All scheduled tasks&lt;/li&gt;
  &lt;li&gt;Run history&lt;/li&gt;
  &lt;li&gt;Manual triggers&lt;/li&gt;
  &lt;li&gt;Configuration at a glance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;privacy-controls&quot;&gt;Privacy Controls&lt;/h3&gt;

&lt;p&gt;When demoing or taking screenshots, you can hide sensitive topics with one click. Learned this the hard way — you don’t want to accidentally share internal project names in a public post.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-technical-details&quot;&gt;The Technical Details&lt;/h2&gt;

&lt;h3 id=&quot;zero-dependencies-instant-startup&quot;&gt;Zero Dependencies, Instant Startup&lt;/h3&gt;

&lt;p&gt;Command Center is deliberately minimal:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;~200KB total&lt;/strong&gt; — dashboard + server&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No build step&lt;/strong&gt; — runs immediately&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No React/Vue/Angular&lt;/strong&gt; — vanilla JS, ES modules&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Single unified API endpoint&lt;/strong&gt; — one call gets all dashboard data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why this approach:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;AI agents can understand and modify it easily&lt;/li&gt;
  &lt;li&gt;No waiting for webpack/vite compilation&lt;/li&gt;
  &lt;li&gt;Works in any environment with Node.js&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;security-first&quot;&gt;Security-First&lt;/h3&gt;

&lt;p&gt;Since this gives visibility into your AI operations, security was non-negotiable:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Localhost by default&lt;/strong&gt; — not exposed to network&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No external calls&lt;/strong&gt; — zero telemetry, no CDNs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Multiple auth modes&lt;/strong&gt; — token, Tailscale, Cloudflare Access&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No secrets in UI&lt;/strong&gt; — API keys never displayed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;real-time-updates&quot;&gt;Real-Time Updates&lt;/h3&gt;

&lt;p&gt;The dashboard uses Server-Sent Events (SSE) for live updates. No polling, no websocket complexity. State refreshes every 2 seconds, cached on the backend to stay responsive under load.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-philosophy-use-ai-to-use-ai&quot;&gt;The Philosophy: Use AI to Use AI&lt;/h2&gt;

&lt;p&gt;Here’s the key insight that changed everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recursion is the most powerful idea in computer science.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not loops. Not conditionals. Recursion — the ability for something to operate on itself. And the same principle applies to AI:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Use AI to use AI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Think about it: Why are you manually configuring your AI agents? Why are you manually scheduling their work? Why are you manually routing tasks to the right model?&lt;/p&gt;

&lt;p&gt;The agents should be doing that. The meta-work of managing AI should itself be done by AI.&lt;/p&gt;

&lt;p&gt;This is how I gain an edge — not just over people still coding manually, but over vanilla OpenClaw users. I built the infrastructure for AI to optimize its own operations.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;advanced-job-scheduling-whats-already-working&quot;&gt;Advanced Job Scheduling (What’s Already Working)&lt;/h2&gt;

&lt;p&gt;After years of production experience with Spark, Airflow, Dagster, Celery, and Beanstalk — each with their own strengths and painful limitations — I had strong opinions about what an AI-native scheduler should look like.&lt;/p&gt;

&lt;p&gt;I pulled concepts straight from CS162 (Operating Systems): multi-threading primitives, semaphores, mutex locks, process scheduling algorithms. These aren’t academic exercises — they’re exactly what you need when orchestrating dozens of AI agents competing for limited resources.&lt;/p&gt;

&lt;p&gt;The scheduling primitives I’ve built:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;run-if-idle&lt;/strong&gt; — Execute only when system has spare capacity (no resource contention)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;run-if-not-run-since&lt;/strong&gt; — Guarantee freshness: “hasn’t run in 4 hours? run now”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;run-at-least-X-times-per-period&lt;/strong&gt; — SLA enforcement: “must run 3x per day minimum”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;skip-if-last-run-within&lt;/strong&gt; — Debouncing: “don’t spam if we just ran 10 min ago”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;conflict-avoidance&lt;/strong&gt; — Greedy algorithm prevents overlapping heavy jobs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;priority-queue&lt;/strong&gt; — Critical tasks preempt background work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t theoretical. It’s running in production right now across my 5 master instances and 10 satellite agents.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;intelligent-quota-management&quot;&gt;Intelligent Quota Management&lt;/h2&gt;

&lt;p&gt;I’m on the $200/month Claude Code Max plan. Without optimization, I’d blow through my weekly quota by Wednesday and be paying overage the rest of the week.&lt;/p&gt;

&lt;p&gt;Instead, I’ve never paid a cent of Extra Usage. Conservatively, this system saves me at least &lt;strong&gt;$10,000/month&lt;/strong&gt; in what would otherwise be API costs and overage charges.&lt;/p&gt;

&lt;p&gt;How? The scheduling system is &lt;strong&gt;quota-aware&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It knows when my weekly quota resets (Saturday night 10pm)&lt;/li&gt;
  &lt;li&gt;It tracks current usage percentage via the API&lt;/li&gt;
  &lt;li&gt;It batches low-priority work for off-peak hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; It’s Thursday night. I’ve used 75% of my weekly quota. The scheduler sees this and thinks: “We have 25% left, 2.5 days until reset, user is asleep. Time to burn cycles on background work.”&lt;/p&gt;

&lt;p&gt;So it wakes up my agents and has them iterate on unit tests — grinding my monorepo toward 100% code coverage while I sleep. Work that needs to get done, but doesn’t need &lt;em&gt;me&lt;/em&gt; present.&lt;/p&gt;

&lt;p&gt;By the time quota resets Saturday, I’ve maximized value from every token. Then Sunday morning I have a full fresh quota for the real creative work.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;llm-routing-right-model-for-the-job&quot;&gt;LLM Routing: Right Model for the Job&lt;/h2&gt;

&lt;p&gt;Not every task needs Claude Opus 4.6.&lt;/p&gt;

&lt;p&gt;I built a routing layer that matches tasks to models:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Task Type&lt;/th&gt;
      &lt;th&gt;Model&lt;/th&gt;
      &lt;th&gt;Why&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Code review, complex reasoning&lt;/td&gt;
      &lt;td&gt;Claude Opus 4.6&lt;/td&gt;
      &lt;td&gt;Worth the tokens&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Boilerplate, formatting, tests&lt;/td&gt;
      &lt;td&gt;Local models (Qwen, Llama)&lt;/td&gt;
      &lt;td&gt;Fast, free, good enough&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RAG retrieval, embeddings&lt;/td&gt;
      &lt;td&gt;Local&lt;/td&gt;
      &lt;td&gt;Zero API cost&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Documentation&lt;/td&gt;
      &lt;td&gt;Claude Sonnet&lt;/td&gt;
      &lt;td&gt;Sweet spot&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The router examines the task, estimates complexity, and picks the appropriate model. Heavy thinking goes to the heavy model. Routine work stays local.&lt;/p&gt;

&lt;p&gt;This is “Use AI to Use AI” in action — I didn’t manually tag every task. The routing agent figures it out.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;

&lt;h3 id=&quot;multi-agent-orchestration&quot;&gt;Multi-Agent Orchestration&lt;/h3&gt;
&lt;p&gt;The real power unlocks when agents work together:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Swarm coordination patterns&lt;/li&gt;
  &lt;li&gt;Structured handoff protocols&lt;/li&gt;
  &lt;li&gt;Specialized agent routing (SQL tasks → SQL agent)&lt;/li&gt;
  &lt;li&gt;Cross-session context sharing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;voice-harness&quot;&gt;Voice Harness&lt;/h3&gt;
&lt;p&gt;Next, I’m working on STT/TTS integration so I can orchestrate my agents with just my voice — while I’m out walking my dogs, playing basketball, lifting weights. The keyboard becomes optional.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h2&gt;

&lt;p&gt;Command Center is open source and free:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Via ClawHub&lt;/span&gt;
clawhub &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;jontsai/command-center

&lt;span class=&quot;c&quot;&gt;# Or git clone&lt;/span&gt;
git clone https://github.com/jontsai/openclaw-command-center
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;openclaw-command-center
node lib/server.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Critical setup:&lt;/strong&gt; Enable Slack threading in your OpenClaw config:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;slack&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;capabilities&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;threading&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is what enables proper topic tracking.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-bigger-picture&quot;&gt;The Bigger Picture&lt;/h2&gt;

&lt;p&gt;We’re at an inflection point. AI agents aren’t just tools anymore — they’re becoming &lt;strong&gt;teammates&lt;/strong&gt;. And like any team, you need visibility, coordination, and management.&lt;/p&gt;

&lt;p&gt;Command Center is my answer to: “How do I actually manage an AI-native life?”&lt;/p&gt;

&lt;p&gt;It’s not the final answer. It’s the foundation I’m building on. And I’m excited to share it with the community.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;OpenClaw Command Center is MIT licensed. &lt;a href=&quot;https://github.com/jontsai/openclaw-command-center&quot;&gt;Star it on GitHub&lt;/a&gt;, try it out, and let me know what you think.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jontsai/openclaw-command-center&quot;&gt;GitHub: openclaw-command-center&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://clawhub.ai/jontsai/command-center&quot;&gt;ClawHub: jontsai/command-center&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;OpenClaw&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/jontsai&quot;&gt;Twitter: @jontsai&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>SSH Commit Signing Part 2: Automation and Multi-Machine Setup</title>
   <link href="http://www.jonathantsai.com/2025/10/17/ssh-commit-signing-part-2-automation-and-multi-machine-setup"/>
   <updated>2025-10-17T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2025/10/17/ssh-commit-signing-part-2-automation-and-multi-machine-setup</id>
   <content type="html">
&lt;p&gt;In my &lt;a href=&quot;/2024/11/21/signing-git-commits-using-ssh-instead-of-gpg&quot;&gt;previous post&lt;/a&gt;, I covered the basics of signing Git commits with SSH keys instead of GPG. This post covers the automation and multi-machine setup I built to make SSH signing seamless across 12+ machines.&lt;/p&gt;

&lt;h2 id=&quot;the-challenge&quot;&gt;The Challenge&lt;/h2&gt;

&lt;p&gt;Managing SSH commit signing across multiple machines introduces several challenges:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Multiple keys&lt;/strong&gt; - Each machine has its own SSH key&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Multiple emails&lt;/strong&gt; - Different projects use different commit emails&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Verification&lt;/strong&gt; - Git needs to verify signatures from any of your keys&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt; - All keys need to be registered as signing keys&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt; - Configuration should be identical across machines&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-solution-centralized-configuration&quot;&gt;The Solution: Centralized Configuration&lt;/h2&gt;

&lt;p&gt;I created three interconnected repositories:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/jontsai/dotfiles&quot;&gt;dotfiles&lt;/a&gt;&lt;/strong&gt; (public) - Git configuration and aliases&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/jontsai/bash-ftw&quot;&gt;bash-ftw&lt;/a&gt;&lt;/strong&gt; (public) - Bash utilities and installation helpers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;pubkeys&lt;/strong&gt; (private) - SSH public keys and automation scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;key-components&quot;&gt;Key Components&lt;/h2&gt;

&lt;h3 id=&quot;1-dynamic-key-selection&quot;&gt;1. Dynamic Key Selection&lt;/h3&gt;

&lt;p&gt;Instead of hardcoding a specific key per machine, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-agent&lt;/code&gt; to automatically select the first available key:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# In ~/.gitconfig or ~/code/dotfiles/.gitconfig
&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;[gpg]&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ssh&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[gpg &quot;ssh&quot;]&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;allowedSignersFile&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/.ssh/allowed_signers&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;defaultKeyCommand&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ssh-add -L | head -n1&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[commit]&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;gpgsign&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No per-machine configuration needed&lt;/li&gt;
  &lt;li&gt;Works with any key loaded in ssh-agent&lt;/li&gt;
  &lt;li&gt;Portable across all your machines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-the-allowed_signers-file&quot;&gt;2. The allowed_signers File&lt;/h3&gt;

&lt;p&gt;Git’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allowed_signers&lt;/code&gt; file verifies commit signatures. The format is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;email key-type key-data comment
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; Create a cross-product of all your emails × all your keys:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hello@jontsai.com ssh-ed25519 AAAAC3... laptop-key
hello@jontsai.com ssh-rsa AAAAB3... desktop-key
hello@jontsai.com ssh-ed25519 AAAAC3... server-key
user@example.com ssh-ed25519 AAAAC3... laptop-key
user@example.com ssh-rsa AAAAB3... desktop-key
user@example.com ssh-ed25519 AAAAC3... server-key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This allows Git to verify commits signed by any of your keys with any of your email addresses.&lt;/p&gt;

&lt;h3 id=&quot;3-automated-generation-script&quot;&gt;3. Automated Generation Script&lt;/h3&gt;

&lt;p&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts/generate_allowed_signers.sh&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Generate allowed_signers file for Git SSH commit signing&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;EMAILS_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;EMAILS_FILE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.txt&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OUTPUT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;OUTPUT&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;allowed_signers&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Read emails (filter out comments and empty lines)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;emails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^#&apos;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$EMAILS_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^[[:space:]]*$&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Clear output file&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OUTPUT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Enable nullglob for Mac compatibility&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;shopt&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; nullglob

&lt;span class=&quot;c&quot;&gt;# Process all .pub files&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;pubkey &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub delegates/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
    if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pubkey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key_content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pubkey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;# For each key, add entry for each email&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$emails&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;IFS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; email&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
            &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$email&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$key_content&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OUTPUT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;done
    fi
done

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;shopt&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; nullglob

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Generated &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OUTPUT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; with &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &amp;lt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OUTPUT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; keys&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emails.txt&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Email addresses used for git commits
hello@jontsai.com
jontsai@users.noreply.github.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4-makefile-for-easy-management&quot;&gt;4. Makefile for Easy Management&lt;/h3&gt;

&lt;p&gt;Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt; to orchestrate everything:&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;.PHONY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;help install install-authorized_keys install-allowed_signers github-signing-keys&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;## help - Display available targets
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;Makefile | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^## &apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;never | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c4-&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	  &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/ - /\t- /;&apos;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | column &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\t&apos;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;## authorized_keys - Generate authorized_keys file
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;authorized_keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;$(wildcard *.pub) $(wildcard delegates/*.pub)&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub delegates/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; authorized_keys
	&lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;600 authorized_keys

&lt;span class=&quot;c&quot;&gt;## allowed_signers - Generate allowed_signers file
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;allowed_signers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;emails.txt scripts/generate_allowed_signers.sh $(wildcard *.pub)&lt;/span&gt;
	scripts/generate_allowed_signers.sh
	&lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;600 allowed_signers

&lt;span class=&quot;c&quot;&gt;## install - Install authorized_keys and allowed_signers to ~/.ssh
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authorized_keys allowed_signers&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; authorized_keys ~/.ssh/authorized_keys
	&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; allowed_signers ~/.ssh/allowed_signers
	&lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;600 ~/.ssh/authorized_keys ~/.ssh/allowed_signers

&lt;span class=&quot;c&quot;&gt;## github-signing-keys - Add all keys to GitHub as signing keys
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;github-signing-keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	scripts/add_github_signing_keys.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-automated-github-key-upload&quot;&gt;5. Automated GitHub Key Upload&lt;/h3&gt;

&lt;p&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts/add_github_signing_keys.sh&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Add all public keys to GitHub as signing keys using gh CLI&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Check if gh is installed&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; gh &amp;amp;&amp;gt; /dev/null&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ERROR: gh CLI is not installed&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Install from: https://cli.github.com/&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Check authentication&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; gh auth status &amp;amp;&amp;gt; /dev/null&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ERROR: Not authenticated with GitHub&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Run: gh auth login&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Check for required permissions&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Checking for required permissions...&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; gh ssh-key list &amp;amp;&amp;gt; /dev/null&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ERROR: Missing required permission scope&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;To grant this permission, run:&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;    gh auth refresh -h github.com -s admin:ssh_signing_key&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Adding all public keys to GitHub as signing keys...&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;success_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;skip_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;error_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0

&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;pubkey &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub delegates/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
    if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pubkey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;basename&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pubkey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; .pub&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Adding &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$title&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;... &quot;&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;gh ssh-key add &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt; signing &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pubkey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--title&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$title&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;exit_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$?&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$exit_code&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eq&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
            &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;done&quot;&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;success_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$((&lt;/span&gt;success_count &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;elif &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;already exists&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
            &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;already exists (skipped)&quot;&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;skip_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$((&lt;/span&gt;skip_count &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else
            &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;FAILED&quot;&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;  Error: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;error_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$((&lt;/span&gt;error_count &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;fi
    fi
done

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Summary: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$success_count&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; added, &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$skip_count&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; skipped, &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$error_count&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; errors&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;6-git-aliases-for-viewing-signatures&quot;&gt;6. Git Aliases for Viewing Signatures&lt;/h3&gt;

&lt;p&gt;Add to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[alias]&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Compact log with signature status
&lt;/span&gt;    &lt;span class=&quot;py&quot;&gt;slog&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;log --pretty=format:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%C(auto)%h %G? %C(blue)%an%C(reset) %s %C(dim)(%ar)%C(reset)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Full signature details
&lt;/span&gt;    &lt;span class=&quot;py&quot;&gt;logs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;log --show-signature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Signature status codes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G&lt;/code&gt; = Good signature&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; = Bad signature&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;U&lt;/code&gt; = Good signature, unknown validity&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt; = No signature&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;7-bash-installation-helper&quot;&gt;7. Bash Installation Helper&lt;/h3&gt;

&lt;p&gt;Add to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.bashrc&lt;/code&gt; or &lt;a href=&quot;https://github.com/jontsai/bash-ftw&quot;&gt;bash-ftw&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# GitHub CLI installation with OS detection&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;install-gh &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;KERNEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;uname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KERNEL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Darwin&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Installing GitHub CLI via Homebrew...&quot;&lt;/span&gt;
        brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;gh
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KERNEL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Linux&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Installing GitHub CLI via apt...&quot;&lt;/span&gt;
        curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://cli.github.com/packages/githubcli-archive-keyring.gpg | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;sudo dd &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/share/keyrings/githubcli-archive-keyring.gpg &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;go+r /usr/share/keyrings/githubcli-archive-keyring.gpg &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;deb [arch=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;--print-architecture&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main&quot;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/github-cli.list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;gh &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Visit https://cli.github.com for installation instructions&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;1
    &lt;span class=&quot;k&quot;&gt;fi

    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GitHub CLI installed! Run &apos;gh auth login&apos; to authenticate&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;complete-setup-workflow&quot;&gt;Complete Setup Workflow&lt;/h2&gt;

&lt;h3 id=&quot;initial-setup-one-time&quot;&gt;Initial Setup (One Time)&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Clone your dotfiles:&lt;/strong&gt;
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/code
git clone https://github.com/yourusername/dotfiles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Create your pubkeys repository structure:&lt;/strong&gt;
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; ~/code/pubkeys/&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;scripts,delegates,obsolete&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/code/pubkeys

&lt;span class=&quot;c&quot;&gt;# Copy all your .pub files here&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; ~/.ssh/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pub &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Create emails.txt&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; emails.txt &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
# Your git commit emails
you@example.com
you@users.noreply.github.com
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Copy the scripts&lt;/strong&gt; (from examples above) into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts/&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Install GitHub CLI:&lt;/strong&gt;
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;install-gh  &lt;span class=&quot;c&quot;&gt;# or manually from https://cli.github.com&lt;/span&gt;
gh auth login
gh auth refresh &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt; github.com &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; admin:ssh_signing_key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Install configuration:&lt;/strong&gt;
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/code/pubkeys
make &lt;span class=&quot;nb&quot;&gt;install

cd&lt;/span&gt; ~/code/dotfiles
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; .gitconfig ~/.gitconfig
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Upload keys to GitHub:&lt;/strong&gt;
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/code/pubkeys
make github-signing-keys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;per-machine-setup&quot;&gt;Per-Machine Setup&lt;/h3&gt;

&lt;p&gt;On each new machine:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 1. Clone repos&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/code
git clone https://github.com/yourusername/dotfiles
git clone your-pubkeys-repo  &lt;span class=&quot;c&quot;&gt;# if you made it a git repo&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 2. Install&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/code/pubkeys &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;nb&quot;&gt;install
cd&lt;/span&gt; ~/code/dotfiles &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; .gitconfig ~/.gitconfig

&lt;span class=&quot;c&quot;&gt;# 3. Configure ssh-agent (if needed)&lt;/span&gt;
ssh-add ~/.ssh/id_ed25519

&lt;span class=&quot;c&quot;&gt;# 4. Test it&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;some-repo
git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test signed commit&quot;&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--show-signature&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;benefits&quot;&gt;Benefits&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Zero per-machine configuration&lt;/strong&gt; - Same setup everywhere&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Automatic key selection&lt;/strong&gt; - Works with any key in ssh-agent&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Multi-email support&lt;/strong&gt; - All your commit emails are verified&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;One-command GitHub sync&lt;/strong&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make github-signing-keys&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Easy verification&lt;/strong&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git slog&lt;/code&gt; shows signature status inline&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Makefile dependencies&lt;/strong&gt; - Auto-regenerates when keys/emails change&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;

&lt;h3 id=&quot;1-mac-compatibility&quot;&gt;1. Mac Compatibility&lt;/h3&gt;
&lt;p&gt;Mac’s bash 3.2 doesn’t support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt; heredoc syntax. Use pipe instead:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Don&apos;t do this (fails on Mac)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;line&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; ...&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$var&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Do this (works everywhere)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$var&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;line&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; ...&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-makefile-dependencies&quot;&gt;2. Makefile Dependencies&lt;/h3&gt;
&lt;p&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(wildcard *.pub)&lt;/code&gt; to track file dependencies:&lt;/p&gt;
&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;allowed_signers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;emails.txt scripts/generate.sh $(wildcard *.pub)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-error-handling-in-scripts&quot;&gt;3. Error Handling in Scripts&lt;/h3&gt;
&lt;p&gt;Always check exit codes and provide remediation:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;2&amp;gt;&amp;amp;1&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;exit_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$?&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$exit_code&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ERROR: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;To fix: &amp;lt;remedy steps&amp;gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4-github-cli-permissions&quot;&gt;4. GitHub CLI Permissions&lt;/h3&gt;
&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin:ssh_signing_key&lt;/code&gt; scope is required for managing signing keys:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gh auth refresh &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt; github.com &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; admin:ssh_signing_key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-verification-is-separate-from-signing&quot;&gt;5. Verification is Separate from Signing&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user.signingkey&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpg.ssh.defaultKeyCommand&lt;/code&gt; - Which key to &lt;strong&gt;sign&lt;/strong&gt; with&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpg.ssh.allowedSignersFile&lt;/code&gt; - Which keys are &lt;strong&gt;trusted&lt;/strong&gt; for verification&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;verification&quot;&gt;Verification&lt;/h2&gt;

&lt;p&gt;Check if commits are signed:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Quick check&lt;/span&gt;
git slog &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Full details&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--show-signature&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Specific commit&lt;/span&gt;
git verify-commit abc123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;public-resources&quot;&gt;Public Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jontsai/dotfiles&quot;&gt;My dotfiles&lt;/a&gt; - Git configuration and aliases&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jontsai/bash-ftw&quot;&gt;bash-ftw&lt;/a&gt; - Bash utilities and helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to adapt these scripts and configurations for your own setup!&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;SSH-based commit signing is simpler than GPG, but managing it across multiple machines requires automation. With centralized configuration, automated scripts, and proper dependency tracking, you can maintain a seamless signing setup across all your machines.&lt;/p&gt;

&lt;p&gt;The key principles:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Automate everything&lt;/strong&gt; - Scripts eliminate manual steps and errors&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Centralize configuration&lt;/strong&gt; - Dotfiles repos ensure consistency&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use cross-products&lt;/strong&gt; - All emails × all keys for maximum flexibility&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Make it idempotent&lt;/strong&gt; - Safe to run commands multiple times&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Provide clear errors&lt;/strong&gt; - Always show how to fix issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now all my commits are automatically signed, verified, and visible on GitHub with that coveted “Verified” badge. 🎉&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Signing Git commits using SSH instead of GPG</title>
   <link href="http://www.jonathantsai.com/2024/11/21/signing-git-commits-using-ssh-instead-of-gpg"/>
   <updated>2024-11-21T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2024/11/21/signing-git-commits-using-ssh-instead-of-gpg</id>
   <content type="html">
&lt;p&gt;TIL you can sign Git commits using SSH instead of GPG&lt;/p&gt;

&lt;p&gt;This tip is 🏆, learned from my &lt;a href=&quot;http://jasonbot.com/&quot;&gt;really smart colleague&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To configure Git to use your key:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Configure Git to use SSH for commit signing:&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global gpg.format ssh&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Specify which public SSH key to use as the signing key and change the
filename (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/examplekey.pub&lt;/code&gt;) to the location of your key. The
filename might differ, depending on how you generated your key:&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global user.signingkey ~/.ssh/examplekey.pub&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;To sign a commit:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-S&lt;/code&gt; flag when signing your commits:&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit -S -m &quot;My commit msg&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Optional. If you don’t want to type the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-S&lt;/code&gt; flag every time you commit,
tell Git to sign your commits automatically:&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global commit.gpgsign true&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Source: &lt;a href=&quot;https://docs.gitlab.com/ee/user/project/repository/signed_commits/ssh.html&quot;&gt;https://docs.gitlab.com/ee/user/project/repository/signed_commits/ssh.html&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Embrace the power of Regex</title>
   <link href="http://www.jonathantsai.com/2023/07/13/embrace-the-power-of-regex"/>
   <updated>2023-07-13T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2023/07/13/embrace-the-power-of-regex</id>
   <content type="html">
&lt;p&gt;Too often, while reviewing code, I’ll see examples like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def extract_id_and_env(key: str) -&amp;gt; dict:
    &quot;&quot;&quot;Extracts the object ID from `key`

    `key` is a string like &apos;namespace_prefix_12345&apos;
    In some cases, `key` could also look like `namespace_prefix_12345_environment`

    Returns a dict with the object ID, an integer
    &quot;&quot;&quot;
    parts = key.split(&apos;_&apos;)

    parsed = {
        &apos;id&apos;: int(parts[2]),
        &apos;environment&apos;: parts[3] if len(parts) == 4 else None
    }
    return parsed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I see this, I ask, “Why?”&lt;/p&gt;

&lt;p&gt;Instead, this is my preferred way of handling this is to use a regex
with named capture groups:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import re

KEY_PATTERN = re.compile(r&apos;(?&amp;lt;namespace&amp;gt;[a-z]+)_(?&amp;lt;prefix&amp;gt;[a-z]+)_(?&amp;lt;object_id&amp;gt;\d+)(?:_(?P&amp;lt;environment&amp;gt;[a-z]+))?

def extract_key_components(key: str):
    m = KEY_PATTERN.match(str)
    parts = [&apos;namespace&apos;, &apos;prefix&apos;, &apos;object_id&apos;, &apos;environment&apos;, ]
    values = [m.group(part) for part in parts]
    return values
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In another example (contrived, but modified from a real world
application), from a Django which serves both students and educators,
and displays two different landing pages depending on the intent:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def login_view(request):
    url = request.GET.get(&apos;next&apos;)
    last_word = url.split(&quot;/&quot;)[-1]
    is_student = True if last_word == &apos;scholarship&apos; else False

    template = &apos;login/student.html&apos; if is_student else &apos;login/educator.html&apos;

    response = render_to_response(request, template)
    return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The problem with this code is not immediately apparent. It
works. However, this code lacks robustness.&lt;/p&gt;

&lt;p&gt;An arguably better approach:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import re

STUDENT_LOGIN_INTENT_PATTERNS = [
    re.compile(r&apos;^/path/to/(?P&amp;lt;some_id&amp;gt;\d+)/scholarship$&apos;),
]

def is_login_intent_student(request):
    is_student = False
    next = request.GET.get(&apos;next&apos;)
    for pattern in STUDENT_LOGIN_INTENT_PATTERNS:
        if pattern.match(next):
            is_student = True
            break
    return is_student
    

def login_view(request):
    is_student = is_login_intent_student(request)
    template = &apos;login/student.html&apos; if is_student else &apos;login/educator.html&apos;

    response = render_to_response(request, template)
    return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In addition to the readability and maintainability of the regex
approach, it is overall more robust, allowing the programmer to
extract multiple components from the string all at once! This
mitigates the need for updating the function in the future, if other
parts of the string are needed later on (and it’s quite often that it
would be the case!).&lt;/p&gt;

&lt;p&gt;My preference for Regex over Split stems from:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Somewhat related to the principle of https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/&lt;/li&gt;
  &lt;li&gt;If code is wrong, it should fail catastrophically and loudly, not subtly or obscurely&lt;/li&gt;
  &lt;li&gt;It’s hard to make a regex that looks &lt;em&gt;maybe right&lt;/em&gt;? Either a regex is right, or obviously wrong. (It could also be that I have lots of experience using regexes, and can write them without lookup up references)&lt;/li&gt;
  &lt;li&gt;OTOH, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;split&lt;/code&gt; is conceptually easier to learn, for me, it’s hard or nearly impossible to see &lt;strong&gt;at a glance&lt;/strong&gt; whether the code is write or wrong. For example, if you look at a block of code using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;split&lt;/code&gt; and various indexes, how would you instantly detect a possible OB1 (aka off-by-one error; https://en.wikipedia.org/wiki/Off-by-one_error)? Not possible. OB1s bugs are prevalent in software because the learning curve, and therefore barrier to entry, is low, so bugs are more likely to be introduced.&lt;/li&gt;
  &lt;li&gt;Regexes, OTOH, have a slightly higher learning curve, slightly higher barrier to entry, so those who use it tend not to make trivial mistakes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;If&lt;/strong&gt; the code never has to update ever again, then, great! &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;split&lt;/code&gt; is sufficient. If the next engineer has to update it, they would not necessarily benefit from the existing code, and would have to re-evaluate all of the code in their head to make sure indexes are right.&lt;/li&gt;
  &lt;li&gt;Maintaining a list of patterns, or regexes, encourages a &lt;strong&gt;Solve for N&lt;/strong&gt; mentality, whereas using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;split&lt;/code&gt; encourages a “solve it quick and dirty mindset”&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Use Fully Qualified datetime in Python</title>
   <link href="http://www.jonathantsai.com/2022/11/02/use-fully-qualified-datetime-in-python"/>
   <updated>2022-11-02T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2022/11/02/use-fully-qualified-datetime-in-python</id>
   <content type="html">
&lt;p&gt;Whenever using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt; module in Python, a highly recommended
practice is to just import &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt; at the top of the file, and use
the fully-qualified module name in the code, as much as possible:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.datetime&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.timedelta&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.date&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one does &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;from datetime import datetime&lt;/code&gt;, it’s hard to figure out
at-a-glance  what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt; is referring to in the middle of a
several-hundred-lines-of-code file.&lt;/p&gt;

&lt;p&gt;For similar reasons, another common best practice in Python when using
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typing&lt;/code&gt; module (https://docs.python.org/3/library/typing.html) is
to import is as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import typing as T&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import typing as t&lt;/code&gt;
(e.g. https://github.com/pallets/flask/blob/cc66213e579d6b35d9951c21b685d0078f373c44/src/flask/app.py#L7; https://github.com/pallets/werkzeug/blob/3115aa6a6276939f5fd6efa46282e0256ff21f1a/src/werkzeug/wrappers/request.py#L4)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Get Is the Worst Function Prefix Ever</title>
   <link href="http://www.jonathantsai.com/2022/07/14/get-is-the-worst-function-prefix-ever"/>
   <updated>2022-07-14T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2022/07/14/get-is-the-worst-function-prefix-ever</id>
   <content type="html">
&lt;h2 id=&quot;tldr&quot;&gt;tl;dr;&lt;/h2&gt;

&lt;p&gt;Unless the function you are writing is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getter&lt;/code&gt; (which complements a &lt;a href=&quot;https://en.wikipedia.org/wiki/Mutator_method&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setter&lt;/code&gt;&lt;/a&gt;), please avoid naming methods &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_&lt;/code&gt; lacks descriptiveness, precision, and is &lt;strong&gt;boring&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;rationale&quot;&gt;Rationale&lt;/h2&gt;

&lt;p&gt;Software engineers are supposed to creative, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get&lt;/code&gt; is possible the &lt;em&gt;least&lt;/em&gt; creative function name possible.&lt;/p&gt;

&lt;p&gt;Too often, I see codebases cluttered with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_&lt;/code&gt; methods throughout, when the implementations of those methods do things far more complex than simply reading or getting a value from an object.&lt;/p&gt;

&lt;p&gt;When half of a file with hundreds of lines of code are all named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_*&lt;/code&gt;, this makes code difficult to read, scan, and &lt;strong&gt;reason&lt;/strong&gt; about. (Future blog posts will address code that is easy vs difficult to reason about.)&lt;/p&gt;

&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives&lt;/h2&gt;

&lt;p&gt;Since much of the world’s software (historically) has been produced from within English-speaking countries and by English-speaking programmers and software engineering teams, please allow me introduce to you the robustness of the English language.&lt;/p&gt;

&lt;p&gt;While my hope and expectation is not for every software engineer to have a Shakespearean command over English vocabulary, I do think that it is quite tenable to learn a few prefixes to help codebases become more manageable and pleasing to read.&lt;/p&gt;

&lt;p&gt;Without further ado, these are my suggestions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculate_&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extract_&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;look_up_&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retrieve_&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform_&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below are examples and sample code, in Python (my language of choice).&lt;/p&gt;

&lt;h3 id=&quot;build_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: Use this prefix for a method which takes in some data, and builds a more complex structure as a result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy&lt;/strong&gt;: You have multiple loose LEGO bricks, and want to assemble those pieces to &lt;strong&gt;build&lt;/strong&gt; a structure out of that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def get_response(color, food, location):
    response = {
        &apos;color&apos;: color,
        &apos;food&apos;: food,
        &apos;location&apos;: location,
    }
    return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def build_response(color, food, location):
    response = {
        &apos;color&apos;: color,
        &apos;food&apos;: food,
        &apos;location&apos;: Location(location),
    }
    return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;response = build_response(&apos;green&apos;, &apos;eggs and ham&apos;, &apos;in a car&apos;)

# Result:
# {
#     &apos;color&apos;: &apos;green&apos;,
#     &apos;food&apos;: &apos;eggs and ham&apos;,
#     &apos;location&apos;: {
#         &apos;name&apos;: &apos;in a car&apos;
#     },
# }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;calculate_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculate_&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: When you have some data, and some formula is applied to &lt;strong&gt;calculate&lt;/strong&gt; a result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy&lt;/strong&gt;: If it’s not doable via “mental math,” and needs to be &lt;strong&gt;calculated&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;items = [
    {
        &apos;color&apos;: &apos;green&apos;,
        &apos;food&apos;: &apos;eggs and ham&apos;,
        &apos;location&apos;: {
            &apos;name&apos;: &apos;in a car&apos;
        },
        &apos;price_cents&apos;: 1525,
    },
    {
        &apos;color&apos;: &apos;red&apos;,
        &apos;food&apos;: &apos;hot chili peppers&apos;,
        &apos;location&apos;: {
            &apos;name&apos;: &apos;with a mouse&apos;
        },
        &apos;price_cents&apos;: 299,
    },
    {
        &apos;color&apos;: &apos;orange&apos;,
        &apos;food&apos;: &apos;carrots&apos;,
        &apos;location&apos;: {
            &apos;name&apos;: &apos;here or there&apos;
        },
        &apos;price_cents&apos;: 399,
    },
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def get_total(items, unit=&apos;cents&apos;):
    total_cents = sum([item[&apos;price_cents&apos;] for item in items])

    if unit == &apos;cents&apos;:
        total = total_cents
    elif unit == &apos;dollars&apos;:
        total = float((Decimal(total_cents) / Decimal(100)).quantize(Decimal(&apos;1.00&apos;)))
    
    return total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def calculate_total(items, unit=&apos;cents&apos;):
    total_cents = sum([item[&apos;price_cents&apos;] for item in items])

    if unit == &apos;cents&apos;:
        total = total_cents
    elif unit == &apos;dollars&apos;:
        total = float((Decimal(total_cents) / Decimal(100)).quantize(Decimal(&apos;1.00&apos;)))
    
    return total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A method named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculate_&lt;/code&gt; will mentally prepare the engineer to be extra careful and meticulous when maintaining this code, because the goal is to be accurate and precise.&lt;/p&gt;

&lt;h3 id=&quot;extract_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extract_&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: When you need one, or a few pieces of information, from a more complex structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy&lt;/strong&gt;: You have a plain rock (diamond ore, gold ore) which is relatively useless on the &lt;em&gt;surface&lt;/em&gt;, and want to &lt;strong&gt;extract&lt;/strong&gt; the valuable contents (diamonds, gold).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;response = {
    &apos;color&apos;: &apos;green&apos;,
    &apos;food&apos;: &apos;eggs and ham&apos;,
    &apos;location&apos;: {
        &apos;name&apos;: &apos;in a car&apos;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def get_color(response):
    return response[&apos;color&apos;]


def get_location_name(response):
    return response[&apos;location&apos;][&apos;name&apos;]

# Usage

color = get_color(response)
location_name = get_location_name(response)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Better&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;response = {
    &apos;color&apos;: &apos;green&apos;,
    &apos;food&apos;: &apos;eggs and ham&apos;,
    &apos;location&apos;: {
        &apos;name&apos;: &apos;in a car&apos;
    }
}

def extract_color(response):
    return response[&apos;color&apos;]


def extract_location_name(response):
    return response[&apos;location&apos;][&apos;name&apos;]

# Usage

color = extract_color(response)
location_name = extract_location_name(response)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Great&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider using object-oriented programming:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Response:
    def __init__(self, raw_response):
        self.raw_response = raw_response

    @property
    def color(self):
        return self.raw_response[&apos;color&apos;]

    @property
    def location_name(self):
        return self.raw_response[&apos;location&apos;][&apos;name&apos;]

# Usage

r = Response(response)

color = r.color
location_name = r.location_name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;fetch_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: Use this prefix when the method makes an HTTP call or other API call. Inspired by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; from JavaScript (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy&lt;/strong&gt;: If you have a warm and cuddly friendly dog, and you’re at the park playing the good ol’ game of &lt;strong&gt;fetch&lt;/strong&gt;; the object you’re intending to &lt;strong&gt;retrieve&lt;/strong&gt; is accessible to you, but not &lt;em&gt;immediately&lt;/em&gt; within reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def get_story_details(story_id):
    response = requests.get(f&apos;https://api.example.com/story/{story_id}/details&apos;)
    return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the method is named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_&lt;/code&gt;, there’s no way to distinguish at a glance whether this function calls out to another server/service.&lt;/p&gt;

&lt;p&gt;Whenever the flow of your code leaves your control (like making an API call), there is inherent risk and potential for errors to occur (e.g. “What if the remote API goes down?”)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def fetch_story_details(story_id):
    response = requests.get(f&apos;https://api.example.com/story/{story_id}/details&apos;)
    return response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By naming methods that make API calls to remote resources with&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_&lt;/code&gt;, you allow engineers to quickly identify risky sections of code at a glance, without requiring them to read the details of a function – and this saves time – allowing a flywheel effect of: &lt;em&gt;by writing code faster, teams produce more code / fix bugs more quickly, delivering more business value, enabling these teams to hire more team members, to build more products…&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So, if I see a method named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_&lt;/code&gt;, I can immediately make mental note  to make these sections of code more resilient (such as with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;except&lt;/code&gt; error handling, retry logic with exponential backoffs, etc).&lt;/p&gt;

&lt;h3 id=&quot;look_up_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;look_up_&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: Use this prefix when the goal of the method is to retrieve data that was previous stored in a local storage, like a database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy&lt;/strong&gt;: There is a single piece of information you want to retrieve from among a larger collection, like *looking up** the defintion of a word in a glossary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# MySQL

| id | item              | price_cents |
----------------------------------------
|  1 | eggs and ham      |        1525 |
|  2 | hot chili peppers |         299 |
|  3 | eggs and ham      |         399 |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def get_price(item):
    sql = connect()
    q = sql.query(&apos;items&apos;).where(item=item)
    price = q.execute()[&apos;price_cents&apos;]
    return price
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Better&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def look_up_price(item):
    sql = connect()
    q = sql.query(&apos;items&apos;).where(item=item)
    price = q.execute()[&apos;price_cents&apos;]
    return price
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By naming a method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;look_up&lt;/code&gt;, you mentally prepare the next engineer who reviews this code that this method involves some form of database retrieval, and they can also keep in mind the performance characteristics of database retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the database repository design pattern.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# repos/items.py

class ItemRepo:
    def get(self, item: str) -&amp;gt; Record:
        sql = connect()
        q = sql.query(&apos;items&apos;).where(item=item)
        record = q.execute()
        return record

    def look_up_price(self, item: str) -&amp;gt; float:
        record = self.get(item)
        price = record[&apos;price_cents&apos;]
        return price
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;format_--transform_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform_&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: When the desire output is a derivative of the inputs, or a &lt;em&gt;metamorphosis&lt;/em&gt; such that output is not recognizable from the original form, but only to a connoiseur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy&lt;/strong&gt;: When the input is uncooked potatoes and the output is mashed potatoes, you are &lt;strong&gt;transforming&lt;/strong&gt; the raw ingredients into a useful end-product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def get_mashed_potato(raw_potato):
    boiled_potato = boil(raw_potato)
    mashed_potato = mash(boiled_potato)
    return mashed_potato
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def transform_potato(raw_potato, form_factor):
    result = raw_potato

    if form_factor == &apos;raw&apos;:
        result = raw_potato
    elif form_factor == &apos;baked&apos;:
        result = bake(raw_potato)
    elif form_factor == &apos;boiled&apos;:
        result = boil(raw_potato)
    elif form_factor == &apos;mashed&apos;:
        result = mash(transform_potato(raw_potato, &apos;boiled&apos;))

    return result
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alternatively, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_potato&lt;/code&gt; with the same function body above would work.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Please, for the love of all things proper, think twice before creating another method with the prefix name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_&lt;/code&gt;, and use one of the better alternatives: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extract_&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;look_up_&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retrieve_&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform_&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I promise you – your future self and your teammates will thank you!&lt;/p&gt;

&lt;h2 id=&quot;feedback&quot;&gt;Feedback&lt;/h2&gt;

&lt;p&gt;Agree? Disagree? Love it? Hate it?&lt;/p&gt;

&lt;p&gt;Please leave comments or drop me a line!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>14 Rules for Being a Godly Employee</title>
   <link href="http://www.jonathantsai.com/2022/06/06/14-rules-for-being-a-godly-employee"/>
   <updated>2022-06-06T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2022/06/06/14-rules-for-being-a-godly-employee</id>
   <content type="html">
&lt;p&gt;Back in 2016, I came across this article: &lt;a href=&quot;https://thecripplegate.com/14-rules-for-being-a-godly-employee/&quot;&gt;https://thecripplegate.com/14-rules-for-being-a-godly-employee/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, I’ve resolved to start my work week by reviewing the 14 rules:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Rule #1&lt;/strong&gt; – Eagerly start the day’s main work&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #2&lt;/strong&gt; – Do not murmur at your busyness or the shortness of time but buy up the time all around.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #3&lt;/strong&gt; – Never murmur when correspondence is brought in.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #4&lt;/strong&gt; – Never exaggerate duties by seeming to suffer under the load, but treat all responsibilities as liberty and gladness.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #5&lt;/strong&gt; – Never call attention to crowded work or trivial experiences.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #6&lt;/strong&gt; – Before confrontation or censure, obtain from God a real love for the one at fault. Know the facts; be generous in your judgment. Otherwise, how ineffective, how unintelligible or perhaps provocative your well-intentioned censure may be.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #7&lt;/strong&gt; – Do not believe everything you hear; do not spread gossip.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #8&lt;/strong&gt; – Do not seek praise, gratitude, respect, or regard for past service.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #9&lt;/strong&gt; – Avoid complaining when your advice or opinion is not consulted, or having been consulted, set aside.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #10&lt;/strong&gt; – Never allow yourself to be placed in favorable contrast with anyone.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #11&lt;/strong&gt; – Do not press conversation to your own needs and concerns.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #12&lt;/strong&gt; – Seek no favors, nor sympathies, do not ask for tenderness, but receive what comes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #13&lt;/strong&gt; – Bear the blame; do not share or transfer it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rule #14&lt;/strong&gt; – Give thanks when credit for your own work or idea is given to another.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Use Python iSort to Automagically Organize Imports within your Favorite Editor</title>
   <link href="http://www.jonathantsai.com/2020/05/07/use-python-isort-to-automagically-organize-imports-within-your-favorite-editor"/>
   <updated>2020-05-07T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2020/05/07/use-python-isort-to-automagically-organize-imports-within-your-favorite-editor</id>
   <content type="html">
&lt;h2 id=&quot;tldr&quot;&gt;tl;dr;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;isort&lt;/strong&gt; (&lt;strong&gt;&lt;a href=&quot;https://pypi.org/project/isort/&quot;&gt;PyPI&lt;/a&gt;&lt;/strong&gt;; &lt;strong&gt;&lt;a href=&quot;https://github.com/timothycrosley/isort&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt;) is a wonderful tool that will sort imports in Python automagically, so that you no longer have to either a) &lt;em&gt;ignore eye-sores during code reviews&lt;/em&gt;, or b) &lt;em&gt;sound like an angry grandparent asking people to sort/organize imports&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;quick-setup&quot;&gt;Quick Setup&lt;/h2&gt;
&lt;p&gt;If you know your away around the *nix environment, these are the abridged instructions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Make the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isort&lt;/code&gt; &lt;strong&gt;binary&lt;/strong&gt; available somewhere in your &lt;strong&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/PATH_(variable)&quot;&gt;path&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Install the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isort&lt;/code&gt; &lt;strong&gt;plugin&lt;/strong&gt; for the &lt;strong&gt;&lt;a href=&quot;https://github.com/timothycrosley/isort/wiki/isort-Plugins&quot;&gt;editor of your choice&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Profit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;NOTE: (Optional but recommended)&lt;/strong&gt; Add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.isort.cfg&lt;/code&gt; file to your HOME directory, so that even you are working on a random script or project that doesn’t have one, the powers of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isort&lt;/code&gt; are still available to you.&lt;/p&gt;

&lt;h2 id=&quot;drudgerous-line-by-line-instructions-or-my-setup&quot;&gt;Drudgerous Line-by-Line Instructions (or &lt;em&gt;my setup&lt;/em&gt;)&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;If you don’t have one already, create a new system-wide &lt;strong&gt;&lt;a href=&quot;https://virtualenv.pypa.io/en/stable/&quot;&gt;Python virtualenv&lt;/a&gt;&lt;/strong&gt;.
    &lt;ol&gt;
      &lt;li&gt;The way I’d do that is to do: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/path/to/bin/python/virtualenv ~/.venv&lt;/code&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Install isort.
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.venv/bin/pip install isort&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;(Generic command: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/path/to/venv/pip install isort&lt;/code&gt;)&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt; directory of your system-wide &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;virtualenv&lt;/code&gt; to your path, or just the select binaries that you want.
    &lt;ol&gt;
      &lt;li&gt;I have already added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin/&lt;/code&gt; to my path via &lt;strong&gt;&lt;a href=&quot;https://github.com/jontsai/bash-ftw&quot;&gt;bash-ftw&lt;/a&gt;&lt;/strong&gt;, so my preference is to just symlink the specific binaries that I need.&lt;/li&gt;
      &lt;li&gt;For convenience, I’ve symlinked the following:
        &lt;ol&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ln -s ~/.venv/bin/isort ~/bin/isort&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ln -s ~/.venv/bin/python3 ~/bin/python3&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ln -s ~/.venv/bin/pip ~/bin/pip&lt;/code&gt;&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Install an &lt;strong&gt;&lt;a href=&quot;https://github.com/timothycrosley/isort/wiki/isort-Plugins&quot;&gt;isort plugin&lt;/a&gt;&lt;/strong&gt; for your editor (in my case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emacs&lt;/code&gt;, &lt;strong&gt;The best text editor in the world™&lt;/strong&gt;).
    &lt;ol&gt;
      &lt;li&gt;For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emacs&lt;/code&gt; only:
        &lt;ol&gt;
          &lt;li&gt;For &lt;strong&gt;EZMODE™&lt;/strong&gt;, my &lt;strong&gt;&lt;a href=&quot;https://github.com/jontsai/dotemacs&quot;&gt;dotemacs setup is on GitHub&lt;/a&gt;&lt;/strong&gt; (just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make install&lt;/code&gt;, and you’re set!)&lt;/li&gt;
          &lt;li&gt;Add two lines to your dotemacs (typically &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.emacs.el&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.emacs.elc&lt;/code&gt;, or somewhere in your Emacs load path):
            &lt;ol&gt;
              &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(require &apos;py-isort)&lt;/code&gt;&lt;/li&gt;
              &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(add-hook &apos;before-save-hook &apos;py-isort-before-save)&lt;/code&gt;&lt;/li&gt;
            &lt;/ol&gt;
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;No longer have to manually organize your Python imports anymore!&lt;/strong&gt; The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isort&lt;/code&gt; plugin will do it for you automatically whenever you save your file.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;thank-you&quot;&gt;Thank You!&lt;/h2&gt;

&lt;p&gt;Thanks for reading; now go forth and write some awesome Python code!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions, comments, suggestions?&lt;/em&gt; Leave a comment or subscribe to the blog for future helpful tips!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>What, How, and Why?</title>
   <link href="http://www.jonathantsai.com/2019/12/12/what-how-and-why-questions-to-ask-for-learners"/>
   <updated>2019-12-12T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2019/12/12/what-how-and-why-questions-to-ask-for-learners</id>
   <content type="html">
&lt;p&gt;In order to learn, there are three kinds of questions to ask: &lt;strong&gt;What?&lt;/strong&gt;, &lt;strong&gt;How?&lt;/strong&gt;, and &lt;strong&gt;Why&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Of these, &lt;strong&gt;Why?&lt;/strong&gt; is the best question to ask, and &lt;strong&gt;What?&lt;/strong&gt; is the worst; &lt;strong&gt;How?&lt;/strong&gt; is in the middle but right near the bottom near &lt;strong&gt;What?&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reason &lt;strong&gt;Why?&lt;/strong&gt; is the best question for learning is that it conditions you to go to &lt;em&gt;first principles&lt;/em&gt; and &lt;em&gt;learn how to learn&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What?&lt;/strong&gt; is the absolute worst type of question to ask, because usually the answer to &lt;strong&gt;What?&lt;/strong&gt; is a reference lookup away. A better question to ask is: &lt;strong&gt;Where?&lt;/strong&gt; can I find my own answer to my &lt;strong&gt;What?&lt;/strong&gt; question?&lt;/p&gt;

&lt;p&gt;Likewise, &lt;strong&gt;How?&lt;/strong&gt; is rarely a good question to ask. Knowing &lt;em&gt;how&lt;/em&gt; to do something is different from being &lt;em&gt;able&lt;/em&gt; to do it. The answers to &lt;strong&gt;How?&lt;/strong&gt; questions are best answered by not asking it directly, but rather by finding an expert who already possesses the knowledge of &lt;strong&gt;How?&lt;/strong&gt;, and simply watch that master at work. To achieve this level of mastery takes years upon years of honing one’s craft, and the answer to &lt;strong&gt;How?&lt;/strong&gt; is rarely succinctly communicable, therefore frustrating both the mentor and the apprentice. Instead, just &lt;strong&gt;Watch and learn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hopefully, at whatever institution you are in, you are surrounded by individuals who have mastered the &lt;strong&gt;How?&lt;/strong&gt; and you can find opportunities to learn from them. If not, there are plentiful and abundant (not to mention, free) resources online to learn &lt;strong&gt;How?&lt;/strong&gt;, such as YouTube. So, really, &lt;strong&gt;How?&lt;/strong&gt; is a pretty bad question to ask since it is actually a series of &lt;strong&gt;What?&lt;/strong&gt; questions in disguise. Instead of asking &lt;strong&gt;How?&lt;/strong&gt;, just observe those who already know how and &lt;em&gt;do&lt;/em&gt;, and as you observe, find opportunities to ask them, &lt;strong&gt;Why?&lt;/strong&gt;. And, if they are not available, a still better question is, &lt;strong&gt;Where?&lt;/strong&gt; can I find the resources to learn &lt;strong&gt;How?&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Debugging Address already in use errors</title>
   <link href="http://www.jonathantsai.com/2019/07/31/debugging-address-already-in-use-errors"/>
   <updated>2019-07-31T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2019/07/31/debugging-address-already-in-use-errors</id>
   <content type="html">
&lt;p&gt;All too frequent an occurrence in the development lifecycle is doing some work, closing up your laptop/suspending the machine, and coming back to your work hours or days later.&lt;/p&gt;

&lt;p&gt;As you try to start up the local webserver or API server, you get cryptic error messages like the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Address already in use&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Another process is already listening on port&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Port xyzw is currently used by another application&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OSError: [Errno 98] Address already in use&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.socket.bind(self.server_address)&lt;/code&gt; blah blah blah blah&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And after checking all of your open terminal windows, that you have not in fact any running or killable-processes…&lt;/p&gt;

&lt;p&gt;At this point, the &lt;strike&gt;n00b&lt;/strike&gt; naive way to fix this problem is to simply restart your entire machine. To be fair, this technique works almost all of the time, but kills productivity, and forces you to save work-in-progress that’s not at a good stopping point, or worse, accidentally restart without saving your progress.&lt;/p&gt;

&lt;p&gt;But, there is a better way.&lt;/p&gt;

&lt;p&gt;Let me introduce you to a command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;netstat -tulpn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command will print out the bound network ports on your machine, and which processes and process ids are running them. To free up the port to be used by your development server once again, simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kill PID&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PID&lt;/code&gt; is the process ID.&lt;/p&gt;

&lt;p&gt;Now, how to remember this command? I haven’t figured that out yet, nor have I thought of an alias I want to save it to, that is just as memorable. The arguments &lt;em&gt;almost&lt;/em&gt; spell out “tulip”–like the flower, except missing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;, and you just add an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; to it. Maybe a mnemonic like, &lt;em&gt;“If you fix your network address port in use issue, you will smell the essence of n tulips”&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Whatever you do, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstat -tulpn&lt;/code&gt; is now a friend and welcome companion in my software toolbox.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Debugging like a Boss with Slack</title>
   <link href="http://www.jonathantsai.com/2019/07/11/debugging-like-a-boss-with-slack"/>
   <updated>2019-07-11T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2019/07/11/debugging-like-a-boss-with-slack</id>
   <content type="html">
&lt;p&gt;&lt;em&gt;Edit: This article also got published on the &lt;a href=&quot;https://blog.goodaudience.com/debugging-like-a-boss-with-slack-59e711079ddd&quot;&gt;GoodAudience Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve been using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println&lt;/code&gt; debugging since forever. It’s the best! It’s minimal, is the least surprising form of debugging, and allows you to set-it-and-forget it. I’ve also used interactive debuggers before, but when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println&lt;/code&gt; debugging techniques are used effectively, I’d argue that step-through interactive debuggers are not necessary at all, and actually slow you down.&lt;/p&gt;

&lt;p&gt;For years now, I’ve been using an evolved form of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println&lt;/code&gt; debugging, which I affectionately call “Slack debugging,” and I’ve written various manifestations of utility/helper functions called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slack_debug&lt;/code&gt; over the years.&lt;/p&gt;

&lt;p&gt;This has been a close-kept secret for myself and select other teammates and colleagues who were curious to know what exact wizardry I was doing.&lt;/p&gt;

&lt;p&gt;And now, for the first time, I’ve decided to clean up the solution, open-source it, and share it with the world.&lt;/p&gt;

&lt;h2 id=&quot;behold-the-power-of-slack-debugging&quot;&gt;Behold, the Power of “Slack Debugging”&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;In [1]: from htk import slack_debug

In [2]: from htk import slack_debug_json

In [3]: slack_debug(&apos;This is seriously awesome!&apos;)
Out[3]: &amp;lt;Response [200]&amp;gt;

In [4]: slack_debug(&apos;Yeah, no kidding.&apos;)
Out[4]: &amp;lt;Response [200]&amp;gt;

In [5]: slack_debug_json({&apos;A&apos;:1,&apos;B&apos;:2,&apos;C&apos;:3,&apos;X&apos;:[&apos;foo&apos;,&apos;bar&apos;,&apos;baz&apos;],&apos;Z&apos;:{&apos;nested_key&apos;:&apos;nested_val
   ...: ue&apos;}}),
Out[5]: (None,)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/422501/61013274-e65e1e00-a336-11e9-90aa-44a6fd1e217c.png&quot; alt=&quot;Debugging like a Boss with Slack&quot; title=&quot;Debugging like a Boss with Slack&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And without further ado, Slack debugging is available here: &lt;a href=&quot;https://github.com/hacktoolkit/python-htk&quot;&gt;https://github.com/hacktoolkit/python-htk&lt;/a&gt; and &lt;a href=&quot;https://github.com/hacktoolkit/pyhtk-lite&quot;&gt;https://github.com/hacktoolkit/pyhtk-lite&lt;/a&gt;. (And for Ruby: &lt;a href=&quot;https://github.com/hacktoolkit/htk-rb&quot;&gt;https://github.com/hacktoolkit/htk-rb&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Love it? Hate it? Please share your thoughts and comments, or even better yet, submit pull requests to make it better!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Kill Netskope Client on Mac</title>
   <link href="http://www.jonathantsai.com/2018/10/23/kill-netskope-client-on-mac"/>
   <updated>2018-10-23T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2018/10/23/kill-netskope-client-on-mac</id>
   <content type="html">
&lt;p&gt;Netskope is a corporate security tool installed on corporate-owned devices that will introduce and override the default SSL certificate authorities by injecting its own local server.&lt;/p&gt;

&lt;p&gt;For developers, this often poses an inconvenience especially if they need to develop applications that make API calls and HTTP requests to other web services, if the developers are hitting a web service that has not previously been white-listed by the IT department. The IT department may not be in close communication with the engineering teams, and require additional information from the developers in order to configure a new whitelist rule, impacting the ability of engineers to meet deadlines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; How to &lt;strike&gt;kill&lt;/strike&gt; temporarily disable Netskope&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo launchctl unload /Library/LaunchDaemons/com.netskope.stagentsvc.plist&lt;/code&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>TIL: pbcopy</title>
   <link href="http://www.jonathantsai.com/2018/10/03/til-pbcopy"/>
   <updated>2018-10-03T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2018/10/03/til-pbcopy</id>
   <content type="html">
&lt;p&gt;&lt;em&gt;TIL&lt;/em&gt;: Discovered a super game changing technique. From command line, you can copy contents of any file to Mac OS clipboard by running this command:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat yourfilename.txt | pbcopy&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Open your other application, press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Command+V&lt;/code&gt; to paste as normal&lt;/li&gt;
  &lt;li&gt;Profit. Immensely.&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>When Slack links don't work, Restart Google Chrome</title>
   <link href="http://www.jonathantsai.com/2018/03/01/when-slack-links-dont-work-restart-google-chrome"/>
   <updated>2018-03-01T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2018/03/01/when-slack-links-dont-work-restart-google-chrome</id>
   <content type="html">
&lt;p&gt;Periodically, web hyperlinks in Slack would stop working.&lt;/p&gt;

&lt;p&gt;The fix is surprisingly quite simple: Restart Google Chrome.&lt;/p&gt;

&lt;p&gt;The best way to restart Google Chrome safely and preserve your current open tabs is to type into the address bar:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://restart&lt;/code&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Is there an engineering talent shortage in Silicon Valley?</title>
   <link href="http://www.jonathantsai.com/2017/10/16/is-there-an-engineering-talent-shortage-in-silicon-valley"/>
   <updated>2017-10-16T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2017/10/16/is-there-an-engineering-talent-shortage-in-silicon-valley</id>
   <content type="html">
&lt;p&gt;Yes, there is a huge engineering talent shortage in Silicon Valley.
There are more companies trying to build disruptive technology that have
greater hiring needs than the available supply of engineering talent.&lt;/p&gt;

&lt;p&gt;I currently work at &lt;a href=&quot;http://iterable.com&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt;,
and we are as of present a Series B company with around $30M raised
total. We get hundreds of applicants a week, but few of them are
qualified enough. I joined as the 3rd engineer of the company and now we
are around 20 engineers; virtually every single engineering hire on our
team has been an internal referral.&lt;/p&gt;

&lt;p&gt;A company like Iterable is able to reward any qualified engineer quite
handsomely; but for these top candidates, they are often like the
proverbial kid-in-a-candy-shop and are highly sought after by multiple
companies working on the most exciting, cutting edge technologies.&lt;/p&gt;

&lt;p&gt;Iterable is poised to be a unicorn or deca-corn some day, as we are
quickly becoming one of the essential, best-in-class tools for
omni-channel growth marketing automation.&lt;/p&gt;

&lt;p&gt;I love to reason and explain concepts using analogies. If you look at
the world of professional sports, such as basketball or baseball, you
will know that it seems every year, only a few teams consistently make
it to the playoffs, semi-finals, and then finals. The teams that make it
to the post-season are a small fraction of the total number of teams in
the league; an even smaller fraction are serious championship
contenders.&lt;/p&gt;

&lt;p&gt;If you equate working at a software company in Silicon Valley to being
an athlete that makes it to the MLB or NBA, then you will have a very
good idea of what people are talking about when it comes to shortage of
engineering talent. Many players are simply happy and consider it to be
a life achievement to make it to the pro-leagues; only some of the
players who are the hungriest and want to achieve greater success are
willing to put in the hard work and dedication to reach the upper
echelons of the game—whether it be basketball, or baseball, or software
engineering.&lt;/p&gt;

&lt;p&gt;Furthermore, just as you have specialist positions in basketball, the
same also applies to software companies. To be a championship contender
(e.g. IPO, $1B+ exit, etc), you need the absolute best, top-notch talent
in each position — backend engineers, frontend engineers, infrastructure
engineers, security engineers, mobile engineers, etc, and finding top
notch talent to fill each of these positions is quite challenging and
difficult. It takes years and years to hone and finely tune the skills
in any particular area, such that for any position, there are only a few
candidates who possess the skills, and then even fewer who are available
and not already committed to another team, or working on their own
startup.&lt;/p&gt;

&lt;p&gt;Hence, it is quite safe to say that there is a huge shortage of
engineering talent in Silicon Valley.&lt;/p&gt;

&lt;p&gt;Edit:&lt;/p&gt;

&lt;p&gt;Initially I did not think to make this a “recruiting” pitch for Iterable
(seriously), but just to give an idea of how good we have it, here are
some of our perks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Free lunches every day, catered by &lt;a href=&quot;https://www.zesty.com/&quot; target=&quot;_blank&quot;&gt;Zesty&lt;/a&gt;,
from a variety of top eateries in San Francisco. Fridays we can eat
out anywhere, with the team, and just &lt;a href=&quot;https://www.expensify.com&quot; target=&quot;_blank&quot;&gt;Expensify&lt;/a&gt; it.
On several occasions we’ve eaten at places where it cost more than
$30 per head.&lt;/li&gt;
  &lt;li&gt;Quarterly stipend of $300 for personal / professional development —
I use it to buy books, audio books, and an Audible subscription —
for any employee, not just engineers.&lt;/li&gt;
  &lt;li&gt;Monthly stipend of $160 for gym membership. I use it for gym
membership and workout sessions with a personal trainer — for
all employees.&lt;/li&gt;
  &lt;li&gt;Quarterly team off-sites — this past week we did an escape room as
an engineering team, for team bonding&lt;/li&gt;
  &lt;li&gt;Semi-annual (twice a year) company outings — an annual sailing trip
on the San Francisco Bay, and an all-expenses-paid company retreat
(we went to Bodega Bay this past year)&lt;/li&gt;
  &lt;li&gt;Monthly all-hands and office happy hour with lots of beer and
delicious food. I don’t really drink, and there are lots of
non-alcoholic options&lt;/li&gt;
  &lt;li&gt;Fully stocked kitchen, refrigerators, and snacks with fresh fruit,
kombucha, Stumptown coffee, Quest protein bars, Krave beef jerky,
Tcho chocolate, etc luxurious snacks&lt;/li&gt;
  &lt;li&gt;Expense &lt;strong&gt;ANY&lt;/strong&gt; computer hardware, monitors, or desktop equipment.
Some of my teammates have dual 34″ ultra widescreen curved monitors
and $300 mechanical ergonomic keyboards and vertical mice&lt;/li&gt;
  &lt;li&gt;Expense &lt;strong&gt;ANY&lt;/strong&gt; resources needed (software, IDE, books)&lt;/li&gt;
  &lt;li&gt;Flexible vacation, flexible hours, and flexible location (remote)&lt;/li&gt;
  &lt;li&gt;Interesting guest speakers and world-class learning opportunities —
the former CEO of Twitter, Dick Costolo, came in for a fireside chat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some photos from the above mentioned team outings: &lt;a href=&quot;https://www.flickr.com/photos/jontsai8601/collections/72157670015331006/&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even with all of these amazing perks, not to mention being able to work
with some of the &lt;a href=&quot;https://iterable.com/company/&quot; target=&quot;_blank&quot;&gt;top minds and high achievers in the industry&lt;/a&gt;,
we have been having such a hard time finding engineers that meet our bar
for good talent &lt;strong&gt;and&lt;/strong&gt; good culture &lt;strong&gt;and&lt;/strong&gt; team players (you see, just
like in sports, sometimes you have superstar athletes who are not team
players, and these would be simply toxic to your organization).&lt;/p&gt;

&lt;p&gt;Iterable is currently ranked at #1 on the &lt;a href=&quot;http://saas1000.com/&quot; target=&quot;_blank&quot;&gt;SaaS1000&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re out there, would you please DM me?&lt;/p&gt;

&lt;p&gt;Edited Oct 27, 2017:&lt;/p&gt;

&lt;p&gt;Someone just shared this article with me that describes the housing
crisis not just in Silicon Valley, but in all of California: &lt;a href=&quot;https://www.bisnow.com/national/news/affordable-housing/housing-crisis-could-tarnish-the-golden-states-economic-future-80556&quot; target=&quot;_blank&quot;&gt;https://www.bisnow.com/national/…&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a severe engineering talent shortage in Silicon Valley, and
that goes hand-in-hand with the housing shortage in California.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;btn quora-link&quot; href=&quot;https://www.quora.com/Is-there-an-engineering-talent-shortage-in-Silicon-Valley/answer/Jonathan-Tsai&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;
  &lt;i class=&quot;fa fa-quora&quot;&gt;&lt;/i&gt;
  View original question on Quora
&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Communicating with Engineers: How to File Good JIRA Tickets</title>
   <link href="http://www.jonathantsai.com/2017/10/12/communicating-with-engineers-how-to-file-good-jira-tickets"/>
   <updated>2017-10-12T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2017/10/12/communicating-with-engineers-how-to-file-good-jira-tickets</id>
   <content type="html">
&lt;p&gt;By Jonathan Tsai (&lt;a href=&quot;http://twitter.com/jontsai&quot;&gt;@jontsai&lt;/a&gt; | &lt;a href=&quot;https://linkedin.com/in/jontsai&quot;&gt;https://linkedin.com/in/jontsai&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Original Permalink (working Quip doc) : &lt;a href=&quot;https://quip.com/bb9rA9Cpmvax&quot;&gt;Communicating with Engineers: How to File Good JIRA Tickets&lt;/a&gt;&lt;br /&gt;
License: &lt;a href=&quot;https://creativecommons.org/licenses/by/3.0/&quot;&gt;CC BY 3.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally written as part of a mini-training for the customer success team at &lt;a href=&quot;http://iterable.com/&quot;&gt;Iterable&lt;/a&gt; in July 2017. For the most updated version of this article, view the Quip version at the above permalink.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit: This article also got published on the &lt;a href=&quot;https://blog.goodaudience.com/communicating-with-engineers-how-to-file-good-jira-tickets-f9f38a5a675d&quot;&gt;GoodAudience Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;purpose&quot;&gt;Purpose&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;“Those who fail to learn from history are doomed to repeat it.” - George Santayana&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Engineers build things, and also fix stuff when things break. Everyone’s time is valuable, so how can we effectively capture and document issues that come up, so that we make the product better, and learn as a team rather than “repeat the mistakes of history.”&lt;/p&gt;

&lt;h2 id=&quot;common-pitfalls-to-avoid&quot;&gt;Common Pitfalls to Avoid&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Conflating issues (that shouldn’t be conflated)&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Symptoms&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;Combining multiple issues into one JIRA ticket.&lt;/li&gt;
          &lt;li&gt;This is happening for me, too!&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Why it happens&lt;/strong&gt;: Upon initial inspection or cursory examination, two or more issues can appear to have the same cause. However&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;How this hurts&lt;/strong&gt;: If all reported under one issue, we may solve one problem but unintentionally overlook others that got masked.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Recent examples&lt;/strong&gt;: Emails being sent out slowly for Spotify&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Actual root causes&lt;/strong&gt;: Three separate issues, actually, with 3 different solutions
        &lt;ul&gt;
          &lt;li&gt;CloudAMQP memory failure&lt;/li&gt;
          &lt;li&gt;Unhandled exception in our messaging dequeuing code causes processor&lt;/li&gt;
          &lt;li&gt;Inadvertently downgraded date-timezone library dependency, which failed to parse a particular timezone&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Recommended alternative courses of action&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;Do some research to pinpoint when (time), where (context: app? mobile? API?), and how (what the user was doing)&lt;/li&gt;
          &lt;li&gt;Capture the major themes&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Prematurely filing a ticket / fragmenting tickets (opposite of conflating issues)&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Symptoms&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;You’re filing too many tickets&lt;/li&gt;
          &lt;li&gt;The rate at which you open tickets far exceeds the rate at which they get closed or completed (optimal ratio is hard to specify, but maybe 3:1 or 5:1, but not definitely 10:1 or 20:1)&lt;/li&gt;
          &lt;li&gt;The ticket has already previously been filed&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Why this happens:&lt;/strong&gt;
        &lt;ul&gt;
          &lt;li&gt;Want to avoid conflating issues&lt;/li&gt;
          &lt;li&gt;Did not research if an existing known issue&lt;/li&gt;
          &lt;li&gt;Did not consult or check with other teammates&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;How this hurts&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;Too many issues get logged, but never worked on.&lt;/li&gt;
          &lt;li&gt;Generates a lot of noise and distraction&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Recommended alternative courses of action&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;Talk to teammates and if at least 1 or 2 other CS persons agree it should be a ticket, or 1 other engineer, then file it&lt;/li&gt;
          &lt;li&gt;Do some research whether an existing ticket already exists. This may take more than a basic search or two. Scan through past 2 weeks at least, and search a couple of most likely specific keywords.&lt;/li&gt;
          &lt;li&gt;When it does happen, find the other ticket, enhance the ticket with more detail, and resolve one as duplicate.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Providing too much detail&lt;/strong&gt;.
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Symptoms&lt;/strong&gt;: Parroting the exact request from the customer&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Why it happens&lt;/strong&gt;: You feel inundated or overwhelmed with customer requests, so you report them in JIRA or to an engineering Slack channel for assistance&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;How this hurts&lt;/strong&gt;: CS is the first line of defense for engineering team, otherwise engineers wouldn’t be able to focus on making stuff better and spend most of the time troubleshooting. Engineers aren’t necessarily better problem solvers than CS, so they would start to feel inundated or overwhelmed, too. The engineer would have to&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Recommended alternative courses of action&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;Imagine you are a 911 operator/dispatcher. How would you field calls?&lt;/li&gt;
          &lt;li&gt;Do some translation or summarization of the actual issues at hand. No need to provide a diagnosis&lt;/li&gt;
          &lt;li&gt;Perform some high-level filtering, triaging, and pattern matching before filing a JIRA ticket or hitting up Slack channel&lt;/li&gt;
          &lt;li&gt;Ask a teammate or someone in person (counter-intuitive, yes, but a poorly researched JIRA ticket or Slack message disrupts more people, since &lt;em&gt;N&lt;/em&gt; individuals need to read and process that message); an alternate could be using Direct Messages in Slack over blasting a channel.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Providing too little detail&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Symptoms&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;JIRA ticket is really short, or took you less than 10 minutes to create.&lt;/li&gt;
          &lt;li&gt;Engineer follows up with more questions. You answer some questions, and more questions are asked.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;How this hurts&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;And endless cycle of questions with no further clarity being established.&lt;/li&gt;
          &lt;li&gt;Or, the JIRA ticket never gets addressed at all, because it wasn’t actionable.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Recommended alternative courses of action&lt;/strong&gt;: A good JIRA ticket:
        &lt;ul&gt;
          &lt;li&gt;Should take at least 5, maybe 10-15 minutes to create, if not longer&lt;/li&gt;
          &lt;li&gt;Is comprehensible by any engineer&lt;/li&gt;
          &lt;li&gt;Actionable. The problem is succinctly described, and it is clear what the next steps are.&lt;/li&gt;
          &lt;li&gt;Tracks only one issue or actionable item. Only has “one user story.”&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Not filing a ticket&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Symptoms&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;You have lots of conversations about an issue.&lt;/li&gt;
          &lt;li&gt;You’ve already spent 20-30 minutes thinking or talking about an issue&lt;/li&gt;
          &lt;li&gt;You’ve already spent 20-60 minutes trying to address and issue on your own, and it is still not resolved&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Why this happens&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;You get caught up in the moment or simply forget&lt;/li&gt;
          &lt;li&gt;Not wanting to file too many tickets&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Recommended alternative courses of action&lt;/strong&gt;:
        &lt;ul&gt;
          &lt;li&gt;File a ticket!&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Include a good title&lt;/strong&gt;. A good title describes a course of action, rather than the symptom of the problem. Focus on solution, rather than the problem&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Include a good description.&lt;/strong&gt;
            &lt;ul&gt;
              &lt;li&gt;One sentence summary that answers several of “Who? What? When? Where? Why? How?”&lt;/li&gt;
              &lt;li&gt;A short 2-4 sentence paragraph of the expanded problem and proposed solution&lt;/li&gt;
              &lt;li&gt;Any supporting details in a bulleted list
                &lt;ul&gt;
                  &lt;li&gt;Links to the actual issue in the product or app&lt;/li&gt;
                  &lt;li&gt;Links to customer conversations, Slack conversations&lt;/li&gt;
                  &lt;li&gt;Links to supporting documentation&lt;/li&gt;
                &lt;/ul&gt;
              &lt;/li&gt;
              &lt;li&gt;Additional attachments, key snippets of conversations or key pieces of “evidence” gathered or research performed, or troubleshooting steps already taken&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;choosing-good-titles&quot;&gt;Choosing Good Titles&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is one of the MOST IMPORTANT things you can do.&lt;/strong&gt; You may not think it, but software engineers spend a lot of time reading every day. Reading code, reading tickets, reading Slack, etc. Therefore, choosing a good title for the ticket will have significant effect on comprehension and speed of delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good title&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Describes a course of action, rather than the symptom of the problem&lt;/li&gt;
  &lt;li&gt;Describes the MVP. Some people like to write user stories, but I’m not a fan.&lt;/li&gt;
  &lt;li&gt;Usually &lt;strong&gt;starts with a verb&lt;/strong&gt;, stated in the form of an imperative, as if authoritative:
    &lt;ul&gt;
      &lt;li&gt;Simply, “Do this” is &lt;strong&gt;much preferred&lt;/strong&gt; to “Can you do this because So and So needs this or that?”&lt;/li&gt;
      &lt;li&gt;This is not rude; it’s succinct and to the point&lt;/li&gt;
      &lt;li&gt;The reasoning and background should go in the description.&lt;/li&gt;
      &lt;li&gt;Engineers can push back or discourse on it in the comments, with their own reasoning&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Important:&lt;/strong&gt; No verb implies no action, and therefore, none will be taken.&lt;/li&gt;
  &lt;li&gt;JIRA ticket titles should not be nouns, generally.
    &lt;ul&gt;
      &lt;li&gt;For new features:
        &lt;ul&gt;
          &lt;li&gt;Bad: Facebook Custom Audiences&lt;/li&gt;
          &lt;li&gt;Good: &lt;em&gt;Implement&lt;/em&gt; Facebook Custom Audiences&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;For improvement or requests:
        &lt;ul&gt;
          &lt;li&gt;Bad: IP Whitelisting&lt;/li&gt;
          &lt;li&gt;Verbose: Customer X is asking for a set of IPs or an IP range that they can whitelist when it comes to receiving webhook posts&lt;/li&gt;
          &lt;li&gt;Good: Provide Fixed IPs for Webhook Servers for Customers to Whitelist&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>How to pretty print JSON from CLI</title>
   <link href="http://www.jonathantsai.com/2017/10/09/how-to-pretty-print-json-from-cli"/>
   <updated>2017-10-09T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2017/10/09/how-to-pretty-print-json-from-cli</id>
   <content type="html">
&lt;p&gt;Software engineers frequently have to deal with JSON and inspect or manipulate it.&lt;/p&gt;

&lt;p&gt;There are two easy ways of pretty-printing JSON from command-line to aid in visual inspection. The first requires no installation, and the second requires a minimal installation but also provides syntax highlighting and manipulation capabilities.&lt;/p&gt;

&lt;h2 id=&quot;method-1-using-python---nothing-to-install&quot;&gt;Method 1: Using Python - Nothing to Install&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Optional&lt;/strong&gt;: Add alias to &lt;code&gt;.bashrc&lt;/code&gt;
&lt;br /&gt;
&lt;code&gt;
alias json=&apos;python -mjson.tool&apos;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ echo &apos;{&quot;cool&quot;: { &quot;story&quot; : { &quot;bro&quot; : [1, 2, 3] } } }&apos; | json
{
    &quot;cool&quot;: {
        &quot;story&quot;: {
            &quot;bro&quot;: [
                1,
                2,
                3,
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reference: &lt;a href=&quot;http://www.restlessprogrammer.com/2013/03/how-to-pretty-print-json-from-command.html&quot; target=&quot;_blank&quot;&gt;http://www.restlessprogrammer.com/2013/03/how-to-pretty-print-json-from-command.html&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;method-2-using-jq---minimal-install&quot;&gt;Method 2: Using JQ - Minimal Install&lt;/h2&gt;

&lt;p&gt;Mac install via &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;: &lt;code&gt;brew install jq&lt;/code&gt;&lt;br /&gt;
Ubuntu install &lt;code&gt;apt&lt;/code&gt;: &lt;code&gt;sudo apt-get install jq&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ echo &apos;{&quot;cool&quot;: { &quot;story&quot; : { &quot;bro&quot; : [1, 2, 3] } } }&apos; | jq .
{
    &quot;cool&quot;: {
        &quot;story&quot;: {
            &quot;bro&quot;: [
                1,
                2,
                3
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reference: &lt;a href=&quot;https://stedolan.github.io/jq/&quot; target=&quot;_blank&quot;&gt;https://stedolan.github.io/jq/&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>If you start learning to code in your mid 20s, can you make a good living off it?</title>
   <link href="http://www.jonathantsai.com/2017/07/06/if-you-start-learning-to-code-in-your-mid-20s-can-you-make-a-good-living-off-it"/>
   <updated>2017-07-06T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2017/07/06/if-you-start-learning-to-code-in-your-mid-20s-can-you-make-a-good-living-off-it</id>
   <content type="html">
&lt;p&gt;I know of several people who start learning to code in their mid 20’s, and are doing fine now.&lt;/p&gt;

&lt;p&gt;However, it’s very subjective what a “good living” is.&lt;/p&gt;

&lt;p&gt;Compared to the rest of society, programmers (anyone who writes code) and software engineers (a subset of programmers, with more emphasis on “engineer”) might make a relatively higher salary.&lt;/p&gt;

&lt;p&gt;However, just as with any field, there are different grade levels, and how well you do depends on what level you are able to attain, and that has a correlation to how much time you spend working on your craft.&lt;/p&gt;

&lt;p&gt;If you work hard and dedicate yourself to software engineering now, you can probably make a decent living in 2–3 years. Whether that will exceed your earning capacity in whatever other endeavors you may try, or are currently in, varies widely depending on your individual background, circumstances, opportunities, and experiences (and ability to grow in all of those).&lt;/p&gt;

&lt;p&gt;I think it may take 10–20 years of smart and hard work to get to the top tier of any field, software engineering included.&lt;/p&gt;

&lt;p&gt;If you’re able to keep your head down and stay focused, go for it. If you’re easily distracted by others around you and are discouraged by others who seemingly effortlessly have more success than you, this may not be the path for you.&lt;/p&gt;

&lt;p&gt;I have one friend who was working in the field of tech and executive recruiting, who was considering a switch to software engineering when he was already in his early thirties. I quoted to him a proverb, “Do you see a man skilled in his work? He will stand before kings; He will not stand before obscure men,” and he decided to stay the course and excel in what he was already good at. Fast-forward two years and he’s now crushing it as an executive recruiter, making much more impact than he would have, compared to if he had switched his career to software engineering.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;btn quora-link&quot; href=&quot;https://www.quora.com/If-you-start-learning-to-code-in-your-mid-20s-can-you-make-a-good-living-off-it/answer/Jonathan-Tsai&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;
  &lt;i class=&quot;fa fa-quora&quot;&gt;&lt;/i&gt;
  View original question on Quora
&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>IntelliJ ProTips™: Start the IntelliJ JVM process with additional memory</title>
   <link href="http://www.jonathantsai.com/2017/05/25/intellij-protips-start-the-intellij-jvm-process-with-additional-memory"/>
   <updated>2017-05-25T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2017/05/25/intellij-protips-start-the-intellij-jvm-process-with-additional-memory</id>
   <content type="html">
&lt;p&gt;Assorted tips and tricks for IntelliJ&lt;/p&gt;

&lt;h2 id=&quot;start-the-intellij-jvm-process-with-additional-memory&quot;&gt;Start the IntelliJ JVM process with additional memory&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Open the application once, normally, after a new install before editing this file&lt;/strong&gt;.&lt;br /&gt;
a. Otherwise, the signatures won’t match and the OS will think that the package has been tampered with, and security settings will prevent launching of the application. You will then have to delete the entire application and reinstall it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Quit IntelliJ (Cmd+Q)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Applications/IntelliJ IDEA.app/Contents/bin/idea.vmoptions&lt;/code&gt;&lt;br /&gt;
a. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emacs &quot;/Applications/IntelliJ IDEA.app/Contents/bin/idea.vmoptions&quot;&lt;/code&gt;&lt;br /&gt;
    or&lt;br /&gt;
    &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vi &quot;/Applications/IntelliJ IDEA.app/Contents/bin/idea.vmoptions&quot;&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Increase the following settings from their defaults (note: only works if your machine has sufficient RAM):&lt;br /&gt;
a. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-Xms2048m&lt;/code&gt;&lt;br /&gt;
b. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-Xmx4096m&lt;/code&gt;&lt;br /&gt;
c. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX:ReservedCodeCacheSize=1024m&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Start up and enjoy your upgraded IntelliJ on steroids.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: These steps must be repeated after every upgrade.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Other options listed here: &lt;a href=&quot;http://tomaszdziurko.pl/2015/11/1-and-the-only-one-to-customize-intellij-idea-memory-settings/&quot;&gt;http://tomaszdziurko.pl/2015/11/1-and-the-only-one-to-customize-intellij-idea-memory-settings/&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Enriched objects</title>
   <link href="http://www.jonathantsai.com/2016/09/21/enriched-objects"/>
   <updated>2016-09-21T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2016/09/21/enriched-objects</id>
   <content type="html">
&lt;p&gt;random: my new favorite coding adjective recently is “enriched”&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;val enrichedObj = obj ++ someAdditionalStuffs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;reminiscent of / inspired by &lt;a href=&quot;https://en.wikipedia.org/wiki/Enriched_uranium&quot;&gt;https://en.wikipedia.org/wiki/Enriched_uranium&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Universal Directives for Shell Scripts</title>
   <link href="http://www.jonathantsai.com/2015/10/12/universal-directives-for-shell-scripts"/>
   <updated>2015-10-12T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2015/10/12/universal-directives-for-shell-scripts</id>
   <content type="html">
&lt;p&gt;When writing shell scripts or other types of executables, you often put a directive at the top of the file to indicate which kind of interpreter to use.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/bin/python&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Oh wait, that didn’t work?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/usr/bin/python&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Okay, that worked on my Mac. But why all the guesswork? There is another command that is better.&lt;/p&gt;

&lt;p&gt;Introducing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt;. Use it as a directive like so:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/usr/bin/env python&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And if it’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; script:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/usr/bin/env bash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt; is kinda like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;which&lt;/code&gt;. It takes the first argument and figures out the full path of that, and uses it as the directive&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man env&lt;/code&gt; to read more about it.&lt;/p&gt;

&lt;p&gt;Basically using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/usr/bin/env INTERPRETER&lt;/code&gt; makes your script more &lt;em&gt;portable&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;portability&lt;/em&gt; (adj.)&lt;br /&gt;
the ability to run your script anywhere, on any OS/platform&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The holy grail goal of programming/CS = &lt;em&gt;write once, run everywhere&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is somewhat circular in that it depends on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt; living inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/bin/&lt;/code&gt;, but I believe it is a &lt;em&gt;convention&lt;/em&gt; that is much more rarely broken than where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; lives.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;convention&lt;/em&gt; (n.)&lt;br /&gt;
another CS vocab word.&lt;br /&gt;
The quality of being predictable, doing expected things.&lt;/p&gt;

  &lt;p&gt;e.g you walk into a dark room, you expect the light switch to be near the doorway opposite of the door hinge, not a mousetrap clamping down on your fingers when you reach along the wall.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>The Most Valuable Programming Languages to Know for the Future</title>
   <link href="http://www.jonathantsai.com/2015/06/01/the-most-valuable-programming-languages-to-know-for-the-future"/>
   <updated>2015-06-01T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2015/06/01/the-most-valuable-programming-languages-to-know-for-the-future</id>
   <content type="html">
&lt;p&gt;Originally posted on Quora: &lt;a href=&quot;http://www.quora.com/What-is-the-most-valuable-programming-language-to-know-for-the-future-and-why/answer/Jonathan-Tsai&quot;&gt;Jonathan Tsai’s answer to What is the most
valuable programming language to know for the future and
why?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will have to agree with &lt;a href=&quot;http://www.quora.com/What-is-the-most-valuable-programming-language-to-know-for-the-future-and-why/answer/Michael-Hanson&quot;&gt;Michael Hanson’s
answer&lt;/a&gt;
as well as some of the other ones that suggest learning multiple
programming languages is more beneficial than picking &lt;em&gt;just one&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I myself am a multi-linguist of both human and programming languages. If
you randomly survey a large body of people living within various
civilizations in the world on what the most valuable human languages
are, the answers you’ll get back are likely to be &lt;strong&gt;English, Chinese,
French, Spanish&lt;/strong&gt; (not necessarily, but roughly, in that order).&lt;/p&gt;

&lt;p&gt;When you know multiple languages, learning additional ones become much
easier, because through immersion/osmosis, you are made aware of the
different types of parts of speech, recognize the cognates and
influences of one language to another, etc. That’s why you will
frequently come across Europeans who speak at least 3 languages
(English, French, and the language of their country), and Indians who
speak 3-5 languages or more (English, Hindi, the language of their
state/region, + neighboring state/region).&lt;/p&gt;

&lt;p&gt;Programming languages actually have a &lt;strong&gt;much&lt;/strong&gt; simpler syntax than human
languages. The more you know, the easier it is to learn more.&lt;/p&gt;

&lt;p&gt;I would rephrase the question as plural instead of asking for a singular
response: What are the most valuable programming &lt;strong&gt;languages&lt;/strong&gt; to know
for the future and why?&lt;/p&gt;

&lt;p&gt;If you like lists, this section is for you, but not in any particular
order:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://pythonhttp//en.wikipedia.org/wiki/Python_(programming_language)&quot;&gt;Python&lt;/a&gt; - frequently used by scientific and statistics communities and data
science; it’s a fairly easy language to learn and remember with
libraries for just about everything you would need, and a very
active development community who can whip up a library if one
doesn’t exist already. Personally, Python is really easy to me and
&lt;strong&gt;reads and writes just like pseudocode&lt;/strong&gt;. I’ve written as many as
100-200 lines on a whiteboard or a text editor in one sitting and
was able to execute the code in the interpreter without any syntax
errors.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Ruby_(programming_language)&quot;&gt;Ruby&lt;/a&gt; - I
am not a regular practitioner, but Ruby is also very powerful in
that it is an interpreted language like Python, which means that it
doesn’t need to be compiled before it’s run. A lot of famous
websites are built on, and many of my friends who are paid
handsomely, use Ruby, due to the popularity of the Rails framework
(&lt;a href=&quot;http://en.wikipedia.org/wiki/Ruby_on_Rails&quot;&gt;Ruby on Rails&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Java_(programming_language)&quot;&gt;Java&lt;/a&gt; -
This one is nice because it’s a compiled language and the compiler
can optimize the heck out of the code, and it runs really fast.
There are lots of Java practitioners, and the majority of financial
websites (banks, stock trading platforms, etc), will use Java
because of its outstanding speed and performance characteristics.
Incidentally, Java is also the language used by the Android
operating systems, so if you want to learn to write for something
like 79-80% of the world’s mobile users, this is the way to go.
(Source: 2014: &lt;a href=&quot;http://www.businessinsider.com/iphone-v-android-market-share-2014-5&quot;&gt;The iPhone 6 Had Better Be Amazing And Cheap,
Because Apple Is Losing The War To
Android&lt;/a&gt;;
2015: &lt;a href=&quot;http://www.forbes.com/sites/dougolenick/2015/05/27/apple-ios-and-google-android-smartphone-market-share-flattening-idc/&quot;&gt;Apple iOS And Google Android Smartphone Market Share
Flattening:
IDC&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://javascript/&quot;&gt;JavaScript&lt;/a&gt; - Rich web applications / the
entire frontend / look-and-feel of all modern websites. Enough said.
JavaScript is a language that is write once, runs everywhere.
Browsers going all the way back to 1998 (?) and even earlier have
been using JavaScript, and the language along with its frameworks
and ecosystem have improved tremendously over time, as well as its
performance characteristics due to progressive developments of
JavaScript runtimes (e.g.
&lt;a href=&quot;http://en.wikipedia.org/wiki/V8_(JavaScript_engine)&quot;&gt;V8&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Shell Scripting / &lt;a href=&quot;http://bash/&quot;&gt;Bash&lt;/a&gt; - Unix utilities are awesome.
They are lightweight, fast, and often times, you can just use an
existing Unix utility for doing common but advanced tasks like
searching and sorting on a single machine, and additional various
things.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://c++/&quot;&gt;C++&lt;/a&gt; / C - it still powers lots of high performance
systems, but is a bit more cumbersome to write than Java, hence the
reason that Java is the language of choice when building such
systems. However, this is a very low-level language, and the thing
about programming languages is that their interpreters and compilers
are quite meta–if you chase it up/down the stack far enough, you’ll
find that some/most of them are inevitably implemented in C. Not
much daily use, but if you like learning the nitty gritty or really
knowing what’s under the hood (analogous to learning Greek / Latin
to having a better grasp on English), then this is something you
have to learn.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Swift_(programming_language)&quot;&gt;Swift&lt;/a&gt; -
this is a new language that Apple developed over the past few years
and just launched last year (2014). I won’t go digging up the links
because you can easily find them, but the language designers
designed Swift to be a modern language that can be compiled but also
has several characteristics of interpreted languages and IDE tools
(like quick turn-around of write code-and-execute), and admittedly
it borrows a lot of ideas and nice features of other languages like
Ruby, Python, JavaScript, as well as Java and C. And it’s also
inter-operable with Objective C. Look up the WWDC videos of the
Swift language being unveiled
(&lt;a href=&quot;https://developer.apple.com/videos/wwdc/2014/&quot;&gt;https://developer.apple.com/vide…&lt;/a&gt;),
and you’ll hear loud cheering and clapping and whooping over small
features. I cheered along with the rest when I watched from home.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would be hard-pressed to give you a &lt;em&gt;ranked&lt;/em&gt; list since I don’t know
the statistics off the top of my head, but my impression is that the
above are your programming language &lt;em&gt;lingua francas&lt;/em&gt;, and most likely
the highest value in terms of popularity/ubiquity, economic value, and
utility.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;btn quora-link&quot; href=&quot;https://www.quora.com/What-are-the-best-programming-languages-to-learn-today/answer/Jonathan-Tsai&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;
  &lt;i class=&quot;fa fa-quora&quot;&gt;&lt;/i&gt;
  View original question on Quora
&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Hack GitHub Streaks</title>
   <link href="http://www.jonathantsai.com/2015/03/25/hack-github-streaks"/>
   <updated>2015-03-25T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2015/03/25/hack-github-streaks</id>
   <content type="html">
&lt;p&gt;Tonight, I was doing some research and maintenance on a couple of my open-source projects. In particular, I had recently learned about &lt;a href=&quot;http://editorconfig.org/&quot;&gt;EditorConfig&lt;/a&gt; (which BTW, is a great idea! and a standard EVERYONE should adopt), and wanted to add it to most of my projects.&lt;/p&gt;

&lt;p&gt;That brought me to &lt;a href=&quot;https://github.com/jontsai&quot;&gt;my GitHub homepage&lt;/a&gt;, and I caught a glimpse of my GitHub streak. Only 2 days! I was on a roll for a few days, and then a few days of working on private projects reset my streak.&lt;/p&gt;

&lt;p&gt;Not that it matters, anyways, but I was slightly bothered, and being the fan of automation that I am, I quickly thought up how to automate my GitHub contributions to keep my streak alive, artificially.&lt;/p&gt;

&lt;p&gt;30 minutes later, &lt;a href=&quot;https://github.com/jontsai/github-streak&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-streak&lt;/code&gt;&lt;/a&gt; is born and released to the world. I call that being diligent at slacking off.&lt;/p&gt;

&lt;p&gt;It’s a fairly straightforward project that had a few requirements/constraints as I designed it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Had to be portable, modular&lt;/li&gt;
  &lt;li&gt;Had to be cross-platform, as much as possible&lt;/li&gt;
  &lt;li&gt;Had to be really simple technology and minimal dependencies–don’t overthink it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Written in BASH/shell script (my favorite shell)&lt;/li&gt;
  &lt;li&gt;Even uses the conscientious &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/usr/bin/env bash&lt;/code&gt; shebang directive&lt;/li&gt;
  &lt;li&gt;Creates a symlink in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/cron.daily&lt;/code&gt; – probably won’t work for every system, but at least was available on my webserver and others that I’ve used in the past&lt;/li&gt;
  &lt;li&gt;The script just appends a date string to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.streak&lt;/code&gt; file (hey, a new convention!) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit&lt;/code&gt;s/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt;es&lt;/li&gt;
  &lt;li&gt;Added in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.editorconfig&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.travis.yml&lt;/code&gt; goodies&lt;/li&gt;
  &lt;li&gt;MIT licensed (my favorite open-source license)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, here you go internet, have at it: &lt;a href=&quot;https://github.com/jontsai/github-streak&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-streak&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Show some star/fork love. Plz thx kbye.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://cl.ly/image/1g1L2R241r2l&quot;&gt;&lt;img src=&quot;https://s3.amazonaws.com/f.cl.ly/items/1N3d1a1D1W1R0C0o1L0i/crappy_streak.png&quot; title=&quot;Crappy streaks be gone&quot; style=&quot;max-width: 100%;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Edit: Here are some other GitHub projects worth checking out that manipulate commit history as art:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jbranchaud/commitart&quot;&gt;https://github.com/jbranchaud/commitart&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/gelstudios/gitfiti&quot;&gt;https://github.com/gelstudios/gitfiti&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/bayandin/github-board&quot;&gt;https://github.com/bayandin/github-board&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Equality and Inequalities in JS and PHP</title>
   <link href="http://www.jonathantsai.com/2015/02/26/equality-and-inequalities-in-js-and-php"/>
   <updated>2015-02-26T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2015/02/26/equality-and-inequalities-in-js-and-php</id>
   <content type="html">
&lt;p&gt;It’s 2015, and from time to time I still see a lot of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; peppered throughout codebases that I work with, and just wanted to share a few posts that might change your mind =)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt; and its complement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!==&lt;/code&gt; and should be used like 99% of the time because it is faster and more accurate.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/359494/does-it-matter-which-equals-operator-vs-i-use-in-JavaScript-comparisons&quot;&gt;http://stackoverflow.com/questions/359494/does-it-matter-which-equals-operator-vs-i-use-in-JavaScript-comparisons&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/2401478/why-is-faster-than-in-php/3333581&quot;&gt;http://stackoverflow.com/questions/2401478/why-is-faster-than-in-php/3333581&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://jsperf.com/comparison-of-comparisons&quot;&gt;http://jsperf.com/comparison-of-comparisons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/f.cl.ly/items/3w1N2T280O032K0v203l/Image%202015-02-26%20at%209.23.35%20AM.png&quot; alt=&quot;&quot; title=&quot;Playing with JS in browser console&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; 0 == &quot;0&quot;
&amp;lt; true
&amp;gt; 0 === &quot;0&quot;
&amp;lt; false
&amp;gt; 0 == false
&amp;lt; true
&amp;gt; &quot;0&quot; == false // really?
&amp;lt; true
&amp;gt; !!&quot;0&quot;
&amp;lt; true
&amp;gt; !&quot;0&quot;
&amp;lt; false
&amp;gt; &quot;0&quot; === false
&amp;lt; false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are more perverse and mind-bending examples that are out there, but I’ll leave you with those for now.&lt;/p&gt;

&lt;p&gt;As you can see, the former &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; does type coercion, which is lazy way of doing comparisons without casting data explicitly, and can find yourself in hot water down the line. The latter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt; does type checking first, and then checks equality of the values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protip #1&lt;/strong&gt;: If you want to test a JS expression or figure out some syntax really quickly, just open your JS console in the browser inspect tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protip #2&lt;/strong&gt;: If testing for something &lt;em&gt;truthy&lt;/em&gt; or &lt;em&gt;falsy&lt;/em&gt;, a nice trick is to just do use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt; operator twice (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!!&lt;/code&gt;). As can be seen above, JS treats non-empty strings as a &lt;em&gt;truthy&lt;/em&gt; (yet, strangely, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;0&quot; == false&lt;/code&gt;)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>WordPress' New JetPacks All-Sites Dashboard</title>
   <link href="http://www.jonathantsai.com/2014/12/16/wordpress-new-jetpacks-all-sites-dashboard"/>
   <updated>2014-12-16T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2014/12/16/wordpress-new-jetpacks-all-sites-dashboard</id>
   <content type="html">
&lt;p&gt;The JetPacks 3.3 upgrade that allows you to manage and administer multiple WordPress sites from one WordPress.com account is a breeze to use!&lt;/p&gt;

&lt;p&gt;I’m not quite used to it yet and still getting acclimated, but I think it’s a great time-saver and will grow on me more over time.&lt;/p&gt;

&lt;p&gt;I can see it as a great feature to use once a site for daily workflows in updating content once a site’s major structural and design work has been done.&lt;/p&gt;

&lt;p&gt;Cheers to the WordPress team for this great feature!&lt;/p&gt;

&lt;p&gt;Read all about it in the &lt;a href=&quot;http://en.blog.wordpress.com/2014/12/16/dashboard-update/&quot;&gt;original post&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Upgrading to Ubuntu 14.04.1 LTS</title>
   <link href="http://www.jonathantsai.com/2014/09/06/upgrading-to-ubuntu-14041-lts"/>
   <updated>2014-09-06T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2014/09/06/upgrading-to-ubuntu-14041-lts</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; When upgarding from Ubuntu 12.04 to 14.04 LTS, the Apache also gets upgraded from 2.2 to 2.4. There are lots of backwards incompatible changes.&lt;/p&gt;

&lt;p&gt;I just spent the last 3 hours fixing my server after impulsively upgrading to Ubuntu 14.04.1 LTS from Ubuntu 12.04.&lt;/p&gt;

&lt;p&gt;Well, not exactly &lt;em&gt;that&lt;/em&gt; impulsively. I had seen this pestering MOTD-style message upon login for weeks now, and I figured that since 14.04.1 is a point release nearly 6 months after the initial release, it should be relatively issue-free.&lt;/p&gt;

&lt;p&gt;I had already done 3 upgrades from 12.04 LTS to 14.04 LTS… on desktops. Two had gone successfully, and one on my parents’ computer unfortunately messed up pretty badly in the middle so that some of the ubuntu-desktop stuff is just not working properly, but I digress.&lt;/p&gt;

&lt;p&gt;I figured that since it was a Friday night in addition to the above–“What the heck, why not?”–and dove right in. The OS upgrade was pretty much issue free. I let out a sigh of relief.&lt;/p&gt;

&lt;p&gt;And then my luck ran out. All of my websites didn’t load. Read: &lt;strong&gt;Nothing worked&lt;/strong&gt;. I started getting Pingdom pages and Nagios alerts non-stop.&lt;/p&gt;

&lt;p&gt;Thankfully, the Internet is a great resource and I was able to identify the problem pretty quickly. Applying the fix is what took most of the time.&lt;/p&gt;

&lt;p&gt;Most useful of all the resources that I came across was &lt;a href=&quot;http://httpd.apache.org/docs/2.4/upgrading.html&quot;&gt;Upgrading Apache 2.2 to 2.4&lt;/a&gt;. I’d recommend reading through it thoroughly if you’re going through the upgrade yourself, but here are some highlights:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Don’t be alarmed if all you’re seeing for any of your websites is just the contents of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/www/&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/www/html&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;All separate virtual host (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VirtualHost&lt;/code&gt;) files and configurations need to end in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.conf&lt;/code&gt; due to the command in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/apache2/apache2.conf&lt;/code&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IncludeOptional sites-enabled/*.conf&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;It still wasn’t pulling in my VirtualHost&lt;/li&gt;
  &lt;li&gt;Got rid of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NameVirtualHost *:80&lt;/code&gt; as the first line in all of my VirtualHost config files. It wasn’t necessary. Good riddance.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory&lt;/code&gt; permissions now need to be explicitly granted. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Require all granted&lt;/code&gt; (the old style was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Order Deny, Allow; Allow from all&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;I noticed that my websites were loading slowly, so needed to set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnableSendfile On&lt;/code&gt; in the main config file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apache2.conf&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;For my &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; sites, I needed to change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory&lt;/code&gt; permissions for the static files directory&lt;/li&gt;
  &lt;li&gt;For my custom &lt;a href=&quot;https://wordpress.org/&quot;&gt;WordPress&lt;/a&gt; sites, I needed to set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory&lt;/code&gt; permissions for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DocumentRoot&lt;/code&gt; as well as explicitly set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AllowOverride all&lt;/code&gt; to allow picking up the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.htaccess&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php5/apache2/php.ini&lt;/code&gt; has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short_open_tag&lt;/code&gt; disabled. I decided to leave it off and change my limited number of PHP applications that were using &lt;a href=&quot;http://php.net/manual/en/ini.core.php#ini.short-open-tag&quot;&gt;short open tags&lt;/a&gt;, because it’s better practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other helpful resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.ubuntu.com/TrustyTahr/ReleaseNotes&quot;&gt;https://wiki.ubuntu.com/TrustyTahr/ReleaseNotes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.spotch.com/wp/?p=28&quot;&gt;http://www.spotch.com/wp/?p=28&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://kb.openstudioproject.com/content/webstuff/apache-virtual-hosts-not-working-after-upgrading-ubuntu-1204-1404&quot;&gt;http://kb.openstudioproject.com/content/webstuff/apache-virtual-hosts-not-working-after-upgrading-ubuntu-1204-1404&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Use Intel Hardware Acceleration for Faster Android Emulation</title>
   <link href="http://www.jonathantsai.com/2013/09/20/use-intel-hardware-acceleration-for-faster-android-emulation"/>
   <updated>2013-09-20T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/09/20/use-intel-hardware-acceleration-for-faster-android-emulation</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If this is you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Android developer with an Intel-based computer&lt;/li&gt;
  &lt;li&gt;Sick of dealing with a slow emulator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install &lt;a href=&quot;http://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager&quot;&gt;Intel HAXM (Intel Hardware Accelerated Execution Manager)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I never knew this, and thought that I was doomed to a slow emulator or had to debug and deploy over a constantly plugged-in device, which could at times be inconvenient.&lt;/p&gt;

&lt;p&gt;Thanks to the guys at &lt;a href=&quot;http://thecodepath.com&quot;&gt;CodePath&lt;/a&gt; (&lt;a href=&quot;http://twitter.com/thecodepath&quot;&gt;@thecodepath&lt;/a&gt;) for this useful tip! If you’re interested in learning iOS or Android, definitely check them out.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Finally! A mobile school for engineers</title>
   <link href="http://www.jonathantsai.com/2013/09/12/finally-a-mobile-school-for-engineers"/>
   <updated>2013-09-12T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/09/12/finally-a-mobile-school-for-engineers</id>
   <content type="html">
&lt;p&gt;Got an email today about a new Android training program starting up in SF for engineers. Best of all, it’s free!&lt;/p&gt;

&lt;p&gt;I’m definitely going to check them out and try to sign up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update (2013-09-15)&lt;/em&gt;:
If you’re unable to attend the class, they may have a “course observer option where you will have access to all the materials for the class, but you just won’t participate in the live classes, discussion forums, or the group projects.”&lt;/p&gt;

&lt;p&gt;–&lt;/p&gt;

&lt;p&gt;Jonathan,&lt;/p&gt;

&lt;p&gt;I’m a co-founder of CodePath, a startup that runs iOS and Android training programs. We’ve trained hundreds of engineers at Yahoo who are moving from web development to mobile development.&lt;/p&gt;

&lt;p&gt;We’re offering the same training program to engineers in the bay area for free.  It’s a project-based course, meets twice a week for 6 weeks, and the next class begins on October 2nd (another one starts in January).  It’s an intensive program, but we’ve seen great results with it.&lt;/p&gt;

&lt;p&gt;We’re currently accepting engineers with a technical degree OR at least two years of object-oriented programming experience.  If you’re interested, sign up here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.surveymonkey.com/s/androidbootcamp2&quot;&gt;Android class, starting October 2&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.surveymonkey.com/s/jan_android_bootcamp&quot;&gt;Android class, starting January 15&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.surveymonkey.com/s/mobileiosbootcamp&quot;&gt;iOS class, starting October 2&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.surveymonkey.com/s/jan_ios_bootcamp&quot;&gt;iOS class, starting January 15&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please forward to any engineers or mailing lists that might be appropriate, additional details below.&lt;/p&gt;

&lt;p&gt;Tim&lt;/p&gt;

&lt;p&gt;–&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional Program Details&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is an evening bootcamp meaning that all sessions are held after work hours and all of the bootcamp projects can be done after hours and on the weekends. Here’s a summary of what you need to know:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Free of charge for engineers&lt;/li&gt;
  &lt;li&gt;We will select up to 30 applicants for each cohort&lt;/li&gt;
  &lt;li&gt;6-week evening bootcamp starting Oct 2nd&lt;/li&gt;
  &lt;li&gt;2 on-site sessions each week in the evening (instruction and lab, 7-9 pm)&lt;/li&gt;
  &lt;li&gt;Sessions held at Zynga SF office at 8th and Brannan&lt;/li&gt;
  &lt;li&gt;A mini-app is built each week (5 total projects)&lt;/li&gt;
  &lt;li&gt;Group project designed and developed over 6 weeks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the requirements for the course:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Physically present in San Francisco and available Mon+Wed from 7-9 pm during sessions&lt;/li&gt;
  &lt;li&gt;Technical (CS, EECS) Degree and/or 2 years of professional software development experience&lt;/li&gt;
  &lt;li&gt;8-10 additional hours every week to dedicate to projects and collaborative learning&lt;/li&gt;
  &lt;li&gt;Existing development experience with object-oriented languages&lt;/li&gt;
  &lt;li&gt;Passionate about learning and collaborating on mobile projects&lt;/li&gt;
  &lt;li&gt;Laptop with Mac OS X (iOS, Android) or Windows (Android)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read more details about this program and us &lt;a href=&quot;https://gist.github.com/nesquena/461a08d1f491613d74f4&quot;&gt;here&lt;/a&gt; and on our website at &lt;a href=&quot;http://thecodepath.com&quot;&gt;http://thecodepath.com&lt;/a&gt;. Also, you can follow us on twitter at &lt;a href=&quot;http://twitter.com/thecodepath&quot;&gt;@thecodepath&lt;/a&gt; for future updates!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Pinterest wasn't hacked</title>
   <link href="http://www.jonathantsai.com/2013/05/12/pinterest-wasnt-hacked"/>
   <updated>2013-05-12T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/05/12/pinterest-wasnt-hacked</id>
   <content type="html">
&lt;p&gt;&lt;a href=&quot;http://cheezburger.com/7452815872&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;http://i.chzbgr.com/maxW500/7452815872/h494891E7/&quot; alt=&quot;Dear Internet, I&apos;m sorry. Plz 4give me? kthxbye&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seriously though, I messed up in my &lt;a href=&quot;/2013/05/11/aws-ec2-security-vulnerability-and-pinterest-hacked/&quot;&gt;previous blog post&lt;/a&gt;. Pinterest wasn’t hacked, and I’m sorry that I jumped to conclusions when I actually stumbled upon a spammer’s C&amp;amp;C. I still feel that the actions I took at the time were correct according to the knowledge that I had, but based on what I know now, I would have approached it different (contacted Pinterest first, now that I actually have their contact info, before writing a blog post).&lt;/p&gt;

&lt;p&gt;Thankfully, &lt;a href=&quot;https://twitter.com/jonjenk&quot;&gt;Jon Jenkins&lt;/a&gt; from Pinterest did contact me:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi Jon,&lt;/p&gt;

  &lt;p&gt;I lead the engineering team at Pinterest.  I saw your blog post you wrote about Pinterest security.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.jontsai.com/2013/05/11/aws-ec2-security-vulnerability-and-pinterest-hacked/&quot;&gt;http://www.jontsai.com/2013/05/11/aws-ec2-security-vulnerability-and-pinterest-hacked/&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;For the record the site you found is not one that is associated with Pinterest in any way.&lt;/p&gt;

  &lt;p&gt;It appears that you most likely found a command interface for a Pinterest spam net.  We have systems that detect and mitigate the impact of these spam nets.  It would be convenient if you could pass along the addresses and passwords you obtained so that we can validate our spam detection systems are picking up these accounts.&lt;/p&gt;

  &lt;p&gt;Thanks,&lt;/p&gt;

  &lt;p&gt;Jon Jenkins&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And my response:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi Jon,&lt;/p&gt;

  &lt;p&gt;Thanks for reaching out to me. I later realized based on the discussions and Hacker News comments that this wasn’t Pinterest (although, there was still a small possibility that it was an internal tool written by a small team within Pinterest, or by an individual employee for a Hack Day contest, etc). I’m sorry for jumping to conclusions and accusing Pinterest of security vulnerabilities, but I had thought that just as if my friend felt he was having a heart attack, I would call 911 first and then wait for diagnosis later, rather than let the window of urgency elapse.  I’m planning on writing a follow-up blog post on that matter.&lt;/p&gt;

  &lt;p&gt;Anyway, regarding the collected addresses and passwords, I forwarded them to &lt;a href=&quot;mailto:engineering@pinterest.com&quot;&gt;engineering@pinterest.com&lt;/a&gt;, not sure if that was the correct email address or you picked it up. I just forwarded it to you again.&lt;/p&gt;

  &lt;p&gt;The majority of the spam accounts came from 3 domains, &lt;i&gt;omitted&lt;/i&gt;, although there were two accounts that weren’t from those domains (&lt;i&gt;omitted&lt;/i&gt;)&lt;/p&gt;

  &lt;p&gt;Also, not sure if it would be worth your time and effort, but maybe Pinterest could issue a subpoena to AWS for the information of the accounts/human users behind that spam net, or simply work with the Spam/Abuse teams at AWS to make sure these guys get shut down. Building systems to detect and prevent spam is a never ending cat and mouse chase, but if we could shut down some of the human elements, that might stir a greater effect?&lt;/p&gt;

  &lt;p&gt;The original discovery happened in the window of about 30 minutes to an hour before I posted this tweet:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://twitter.com/jontsai/status/333090050231373825&quot;&gt;http://twitter.com/jontsai/status/333090050231373825&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;The Elastic IP address of the C&amp;amp;C during that window:&lt;/p&gt;

  &lt;p&gt;ec2-&lt;i&gt;omitted&lt;/i&gt;.compute-&lt;i&gt;omitted&lt;/i&gt;.amazonaws.com&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thanks,&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;jontsai&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jon’s response to my response:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thanks for passing along the email addresses.  It’s always useful to get a known bad set of accounts to verify our classifiers are working as intended.&lt;/p&gt;

  &lt;p&gt;I was planning to write a blog post pointing out some of the problems in your original post.  However, if you are going to correct things that might be good enough.&lt;/p&gt;

  &lt;p&gt;Regarding the specific claims you made in the original post: 1) We salt and encrypt all the passwords in our systems; 2) Our admin servers are protected in a number of ways in addition to those you suggest in your second point, 3) Regarding your third point we use more than just passwords to restrict access to our admin applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To everyone who made comments (constructive and otherwise) on the &lt;a href=&quot;/2013/05/11/aws-ec2-security-vulnerability-and-pinterest-hacked/&quot;&gt;original post&lt;/a&gt; and on &lt;a href=&quot;https://news.ycombinator.com/item?id=5689821&quot;&gt;Hacker News&lt;/a&gt;, thanks. To the critics and the snide commenters, thanks for making me laugh at my own mistakes. I’m interested in learning about what others would have done differently if they were in my situation. Should I have spent more time collecting data about the spam C&amp;amp;C? What kinds of data?&lt;/p&gt;

&lt;p&gt;What kinds of action can be taken against spammers like these? Is it worth having lawyers issue cease &amp;amp; decist letters and takedowns to the owners of those domains, knowing they can just as easily pop up in more places?&lt;/p&gt;

&lt;p&gt;I certainly don’t have the time and resources (and motivation) to proactively fight against spammers, but I was hoping that Pinterest and AWS might. Also, this was a good reminder (similar to things I’ve heard in a security talk a few years ago by &lt;a href=&quot;http://xs-sniper.com/blog/&quot;&gt;Billy Rios&lt;/a&gt;) that all the spammers and malicious hackers out there are just regular programmers like you and me. They aren’t necessarily super geniuses, they use pretty much the same languages and tools we do, and as all humans do, they make mistakes (like letting anyone with the IP address access the C&amp;amp;C).&lt;/p&gt;

&lt;p&gt;It would also be interesting to see Amazon improve the way they are currently giving out Elastic IPs. AWS probably shouldn’t reassign recently released Elastic IP addresses and delay recycling for as long as possible, like how telecom companies don’t reassign discarded phone numbers until 3-6 months afterward. They could use an LRU list for assigning Elastic IPs out of regions–unless Elastic IPs really are that scarce a resource in high demand that even an LRU implementation would cause an Elastic IP to effectively get reassigned right away?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>AWS EC2 Security Vulnerability and Pinterest Hacked</title>
   <link href="http://www.jonathantsai.com/2013/05/11/aws-ec2-security-vulnerability-and-pinterest-hacked"/>
   <updated>2013-05-11T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/05/11/aws-ec2-security-vulnerability-and-pinterest-hacked</id>
   <content type="html">
&lt;p&gt;Update 2013-05-12: Pinterest wasn’t really hacked. See &lt;a href=&quot;/security/2013/05/12/pinterest-wasnt-hacked/&quot;&gt;follow up to this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;–&lt;/p&gt;

&lt;p&gt;Well, almost hacked. This is rather embarassing (for Pinterest, and maybe AWS?), in that I was able to access what seemed to be their admin page. Furthermore, I discovered through this interface that it seems they do not store passwords encrypted or salted. If I’m not mistaken, I saw usernames, emails, and passwords in plaintext. Let me describe what happened.&lt;/p&gt;

&lt;p&gt;I was migrating a Nagios monitoring server, which involved decommissioning the existing EC2 instance and releasing the Elastic IP. Because I didn’t delete my Route 53 DNS entry, and left the Nagios URL open in my browser, I soon noticed that some other content had taken the place in my browser window.&lt;/p&gt;

&lt;p&gt;Basically, the apparent Pinterest admin server had requested an Elastic IP from the useast region, and had associated itself to the IP address that I had just released.&lt;/p&gt;

&lt;p&gt;A few lessons can be learned from this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Pinterest should encrypt and salt their passwords, shame on them for not doing so.&lt;/li&gt;
  &lt;li&gt;Pinterest should put their admin server so that it is only accessible from behind a firewall. Ever heard of EC2 security groups? I set up all my servers to expose only certain ports to certain other security groups. Only the external-facing webapp servers have port 80 open to the world.&lt;/li&gt;
  &lt;li&gt;Pinterest should put a password on their admin site. Even a shared password internally on the HTTP server is better than having nothing at all.&lt;/li&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;A good hacking strategy would be to allocate&lt;/td&gt;
          &lt;td&gt;associate&lt;/td&gt;
          &lt;td&gt;disassociate&lt;/td&gt;
          &lt;td&gt;release Elastic IPs, and to squat on/poll that IP address via several common ports. I got lucky and found one immediately through a browser.&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Want proof that I actually did this? How about a screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/jontsai8601/8726964707/&quot; title=&quot;Pinterest admin site by jontsai8601, on Flickr&quot;&gt;&lt;img src=&quot;http://farm8.staticflickr.com/7365/8726964707_066174c2ed.jpg&quot; width=&quot;500&quot; height=&quot;313&quot; alt=&quot;Pinterest admin site&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;–Edit–&lt;/p&gt;

&lt;p&gt;My friend tells me that I should also mention that I was able to log in with the emails/passwords leaked. I saved around 37 of them, and tested successful logins for 2 of 2 that I tried. For discretionary purposes, those credentials will not be posted here. However, I will say that those accounts I saw in the admin interface seem to be fake hacker accounts, with the emails being mostly from the same domains.&lt;/p&gt;

&lt;p&gt;I have contacted &lt;a href=&quot;http://engineering.pinterest.com/&quot;&gt;Pinterest&lt;/a&gt; through whatever means I could find, via a Twitter @Pinterest msg and #pinterest hash tag, and also emailing &lt;a href=&quot;mailto:engineering@pinterest.com&quot;&gt;engineering@pinterest.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;–Edit #2–&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=5689821&quot;&gt;Discussion on Hacker News&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Sticky Footer Redux</title>
   <link href="http://www.jonathantsai.com/2013/02/23/sticky-footer-redux"/>
   <updated>2013-02-23T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/02/23/sticky-footer-redux</id>
   <content type="html">
&lt;p&gt;I call myself a generalist, but feel that I’m more adept at backend development than web frontend, so I’m always amazed by the simplicity and cleverness of really cool HTML/CSS/JS techniques out there.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://timothy-long.com/responsive-sticky-footer/&quot;&gt;Responsive Sticky Footer&lt;/a&gt; by Timothy Long [&lt;a href=&quot;http://timothy-long.com/examples/responsive-sticky-footer/&quot;&gt;demo&lt;/a&gt;] [&lt;a href=&quot;https://github.com/timothylong/Responsive-Sticky-Footer&quot;&gt;code&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This was very informative. I have yet to use this solution (maybe when I need a dynamically-sized footer), but it seems to be very promising–not to mention that the solution is very clever.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cssstickyfooter.com/&quot;&gt;CSSSitckyFooter&lt;/a&gt; [&lt;a href=&quot;http://www.cssstickyfooter.com/style.css&quot;&gt;code&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ve been using this exact technique for several (dozens) websites, and it’s a tried-and-true method.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Another &lt;a href=&quot;http://ryanfait.com/sticky-footer/&quot;&gt;CSS Sticky Footer&lt;/a&gt; by Ryan Fait [&lt;a href=&quot;http://ryanfait.com/sticky-footer/layout.css&quot;&gt;code&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I haven’t used this one, but it’s neat to see the code in action and how few lines it requires.&lt;/p&gt;

&lt;p&gt;On a separate note, I recently started using &lt;a href=&quot;http://lesscss.org/&quot;&gt;LESS&lt;/a&gt;, a dynamic stylesheet language which allows you to write less CSS by using variables and functions. I can’t emphasize enough that it’s a HUGE timesaver. It’s a must-have for any project.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Domino's ShopRunner Flub</title>
   <link href="http://www.jonathantsai.com/2013/02/06/dominos-shoprunner-flub"/>
   <updated>2013-02-06T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/02/06/dominos-shoprunner-flub</id>
   <content type="html">
&lt;p&gt;On Sunday, February 3, 2013 (Super Bowl Sunday), Domino’s Pizza disabled ShopRunner free delivery on their site. This screenshot was taken from the &lt;a href=&quot;https://order.dominos.com/en/pages/order/?route=1#/locations/search/&quot;&gt;Domino’s Order Page&lt;/a&gt; on Tuesday, February 5, 2013, when they added it back in.&lt;/p&gt;

&lt;p&gt;Coincidence? Sneaky Domino’s. I didn’t think to take a screenshot at the time, though. Too bad!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/jontsai8601/8449740950/&quot; title=&quot;Domino&apos;s ShopRunner Flub by jontsai8601, on Flickr&quot;&gt;&lt;img src=&quot;http://farm9.staticflickr.com/8503/8449740950_420b125ffc.jpg&quot; width=&quot;500&quot; height=&quot;325&quot; alt=&quot;Domino&apos;s ShopRunner Flub&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see what &lt;a href=&quot;https://twitter.com/jontsai/status/298995838238203905&quot;&gt;sharing&lt;/a&gt; to &lt;a href=&quot;https://twitter.com/jontsai/status/298996999091519488&quot;&gt;Twitter&lt;/a&gt; yields…&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to Clone a Car</title>
   <link href="http://www.jonathantsai.com/2013/01/24/how-to-clone-a-car"/>
   <updated>2013-01-24T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2013/01/24/how-to-clone-a-car</id>
   <content type="html">
&lt;p&gt;Today, I was looking up biking directions to my &lt;a href=&quot;http://plugandplaytechcenter.com/&quot;&gt;new office&lt;/a&gt;. I wanted to take VTA Express from Fremont to Sunnyvale, and then bike the rest of the way.&lt;/p&gt;

&lt;p&gt;So, I looked up biking directions on Google Maps, and, to have a better idea of the actual road conditions, &lt;a href=&quot;https://maps.google.com/maps?saddr=E+Java+Dr&amp;amp;daddr=37.4023654,-122.0106744+to:440+N+Wolfe+Rd,+Sunnyvale,+CA+94085&amp;amp;hl=en&amp;amp;ll=37.409778,-122.010641&amp;amp;spn=0.046292,0.092268&amp;amp;sll=37.39696,-121.997166&amp;amp;sspn=0.0463,0.092268&amp;amp;geocode=FZvXOgIdija6-A%3BFf22OgIdzkO6-CkPdCCSy7ePgDGG9szsfm79Ow%3BFXJwOgIdVTy6-Clr8jgqObaPgDH-M1x8dinBkg&amp;amp;dirflg=b&amp;amp;mra=ltm&amp;amp;t=h&amp;amp;z=14&amp;amp;layer=c&amp;amp;cbll=37.409867,-122.011946&amp;amp;panoid=bZOQBjyq7oJt34KWO7iN2w&amp;amp;cbp=12,99.89,,0,3.34&amp;amp;via=1&quot;&gt;looked at street view&lt;/a&gt;. That’s when I noticed something interesting–all the cars on the road look the same!&lt;/p&gt;

&lt;h2 id=&quot;am-i-seeing-multiples&quot;&gt;Am I seeing multiples?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/jontsai8601/8411654161/&quot; title=&quot;Car Clones by jontsai8601, on Flickr&quot;&gt;&lt;img src=&quot;http://farm9.staticflickr.com/8078/8411654161_4bfb87f11e.jpg&quot; width=&quot;500&quot; height=&quot;313&quot; alt=&quot;Car Clones&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/jontsai8601/8412752370/&quot; title=&quot;Car Clones by jontsai8601, on Flickr&quot;&gt;&lt;img src=&quot;http://farm9.staticflickr.com/8081/8412752370_57bfc44087.jpg&quot; width=&quot;500&quot; height=&quot;313&quot; alt=&quot;Car Clones&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Google Streetview car was slightly ahead and to the right of the car in the picture driving down Java Drive, and therefore, that same car was pictured in every frame of map tiles along that stretch of road.&lt;/p&gt;

&lt;p&gt;What appears to be an optical illusion is… an optical illusion created by stitching together several separate frames together.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dropbox on NTFS Mount in Ubuntu</title>
   <link href="http://www.jonathantsai.com/2012/11/07/dropbox-on-ntfs-mount-in-ubuntu"/>
   <updated>2012-11-07T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2012/11/07/dropbox-on-ntfs-mount-in-ubuntu</id>
   <content type="html">
&lt;p&gt;After I switched to Ubuntu as my primary OS (dual-boot alongside Windows XP), I had a problem syncing some Dropbox files to an NTFS mount.&lt;/p&gt;

&lt;p&gt;While some files synced without much trouble, the Dropbox icon would consistently be spinning and several files and folders would not sync.&lt;/p&gt;

&lt;p&gt;I got around to searching for the issue today “dropbox ntfs ubuntu” and found this &lt;a href=&quot;http://blog.vnox.de/2010/12/ubuntu-linux-with-dropbox-on-an-ntfs-drive/&quot;&gt;helpful blog post&lt;/a&gt; that answered my problems.&lt;/p&gt;

&lt;p&gt;tl;dr; – the fix was to simply add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uid=1000&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/fstab&lt;/code&gt; entry for the NTFS mount, because Dropbox, running as your user, tries to change permissions on the file (owned by root in the absence of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uid=1000&lt;/code&gt; option).&lt;/p&gt;

&lt;p&gt;Don’t have Dropbox yet? Why not use my &lt;a href=&quot;http://db.tt/KfzTgNs&quot;&gt;referral link&lt;/a&gt; to sign up to get a free bonus and start off with 2.25GB? Dropbox is one of the best cloud-based file-sync programs for the average user, allowing access from Windows, Linux, Mac OSX, iOS, and Android!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Apache on OSX 10.8 Mountain Lion</title>
   <link href="http://www.jonathantsai.com/2012/11/07/apache-on-osx-108-mountain-lion"/>
   <updated>2012-11-07T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2012/11/07/apache-on-osx-108-mountain-lion</id>
   <content type="html">
&lt;p&gt;I upgraded to Mountain Lion (10.8.x) from Lion (10.7.x) a few months ago, and only discovered today that Apache wasn’t working–going to http://localhost showed the default “It works!” page.&lt;/p&gt;

&lt;p&gt;A quick search for “apache mountain lion” found this helpful guide:
&lt;a href=&quot;http://www.coolestguyplanettech.com/downtown/install-and-configure-apache-mysql-php-and-phpmyadmin-osx-108-mountain-lion&quot;&gt;AMP Guide for Mac OSX 10.8&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;tl;dr; for those who know what they are doing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There is no longer a Web Sharing section in System Preferences &amp;gt; Sharing.&lt;/li&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo httpd start|stop|restart&lt;/code&gt; to control the Apache process&lt;/li&gt;
  &lt;li&gt;Main configuration file: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/apache2/httpd.conf&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DocumentRoot&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Library/WebServer/Documents&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Enable PHP by uncommenting the PHP module include in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpd.conf&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;User configuration file: create one at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;etc/apache2/users/YOURUSERNAME.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sample user conf contents:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;Directory &quot;/Users/YOURUSERNAME/Sites/&quot;&amp;gt;
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
&amp;lt;/Directory&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Cool Video of Strange Chinese Song</title>
   <link href="http://www.jonathantsai.com/2012/10/05/cool-video-of-strange-chinese-song"/>
   <updated>2012-10-05T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2012/10/05/cool-video-of-strange-chinese-song</id>
   <content type="html">
&lt;p&gt;Here’s a cool video.&lt;/p&gt;

&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;http://www.youtube.com/embed/fNF4siu5vAo&quot; frameborder=&quot;0&quot;&gt; &lt;/iframe&gt;

&lt;p&gt;The song, named &lt;em&gt;Tan Te&lt;/em&gt;, is sung by Gong LinNa. It is considered both modern and traditional Chinese music.&lt;/p&gt;

&lt;p&gt;And in case you’re wondering–no, the words don’t make any sense.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ubuntu'd out</title>
   <link href="http://www.jonathantsai.com/2012/10/04/ubuntud-out"/>
   <updated>2012-10-04T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2012/10/04/ubuntud-out</id>
   <content type="html">
&lt;p&gt;Last night, I made the best decision of my computing life, ever.&lt;/p&gt;

&lt;h3&gt;Old Setup:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Two desktop machines under my desk, two 24” LCD monitors, controlled by one keyboard and mouse via &lt;a href=&quot;http://synergy-foss.org/&quot;&gt;Synergy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Main computer was Windows XP 32-bit Core2 Quad 8200 4GB RAM (2.8GB usable after OS and video card reserved)&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Primary uses were general browsing, word processing, gaming (Starcraft 2, WoW, Diablo 3), organizing and uploading photos (Picasa + Flickr), IE-only testing, some    coding&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Secondary computer was Ubuntu 12.04 64-bit Core2 Quad 6600 6GB RAM&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Ran 4 Ubuntu VMs in VirtualBox for various development and ghetto dynamic DNS hosting during the days before I rented my current &lt;a href=&quot;http://www.linode.com/?r=65762fd9ef89c62a08eddbb4c641c9b9a5415ba9&quot;&gt;VPS from Linode&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;MacBook Air that I would use when working outside of my home office or lounging in the living room&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Transition to New Setup:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Transferred the VMs onto my main computer–turns out that I really only need 2 of the 4.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;The problem I had before was that the Core2 Quad 8200 doesn’t have Virtualization Technology enabled, so it can’t emulate 64-bit VMs even if the host machine is 64-bit, so I had to run it on the Core2 Quad 6600. Now, I only need 2 32-bit VMs that will run fine on the C2Q 8200.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Decommissioned the Ubuntu 12.04 64-bit, pulled out 2x2GB and stuck it in the main computer for 8GB RAM total&lt;/li&gt;
  &lt;li&gt;Install Ubuntu 12.04 LTS 64-bit on the main computer, can still dual-boot into Windows if I want to&lt;/li&gt;
  &lt;li&gt;Connect MacBook Air with USB hub and video dongle to secondary monitor&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Thoughts So Far:&lt;/h3&gt;
&lt;p&gt;It feels good. It feels REALLY good.&lt;/p&gt;

&lt;p&gt;Since I’ve been using Ubuntu for over 6 years now, it feels really comfortable. In the past, I had run Ubuntu exclusively on several machines, including netbooks like Dell Inspiron Mini 9 and Samsung Series 5 Chromebook.&lt;/p&gt;

&lt;h4&gt;All Pros, No Cons So Far:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Lots of good FOSS replacements for Windows-only software, like &lt;a href=&quot;http://www.valo-cd.net/&quot;&gt;VALO-CD&lt;/a&gt; replacing a bunch of stuff and &lt;a href=&quot;http://code.google.com/p/frogr/&quot;&gt;Frogr&lt;/a&gt; replacing Flickr&lt;/li&gt;
  &lt;li&gt;I already feel a productivity increase since the terminal is right there&lt;/li&gt;
  &lt;li&gt;Removed temptation to waste time playing games.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Brogrammer</title>
   <link href="http://www.jonathantsai.com/2012/10/04/brogrammer"/>
   <updated>2012-10-04T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2012/10/04/brogrammer</id>
   <content type="html">
&lt;!-- &lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;//www.youtube.com/embed/Qi_AAqi0RZM&quot; frameborder=&quot;0&quot;&gt; &lt;/iframe&gt; --&gt;
&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;//www.youtube.com/embed/BWsAQsydzR4&quot; frameborder=&quot;0&quot;&gt; &lt;/iframe&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/422501/81266528-ba183a00-8ff9-11ea-9685-389076acb6e2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;See original blog post from Twilio Con: &lt;a href=&quot;https://www.twilio.com/blog/2011/09/twilio-presents-the-definitive-brogramming-primer.html&quot;&gt;https://www.twilio.com/blog/2011/09/twilio-presents-the-definitive-brogramming-primer.html&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Code Intro</title>
   <link href="http://www.jonathantsai.com/2012/04/03/code-intro"/>
   <updated>2012-04-03T00:00:00+00:00</updated>
   <id>http://www.jonathantsai.com/2012/04/03/code-intro</id>
   <content type="html">
&lt;p&gt;I’m newer to open-sourcing projects of my own on GitHub, so it’ll take me some time to “wrap” things properly and provide some documentation and examples so that these projects are more useful to others. I think sharing is viral, and I want to thank the open-source community for making it what it is today. I love what &lt;a href=&quot;http://plusjade.com&quot;&gt;Jade Dominguez&lt;/a&gt; has done with &lt;a href=&quot;http://jekyllbootstrap.com&quot;&gt;Jekyll Bootstrap&lt;/a&gt;. Through him, I also learned about Derek Siver’s post on the &lt;a href=&quot;http://sivers.org/sharing&quot;&gt;benefits of sharing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, check out the &lt;a href=&quot;/code.html&quot;&gt;codes&lt;/a&gt;!&lt;/p&gt;
</content>
 </entry>
 
 
</feed>
