<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>DevOps on Fabio Lopes | Cloud Architect &amp; DevOps | AWS, Azure, GCP Expert</title><link>https://fabiolopes.page/categories/devops/</link><description>Recent content in DevOps on Fabio Lopes | Cloud Architect &amp; DevOps | AWS, Azure, GCP Expert</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Fabio M. Lopes</copyright><lastBuildDate>Fri, 23 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://fabiolopes.page/categories/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>Production Engineering for Single-Node Edge Workloads: Zero-Downtime Releases, WAL Backups, and Native ARM Compilation</title><link>https://fabiolopes.page/p/production-engineering-for-single-node-edge-workloads-zero-downtime-releases-wal-backups-and-native-arm-compilation/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0000</pubDate><guid>https://fabiolopes.page/p/production-engineering-for-single-node-edge-workloads-zero-downtime-releases-wal-backups-and-native-arm-compilation/</guid><description>&lt;img src="https://fabiolopes.page/p/production-engineering-for-single-node-edge-workloads-zero-downtime-releases-wal-backups-and-native-arm-compilation/ibbkids.png" alt="Featured image of post Production Engineering for Single-Node Edge Workloads: Zero-Downtime Releases, WAL Backups, and Native ARM Compilation" />&lt;p>I originally built IBB Kids as a volunteer project to streamline weekly classroom material management for a non-profit community. Teachers needed instant access to semester schedules, locations, and lesson links without digging through shared spreadsheets.&lt;/p>
&lt;p>While the functional requirement was straightforward, the operational constraint was non-negotiable: &lt;strong>the system had to run at zero cost on an ARM-based cloud instance with near-zero maintenance overhead, while remaining reliable enough to be operated by non-technical volunteers.&lt;/strong>&lt;/p>
&lt;p>Ten months after its initial launch, the core challenge shifted from feature development to &lt;strong>production engineering&lt;/strong>: ensuring stateless JWT security, cross-compiling native C++ modules across heterogeneous architectures, executing atomic zero-downtime deployments, and achieving point-in-time database backup integrity without data corruption.&lt;/p>
&lt;p>This post documents the architectural decisions, resilience patterns, and operational lessons learned while running a single-node SQLite workload in production up to October 2026.&lt;/p>
&lt;h2 id="architectural-steering--ai-augmented-engineering">Architectural Steering &amp;amp; AI-Augmented Engineering
&lt;/h2>&lt;p>To maintain high delivery velocity while keeping my primary focus on system reliability, state consistency, and platform security, I adopted an &lt;strong>AI-augmented software engineering workflow&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Architectural Steering &amp;amp; Systems Design (Human):&lt;/strong> I designed the system topology, multi-environment isolation model, database state transition rules, JWT session versioning schemes, SQLite WAL backup strategies, native C++ binding validation scripts, and automated CI/CD rollback pipelines.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Code Implementation (Codex):&lt;/strong> I leveraged OpenAI Codex as an execution engine to translate my technical specifications, API schemas, and architectural constraints into full-stack TypeScript (&lt;code>React&lt;/code> + &lt;code>Express&lt;/code> + &lt;code>SQLite&lt;/code>) code.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Validation &amp;amp; Production Hardening (Human):&lt;/strong> I established the operational quality gates—including unit/integration testing with isolated in-memory SQLite instances, static security analysis via Semgrep SAST, build-id health checks, and cross-compilation checks for ARM64 Linux runtimes.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>This division of responsibilities allowed me to act as a &lt;strong>Tech Lead / Systems Architect&lt;/strong>, focusing strictly on system resilience and operability without spending hundreds of hours writing boilerplate application code.&lt;/p>
&lt;h2 id="system-architecture-single-node-edge-topology">System Architecture: Single-Node Edge Topology
&lt;/h2>&lt;p>The application relies on a lightweight, decoupled stack designed to maximize performance on low-spec hardware while enforcing strict domain boundary separation:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Frontend:&lt;/strong> React with TypeScript, bundled by Vite, styled via Tailwind CSS v4.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Backend:&lt;/strong> Express API server hosting static production assets.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Database:&lt;/strong> SQLite running locally via &lt;code>better-sqlite3&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Process Management:&lt;/strong> PM2 enforcing daemon isolation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Edge Proxy:&lt;/strong> Nginx managing HTTPS termination via Certbot/Let&amp;rsquo;s Encrypt certificates on Oracle Linux ARM.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Browser (React Single-Page App)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓ HTTPS (Port 443)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Nginx (TLS Termination &amp;amp; Reverse Proxy)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓ HTTP Proxy (Internal Loopback)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Express Server (JWT Auth, REST API, Static Asset Delivery)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓ C++ Native Bindings (better-sqlite3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SQLite Database (WAL Mode: Schedule, Materials, Credentials)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In development, Vite proxies &lt;code>/api&lt;/code> calls to Express. In production, Vite is not running; Express directly serves optimized static assets alongside the API endpoints.&lt;/p>
&lt;p>To guarantee environment isolation, &lt;strong>Development&lt;/strong> and &lt;strong>Production&lt;/strong> co-exist on the same VM but operate with dedicated directories, internal ports, SQLite files, environment secrets, and PM2 instance names.&lt;/p>
&lt;h2 id="native-c-bindings--cross-compilation-across-environments">Native C++ Bindings &amp;amp; Cross-Compilation Across Environments
&lt;/h2>&lt;p>Developing on Windows via WSL2 (x86_64) and deploying to Oracle Linux ARM (ARM64) introduced cross-compilation friction—specifically when loading native C++ modules like &lt;code>better-sqlite3&lt;/code> and &lt;code>bcrypt&lt;/code>.&lt;/p>
&lt;p>A common failure mode in multi-platform deployments is the dreaded &lt;code>invalid ELF header&lt;/code> error, caused by attempting to execute C++ binaries compiled for x86_64 inside an ARM64 Linux runtime (or sharing &lt;code>node_modules&lt;/code> across WSL2 and host Windows filesystems).&lt;/p>
&lt;h3 id="rules-for-native-module-isolation">Rules for Native Module Isolation
&lt;/h3>&lt;ol>
&lt;li>
&lt;p>&lt;strong>Strict Environment Boundary:&lt;/strong> Never share or mount &lt;code>node_modules&lt;/code> between Windows, WSL2, or remote Linux servers.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Version Pinning:&lt;/strong> Enforce Node.js runtime alignment using &lt;code>.nvmrc&lt;/code> (pinned to Node 22 LTS) and commit explicit lockfiles (&lt;code>package-lock.json&lt;/code>).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pre-flight Native Compilation Check:&lt;/strong> Introduced a dedicated &lt;code>npm run prepare-native&lt;/code> lifecycle script during the build step:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvm install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvm use
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm ci
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm run prepare-native
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm run build
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The &lt;code>prepare-native&lt;/code> script attempts to initialize an in-memory SQLite instance and test &lt;code>bcrypt&lt;/code> hash routines. If native bindings fail to load due to an architecture mismatch, the script automatically invokes &lt;code>node-gyp&lt;/code> to recompile C++ bindings against local system tools (using &lt;code>GCC Toolset&lt;/code> and &lt;code>Python 3.12&lt;/code>) before validating the result.&lt;/p>
&lt;h2 id="sqlite-in-production-wal-mode-backup-integrity-and-oci-sync">SQLite in Production: WAL Mode, Backup Integrity, and OCI Sync
&lt;/h2>&lt;p>SQLite is a fantastic engine for single-instance workloads, but copying a live &lt;code>.sqlite&lt;/code> file directly while the process is running is a recipe for database corruption when Write-Ahead Logging (WAL) is enabled. Uncommitted WAL frames will be missed, leading to inconsistent state upon recovery.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[ Live Application Writes ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> SQLite DB File + WAL Journal
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ SQLite Online Backup API ] ---&amp;gt; Freezes Consistent Snapshot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ PRAGMA Integrity Checks ] ---&amp;gt; Validates Headers &amp;amp; Foreign Keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ Compressed Local Snapshot ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ OCI Instance Principal ] ---&amp;gt; Authenticated Direct Stream to Private Object Storage
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="1-online-backup-api-execution">1. Online Backup API Execution
&lt;/h3>&lt;p>Instead of raw file copies, backups utilize the &lt;strong>SQLite Online Backup API&lt;/strong> (exposed via &lt;code>better-sqlite3&lt;/code>). This locks the database safely in read-mode, flushes uncommitted WAL frames, and writes a point-in-time snapshot to a temporary file.&lt;/p>
&lt;h3 id="2-automated-integrity-validation">2. Automated Integrity Validation
&lt;/h3>&lt;p>Before any backup is marked valid, the pipeline executes rigorous structural checks:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">integrity_check&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">foreign_key_check&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If either query returns errors, the snapshot is instantly discarded, and an alert is raised.&lt;/p>
&lt;h3 id="3-keyless-cloud-replication-via-oci-instance-principals">3. Keyless Cloud Replication via OCI Instance Principals
&lt;/h3>&lt;p>Once validated locally, daily backup archives are streamed directly to a private &lt;strong>Oracle Cloud Infrastructure (OCI) Object Storage&lt;/strong> bucket.&lt;/p>
&lt;p>To prevent security credential leaks, the VM relies on &lt;strong>OCI Instance Principals&lt;/strong> (IAM dynamic group policies attached to the VM instance). No static private keys, AWS-style secret pairs, or cloud tokens exist in &lt;code>.env&lt;/code> files or application code.&lt;/p>
&lt;h3 id="4-zero-downtime-restores--state-purging">4. Zero-Downtime Restores &amp;amp; State Purging
&lt;/h3>&lt;p>Restoring a backup automatically triggers:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>File validation and integrity checks on the candidate snapshot.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>A compulsory safety backup of the current live database.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Graceful application process suspension via PM2 lock files.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Database file replacement and global JWT session version invalidation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Application restart and automated &lt;code>/health&lt;/code> check validation.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="process-management--zero-downtime-releases">Process Management &amp;amp; Zero-Downtime Releases
&lt;/h2>&lt;p>Relying on &lt;code>git pull&lt;/code> and in-place &lt;code>npm restart&lt;/code> on a production server creates a window of downtime and risks leaving the application broken if native compilation or build steps fail halfway through.&lt;/p>
&lt;h3 id="why-pm2-fork-mode-trumped-cluster-mode">Why PM2 Fork Mode Trumped Cluster Mode
&lt;/h3>&lt;p>While PM2 &lt;code>cluster&lt;/code> mode is popular for horizontal scaling, our workload required single-process &lt;code>fork&lt;/code> mode for three critical architectural reasons:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>SQLite Concurrency &amp;amp; Locking Constraints:&lt;/strong> SQLite (&lt;code>better-sqlite3&lt;/code>) operates as an embedded single-file database. Running multiple worker processes under PM2 &lt;code>cluster&lt;/code> mode causes multi-process write contention, &lt;code>SQLITE_BUSY&lt;/code> errors, and potential WAL index corruption.&lt;/li>
&lt;li>&lt;strong>TypeScript Loader (&lt;code>tsx&lt;/code>) Hook Resolution:&lt;/strong> Executing TypeScript directly via &lt;code>tsx&lt;/code> requires custom Node.js loader hooks. PM2 cluster mode bypasses standard CLI loaders during &lt;code>cluster.fork()&lt;/code>, causing socket binding failures.&lt;/li>
&lt;li>&lt;strong>Deterministic Node.js Runtime Isolation:&lt;/strong> Clustered PM2 workers inherit the Node.js version of the running background PM2 daemon. Single-process &lt;code>fork&lt;/code> mode allows specifying the explicit Node 22 binary path, decoupling application execution from ambient daemon state during runtime upgrades.&lt;/li>
&lt;/ol>
&lt;h3 id="versioned-release-directories">Versioned Release Directories
&lt;/h3>&lt;p>To achieve zero-downtime releases, deployments use &lt;strong>isolated release directories&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/opt/ibbkids/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── current -&amp;gt; /opt/ibbkids/releases/20261023_140000/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── shared/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── .env
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── database.sqlite
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── releases/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── 20261020_091500/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 20261023_140000/ (Active Version)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="deployment-lifecycle-execution">Deployment Lifecycle Execution
&lt;/h3>&lt;ol>
&lt;li>
&lt;p>&lt;strong>Checkout SHA:&lt;/strong> The CI/CD runner fetches the exact commit SHA into a new folder under &lt;code>/releases/&amp;lt;timestamp&amp;gt;&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Build &amp;amp; Prepare:&lt;/strong> Dependencies are installed (&lt;code>npm ci&lt;/code>), C++ native bindings are compiled (&lt;code>npm run prepare-native&lt;/code>), unit tests run against an isolated SQLite instance, and Vite builds static assets.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>PM2 Process Spawn:&lt;/strong> The application is booted in PM2 &lt;code>fork&lt;/code> mode using &lt;code>tsx&lt;/code> and the explicitly configured Node 22 runtime.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Healthcheck Probe Verification:&lt;/strong> The deployment script polls the &lt;code>/health&lt;/code> endpoint. The endpoint returns a unique JSON payload containing the release commit SHA:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;ok&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;environment&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;deployId&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sha-8f2a1b9c&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dbStatus&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;connected&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ol start="5">
&lt;li>
&lt;p>&lt;strong>Atomic Symlink Switch:&lt;/strong> The script accepts the deployment &lt;em>only&lt;/em> if the &lt;code>deployId&lt;/code> matches the newly published SHA. Nginx requests are instantly routed to the new code path.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Automated Rollback:&lt;/strong> If the health check fails or times out, the deployment script aborts, re-points the symlink to the previous working release folder, restarts the old PM2 process, and preserves the database state.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="stateful-security--deterministic-session-invalidation">Stateful Security &amp;amp; Deterministic Session Invalidation
&lt;/h2>&lt;p>JWTs are inherently stateless, which presents a security challenge: if an administrative credential is changed, previously issued JWTs remain valid until their natural expiration.&lt;/p>
&lt;h3 id="session-versioning-via-auth_state">Session Versioning via &lt;code>auth_state&lt;/code>
&lt;/h3>&lt;p>To solve this without adding a heavy Redis layer, I implemented a &lt;strong>database-backed session versioning scheme&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">auth_state&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">profile&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">TEXT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">PRIMARY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">KEY&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">session_version&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INTEGER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">updated_at&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DATETIME&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">CURRENT_TIMESTAMP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ol>
&lt;li>
&lt;p>Every issued JWT payload includes the current &lt;code>sessionVersion&lt;/code> for that profile.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Incoming API requests decode the token and cross-check its &lt;code>sessionVersion&lt;/code> against the &lt;code>auth_state&lt;/code> table in SQLite.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>When a user updates their password (or when a database restoration occurs), a single SQLite transaction updates the &lt;code>bcrypt&lt;/code> password hash and increments &lt;code>session_version&lt;/code>:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">BEGIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TRANSACTION&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">UPDATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">credentials&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password_hash&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">profile&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">UPDATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">auth_state&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">session_version&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">session_version&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">profile&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">COMMIT&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This instantly invalidates &lt;strong>all active JWT tokens&lt;/strong> across all devices for that specific profile in a single atomic database write.&lt;/p>
&lt;h3 id="cicd-security-analysis-semgrep-sast">CI/CD Security Analysis (Semgrep SAST)
&lt;/h3>&lt;p>The GitHub Actions pipeline includes a dedicated security job running &lt;strong>Semgrep Community Edition (Python 3.12)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Scans pull requests and pushes to &lt;code>dev&lt;/code>/&lt;code>develop&lt;/code> branches.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Enforces rules for hardcoded secrets, SQL injection vectors, and unsafe path interpolations.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Exports results as SARIF artifacts for historical audit without relying on paid third-party security dashboards.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="operational-evolution-after-10-months-october-2026-review">Operational Evolution After 10 Months (October 2026 Review)
&lt;/h2>&lt;p>Returning to the system after 10 months of real-world use provided concrete feedback from the volunteers operating the application:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Operational Dimension&lt;/th>
&lt;th>Initial Design (Dec 2025)&lt;/th>
&lt;th>Hardened Architecture (Oct 2026)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Material Management&lt;/strong>&lt;/td>
&lt;td>Teacher and Parent material links&lt;/td>
&lt;td>Added &lt;strong>Visual Aids&lt;/strong> tracking across database, APIs, and UI layers&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Data Granularity&lt;/strong>&lt;/td>
&lt;td>Single count of incomplete lessons&lt;/td>
&lt;td>&lt;strong>Split tracking&lt;/strong> for missing Teacher, Parent, and Visual materials&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Runtime &amp;amp; PM2 Isolation&lt;/strong>&lt;/td>
&lt;td>Hybrid Node 20 / Node 22 PM2 daemons&lt;/td>
&lt;td>Unified across environments on &lt;strong>Node 22 LTS (ARM64)&lt;/strong> using PM2 &lt;code>fork&lt;/code> mode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Database Purging&lt;/strong>&lt;/td>
&lt;td>Manual SQL execution&lt;/td>
&lt;td>Secure double-confirmation workflow with BCrypt authorization salt and mandatory local WAL snapshot&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Backup Redundancy&lt;/strong>&lt;/td>
&lt;td>Local disk snapshots only&lt;/td>
&lt;td>Local integrity-verified snapshots + &lt;strong>OCI Object Storage sync via IAM Instance Principals&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Test &amp;amp; Security Coverage&lt;/strong>&lt;/td>
&lt;td>Basic build checks&lt;/td>
&lt;td>&lt;strong>48 automated unit tests&lt;/strong> + &lt;strong>74 Semgrep SAST rules&lt;/strong> across 37 JS/TS files&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="key-takeaways-for-sres-and-platform-engineers">Key Takeaways for SREs and Platform Engineers
&lt;/h2>&lt;ol>
&lt;li>
&lt;p>&lt;strong>Single-Node Architectures Can Be Resilient:&lt;/strong> You don&amp;rsquo;t always need a multi-node Kubernetes cluster to achieve high availability. When engineered with WAL-aware backups, atomic directory-based releases, and process isolation, single-node systems offer incredible stability at zero cloud cost.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Treat AI as a Force Multiplier, Not a Black Box:&lt;/strong> Delegating code generation to tools like Codex works best when the engineer owns the system architecture, boundary constraints, test design, and failure modes.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Align Process Management with Data Constraints:&lt;/strong> Single-writer databases like SQLite require single-process (&lt;code>fork&lt;/code> mode) execution to avoid multi-process file contention (&lt;code>SQLITE_BUSY&lt;/code>), while ensuring clean TypeScript loader resolution and Node runtime isolation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Never Trust Raw File Copies on Active Databases:&lt;/strong> Always leverage native backup APIs (like SQLite&amp;rsquo;s Online Backup API) to ensure uncommitted WAL frames are safely flushed before snapshotting or shipping data off-site.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="references">References
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>&lt;a class="link" href="https://react.dev/" target="_blank" rel="noopener"
>React Documentation&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://expressjs.com/" target="_blank" rel="noopener"
>Express Framework&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://sqlite.org/lang_transaction.html" target="_blank" rel="noopener"
>SQLite Transaction Mechanics&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://sqlite.org/backup.html" target="_blank" rel="noopener"
>SQLite Online Backup API&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://github.com/WiseLibs/better-sqlite3" target="_blank" rel="noopener"
>better-sqlite3 Documentation&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm" target="_blank" rel="noopener"
>OCI Instance Principals Authentication&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://semgrep.dev/products/community-edition" target="_blank" rel="noopener"
>Semgrep Community Edition&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></description></item></channel></rss>