<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vicente G. Reyes</title>
    <description>The latest articles on DEV Community by Vicente G. Reyes (@highcenburg).</description>
    <link>https://dev.to/highcenburg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F126345%2F84bad9a2-d302-4943-8934-6c27a497daa1.png</url>
      <title>DEV Community: Vicente G. Reyes</title>
      <link>https://dev.to/highcenburg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/highcenburg"/>
    <language>en</language>
    <item>
      <title>When's the DEV shop coming back? I need to buy stuff before the end of June!</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Thu, 14 May 2026 06:19:27 +0000</pubDate>
      <link>https://dev.to/highcenburg/whens-the-dev-shop-coming-back-i-need-to-buy-stuff-before-the-end-of-june-55l8</link>
      <guid>https://dev.to/highcenburg/whens-the-dev-shop-coming-back-i-need-to-buy-stuff-before-the-end-of-june-55l8</guid>
      <description></description>
      <category>community</category>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Deploying Cookiecutter Django on DigitalOcean (Ubuntu 24.04 (LTS) x64)</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Sat, 09 May 2026 03:16:05 +0000</pubDate>
      <link>https://dev.to/highcenburg/deploying-cookiecutter-django-on-digitalocean-ubuntu-2404-lts-x64-1mbi</link>
      <guid>https://dev.to/highcenburg/deploying-cookiecutter-django-on-digitalocean-ubuntu-2404-lts-x64-1mbi</guid>
      <description>&lt;p&gt;A no-fluff deployment runbook for getting a Cookiecutter Django project live on DigitalOcean using Docker and Traefik. Covers the full path from droplet provisioning to a working production deployment with SSL, plus the gotchas I keep hitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-flight Checklist
&lt;/h2&gt;

&lt;p&gt;Before touching the droplet, confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookiecutter Django project generated locally with &lt;strong&gt;production = Docker&lt;/strong&gt;, &lt;strong&gt;Traefik&lt;/strong&gt; (or Nginx), &lt;strong&gt;Postgres&lt;/strong&gt;, and your email backend of choice (Mailgun, SendGrid, Anymail, etc.)&lt;/li&gt;
&lt;li&gt;Project pushed to a &lt;strong&gt;private GitHub repo&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Domain registered with DNS access&lt;/li&gt;
&lt;li&gt;DigitalOcean account ready&lt;/li&gt;
&lt;li&gt;Local SSH keypair (&lt;code&gt;~/.ssh/id_ed25519&lt;/code&gt;) ready&lt;/li&gt;
&lt;li&gt;All &lt;code&gt;.envs/.production/*&lt;/code&gt; files prepared locally (these are git-ignored and must be transferred separately)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Required env files:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.envs/.production/.django
.envs/.production/.postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate strong values for &lt;code&gt;DJANGO_SECRET_KEY&lt;/code&gt;, &lt;code&gt;DJANGO_ADMIN_URL&lt;/code&gt;, &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt;, etc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import secrets; print(secrets.token_urlsafe(64))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. Spin Up the Droplet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Image:&lt;/strong&gt; Ubuntu 24.04 (LTS) x64&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan:&lt;/strong&gt; Basic — minimum 2 GB RAM / 1 vCPU. Postgres + Django + Traefik + Redis on 1 GB will OOM during builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth:&lt;/strong&gt; SSH key (paste your &lt;code&gt;~/.ssh/id_ed25519.pub&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Closest to your users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hostname:&lt;/strong&gt; something descriptive (e.g. &lt;code&gt;myapp-prod-sg1&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the public IPv4 once it's provisioned.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. DNS Records
&lt;/h2&gt;

&lt;p&gt;In your domain registrar (or DigitalOcean DNS):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;droplet_ip&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;www&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;droplet_ip&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3600&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Traefik will provision Let's Encrypt SSL automatically once DNS resolves and ports 80/443 are open. &lt;strong&gt;Wait for DNS to propagate&lt;/strong&gt; before bringing up the stack — otherwise Let's Encrypt will rate-limit you on failed challenges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig yourdomain.com +short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Initial Server Hardening
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Connect as root
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@&amp;lt;droplet_ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update packages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a non-root user
&lt;/h3&gt;

&lt;p&gt;Replace &lt;code&gt;&amp;lt;username&amp;gt;&lt;/code&gt; with your chosen username throughout this guide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser &amp;lt;username&amp;gt;
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mirror SSH keys to the new user
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;--archive&lt;/span&gt; &lt;span class="nt"&gt;--chown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;username&amp;gt;:&amp;lt;username&amp;gt; ~/.ssh /home/&amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure UFW
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 80 needs to be open (not just 443) because Traefik uses it for the Let's Encrypt HTTP-01 challenge and to redirect HTTP traffic to HTTPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconnect as the new user
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exit
&lt;/span&gt;ssh &amp;lt;username&amp;gt;@&amp;lt;droplet_ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lock down SSH
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;PermitRootLogin&lt;/span&gt; &lt;span class="no"&gt;no&lt;/span&gt;
&lt;span class="k"&gt;PasswordAuthentication&lt;/span&gt; &lt;span class="no"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload SSH (no full reboot needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test from a &lt;strong&gt;new terminal&lt;/strong&gt; before closing the current session — if you locked yourself out, the live session is your only way back in.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Install Docker &amp;amp; Compose Plugin
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove any old versions&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt remove &lt;span class="nt"&gt;-y&lt;/span&gt; docker docker-engine docker.io containerd runc

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ca-certificates curl gnupg lsb-release

&lt;span class="c"&gt;# Add Docker's GPG key&lt;/span&gt;
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="c"&gt;# Add the repo&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# Install&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

&lt;span class="c"&gt;# Allow your user to run docker without sudo&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker compose version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Set Up GitHub Deploy Key
&lt;/h2&gt;

&lt;p&gt;Since the repo is private, the droplet needs SSH access to clone and pull.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;username&amp;gt;@yourdomain.com"&lt;/span&gt;
&lt;span class="c"&gt;# Press enter through prompts (no passphrase, default location)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the output and add it as a &lt;strong&gt;deploy key&lt;/strong&gt; on the GitHub repo:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Settings → Deploy keys → Add deploy key&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Read-only access is fine unless you're pushing from the server.&lt;/p&gt;

&lt;p&gt;Test the connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-T&lt;/span&gt; git@github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Clone the Project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
git clone git@github.com:&amp;lt;your-username&amp;gt;/&amp;lt;your-repo&amp;gt;.git
&lt;span class="nb"&gt;cd&lt;/span&gt; &amp;lt;your-repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Transfer Production Env Files
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.envs/.production/&lt;/code&gt; folder is git-ignored, so SCP it from local:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From your local machine:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp &lt;span class="nt"&gt;-r&lt;/span&gt; .envs/.production &amp;lt;username&amp;gt;@&amp;lt;droplet_ip&amp;gt;:~/&amp;lt;your-repo&amp;gt;/.envs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify on the droplet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/&amp;lt;your-repo&amp;gt;/.envs/.production/
&lt;span class="c"&gt;# Should show .django and .postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lock down permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/&amp;lt;your-repo&amp;gt;/.envs/.production/.django
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/&amp;lt;your-repo&amp;gt;/.envs/.production/.postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Configure Production Domain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Update &lt;code&gt;.envs/.production/.django&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano .envs/.production/.django
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Critical variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;DJANGO_ALLOWED_HOSTS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yourdomain.com,www.yourdomain.com&lt;/span&gt;
&lt;span class="py"&gt;DJANGO_SECURE_SSL_REDIRECT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;
&lt;span class="py"&gt;DJANGO_SERVER_EMAIL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;noreply@yourdomain.com&lt;/span&gt;
&lt;span class="py"&gt;DJANGO_DEFAULT_FROM_EMAIL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;hello@yourdomain.com&lt;/span&gt;
&lt;span class="py"&gt;MAILGUN_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;MAILGUN_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mg.yourdomain.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update &lt;code&gt;compose/production/traefik/traefik.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano compose/production/traefik/traefik.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace every instance of the placeholder domain with yours. Look for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Host(`example.com`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Host(`www.example.com`)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Let's Encrypt email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_real_email@yourdomain.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Use a real, monitored email — Let's Encrypt sends expiry warnings here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9. Build and Launch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First build takes 5–10 minutes. Watch logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml logs &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;traefik&lt;/code&gt; should successfully obtain Let's Encrypt cert (search logs for &lt;code&gt;certificate obtained&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;django&lt;/code&gt; should boot without import errors&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postgres&lt;/code&gt; should be ready and accepting connections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common first-run failures:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cert acquisition fails&lt;/strong&gt; → DNS hasn't propagated yet, or port 80 is blocked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Django can't connect to DB&lt;/strong&gt; → &lt;code&gt;.envs/.production/.postgres&lt;/code&gt; mismatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;502 from Traefik&lt;/strong&gt; → Django container crashed, check &lt;code&gt;logs django&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Run Migrations &amp;amp; Create Superuser
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Migrations&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml run &lt;span class="nt"&gt;--rm&lt;/span&gt; django python manage.py migrate

&lt;span class="c"&gt;# Superuser&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml run &lt;span class="nt"&gt;--rm&lt;/span&gt; django python manage.py createsuperuser

&lt;span class="c"&gt;# Collect static (usually handled at build time, but run if needed)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml run &lt;span class="nt"&gt;--rm&lt;/span&gt; django python manage.py collectstatic &lt;span class="nt"&gt;--noinput&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never run &lt;code&gt;makemigrations&lt;/code&gt; on the server.&lt;/strong&gt; Generate migrations locally, commit them, pull on the server, then &lt;code&gt;migrate&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  11. Update the Sites Framework
&lt;/h2&gt;

&lt;p&gt;Cookiecutter Django uses Django's Sites framework (especially for django-allauth email links). Update the default site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml run &lt;span class="nt"&gt;--rm&lt;/span&gt; django python manage.py shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.sites.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Site&lt;/span&gt;
&lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yourdomain.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your App&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  12. Smoke Test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://yourdomain.com&lt;/code&gt; loads with valid SSL (no warnings)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://yourdomain.com/&amp;lt;DJANGO_ADMIN_URL&amp;gt;/&lt;/code&gt; → admin login works&lt;/li&gt;
&lt;li&gt;Sign up flow → confirmation email arrives&lt;/li&gt;
&lt;li&gt;Password reset email arrives&lt;/li&gt;
&lt;li&gt;Static files serving (CSS/JS load, no 404s in DevTools)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http://yourdomain.com&lt;/code&gt; redirects to &lt;code&gt;https://&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  13. Updating Deployments
&lt;/h2&gt;

&lt;p&gt;For subsequent deploys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/&amp;lt;your-repo&amp;gt;
git pull
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml run &lt;span class="nt"&gt;--rm&lt;/span&gt; django python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you changed env vars, restart the django service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml restart django
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  14. Backups
&lt;/h2&gt;

&lt;p&gt;Cookiecutter Django ships with a &lt;code&gt;backup&lt;/code&gt; command for Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create backup&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml &lt;span class="nb"&gt;exec &lt;/span&gt;postgres backup

&lt;span class="c"&gt;# List backups&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml &lt;span class="nb"&gt;exec &lt;/span&gt;postgres backups

&lt;span class="c"&gt;# Restore (replace with actual backup filename)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml &lt;span class="nb"&gt;exec &lt;/span&gt;postgres restore backup_2026_05_09T00_00_00.sql.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a cron job for daily backups + offsite sync to S3 / DO Spaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /home/&amp;lt;username&amp;gt;/&amp;lt;your-repo&amp;gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.production.yml &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; postgres backup &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/&amp;lt;username&amp;gt;/backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  15. Troubleshooting Cheatsheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;500 on signup/login&lt;/td&gt;
&lt;td&gt;Email backend misconfigured&lt;/td&gt;
&lt;td&gt;Check Mailgun/SendGrid keys in &lt;code&gt;.envs/.production/.django&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;502 Bad Gateway&lt;/td&gt;
&lt;td&gt;Django container down&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker compose ... logs django&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL cert not issued&lt;/td&gt;
&lt;td&gt;DNS not propagated, port 80 blocked, or Let's Encrypt rate limit&lt;/td&gt;
&lt;td&gt;Wait, check &lt;code&gt;ufw status&lt;/code&gt;, check Traefik logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static files 404&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;collectstatic&lt;/code&gt; not run, or whitenoise misconfigured&lt;/td&gt;
&lt;td&gt;Re-run collectstatic, check &lt;code&gt;STATIC_ROOT&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; error&lt;/td&gt;
&lt;td&gt;Domain missing from env&lt;/td&gt;
&lt;td&gt;Add to &lt;code&gt;DJANGO_ALLOWED_HOSTS&lt;/code&gt;, restart django&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OOM during build&lt;/td&gt;
&lt;td&gt;Droplet too small&lt;/td&gt;
&lt;td&gt;Resize to 2GB+ or build images locally and push to registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;permission denied&lt;/code&gt; on docker socket&lt;/td&gt;
&lt;td&gt;User not in docker group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo usermod -aG docker $USER &amp;amp;&amp;amp; newgrp docker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  16. Next Steps (Optional Hardening)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD:&lt;/strong&gt; GitHub Actions workflow → SSH into droplet → &lt;code&gt;git pull &amp;amp;&amp;amp; docker compose up --build -d&lt;/code&gt;. Use repo secrets for the SSH key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring:&lt;/strong&gt; Sentry (already wired in Cookiecutter Django) + Uptime Robot for external checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs:&lt;/strong&gt; Ship to a service (Logtail, Papertrail, Datadog) instead of relying on &lt;code&gt;docker logs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets management:&lt;/strong&gt; Move from &lt;code&gt;.env&lt;/code&gt; files to Doppler, Infisical, or DO's encrypted env vars for team workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Move Postgres off the droplet to DO Managed Postgres once you have real traffic. Update &lt;code&gt;DATABASE_URL&lt;/code&gt; and you're done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN:&lt;/strong&gt; Serve static/media from DO Spaces + a CDN edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Your Django app should now be live behind HTTPS, with auto-renewing SSL, a hardened server, and a clear path for future deploys and backups. From here, the obvious next investments are CI/CD, observability, and moving your database to a managed service once traffic justifies it.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>python</category>
      <category>django</category>
    </item>
    <item>
      <title>Automating Cloudflare WARP Based on WiFi SSID (Linux Guide)</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Wed, 06 May 2026 05:53:26 +0000</pubDate>
      <link>https://dev.to/highcenburg/automating-cloudflare-warp-based-on-wifi-ssid-linux-guide-3jca</link>
      <guid>https://dev.to/highcenburg/automating-cloudflare-warp-based-on-wifi-ssid-linux-guide-3jca</guid>
      <description>&lt;p&gt;If you frequently switch between trusted and untrusted networks, manually toggling your VPN becomes tedious fast.&lt;/p&gt;

&lt;p&gt;This guide shows how to &lt;strong&gt;automatically connect or disconnect Cloudflare WARP based on your WiFi network name (SSID)&lt;/strong&gt; using NetworkManager on Linux.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Why This Matters
&lt;/h2&gt;

&lt;p&gt;Not all networks are equal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏠 &lt;strong&gt;Trusted WiFi (Home)&lt;/strong&gt; → You may not need WARP
&lt;/li&gt;
&lt;li&gt;☕ &lt;strong&gt;Public WiFi&lt;/strong&gt; → You &lt;em&gt;definitely&lt;/em&gt; want WARP
&lt;/li&gt;
&lt;li&gt;🏢 &lt;strong&gt;Office networks&lt;/strong&gt; → Might conflict with VPN routing
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of manually toggling WARP every time, we can hook into &lt;strong&gt;network state changes&lt;/strong&gt; and automate it.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ How It Works
&lt;/h2&gt;

&lt;p&gt;Linux systems using NetworkManager support &lt;strong&gt;dispatcher scripts&lt;/strong&gt;—these are triggered automatically when network events occur (e.g., connecting to WiFi).&lt;/p&gt;

&lt;p&gt;We leverage this to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect the current SSID
&lt;/li&gt;
&lt;li&gt;Apply conditional logic
&lt;/li&gt;
&lt;li&gt;Toggle WARP via CLI
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔧 Step-by-Step Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Ensure WARP CLI is Installed
&lt;/h3&gt;

&lt;p&gt;Make sure &lt;code&gt;warp-cli&lt;/code&gt; is available. Then register and test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;warp-cli register
warp-cli connect
warp-cli status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create a NetworkManager Dispatcher Script
&lt;/h3&gt;

&lt;p&gt;Dispatcher scripts live here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/etc/NetworkManager/dispatcher.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/NetworkManager/dispatcher.d/99-warp-toggle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Add Logic Based on SSID
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;INTERFACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Trigger only when a connection is established&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"up"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;SSID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;iwgetid &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"home_wifi"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Connecting WARP for &lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        warp-cli connect

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"office_wifi"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Disconnecting WARP for &lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        warp-cli disconnect

    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unknown network: &lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt; — no action taken"&lt;/span&gt;
    &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Make the Script Executable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /etc/NetworkManager/dispatcher.d/99-warp-toggle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Apply Changes
&lt;/h3&gt;

&lt;p&gt;Restart NetworkManager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart NetworkManager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or simply reconnect your WiFi.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Testing
&lt;/h3&gt;

&lt;p&gt;Switch between your networks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to &lt;code&gt;home_wifi&lt;/code&gt; → WARP should connect&lt;/li&gt;
&lt;li&gt;Connect to &lt;code&gt;office_wifi&lt;/code&gt; → WARP should disconnect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;warp-cli status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⚠️ Things to Watch Out For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requires &lt;code&gt;iwgetid&lt;/code&gt; (usually part of &lt;code&gt;wireless-tools&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Dispatcher scripts run as root&lt;/li&gt;
&lt;li&gt;Some networks may block WARP traffic&lt;/li&gt;
&lt;li&gt;Avoid rapid toggling (WARP CLI is tolerant, but don’t spam it)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🧩 Optional Enhancements
&lt;/h3&gt;

&lt;p&gt;🔹 Add Logging&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: Connected to &lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/warp-toggle.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔹 Use a case Statement (Cleaner Scaling)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="s2"&gt;"home_wifi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    warp-cli connect
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="s2"&gt;"office_wifi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    warp-cli disconnect
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No rule for &lt;/span&gt;&lt;span class="nv"&gt;$SSID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔹 Default Behavior Strategy
&lt;/h3&gt;

&lt;p&gt;You can invert the logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always connect WARP by default&lt;/li&gt;
&lt;li&gt;Explicitly disable only on trusted networks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💡 Final Thoughts
&lt;/h3&gt;

&lt;p&gt;This approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ Event-driven — no polling loops&lt;/li&gt;
&lt;li&gt;🪶 Lightweight — no extra services&lt;/li&gt;
&lt;li&gt;🔌 Extensible — plug in more automations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’re essentially turning your machine into a context-aware system—reacting intelligently to its environment.&lt;/p&gt;

&lt;p&gt;Once you get comfortable with dispatcher scripts, you can extend this pattern to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-sync files on trusted networks&lt;/li&gt;
&lt;li&gt;Trigger backups only at home&lt;/li&gt;
&lt;li&gt;Change DNS / proxies dynamically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy hacking 🚀&lt;/p&gt;

</description>
      <category>linux</category>
      <category>automation</category>
      <category>vpn</category>
      <category>networking</category>
    </item>
    <item>
      <title>I vibe coded a Design System! See it here! https://ice-design-system.vicentereyes.org See the landing page here https://ds.vicentereyes.org</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Tue, 05 May 2026 11:25:27 +0000</pubDate>
      <link>https://dev.to/highcenburg/i-vibe-coded-a-design-system-see-it-here-httpsice-design-systemvicentereyesorg-see-the-3n44</link>
      <guid>https://dev.to/highcenburg/i-vibe-coded-a-design-system-see-it-here-httpsice-design-systemvicentereyesorg-see-the-3n44</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://ice-design-system.vicentereyes.org" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;ice-design-system.vicentereyes.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://ds.vicentereyes.org/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUAVGh1IDA0IERlYyAyMDI1IDAyOjE4OjI5IFBNIFBTVHINJwcAAAIISURBVFiF7ZlNTxNBGICfbgi9bD9SbrskchE0nsymHIwHpFxQCjVqbUSBmqh%2FDCF6UAQKxCvhU5B4MMYLW%2BmHN2PanlsPpGs31hA6M2kI85zezLzZ99k3s7uT2UAkEmlwgTC6LXBeeprB71%2FlbnqcSTRmAReww1pYNVpYNVpYNVpYNVpYNZdHuFgsEY1ZRGMWN51bNBr%2F36WO37vv5ZZKYpusjoX7%2B22G4w4Arptnb%2F%2BgbZ7r5tnZ2QNgOO5g21anJQHBJZFKJb04l9tom7O%2B8bFtfqcICU9NTnhxbm29bc5qy4205neKkLBtWyRGRwA4Ps5zdPTFN18u%2F2R7exeAscQd4eUAEt4S6fQDL15ZXfPNtXY3k3kkWgqQIJyaShIOhwFY%2BrDim2veQF9fjOTEXdFSgAThYLCXp9MZ4HRZ7H86BKBQKLK5uQXA4%2FRDgsFe0VKApA%2FHzMwTL56fXwTg9cIbb2x2dlpGGUCS8LWhQe%2Fhe%2Fd%2BmUqlwsLiWwASoyMMDV6VUQaQ%2BGnOzj0DoFarMZd9xclJwTcui0Dz5Ef0XKJer%2BPEb%2BO6eW9sYOAKnw%2B2MAzxvkg%2FlzAMg%2BdZfzdfvshKkW1FWofhdDlcv%2BFQrVYJhUJ8%2B3qIaZrC14W%2FHe45I%2B9cmKZJ4cd3mZf8h8uzH%2B4WWlg1Wlg1Wlg1Wlg1Wlg1Af0nVDF%2FAK3mhU48E7dAAAAAAElFTkSuQmCC" height="44" class="m-0" width="44"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://ds.vicentereyes.org/" rel="noopener noreferrer" class="c-link"&gt;
            ice-ds — Neobrutalist React Component Library
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A neobrutalist React component library with hard shadows, bold colors, and accessible primitives. 30+ components. MIT licensed. TypeScript-first.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fds.vicentereyes.org%2Fassets%2Ffavicon-B8fnmPTF.ico" width="44" height="44"&gt;
          ds.vicentereyes.org
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>design</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Client 3 did not reply after I deployed milestone 1 for them to test. Good thing is I didn't give them the source code🤪</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Fri, 01 May 2026 12:47:40 +0000</pubDate>
      <link>https://dev.to/highcenburg/client-3-did-not-reply-after-i-deployed-milestone-1-for-them-to-test-good-thing-is-i-didnt-give-1f0a</link>
      <guid>https://dev.to/highcenburg/client-3-did-not-reply-after-i-deployed-milestone-1-for-them-to-test-good-thing-is-i-didnt-give-1f0a</guid>
      <description></description>
      <category>career</category>
      <category>discuss</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Own design system up on my domain https://ds.vicentereyes.org Now to use it on my projects!!</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Wed, 29 Apr 2026 15:45:56 +0000</pubDate>
      <link>https://dev.to/highcenburg/own-design-system-up-on-my-domain-httpsdsvicentereyesorg-now-to-use-it-on-my-projects-22cb</link>
      <guid>https://dev.to/highcenburg/own-design-system-up-on-my-domain-httpsdsvicentereyesorg-now-to-use-it-on-my-projects-22cb</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://ds.vicentereyes.org" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;ds.vicentereyes.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>design</category>
      <category>frontend</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Frontend and Backend of client #3 finally deployed to their respective cloud hosting providers! Now to test it..</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Fri, 24 Apr 2026 10:05:58 +0000</pubDate>
      <link>https://dev.to/highcenburg/frontend-and-backend-of-client-3-finally-deployed-to-their-respective-cloud-hosting-providers-now-1jl9</link>
      <guid>https://dev.to/highcenburg/frontend-and-backend-of-client-3-finally-deployed-to-their-respective-cloud-hosting-providers-now-1jl9</guid>
      <description></description>
      <category>cloud</category>
      <category>showdev</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hunting Disk Hogs on Ubuntu: A Shell Script for Finding the Largest Files</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Tue, 21 Apr 2026 13:57:42 +0000</pubDate>
      <link>https://dev.to/highcenburg/hunting-disk-hogs-on-ubuntu-a-shell-script-for-finding-the-largest-files-52gk</link>
      <guid>https://dev.to/highcenburg/hunting-disk-hogs-on-ubuntu-a-shell-script-for-finding-the-largest-files-52gk</guid>
      <description>&lt;h2&gt;
  
  
  Why this script exists
&lt;/h2&gt;

&lt;p&gt;If you've ever watched your free disk space quietly shrink over a few weeks of active development, you know the feeling: yesterday you had plenty of headroom, today your IDE is yelling about low disk space, and you have no idea what ate the difference. Active Node.js and Python projects are especially good at this — &lt;code&gt;node_modules&lt;/code&gt;, build caches, &lt;code&gt;.next&lt;/code&gt; directories, virtual environments, and compiled artifacts accumulate silently with every install and every build.&lt;/p&gt;

&lt;p&gt;This article walks through a bash script, &lt;code&gt;find_largest_files.sh&lt;/code&gt;, that scans a directory tree and writes the largest files to a timestamped text report. It's designed to be a first diagnostic tool when you're trying to answer the question &lt;em&gt;"where did all my disk space go?"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The script at a glance
&lt;/h2&gt;

&lt;p&gt;The full script is in &lt;code&gt;find_largest_files.sh&lt;/code&gt;. Here's what it does, step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes three optional arguments: search directory, number of results, and output filename.&lt;/li&gt;
&lt;li&gt;Validates that the search directory exists and that the count is a positive integer.&lt;/li&gt;
&lt;li&gt;Writes a header to the output file with timestamp, host, user, and scan parameters.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;find&lt;/code&gt; to list every regular file under the target directory, along with its size in bytes.&lt;/li&gt;
&lt;li&gt;Sorts the list numerically by size (largest first), takes the top N, and converts byte counts into human-readable units (K/M/G/T).&lt;/li&gt;
&lt;li&gt;Appends the formatted list to the report and prints it to your terminal.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key design decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;find -printf&lt;/code&gt; instead of &lt;code&gt;ls&lt;/code&gt; or &lt;code&gt;du&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SEARCH_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\t%p\n'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;find -printf&lt;/code&gt; outputs size (&lt;code&gt;%s&lt;/code&gt;) in bytes and the full path (&lt;code&gt;%p&lt;/code&gt;), tab-separated. This matters for three reasons: byte-level precision means sorting stays accurate; tab separation survives filenames with spaces; and restricting to &lt;code&gt;-type f&lt;/code&gt; means we report on actual files, not directory aggregates the way &lt;code&gt;du&lt;/code&gt; would.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pruning pseudo-filesystems
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; /proc &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; /sys &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; /dev &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; /run &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; /snap &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-prune&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;/sys&lt;/code&gt;, &lt;code&gt;/dev&lt;/code&gt;, and &lt;code&gt;/run&lt;/code&gt; are kernel-provided virtual filesystems. They contain "files" whose reported sizes are often meaningless (a &lt;code&gt;/proc/kcore&lt;/code&gt; can appear to be 128 TB). &lt;code&gt;/snap&lt;/code&gt; is pruned because snap mount points produce duplicate entries. Skipping all five keeps the report focused on real files on your actual disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silencing permission errors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a full &lt;code&gt;/&lt;/code&gt; scan, &lt;code&gt;find&lt;/code&gt; will hit directories your user can't read and print &lt;code&gt;Permission denied&lt;/code&gt; for every one of them — noise that can bury the real output. Redirecting stderr to &lt;code&gt;/dev/null&lt;/code&gt; cleans that up. Run the script with &lt;code&gt;sudo&lt;/code&gt; if you want complete coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human-readable sizes in &lt;code&gt;awk&lt;/code&gt;, not &lt;code&gt;find&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We sort the raw byte counts first, &lt;em&gt;then&lt;/em&gt; format them in &lt;code&gt;awk&lt;/code&gt;. If we formatted early (e.g. via &lt;code&gt;find ... | sort -h&lt;/code&gt;), we'd either give up precision or depend on &lt;code&gt;sort -h&lt;/code&gt; parsing variants. Keeping bytes for sorting and converting after is simpler and portable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it against your scenario
&lt;/h2&gt;

&lt;p&gt;You mentioned roughly 30 GB of disk disappeared recently while you've been building &lt;code&gt;mortgage_system&lt;/code&gt; and &lt;code&gt;mortgage_frontend&lt;/code&gt; with Claude. Those kinds of projects are classic sources of silent disk bloat. Here's how I'd approach the investigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Get the big picture
&lt;/h3&gt;

&lt;p&gt;Start at root to confirm whether the missing space is actually inside your project folders, or somewhere else entirely (logs, Docker, snap revisions, trash, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./find_largest_files.sh / 50 full_scan.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the 50 biggest files system-wide. If most of them are under &lt;code&gt;/home/you/mortgage_system/...&lt;/code&gt; or &lt;code&gt;/home/you/mortgage_frontend/...&lt;/code&gt;, your instinct was right. If the top entries are elsewhere — &lt;code&gt;/var/lib/docker&lt;/code&gt;, &lt;code&gt;/var/log&lt;/code&gt;, &lt;code&gt;~/.cache&lt;/code&gt;, snap backups — the real culprit is somewhere you weren't looking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Focus on the suspects
&lt;/h3&gt;

&lt;p&gt;Once you've confirmed the project folders are the problem, narrow the scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./find_largest_files.sh ~/mortgage_system 30 mortgage_system_report.txt
./find_largest_files.sh ~/mortgage_frontend 30 mortgage_frontend_report.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Check directory-level size too
&lt;/h3&gt;

&lt;p&gt;Individual largest files tell one story; directory totals tell another. A million tiny files in &lt;code&gt;node_modules&lt;/code&gt; won't show up in a largest-files report, but they'll still eat gigabytes. Pair the script with &lt;code&gt;du&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;--max-depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ~/mortgage_system | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rh&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;--max-depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ~/mortgage_frontend | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rh&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common culprits in active dev folders
&lt;/h2&gt;

&lt;p&gt;Based on the stack you're likely using, here are the usual suspects, roughly in order of how often they're the answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;node_modules/&lt;/code&gt;&lt;/strong&gt; — routinely 500 MB to 2 GB &lt;em&gt;per project&lt;/em&gt;. Two full Next.js/React projects with heavy dependency trees can easily account for 3–5 GB each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.next/&lt;/code&gt; or &lt;code&gt;dist/&lt;/code&gt; or &lt;code&gt;build/&lt;/code&gt;&lt;/strong&gt; — production builds and incremental build caches. Next.js's &lt;code&gt;.next/cache&lt;/code&gt; in particular can grow to several GB over weeks of &lt;code&gt;npm run dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.git/&lt;/code&gt;&lt;/strong&gt; — if you've committed large binaries or have a long history, &lt;code&gt;.git/objects&lt;/code&gt; can be surprisingly fat. &lt;code&gt;git gc --aggressive&lt;/code&gt; helps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python &lt;code&gt;__pycache__/&lt;/code&gt; and &lt;code&gt;.venv/&lt;/code&gt;&lt;/strong&gt; — virtual environments with ML/data dependencies (torch, tensorflow, pandas) are often 3–8 GB each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker layers&lt;/strong&gt; — &lt;code&gt;/var/lib/docker&lt;/code&gt; is the single most common "where did my disk go?" answer on dev machines. &lt;code&gt;docker system df&lt;/code&gt; shows the breakdown; &lt;code&gt;docker system prune -a&lt;/code&gt; reclaims it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log files&lt;/strong&gt; — &lt;code&gt;/var/log/journal/&lt;/code&gt;, application logs, and PM2 logs can grow indefinitely if no rotation is configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snap revisions&lt;/strong&gt; — Ubuntu keeps old snap versions by default. &lt;code&gt;sudo snap set system refresh.retain=2&lt;/code&gt; caps retention at two revisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trash&lt;/strong&gt; — &lt;code&gt;~/.local/share/Trash/&lt;/code&gt; is easy to forget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser caches&lt;/strong&gt; — &lt;code&gt;~/.cache/google-chrome&lt;/code&gt;, &lt;code&gt;~/.cache/mozilla&lt;/code&gt;, and similar can hit several GB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Node projects specifically, a quick sanity check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/mortgage_system/node_modules ~/mortgage_frontend/node_modules 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If each is over a gigabyte, that's your ~30 GB budget explained between two projects, their build caches, and a &lt;code&gt;.git&lt;/code&gt; folder or two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful companion commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Overall disk usage at a glance&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# Top-level directories sorted by size (run from /)&lt;/span&gt;
&lt;span class="nb"&gt;sudo du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;--max-depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 / 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rh&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# What's eating your home directory&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;--max-depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ~ | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rh&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# Docker-specific&lt;/span&gt;
docker system &lt;span class="nb"&gt;df
&lt;/span&gt;docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt;  &lt;span class="c"&gt;# aggressive, frees everything unused&lt;/span&gt;

&lt;span class="c"&gt;# Clean npm cache&lt;/span&gt;
npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Clean pip cache&lt;/span&gt;
pip cache purge

&lt;span class="c"&gt;# Clear systemd journal older than 7 days&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;--vacuum-time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Going further: &lt;code&gt;ncdu&lt;/code&gt; for interactive exploration
&lt;/h2&gt;

&lt;p&gt;The script gives you a static report, which is great for archiving and diffing over time. For interactive drilling, install &lt;code&gt;ncdu&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ncdu
ncdu ~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gives you a terminal UI where you can navigate directories by size, delete things on the spot, and generally understand disk usage faster than any CLI combination. It's the tool I reach for once the script points me to the neighbourhood and I need to find the exact house.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up ongoing monitoring
&lt;/h2&gt;

&lt;p&gt;If you want to catch disk bloat as it happens instead of after the fact, schedule the script via cron and diff the reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Edit your crontab&lt;/span&gt;
crontab &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Add: run every Sunday at 2 AM, save to a reports directory&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 0 /home/you/find_largest_files.sh / 50 /home/you/disk_reports/scan_&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="se"&gt;\%&lt;/span&gt;Y&lt;span class="se"&gt;\%&lt;/span&gt;m&lt;span class="se"&gt;\%&lt;/span&gt;d&lt;span class="si"&gt;)&lt;/span&gt;.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A week later, &lt;code&gt;diff&lt;/code&gt; two reports to see what grew.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.gnu.org/software/findutils/manual/html_mono/find.html" rel="noopener noreferrer"&gt;GNU findutils manual — find&lt;/a&gt; — authoritative reference for &lt;code&gt;find&lt;/code&gt;, including the full &lt;code&gt;-printf&lt;/code&gt; format spec.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man1/du.1.html" rel="noopener noreferrer"&gt;Linux &lt;code&gt;du&lt;/code&gt; man page&lt;/a&gt; — directory-aggregate sizing, the natural complement to this script.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man1/df.1.html" rel="noopener noreferrer"&gt;Linux &lt;code&gt;df&lt;/code&gt; man page&lt;/a&gt; — filesystem-level free space.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.yorhel.nl/ncdu" rel="noopener noreferrer"&gt;ncdu home page&lt;/a&gt; — interactive disk usage analyzer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/engine/manage-resources/pruning/" rel="noopener noreferrer"&gt;Docker: prune unused objects&lt;/a&gt; — official guide to reclaiming Docker space.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.npmjs.com/cli/v10/commands/npm-cache" rel="noopener noreferrer"&gt;npm cache documentation&lt;/a&gt; — how npm's cache works and how to clean it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://askubuntu.com/questions/7738/how-to-find-the-largest-ten-files-on-the-hard-drive" rel="noopener noreferrer"&gt;Ask Ubuntu: Why is my disk full?&lt;/a&gt; — community thread with many alternative one-liners.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wiki.archlinux.org/title/Disk_usage_analyzer" rel="noopener noreferrer"&gt;Arch Wiki: Disk usage analyzers&lt;/a&gt; — concise overview of CLI and GUI tools across the Linux ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;If the script points at &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;.next&lt;/code&gt; inside your two mortgage projects, that's consistent with the 30 GB of lost disk — two mature JS/TS codebases with their build artifacts can account for exactly that range. The usual remediation is &lt;code&gt;rm -rf node_modules .next&lt;/code&gt; in each project, followed by a fresh &lt;code&gt;npm install&lt;/code&gt; only on the one you're actively working on. If instead the biggest files live under &lt;code&gt;/var/lib/docker&lt;/code&gt; or &lt;code&gt;/var/log&lt;/code&gt;, the fix is completely different, which is exactly why running a scan first beats guessing.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>bash</category>
      <category>devops</category>
    </item>
    <item>
      <title>Renaming 1000+ Pages Worth of "Levels" Without Losing My Mind</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Tue, 21 Apr 2026 05:02:35 +0000</pubDate>
      <link>https://dev.to/highcenburg/renaming-1000-pages-worth-of-levels-without-losing-my-mind-1cnk</link>
      <guid>https://dev.to/highcenburg/renaming-1000-pages-worth-of-levels-without-losing-my-mind-1cnk</guid>
      <description>&lt;p&gt;A message from a colleague dropped into my inbox one morning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi Ice, now that we pushed the changes sa site, we need to scour the site for mentions of Level 1, Level 2, Level 1/2 and change them accordingly to Level 1 → Intro to Neurofascial Training, Level 1/2 → Intermediate Instability Training, Level 2 → Advanced Neurofascial Training&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the surface, this looks like a simple find-and-replace job. Read the message, open the WordPress admin, use the search feature, fix each page. Done before lunch.&lt;/p&gt;

&lt;p&gt;Then I actually opened the site and pulled the sitemap. &lt;strong&gt;1,074 URLs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That changes the math pretty quickly. Even if only a fraction of those pages mention "Level" anywhere, manually clicking through every post and page, ctrl-F-ing for three different strings, and then figuring out which instance needs which replacement — that's a full day of soul-crushing work at minimum, and a very real chance of missing something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mapping
&lt;/h2&gt;

&lt;p&gt;Before anything else, I wrote the rename table down somewhere I couldn't lose it, because a misread mapping here would mean renaming correct text into incorrect text, which is strictly worse than leaving it alone:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Old text&lt;/th&gt;
&lt;th&gt;New text&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level 1&lt;/td&gt;
&lt;td&gt;Intro to Neurofascial Training&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 1/2&lt;/td&gt;
&lt;td&gt;Intermediate Instability Training&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 2&lt;/td&gt;
&lt;td&gt;Advanced Neurofascial Training&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important ordering detail: "Level 1/2" has to be matched &lt;em&gt;before&lt;/em&gt; "Level 1", or a naive find-and-replace would turn "Level 1/2" into "Intro to Neurofascial Training/2". Easy to miss, painful to undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use WordPress search-replace plugins?
&lt;/h2&gt;

&lt;p&gt;Plugins like Better Search Replace do exist, and in a simpler world I'd use one. But they operate on the database directly, which means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One wrong checkbox and you nuke every "Level 1" across post content, post meta, widget text, theme options, and anywhere else the phrase appears — including places it &lt;em&gt;shouldn't&lt;/em&gt; be replaced, like analytics or audit logs.&lt;/li&gt;
&lt;li&gt;There's no preview of &lt;em&gt;where&lt;/em&gt; each match lives. You're trusting the plugin's count and hoping nothing weird is hiding in a shortcode.&lt;/li&gt;
&lt;li&gt;The three strings overlap, so the order of operations matters and plugins don't always let you sequence replacements atomically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I actually wanted was a map — a list of every occurrence with enough surrounding context to judge whether it's safe to change, plus a direct link to the WordPress editor for that specific page. The replacement itself I'd still do by hand, but informed by real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;

&lt;p&gt;I wrote a Python script that walks the site's sitemap, visits each URL, and searches the rendered HTML for the three patterns using a single regex with word boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;LEVEL_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bLevel\s*1\s*/\s*2\b|\bLevel\s*1\b|\bLevel\s*2\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Word boundaries matter here. Without &lt;code&gt;\b&lt;/code&gt;, a page mentioning "Level 10" or "Level 12-week program" would get flagged as a false "Level 1" match. And the alternation order mirrors the mapping problem above — "Level 1/2" is tried first so it doesn't get shadowed by the simpler "Level 1" pattern.&lt;/p&gt;

&lt;p&gt;For each match, the script captures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page URL and &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The WordPress post ID, extracted from the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; class (WP adds &lt;code&gt;page-id-123&lt;/code&gt; or &lt;code&gt;postid-123&lt;/code&gt; automatically)&lt;/li&gt;
&lt;li&gt;A direct admin edit link built from that ID: &lt;code&gt;/wp-admin/post.php?post=123&amp;amp;action=edit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;About 80 characters of context on either side of the match&lt;/li&gt;
&lt;li&gt;The HTML tag wrapping the match (&lt;code&gt;h2&lt;/code&gt;, &lt;code&gt;li&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, etc.) to help me find it inside the block editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it dumped to a CSV. Rows sorted, duplicates within a page collapsed, system paths like &lt;code&gt;/wp-admin&lt;/code&gt; and &lt;code&gt;/wp-json&lt;/code&gt; filtered out so the scanner doesn't waste time on pages that aren't user content.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the CSV actually gives me
&lt;/h2&gt;

&lt;p&gt;Instead of 1,074 pages to click through blindly, I now have a focused list of every page that mentions "Level" in any form, with a one-click link to the editor and enough context to know which replacement to apply. The context column is the real magic — I can see a snippet like &lt;code&gt;…our flagship Level 1 class is held every Tuesday…&lt;/code&gt; and immediately know it's the class name that needs to become "Intro to Neurofascial Training", not some incidental use of the word "level".&lt;/p&gt;

&lt;p&gt;For the rare edge case — a page that mentions "Level 1" in a way that &lt;em&gt;shouldn't&lt;/em&gt; be renamed, like historical text or a testimonial quoting someone — I can spot it in the context column and skip that row. That judgment call is exactly the part you don't want a blind database replacement making on your behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The temptation with a task like this is to just start clicking. It feels productive. But on a site of any real size, a half hour spent writing a scanner pays for itself almost immediately — not just in saved time, but in the confidence that you actually caught everything and didn't quietly corrupt adjacent content along the way.&lt;/p&gt;

&lt;p&gt;Sometimes the most valuable thing automation gives you isn't speed. It's the audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;New XML Sitemaps Functionality in WordPress 5.5 — Make WordPress Core: &lt;a href="https://make.wordpress.org/core/2020/07/22/new-xml-sitemaps-functionality-in-wordpress-5-5/" rel="noopener noreferrer"&gt;https://make.wordpress.org/core/2020/07/22/new-xml-sitemaps-functionality-in-wordpress-5-5/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Sitemaps XML Protocol — sitemaps.org: &lt;a href="https://www.sitemaps.org/protocol.html" rel="noopener noreferrer"&gt;https://www.sitemaps.org/protocol.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;body_class()&lt;/code&gt; Function Reference — WordPress Developer Resources: &lt;a href="https://developer.wordpress.org/reference/functions/body_class/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/reference/functions/body_class/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;get_body_class()&lt;/code&gt; Function Reference — WordPress Developer Resources: &lt;a href="https://developer.wordpress.org/reference/functions/get_body_class/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/reference/functions/get_body_class/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;re&lt;/code&gt; — Regular expression operations — Python 3 docs: &lt;a href="https://docs.python.org/3/library/re.html" rel="noopener noreferrer"&gt;https://docs.python.org/3/library/re.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Better Search Replace — WordPress Plugin Directory: &lt;a href="https://wordpress.org/plugins/better-search-replace/" rel="noopener noreferrer"&gt;https://wordpress.org/plugins/better-search-replace/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Requests: HTTP for Humans — &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;https://requests.readthedocs.io/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Beautiful Soup Documentation — &lt;a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/" rel="noopener noreferrer"&gt;https://www.crummy.com/software/BeautifulSoup/bs4/doc/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>wordpress</category>
      <category>softwaredevelopment</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Manually Backup WordPress Sites via SSH</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Fri, 17 Apr 2026 06:14:35 +0000</pubDate>
      <link>https://dev.to/highcenburg/how-to-manually-backup-wordpress-sites-via-ssh-22o4</link>
      <guid>https://dev.to/highcenburg/how-to-manually-backup-wordpress-sites-via-ssh-22o4</guid>
      <description>&lt;p&gt;Backing up your WordPress site is one of the most important maintenance tasks you can do as a site owner. While plugins like UpdraftPlus or Jetpack make this easy, knowing how to do it &lt;strong&gt;manually via SSH&lt;/strong&gt; gives you full control — no third-party dependencies, no bloat, just a clean archive you own.&lt;/p&gt;

&lt;p&gt;This guide walks you through creating a full file backup of your WordPress site directly from the server using the command line.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing the Required Tools
&lt;/h2&gt;

&lt;p&gt;Before connecting to your server, make sure the necessary tools are installed on your &lt;strong&gt;local machine&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;macOS comes with &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;scp&lt;/code&gt; pre-installed. No action needed — just open Terminal and you're ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux (Ubuntu/Debian)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;openssh-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Option A — Windows Subsystem for Linux (WSL)&lt;/strong&gt; &lt;em&gt;(recommended)&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once WSL is set up, &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;scp&lt;/code&gt; are available inside the Linux shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B — OpenSSH via PowerShell&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OpenSSH.Client~~~~0.0.1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing, &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;scp&lt;/code&gt; will be available directly in PowerShell or Command Prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option C — GUI alternative&lt;/strong&gt;: Install &lt;a href="https://winscp.net/" rel="noopener noreferrer"&gt;WinSCP&lt;/a&gt; for a drag-and-drop interface to transfer files instead of using &lt;code&gt;scp&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  On the Server
&lt;/h3&gt;

&lt;p&gt;Your server should already have &lt;code&gt;tar&lt;/code&gt; and &lt;code&gt;mysqldump&lt;/code&gt; available. If for any reason they are missing, install them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# tar&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install tar&lt;/span&gt;          &lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install tar&lt;/span&gt;          &lt;span class="c"&gt;# CentOS/RHEL&lt;/span&gt;

&lt;span class="c"&gt;# mysqldump (part of the MySQL client)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;mysql-client          &lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;mysql                 &lt;span class="c"&gt;# CentOS/RHEL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH access to your server&lt;/li&gt;
&lt;li&gt;The server's IP address&lt;/li&gt;
&lt;li&gt;Your SSH credentials (username and password, or an SSH key)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scp&lt;/code&gt; or an SFTP client installed on your local machine&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: SSH Into Your Server
&lt;/h2&gt;

&lt;p&gt;Open your terminal and connect to your server using SSH. Replace &lt;code&gt;ip_address&lt;/code&gt; with your actual server IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@ip_address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for your password (or authenticated via SSH key). Once connected, you'll be inside your server's shell.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you're using a non-root user, replace &lt;code&gt;root&lt;/code&gt; with your username (e.g., &lt;code&gt;ssh deploy@192.168.1.100&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2: Create a Compressed Archive of Your Site
&lt;/h2&gt;

&lt;p&gt;Your WordPress files typically live inside the &lt;code&gt;public_html&lt;/code&gt; directory. The following command creates a compressed &lt;code&gt;.tar.gz&lt;/code&gt; archive of the entire folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-czvf&lt;/span&gt; ~/public_html/backup-site.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; ~/ public_html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What each flag does:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a new archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compress with gzip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verbose output (shows files being archived)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies the output filename&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-C ~/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Changes to the home directory before archiving&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The backup file &lt;code&gt;backup-site.tar.gz&lt;/code&gt; will be saved inside &lt;code&gt;~/public_html/&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Depending on your site size, this may take a few minutes. Large media libraries will increase the archive size significantly.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3: Download the Backup to Your Local Machine
&lt;/h2&gt;

&lt;p&gt;Once the archive is created, exit the SSH session and run the following &lt;code&gt;scp&lt;/code&gt; command on your &lt;strong&gt;local machine&lt;/strong&gt; to download the backup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp root@ip_address:~/public_html/backup-site.tar.gz ~/Downloads/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This securely copies the file from your server to your local &lt;code&gt;~/Downloads/&lt;/code&gt; folder.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you're on Windows, you can use &lt;a href="https://winscp.net/" rel="noopener noreferrer"&gt;WinSCP&lt;/a&gt; or the built-in &lt;code&gt;scp&lt;/code&gt; command in PowerShell/WSL as an alternative.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4: Don't Forget the Database
&lt;/h2&gt;

&lt;p&gt;A full WordPress backup requires &lt;strong&gt;both the files and the database&lt;/strong&gt;. Your files backup covers themes, plugins, and uploads — but your posts, pages, and settings live in MySQL.&lt;/p&gt;

&lt;p&gt;To export your database, run this on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; your_database_name &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/public_html/backup-db.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then download it the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp root@ip_address:~/public_html/backup-db.sql ~/Downloads/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;your_database_name&lt;/code&gt; with the database name found in your &lt;code&gt;wp-config.php&lt;/code&gt; file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Clean Up the Server
&lt;/h2&gt;

&lt;p&gt;To avoid using up disk space on your server, delete the backup files after downloading them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; ~/public_html/backup-site.tar.gz
&lt;span class="nb"&gt;rm&lt;/span&gt; ~/public_html/backup-db.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Restoring from a Backup
&lt;/h2&gt;

&lt;p&gt;To restore, simply reverse the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload the &lt;code&gt;.tar.gz&lt;/code&gt; file back to the server using &lt;code&gt;scp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extract it with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzvf&lt;/span&gt; backup-site.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; ~/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Import the database with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; your_database_name &amp;lt; backup-db.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Manual backups via SSH are reliable, fast, and give you a portable snapshot of your entire site. For production sites, consider automating this process with a cron job or combining it with offsite storage like S3 or Google Drive.&lt;/p&gt;

&lt;p&gt;A backup you never tested is a backup you can't trust — make sure to do a test restore at least once.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>7/9 implementations done quick!</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Mon, 13 Apr 2026 18:36:37 +0000</pubDate>
      <link>https://dev.to/highcenburg/79-implementations-done-quick-39jh</link>
      <guid>https://dev.to/highcenburg/79-implementations-done-quick-39jh</guid>
      <description></description>
      <category>coding</category>
      <category>devchallenge</category>
      <category>devjournal</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built an Enterprise Coffee Dashboard (That Refuses to Brew Coffee)</title>
      <dc:creator>Vicente G. Reyes</dc:creator>
      <pubDate>Sat, 04 Apr 2026 08:08:30 +0000</pubDate>
      <link>https://dev.to/highcenburg/i-built-an-enterprise-coffee-dashboard-that-refuses-to-brew-coffee-2gik</link>
      <guid>https://dev.to/highcenburg/i-built-an-enterprise-coffee-dashboard-that-refuses-to-brew-coffee-2gik</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built the &lt;strong&gt;Enterprise HTCPCP Interface (HTCPCP_CTRL_NODE_v1.0)&lt;/strong&gt;. It is a highly over-engineered, unnecessarily complex, and deeply frustrating dashboard for brewing coffee using the Hyper Text Coffee Pot Control Protocol (RFC 2324).&lt;/p&gt;

&lt;p&gt;It solves absolutely zero real-world problems and introduces several new ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Contaminating Sliders:&lt;/strong&gt; Trying to set the "Thermal Kinetic Energy" (temperature)? Oops, that just randomly altered your "Extraction Pressure". Adjusting the "Particulate Granularity"? Say goodbye to your "Lactose Aeration Quotient". Getting the perfect brew settings is a Sisyphean task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual Hand-Crank Power Generator:&lt;/strong&gt; You can't just click "Brew". You have to rapidly mash a button to charge the system's power capacitor to 100%. If you stop clicking, the power drains back to zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inevitable Failure:&lt;/strong&gt; After all that hard work, the system connects to a sentient AI teapot that &lt;em&gt;always&lt;/em&gt; rejects your request with a dramatic, dynamically generated HTTP 418 error.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;You can try to brew some coffee (and inevitably fail) here:&lt;br&gt;
&lt;strong&gt;&lt;a&gt;Live Demo: Enterprise HTCPCP Interface&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Warning: May cause mild frustration and a sudden craving for tea).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The project was built entirely within Google AI Studio. Here is a snippet of the delightfully terrible UX logic where the sliders sabotage each other, and the Gemini AI prompt that generates the 418 excuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The delightfully terrible cross-contaminating sliders&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleTempChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setPressure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleGrindChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setGrind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setAeration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// ... (other sliders do the same)&lt;/span&gt;

  &lt;span class="c1"&gt;// The AI Sentient Teapot Prompt&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a sentient teapot that has just received a highly complex, over-engineered request to brew coffee via the HTCPCP (Hyper Text Coffee Pot Control Protocol). 
  You must reject this request with a 418 I'm a teapot error. 
  Generate a highly dramatic, passive-aggressive, and overly technical excuse explaining why you cannot brew coffee because you are, in fact, a teapot. 
  Mention Larry Masinter (the creator of the 418 status code) in a reverent or funny way.
  Keep it under 3 sentences.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-3-flash-preview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I built this using &lt;strong&gt;Google AI Studio's&lt;/strong&gt; agentic build environment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js App Router, React, and Tailwind CSS for that overly serious, dark-mode "enterprise hacker" aesthetic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animations:&lt;/strong&gt; motion (Framer Motion) for the dramatic terminal scanlines, the flashing 418 error screen, and the floating tea emoji.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Integration:&lt;/strong&gt; The @google/genai SDK. I used the gemini-3-flash-preview model to act as the backend "appliance". Instead of actually brewing coffee, it generates a unique, passive-aggressive excuse every single time you hit the brew button.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;I am submitting this for &lt;strong&gt;two&lt;/strong&gt; prize categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Best Google AI Usage:&lt;/strong&gt; I used Google AI Studio to build the app, and I embedded the Gemini API (gemini-3-flash-preview) directly into the application logic. Instead of using AI to solve a complex problem, I used cutting-edge generative AI to roleplay as a stubborn, sentient teapot that refuses to do its job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Ode to Larry Masinter:&lt;/strong&gt; The entire application is a massive, over-engineered tribute to HTCPCP and the 418 status code. Furthermore, the Gemini system prompt explicitly instructs the AI to mention Larry Masinter in a reverent or funny way in every single error message it generates!&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
