<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>StackOps Blog</title>
        <link>https://stackops.link/journal</link>
        <description>StackOps Blog</description>
        <lastBuildDate>Thu, 19 Feb 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[OxideAuth — Decentralized Identity & Access Management Platform]]></title>
            <link>https://stackops.link/journal/oxideauth</link>
            <guid>https://stackops.link/journal/oxideauth</guid>
            <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A fully featured, decentralized Identity and Access Management (IAM) platform built in Rust. Multi-tenant workspaces with fine-grained RBAC via roles, permissions, and policies. Stateless JWT authentication with a Redis-backed blacklist pattern. Designed as a general-purpose auth service — not tied to any single product or organization.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/oxideauth#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>OxideAuth</strong> (codename: <strong>Ironstone</strong>) is a decentralized, multi-tenant Identity and Access Management platform built in Rust. It offloads the full complexity of authentication and authorization from individual services — allowing developers to focus on business logic rather than identity plumbing.</p>
<p>Unlike the previous Codativity-specific version, this is a <strong>general-purpose, self-contained IAM platform</strong>. Any organization can run it as their central auth service. It supports workspace-level multi-tenancy with complete data isolation, fine-grained RBAC with roles and permissions, stateless JWT tokens with a Redis-backed blacklist pattern, and a composable architecture of services, stores, and caches.</p>
<p>The codebase lives entirely in this repository — Rust source, database migrations, documentation, and Docker infrastructure all in one place.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/oxideauth#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Multi-tenant workspaces</strong> — Three workspace types (GLOBAL, REGISTRY, STANDARD) with fully isolated data, roles, permissions, and configurations per tenant</li>
<li class=""><strong>Fine-grained RBAC</strong> — Roles bundle permissions; memberships link accounts to workspaces and projects; policies constrain access contextually without granting new abilities</li>
<li class=""><strong>Stateless JWT auth with blacklist</strong> — Tokens are validated by cryptographic signature with a fast Redis cache check for revoked tokens; database fallback ensures durability</li>
<li class=""><strong>Polymorphic token system</strong> — Unified token table supports BLACKLIST (revoked JWTs), REFRESH (session renewal), API_KEY (machine-to-machine), and RESET_PASSWORD tokens</li>
<li class=""><strong>Workspace-scoped login</strong> — Credentials (password, OAuth, SSO, API key) are scoped per workspace; login resolves the correct membership context for the JWT</li>
<li class=""><strong>Embedded background worker</strong> — Tokio-spawned janitor task auto-prunes expired tokens from the database on a schedule</li>
<li class=""><strong>AWS S3 &amp; SES integration</strong> — Object storage for user uploads and transactional email delivery via AWS services</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/oxideauth#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A layered, service-oriented Rust application backed by PostgreSQL and Redis:</p>
<table><thead><tr><th>Layer</th><th>Role</th></tr></thead><tbody><tr><td><strong>Web</strong></td><td>Axum HTTP handlers, routing, middleware (CORS, Ctx, Tracing, Response)</td></tr><tr><td><strong>Core (Services)</strong></td><td>Business logic — <code>ServiceFactory</code>, <code>TokenService</code>, <code>AuthService</code>, CRUD</td></tr><tr><td><strong>Store</strong></td><td>SQLx/PostgreSQL persistence with <code>StoreManager</code> and per-entity stores</td></tr><tr><td><strong>Cache</strong></td><td>Redis-backed <code>CacheManager</code> for session validation and blacklist checks</td></tr><tr><td><strong>Worker</strong></td><td>Embedded <code>tokio::spawn</code> background tasks (token expiry cleanup)</td></tr></tbody></table>
<p>All layers share an <code>Arc&lt;StoreManager&gt;</code> and <code>Arc&lt;CacheManager&gt;</code> via the <code>ServiceFactory</code>, with request-scoped services instantiated per-request to prevent state leakage.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-diagram">Architecture Diagram<a href="https://stackops.link/journal/oxideauth#architecture-diagram" class="hash-link" aria-label="Direct link to Architecture Diagram" title="Direct link to Architecture Diagram" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">HTTP Request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Axum Handler </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">web/handlers/*.rs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">CoreCtx Middleware → builds per-request context </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">membership, roles, permissions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Service Layer </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">core/services/*.rs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> → business logic, hydration, authorization</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Store Layer </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">store/stores/*.rs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> → sea-query + SQLx</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">PostgreSQL / Redis</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-model">Data Model<a href="https://stackops.link/journal/oxideauth#data-model" class="hash-link" aria-label="Direct link to Data Model" title="Direct link to Data Model" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-entities">Core Entities<a href="https://stackops.link/journal/oxideauth#core-entities" class="hash-link" aria-label="Direct link to Core Entities" title="Direct link to Core Entities" translate="no">​</a></h3>
<table><thead><tr><th>Entity</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>Account</strong></td><td>A user identity (email, name, verified, enabled)</td></tr><tr><td><strong>Workspace</strong></td><td>A tenant container — isolates projects, memberships, and RBAC</td></tr><tr><td><strong>Project</strong></td><td>A sub-area inside a workspace for finer scoping</td></tr><tr><td><strong>Membership</strong></td><td>Links an account to a workspace (or project), carries scope and status</td></tr><tr><td><strong>Credential</strong></td><td>A login method scoped to a workspace (password, OAuth, SSO, API key)</td></tr><tr><td><strong>Role</strong></td><td>A named bundle of permissions defined inside a workspace</td></tr><tr><td><strong>Permission</strong></td><td>A single capability (e.g., <code>projects:create</code>, <code>members:read</code>)</td></tr><tr><td><strong>Token</strong></td><td>Unified table for blacklisted JWTs, refresh tokens, API keys, and resets</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="rbac-flow">RBAC Flow<a href="https://stackops.link/journal/oxideauth#rbac-flow" class="hash-link" aria-label="Direct link to RBAC Flow" title="Direct link to RBAC Flow" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Account → Membership → Role → Permissions</span><br></div></code></pre></div></div>
<p>A user's effective permissions are the union of all permissions granted by all roles assigned to their membership(s). Policies can only <strong>constrain</strong> — they never grant new abilities.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="workspace-types">Workspace Types<a href="https://stackops.link/journal/oxideauth#workspace-types" class="hash-link" aria-label="Direct link to Workspace Types" title="Direct link to Workspace Types" translate="no">​</a></h3>
<table><thead><tr><th>Type</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>GLOBAL</strong></td><td>Singleton root — system superuser and bootstrap control</td></tr><tr><td><strong>REGISTRY</strong></td><td>Discovery/index — lists all workspaces; controls visibility</td></tr><tr><td><strong>STANDARD</strong></td><td>Normal tenant workspaces — isolated RBAC, projects, and policies</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="token-architecture">Token Architecture<a href="https://stackops.link/journal/oxideauth#token-architecture" class="hash-link" aria-label="Direct link to Token Architecture" title="Direct link to Token Architecture" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="stateless-jwt-with-blacklist">Stateless JWT with Blacklist<a href="https://stackops.link/journal/oxideauth#stateless-jwt-with-blacklist" class="hash-link" aria-label="Direct link to Stateless JWT with Blacklist" title="Direct link to Stateless JWT with Blacklist" translate="no">​</a></h3>
<p>The system uses <strong>stateless JWTs</strong> — the server trusts the token's cryptographic signature without a database lookup. To support logout and revocation, a <strong>blacklist pattern</strong> is used over a whitelist:</p>
<table><thead><tr><th style="text-align:left">Strategy</th><th style="text-align:left">Logic</th><th style="text-align:left">Performance</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Blacklist</strong></td><td style="text-align:left">All signed tokens valid unless revoked</td><td style="text-align:left">Fast (check Redis for exceptions)</td></tr><tr><td style="text-align:left"><strong>Whitelist</strong></td><td style="text-align:left">Only tokens in DB are valid</td><td style="text-align:left">Slow (DB hit every request)</td></tr></tbody></table>
<p>Every request: Signature check → Redis cache check (<code>bl:{jti}</code>) → Database fallback (if cache miss).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="token-types">Token Types<a href="https://stackops.link/journal/oxideauth#token-types" class="hash-link" aria-label="Direct link to Token Types" title="Direct link to Token Types" translate="no">​</a></h3>
<table><thead><tr><th style="text-align:left">Type</th><th style="text-align:left">Purpose</th></tr></thead><tbody><tr><td style="text-align:left"><strong>BLACKLIST</strong></td><td style="text-align:left">A <code>jti</code> revoked before expiry (logout, security lockout)</td></tr><tr><td style="text-align:left"><strong>REFRESH</strong></td><td style="text-align:left">Long-lived tokens for generating new access tokens</td></tr><tr><td style="text-align:left"><strong>API_KEY</strong></td><td style="text-align:left">Permanent/long-lived tokens for machine-to-machine access</td></tr><tr><td style="text-align:left"><strong>RESET_PASSWORD</strong></td><td style="text-align:left">Short-lived, single-use tokens for account recovery</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-design">API Design<a href="https://stackops.link/journal/oxideauth#api-design" class="hash-link" aria-label="Direct link to API Design" title="Direct link to API Design" translate="no">​</a></h2>
<p>All endpoints use HTTP <strong>POST</strong> with the action in the URL path (JSON-RPC style). This keeps sensitive data (tokens, credentials) in the request body and intentionally disables HTTP caching — a security requirement for real-time authorization.</p>
<table><thead><tr><th>Endpoint</th><th>Purpose</th></tr></thead><tbody><tr><td><code>POST /accounts/create</code></td><td>Register a new user account</td></tr><tr><td><code>POST /accounts/describe</code></td><td>Get account details</td></tr><tr><td><code>POST /accounts/list</code></td><td>List/filter accounts</td></tr><tr><td><code>POST /accounts/update</code></td><td>Modify account fields</td></tr><tr><td><code>POST /accounts/delete</code></td><td>Delete an account</td></tr><tr><td><code>POST /workspace/create</code></td><td>Create a new workspace</td></tr><tr><td><code>POST /workspace/describe</code></td><td>Get workspace details</td></tr><tr><td><code>POST /workspace/list</code></td><td>List/filter workspaces</td></tr><tr><td><code>POST /workspace/update</code></td><td>Modify workspace settings</td></tr><tr><td><code>POST /workspace/delete</code></td><td>Delete a workspace</td></tr></tbody></table>
<p>Projects follow the same pattern at <code>/projects/create</code>, <code>/projects/describe</code>, etc.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/oxideauth#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Language</strong></td><td>Rust (edition 2024)</td></tr><tr><td><strong>Framework</strong></td><td>Axum 0.7 + Tower 0.5</td></tr><tr><td><strong>Runtime</strong></td><td>Tokio</td></tr><tr><td><strong>Database</strong></td><td>PostgreSQL 16 + SQLx 0.8 + sea-query 0.32 + modql 0.4</td></tr><tr><td><strong>Cache</strong></td><td>Redis 7</td></tr><tr><td><strong>Auth</strong></td><td>JWT (jsonwebtoken), Argon2 password hashing</td></tr><tr><td><strong>Email</strong></td><td>AWS SES (lettre)</td></tr><tr><td><strong>Storage</strong></td><td>AWS S3</td></tr><tr><td><strong>Logging</strong></td><td>Tracing + EnvFilter</td></tr><tr><td><strong>Deployment</strong></td><td>Docker, Docker Compose (Postgres + Redis)</td></tr><tr><td><strong>Migration</strong></td><td>SQLx CLI with per-environment migration directories</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="default-rbac">Default RBAC<a href="https://stackops.link/journal/oxideauth#default-rbac" class="hash-link" aria-label="Direct link to Default RBAC" title="Direct link to Default RBAC" translate="no">​</a></h2>
<p>When a new workspace is created, four default roles are seeded:</p>
<table><thead><tr><th style="text-align:left">Role</th><th style="text-align:left">Description</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Owner</strong></td><td style="text-align:left">Full unrestricted access; can delete the workspace and manage billing</td></tr><tr><td style="text-align:left"><strong>Admin</strong></td><td style="text-align:left">Manage all resources except workspace deletion and billing</td></tr><tr><td style="text-align:left"><strong>Member</strong></td><td style="text-align:left">Standard role — view resources, create projects</td></tr><tr><td style="text-align:left"><strong>Billing</strong></td><td style="text-align:left">View members and manage subscription/payment only; no project data access</td></tr></tbody></table>
<p>Permissions follow a <code>resource:action</code> convention (e.g., <code>projects:create</code>, <code>members:invite</code>, <code>billing:manage</code>). Over 20 default permissions are seeded across workspace, members, projects, roles, billing, and audit-log resources.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="development">Development<a href="https://stackops.link/journal/oxideauth#development" class="hash-link" aria-label="Direct link to Development" title="Direct link to Development" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://stackops.link/journal/oxideauth#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h3>
<ul>
<li class="">Rust (latest stable)</li>
<li class="">PostgreSQL 16</li>
<li class="">Redis 7</li>
<li class="">Docker &amp; Docker Compose (optional, for infra)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start">Quick Start<a href="https://stackops.link/journal/oxideauth#quick-start" class="hash-link" aria-label="Direct link to Quick Start" title="Direct link to Quick Start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Start PostgreSQL and Redis</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose up </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> postgres redis</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Copy and fill in the config</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> ironstone/.cargo/config.example.toml ironstone/.cargo/config.toml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Run dev migrations</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> db-dev-run</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Start the dev server</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--bin</span><span class="token plain"> ironstone</span><br></div></code></pre></div></div>
<p>The API starts on <code>http://localhost:8000</code> by default. Health check at <code>GET /health-check</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-structure">Project Structure<a href="https://stackops.link/journal/oxideauth#project-structure" class="hash-link" aria-label="Direct link to Project Structure" title="Direct link to Project Structure" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">oxideauth/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── ironstone/              </span><span class="token comment" style="color:rgb(98, 114, 164)"># Main Rust application</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── src/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── main.rs         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Entrypoint, tokio runtime</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── app.rs          </span><span class="token comment" style="color:rgb(98, 114, 164)"># AppState, env config, init</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── config.rs       </span><span class="token comment" style="color:rgb(98, 114, 164)"># Config from environment</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── web/            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Axum handlers, router, middleware, DTOs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── core/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Services, models, traits, context, worker</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── store/          </span><span class="token comment" style="color:rgb(98, 114, 164)"># StoreManager, entities, stores, query traits</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── cache/          </span><span class="token comment" style="color:rgb(98, 114, 164)"># CacheManager, Redis, cache stores</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── macros/         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Custom derive macros (filters)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── dev/            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Dev environment fixtures &amp; seeding</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── utils/          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Shared utilities</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── sql/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── migrations/dev/ </span><span class="token comment" style="color:rgb(98, 114, 164)"># Development schema migrations</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── migrations/prod/</span><span class="token comment" style="color:rgb(98, 114, 164)"># Production schema migrations</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       └── fixtures/       </span><span class="token comment" style="color:rgb(98, 114, 164)"># Bootstrap data (accounts, workspaces, roles, etc.)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── ironstone-macros/       </span><span class="token comment" style="color:rgb(98, 114, 164)"># Procedural macro crate</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── docs/                   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Architecture &amp; design documentation (24 docs)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── docker-compose.yaml     </span><span class="token comment" style="color:rgb(98, 114, 164)"># PostgreSQL + Redis infra</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── Dockerfile.dev          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Dev container with cargo-watch</span><br></div></code></pre></div></div>]]></content:encoded>
            <category>rust</category>
            <category>postgresql</category>
            <category>redis</category>
            <category>jwt</category>
            <category>authentication</category>
            <category>authorization</category>
            <category>rbac</category>
            <category>multi-tenant</category>
            <category>iam</category>
            <category>ironstone</category>
            <category>axum</category>
            <category>sqlx</category>
        </item>
        <item>
            <title><![CDATA[Raderbot — Rust Trading Bot]]></title>
            <link>https://stackops.link/journal/raderbot</link>
            <guid>https://stackops.link/journal/raderbot</guid>
            <pubDate>Tue, 05 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A Rust-based trading bot connecting to Binance / BingX WebSocket APIs for live market data (kline, ticker, trade streams), position management, and algorithmic strategies — exposed through an Actix Web REST API and static UI on `localhost:3000`.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/raderbot#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p>Raderbot is a single-binary Rust app on Actix Web. It maintains WebSocket streams to an exchange, stores incoming market data, runs algorithmic trading strategies, and exposes everything through a REST API. The architecture is trait-based: exchange adapters and storage backends can be swapped at runtime via the <code>.env</code> file or an API call.</p>
<ul>
<li class=""><strong>Dry-run by default</strong> — <code>DRY_RUN=True</code> routes all trading through a <code>MockExchangeApi</code>.</li>
<li class=""><strong>Decoupled account &amp; market layers</strong> — Account uses its own exchange API instance; market data can come from a different source.</li>
<li class=""><strong>Pluggable storage</strong> — file system (default), MongoDB time-series, or InfluxDB.</li>
<li class=""><strong>Dynamic exchange switching</strong> — an endpoint swaps the account adapter between Binance, BingX, and Mock without restart.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="module-map">Module map<a href="https://stackops.link/journal/raderbot#module-map" class="hash-link" aria-label="Direct link to Module map" title="Direct link to Module map" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">src/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── main.rs            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Entry point; Actix HttpServer, service registration</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── app.rs             </span><span class="token comment" style="color:rgb(98, 114, 164)"># AppState — shared Arc&lt;Mutex&lt;...&gt;&gt; for bot, account, market, exchange</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── bot.rs             </span><span class="token comment" style="color:rgb(98, 114, 164)"># RaderBot core + StrategyManager + async init loop</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── account/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Position / TradeTx models, position lifecycle, adapter swap</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── algo/              </span><span class="token comment" style="color:rgb(98, 114, 164)"># 12+ trading algorithms</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── analytics/         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Volume analysis (PriceVolume, TimeVolume, TradeVolume)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── api/               </span><span class="token comment" style="color:rgb(98, 114, 164)"># 5 route scopes: account, exchange, market, strategy, utils</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── exchange/          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Trait-based adapters: BinanceApi, BingXApi, MockExchangeApi + streams</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── market/            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Kline, Ticker, Trade structs + in-memory Market store</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── storage/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># FsStorage, MongoDbStorage, InfluxStorage behind StorageManager trait</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── strategy/          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Strategy loop, algorithm binding, SignalHandler, BackTest</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── utils/             </span><span class="token comment" style="color:rgb(98, 114, 164)"># Channels, json helpers, time helpers</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="root-files">Root files<a href="https://stackops.link/journal/raderbot#root-files" class="hash-link" aria-label="Direct link to Root files" title="Direct link to Root files" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Cargo.toml             </span><span class="token comment" style="color:rgb(98, 114, 164)"># tokio, actix-web, reqwest, tokio-tungstenite, serde, ta, mongodb...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Makefile               </span><span class="token comment" style="color:rgb(98, 114, 164)"># make build / dev / test / fix / clean / docs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">docker-compose.yaml     </span><span class="token comment" style="color:rgb(98, 114, 164)"># InfluxDB 2.7 (MongoDB commented out)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">.env.example            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Exchange keys, DRY_RUN, storage backend, RUST_LOG</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">raderbot                </span><span class="token comment" style="color:rgb(98, 114, 164)"># Prebuilt release binary</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">static/                 </span><span class="token comment" style="color:rgb(98, 114, 164)"># Frontend assets</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/                   </span><span class="token comment" style="color:rgb(98, 114, 164)"># strategies.md</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">LICENSE.md              </span><span class="token comment" style="color:rgb(98, 114, 164)"># MIT</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="exchange-layer">Exchange layer<a href="https://stackops.link/journal/raderbot#exchange-layer" class="hash-link" aria-label="Direct link to Exchange layer" title="Direct link to Exchange layer" translate="no">​</a></h2>
<p>Everything flows through a single async trait, <code>ExchangeApi</code> (<code>src/exchange/api.rs</code>):</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token attribute attr-name" style="color:rgb(241, 250, 140)">#[async_trait]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">pub</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">trait</span><span class="token plain"> </span><span class="token type-definition class-name">ExchangeApi</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">Send</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token class-name">Sync</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">get_account</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">ApiResult</span><span class="token operator">&lt;</span><span class="token class-name">Value</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">open_position</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> symbol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> margin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> leverage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> side</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> price</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">ApiResult</span><span class="token operator">&lt;</span><span class="token class-name">Position</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">close_position</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> position</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> close_price</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">ApiResult</span><span class="token operator">&lt;</span><span class="token class-name">TradeTx</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">get_kline</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> symbol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> interval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">ApiResult</span><span class="token operator">&lt;</span><span class="token class-name">Kline</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">get_ticker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> symbol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">ApiResult</span><span class="token operator">&lt;</span><span class="token class-name">Ticker</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">get_stream_manager</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">ArcMutex</span><span class="token operator">&lt;</span><span class="token class-name">Box</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">dyn</span><span class="token plain"> </span><span class="token class-name">StreamManager</span><span class="token operator">&gt;&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>Three implementations:</p>
<table><thead><tr><th>Adapter</th><th>File</th><th>Role</th></tr></thead><tbody><tr><td><code>BinanceApi</code></td><td><code>exchange/binance.rs</code></td><td>Default — real Binance API over REST + WebSocket streams</td></tr><tr><td><code>BingXApi</code></td><td><code>exchange/bingx.rs</code></td><td>Alternative BingX adapter</td></tr><tr><td><code>MockExchangeApi</code></td><td><code>exchange/mock.rs</code></td><td>Simulated exchange; used automatically when <code>DRY_RUN=True</code></td></tr></tbody></table>
<p><strong>Streams</strong> are managed through a <code>StreamManager</code> trait (<code>exchange/stream.rs</code>). Each stream has metadata: id, URL, symbol, interval, start/last-update timestamps. Stream IDs follow <code>&lt;symbol&gt;@kline_&lt;interval&gt;</code> / <code>&lt;symbol&gt;@ticker</code> / <code>&lt;symbol&gt;@trade</code>.</p>
<p><strong>Key detail from <code>bot.rs</code>:</strong> on startup the bot creates <em>two</em> exchange API instances — one for market data (hardcoded to <code>BinanceApi</code>), one for the account that can differ. When dry-run is on, the account gets a <code>MockExchangeApi</code>; otherwise it shares the market's <code>BinanceApi</code> or creates its own.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="market-data">Market data<a href="https://stackops.link/journal/raderbot#market-data" class="hash-link" aria-label="Direct link to Market data" title="Direct link to Market data" translate="no">​</a></h2>
<p>Models live in <code>src/market/</code>:</p>
<table><thead><tr><th>Struct</th><th>File</th><th>Fields</th></tr></thead><tbody><tr><td><code>Kline</code></td><td><code>kline.rs</code></td><td>symbol, interval, open/high/low/close, volume, open_time, close_time</td></tr><tr><td><code>Ticker</code></td><td><code>ticker.rs</code></td><td>last price, 24h change, high, low, volume</td></tr><tr><td><code>Trade</code></td><td><code>trade.rs</code></td><td>timestamped public market trades</td></tr><tr><td><code>Interval</code></td><td><code>interval.rs</code></td><td>1m, 3m, 5m, 15m, 30m, 1h, 4h, 1d, 1w (string-serializable enum)</td></tr></tbody></table>
<p>The <strong><code>Market</code></strong> (<code>market/market.rs</code>) is the in-memory store — it receives data from active WebSocket streams via an async channel (<code>MarketMessage</code> in <code>messages.rs</code>) and supports range queries against both live and bootstrapped historical data.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="storage-backends">Storage backends<a href="https://stackops.link/journal/raderbot#storage-backends" class="hash-link" aria-label="Direct link to Storage backends" title="Direct link to Storage backends" translate="no">​</a></h2>
<p>All implement the <code>StorageManager</code> trait. Selected at boot from <code>.env</code>:</p>
<table><thead><tr><th>Backend</th><th>File</th><th>When to use</th></tr></thead><tbody><tr><td><strong>FsStorage</strong> (default)</td><td><code>storage/fs.rs</code></td><td><code>STORAGE_TYPE=FS</code> — writes to <code>~/.raderbot/</code></td></tr><tr><td><strong>MongoDbStorage</strong></td><td><code>storage/mongo.rs</code></td><td><code>STORAGE_TYPE=MONGO</code> — time-series collections</td></tr><tr><td><strong>InfluxStorage</strong></td><td><code>storage/influx.rs</code></td><td><code>STORAGE_TYPE=INFLUX</code> — InfluxDB 2.7 point writes</td></tr></tbody></table>
<p><strong>Fallback behavior</strong> (from <code>bot.rs</code>): if Mongo or Influx fail to connect, the bot logs the error and falls back to <code>FsStorage</code> — no crash.</p>
<p><strong>InfluxDB setup</strong> (<code>docker-compose.yaml</code>):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key atrule">influxdb</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> influxdb</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">2.7</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"8086:8086"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">environment</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">DOCKER_INFLUXDB_INIT_MODE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> setup</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">DOCKER_INFLUXDB_INIT_ORG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> raderbot</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">DOCKER_INFLUXDB_INIT_BUCKET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> trade_data</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">DOCKER_INFLUXDB_INIT_ADMIN_TOKEN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> myadmintoken</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="strategies--algorithms">Strategies &amp; algorithms<a href="https://stackops.link/journal/raderbot#strategies--algorithms" class="hash-link" aria-label="Direct link to Strategies &amp; algorithms" title="Direct link to Strategies &amp; algorithms" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="lifecycle">Lifecycle<a href="https://stackops.link/journal/raderbot#lifecycle" class="hash-link" aria-label="Direct link to Lifecycle" title="Direct link to Lifecycle" translate="no">​</a></h3>
<p><code>POST /new-strategy</code> → <code>bot.start_strategy(...)</code> → picks an algorithm from <code>src/algo/</code> → spawns a <code>Strategy</code> via <code>tokio::spawn</code> → tracks handle + settings in <code>StrategyManager</code>. A signal handler loop consumes <code>SignalMessage</code>s and fires orders through the <code>Account</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="available-algorithms-srcalgo">Available algorithms (<code>src/algo/</code>)<a href="https://stackops.link/journal/raderbot#available-algorithms-srcalgo" class="hash-link" aria-label="Direct link to available-algorithms-srcalgo" title="Direct link to available-algorithms-srcalgo" translate="no">​</a></h3>
<table><thead><tr><th>File</th><th>Strategy</th></tr></thead><tbody><tr><td><code>bollinger_bands.rs</code></td><td>Price bands around moving average; volatility envelope</td></tr><tr><td><code>ma_crossover.rs</code></td><td>Short-term MA crosses long-term MA → buy/sell</td></tr><tr><td><code>ma_simple.rs</code></td><td>Simple moving average signal</td></tr><tr><td><code>ma_three_crossover.rs</code></td><td>Three-MA crossover (fast/med/slow)</td></tr><tr><td><code>macd.rs</code></td><td>MACD (diff of EMAs) vs signal line</td></tr><tr><td><code>macd_bollinger.rs</code></td><td>MACD + Bollinger Bands combined</td></tr><tr><td><code>rsi.rs</code></td><td>RSI momentum oscillator (overbought more than 70, oversold less than 30)</td></tr><tr><td><code>rsi_ema_sma.rs</code></td><td>RSI + EMA + SMA mix</td></tr><tr><td><code>volume_continuation.rs</code></td><td>Volume-confirmed trend continuation</td></tr><tr><td><code>volume_continuation_reversal.rs</code></td><td>Volume reversal detection</td></tr><tr><td><code>volume_profile.rs</code></td><td>Volume-weighted price profiling</td></tr><tr><td><code>builder.rs</code></td><td>Factory — maps algorithm name string → concrete impl</td></tr><tr><td><code>template.rs</code></td><td>Scaffold for writing new algorithms</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="backtesting">Backtesting<a href="https://stackops.link/journal/raderbot#backtesting" class="hash-link" aria-label="Direct link to Backtesting" title="Direct link to Backtesting" translate="no">​</a></h3>
<p><code>BackTest</code> (<code>strategy/backer.rs</code>) replays KlineData over a strategy and produces a <code>StrategySummary</code> (PnL, trade count, win rate, etc.). Triggered via <code>POST /run-back-test</code> with symbol, interval, date range, and algorithm params. Uses the <code>KlineData</code> from the market's historical store.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="trade-models">Trade models<a href="https://stackops.link/journal/raderbot#trade-models" class="hash-link" aria-label="Direct link to Trade models" title="Direct link to Trade models" translate="no">​</a></h3>
<ul>
<li class=""><strong><code>Position</code></strong> — symbol, order side (Buy/Sell), open price &amp; time, quantity (<code>margin × leverage / price</code>), margin_usd, leverage, optional stop-loss and strategy ID. Uses <code>uuid::v4</code> for IDs.</li>
<li class=""><strong><code>TradeTx</code></strong> — close price, close time, computed <code>profit</code> (positive for winning trades on Buy, inverse for Sell), and linked source position with optional associated <code>SignalMessage</code> metadata.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rest-api">REST API<a href="https://stackops.link/journal/raderbot#rest-api" class="hash-link" aria-label="Direct link to REST API" title="Direct link to REST API" translate="no">​</a></h2>
<p>All endpoints under <code>http://localhost:3000/api</code>. Full request/response docs at the <a href="https://documenter.getpostman.com/view/22215488/2sA2xfYYa5" target="_blank" rel="noopener noreferrer" class="">Postman collection</a> — or <a href="https://stackops.link/assets/files/RaderBot.postman_collection-3158aa9247b9957668d8811c48f807f4.json" target="_blank" class="">download the collection JSON</a> to import directly into Postman / Insomnia.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="apimarket"><code>/api/market</code><a href="https://stackops.link/journal/raderbot#apimarket" class="hash-link" aria-label="Direct link to apimarket" title="Direct link to apimarket" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /info                         Market summary</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /active-streams               Live stream metadata</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /open-stream                  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">stream_type, symbol, interval?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /close-stream                 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">stream_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /last-price                   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /kline-data                   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, interval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /kline-data-range             </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, interval, from_ts?, to_ts?, limit?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /ticker-data                  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /trade-data                   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, from_ts?, to_ts?, limit?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /trade-volume-data            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, </span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">., bucket_size? </span><span class="token operator">|</span><span class="token plain"> time_interval?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="apiaccount"><code>/api/account</code><a href="https://stackops.link/journal/raderbot#apiaccount" class="hash-link" aria-label="Direct link to apiaccount" title="Direct link to apiaccount" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /account-info</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /set-exchange-api             </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">exchange, dry_run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">  → swap Binance/BingX/Mock</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /active-positions</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /trades</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /open-position                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, margin, leverage, order_side, stop_loss?, strategy_id?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /close-position               </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">position_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /close-all-positions</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="apistrategy"><code>/api/strategy</code><a href="https://stackops.link/journal/raderbot#apistrategy" class="hash-link" aria-label="Direct link to apistrategy" title="Direct link to apistrategy" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /new-strategy                 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, strategy_name, algorithm_params, interval, margin?, leverage?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /stop-strategy                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id, close_positions?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /stop-all-strategies          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">close_positions?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /set-params                   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id, params</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /change-settings              </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id, settings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /active-strategies</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET  /historical-strategies</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /historical-summary           </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /list-positions               </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /summary                      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /info                         </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">strategy_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /run-back-test                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">symbol, strategy_name, algorithm_params, interval, from_ts, to_ts, </span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-root--static"><code>/api</code> (root + static)<a href="https://stackops.link/journal/raderbot#api-root--static" class="hash-link" aria-label="Direct link to api-root--static" title="Direct link to api-root--static" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api                 → serves static/index.html</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET /static/*            → frontend assets </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">actix-files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting started<a href="https://stackops.link/journal/raderbot#getting-started" class="hash-link" aria-label="Direct link to Getting started" title="Direct link to Getting started" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Prereqs: Rust, cargo, make (Docker optional)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone https://github.com/subaquatic-pierre/raderbot.git</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> raderbot</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Configure</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> .env.example .env</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># .env:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   BINANCE_API_KEY=...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   BINANCE_SECRET_KEY=...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   DRY_RUN=True</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   STORAGE_TYPE=FS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   RUST_LOG=info</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Build release binary</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">make</span><span class="token plain"> build</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Run</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./raderbot</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># → Actix on 127.0.0.1:3000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Dev mode (auto-reload on file changes)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">make</span><span class="token plain"> dev</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-env-values">Key <code>.env</code> values<a href="https://stackops.link/journal/raderbot#key-env-values" class="hash-link" aria-label="Direct link to key-env-values" title="Direct link to key-env-values" translate="no">​</a></h3>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key attr-name" style="color:rgb(241, 250, 140)">DRY_RUN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">True              # True → MockExchangeApi (no real trades)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">EXCHANGE_API</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">BINANCE      # BINANCE or BINGX</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">STORAGE_TYPE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">FS           # FS | MONGO | INFLUX</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">MONGO_URI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">mongodb://user:pass@localhost:27017/?authSource=admin</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">INFLUX_DB_HOST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">http://localhost:8086</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">INFLUX_TOKEN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">myadmintoken</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="optional-influxdb-for-time-series-metrics">Optional: InfluxDB for time-series metrics<a href="https://stackops.link/journal/raderbot#optional-influxdb-for-time-series-metrics" class="hash-link" aria-label="Direct link to Optional: InfluxDB for time-series metrics" title="Direct link to Optional: InfluxDB for time-series metrics" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker-compose</span><span class="token plain"> up </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> influxdb</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Update .env: STORAGE_TYPE=INFLUX, restart bot</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="historical-data">Historical data<a href="https://stackops.link/journal/raderbot#historical-data" class="hash-link" aria-label="Direct link to Historical data" title="Direct link to Historical data" translate="no">​</a></h2>
<p>For backtests and <code>BootstrapKlineData</code>/<code>BootstrapTradeData</code> requests, place CSV data from Binance public market data at:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">~/Projects/BinanceData/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── Kline/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── BTCUSDT-5m-2023-12.csv</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── </span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── MarketTrade/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── BTCUSDT-aggTrades-2020-01.csv</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── </span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><br></div></code></pre></div></div>
<p>Source: <a href="https://data.binance.vision/" target="_blank" rel="noopener noreferrer" class="">data.binance.vision</a> — download ZIP files from <code>data/spot/monthly/klines/&lt;SYMBOL&gt;/&lt;INTERVAL&gt;/</code> (and <code>aggTrades/</code> for trades). Extraction scripts in <a href="https://github.com/binance/binance-public-data" target="_blank" rel="noopener noreferrer" class="">github.com/binance/binance-public-data</a>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="notes">Notes<a href="https://stackops.link/journal/raderbot#notes" class="hash-link" aria-label="Direct link to Notes" title="Direct link to Notes" translate="no">​</a></h2>
<ul>
<li class=""><strong>Experimental</strong> — the bot is still in active development. Many features are undocumented or rough.</li>
<li class=""><strong>Unix only</strong> — build targets Unix systems.</li>
<li class=""><strong>Dry-run only by default</strong> — live trading requires <code>DRY_RUN=False</code> and a funded exchange API key.</li>
<li class=""><strong>Market data required for orders</strong> — opening/closing positions requires a live last price from a stream or ticker call. Make sure the symbol has an active stream.</li>
<li class=""><strong>Storage fallback</strong> — if Mongo or InfluxDB can't connect, the bot silently falls back to file-system storage.</li>
</ul>]]></content:encoded>
            <category>rust</category>
            <category>trading</category>
            <category>cryptocurrency</category>
            <category>websocket</category>
            <category>actix</category>
            <category>bingx</category>
            <category>binance</category>
            <category>algorithms</category>
        </item>
        <item>
            <title><![CDATA[Sera Villas — Luxury Villa Booking Platform]]></title>
            <link>https://stackops.link/journal/sera-villas</link>
            <guid>https://stackops.link/journal/sera-villas</guid>
            <pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A luxury vacation rental marketing website and booking platform for high-end villas in Mallorca, Spain. Full booking engine with availability checking, Stripe payments, dynamic pricing, and automated emails — powered by a Directus CMS and Uplisting   property management system.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/sera-villas#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Sera Villas</strong> is a luxury booking platform showcasing four high-end vacation properties in Mallorca, Spain — Villa Escondido, Villa Lumina, Villa Vertice, and Villa Terra. The site combines the visual polish of a luxury brand with a complete booking engine that handles availability lookups, multi-step reservation flow, Stripe payment processing, and automated confirmation emails.</p>
<p>The platform connects to <strong>Uplisting</strong> (a property management system) for real-time availability and pricing, proxies all sensitive API calls through Next.js server-side routes to keep credentials secure, and uses Directus as a headless CMS for blog posts and page content.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/sera-villas#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Full booking engine:</strong> Multi-step flow with guest details, Stripe payment, and automated confirmation emails</li>
<li class=""><strong>Secure Uplisting proxy:</strong> All PMS API calls go through Next.js server routes — credentials never reach the browser</li>
<li class=""><strong>Dynamic pricing:</strong> Cleaning fees, extra guest charges, taxes, weekly discounts (5%), and monthly discounts (10%)</li>
<li class=""><strong>Availability calendars:</strong> Normalized calendar data from Uplisting displayed with custom UI components</li>
<li class=""><strong>Multi-channel commissions:</strong> Tracks revenue across Airbnb, Booking.com, HomeAway, Google, and Direct channels</li>
<li class=""><strong>SEO:</strong> Dynamic sitemap, per-page metadata, OpenGraph images, structured data for rich search results</li>
<li class=""><strong>CDN delivery:</strong> Bunny CDN for static assets, CloudFront for Uplisting property photos</li>
<li class=""><strong>Concierge mentions:</strong> All properties advertise premium services (private chefs, airport transfers, grocery pre-stocking)</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/sera-villas#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>headless CMS + external PMS</strong> architecture:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Luxury villa website + booking engine</td></tr><tr><td><strong>directus</strong> (CMS)</td><td>Headless content management</td></tr><tr><td><strong>PostgreSQL</strong></td><td>CMS database</td></tr><tr><td><strong>Uplisting</strong> (PMS)</td><td>External property management system</td></tr><tr><td><strong>Stripe</strong></td><td>Payment processing</td></tr><tr><td><strong>AWS SES</strong></td><td>Transactional emails</td></tr></tbody></table>
<p>The frontend is the sole consumer-facing service. Uplisting, Stripe, and AWS SES are integrated through Next.js API routes that proxy requests — keeping API keys server-side and avoiding browser CORS issues.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend--nextjs-app-router-with-tailwind--shadcnui">Frontend — Next.js App Router with Tailwind &amp; shadcn/ui<a href="https://stackops.link/journal/sera-villas#frontend--nextjs-app-router-with-tailwind--shadcnui" class="hash-link" aria-label="Direct link to Frontend — Next.js App Router with Tailwind &amp; shadcn/ui" title="Direct link to Frontend — Next.js App Router with Tailwind &amp; shadcn/ui" translate="no">​</a></h2>
<p>Built with Next.js 15 App Router, Tailwind CSS, and shadcn/ui (New York style, Neutral base). The site uses 60+ custom components and 9 context providers for booking state, villa data, theming, notifications, and viewport management.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pages">Pages<a href="https://stackops.link/journal/sera-villas#pages" class="hash-link" aria-label="Direct link to Pages" title="Direct link to Pages" translate="no">​</a></h3>
<ul>
<li class=""><strong>Home</strong> — Hero section, villa listing grid, testimonials, blog highlights, Instagram feed</li>
<li class=""><strong>Villas</strong> — Listing page with all four properties</li>
<li class=""><strong>Villa Detail</strong> — Individual villa pages with photo gallery (Embla carousel), features grid, room layouts, map, amenity icons, and booking panel</li>
<li class=""><strong>Booking</strong> — Multi-step flow: date selection → guest details → price breakdown → payment</li>
<li class=""><strong>Booking Confirmation</strong> — Success page with booking summary</li>
<li class=""><strong>Blog</strong> — CMS-managed articles from Directus</li>
<li class=""><strong>About Us</strong> — Brand story and team</li>
<li class=""><strong>Privacy / Cookie / Terms</strong> — Legal pages</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="booking-engine">Booking Engine<a href="https://stackops.link/journal/sera-villas#booking-engine" class="hash-link" aria-label="Direct link to Booking Engine" title="Direct link to Booking Engine" translate="no">​</a></h3>
<p>The booking flow is a polished multi-step experience:</p>
<ol>
<li class=""><strong>Date selection</strong> — Availability calendar powered by Uplisting data</li>
<li class=""><strong>Guest details</strong> — Contact information and guest count</li>
<li class=""><strong>Price breakdown</strong> — Dynamic pricing with cleaning fees, extra guest charges, taxes, and weekly/monthly discounts</li>
<li class=""><strong>Stripe checkout</strong> — Secure card payment processing</li>
<li class=""><strong>Confirmation</strong> — Success page with automated AWS SES email</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cms--directus">CMS — Directus<a href="https://stackops.link/journal/sera-villas#cms--directus" class="hash-link" aria-label="Direct link to CMS — Directus" title="Direct link to CMS — Directus" translate="no">​</a></h2>
<p>Directus manages blog posts, CMS pages, and SEO metadata. Content is fetched via the <code>@directus/sdk</code> and served through Next.js ISR. Static assets are stored in a Hetzner S3 bucket and delivered through Bunny CDN at <code>cdt-sera-villas.b-cdn.net</code> for fast global delivery.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="third-party-integrations">Third-Party Integrations<a href="https://stackops.link/journal/sera-villas#third-party-integrations" class="hash-link" aria-label="Direct link to Third-Party Integrations" title="Direct link to Third-Party Integrations" translate="no">​</a></h2>
<table><thead><tr><th>Integration</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>Uplisting</strong></td><td>Property details, availability calendars, dynamic pricing, cleaning fees, channel commissions</td></tr><tr><td><strong>Stripe</strong></td><td>Payment processing — customer creation, payment intents, checkout sessions, refunds</td></tr><tr><td><strong>AWS SES</strong></td><td>Booking confirmation and contact form emails</td></tr><tr><td><strong>Hetzner S3 + Bunny CDN</strong></td><td>Asset storage and CDN delivery</td></tr><tr><td><strong>CloudFront</strong></td><td>Uplisting property photo delivery</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-channel-commission-tracking">Multi-Channel Commission Tracking<a href="https://stackops.link/journal/sera-villas#multi-channel-commission-tracking" class="hash-link" aria-label="Direct link to Multi-Channel Commission Tracking" title="Direct link to Multi-Channel Commission Tracking" translate="no">​</a></h3>
<p>Uplisting provides commission data across booking channels: Airbnb (18.34%), Booking.com (15%), HomeAway (8%), Google (6%), and Direct (0%) — allowing the business to track profitability by channel.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/sera-villas#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (App Router), React, TypeScript, Tailwind CSS, shadcn/ui</td></tr><tr><td><strong>Animations</strong></td><td>Motion (Framer Motion successor), Embla Carousel</td></tr><tr><td><strong>Forms</strong></td><td>React Hook Form + Zod (type-safe validation)</td></tr><tr><td><strong>CMS</strong></td><td>Directus (headless), PostgreSQL</td></tr><tr><td><strong>PMS</strong></td><td>Uplisting (property management)</td></tr><tr><td><strong>Payments</strong></td><td>Stripe</td></tr><tr><td><strong>Email</strong></td><td>AWS SES</td></tr><tr><td><strong>Storage</strong></td><td>Hetzner S3 + Bunny CDN</td></tr><tr><td><strong>Server Actions</strong></td><td><code>next-safe-action</code> for type-safe server logic</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>tailwind</category>
            <category>shadcn-ui</category>
            <category>directus</category>
            <category>cms</category>
            <category>postgresql</category>
            <category>stripe</category>
            <category>typescript</category>
            <category>villas</category>
            <category>booking</category>
            <category>luxury</category>
        </item>
        <item>
            <title><![CDATA[RAK NYE CarPass — Parking & Access Management System]]></title>
            <link>https://stackops.link/journal/raknye-carpass</link>
            <guid>https://stackops.link/journal/raknye-carpass</guid>
            <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Comprehensive parking and access management system for the Ras Al Khaimah New Year's Eve celebration on Al Marjan Island. Handles vehicle registration, car pass issuance, ANPR camera integration, barcode/QR access control, hotel quota management,   and live parking statistics.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/raknye-carpass#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>RAK NYE CarPass</strong> is a production-scale parking and access management system built for the Ras Al Khaimah New Year's Eve festival on Al Marjan Island, UAE. It manages every aspect of vehicle access to the event area — from visitor car pass registration and hotel quota management to real-time ANPR (Automatic Number Plate Recognition) camera integration and live parking occupancy dashboards.</p>
<p>The system handles <strong>10 parking locations</strong> across Al Marjan Island with traffic flow balancing, WhatsApp + SMS communication, barcode/QR entry passes, and a dedicated public display screen showing real-time parking availability. Built as a polyglot microservices system spanning TypeScript, C# (.NET), and Python.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/raknye-carpass#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>ANPR camera integration:</strong> Direct Hikvision camera connection with real-time plate registration and allow-list management</li>
<li class=""><strong>Multi-format scan parsing:</strong> XML, JSON, BSON, and multipart/form-data with robust fallback handling</li>
<li class=""><strong>10 parking locations:</strong> Jais, Yanas, Dhayah, Mega, Marjan1-4, VIP1, VIP2 — each with live occupancy tracking</li>
<li class=""><strong>Traffic flow balancing:</strong> Duplicate scan events across related locations for accurate occupancy counts</li>
<li class=""><strong>Hotel quota system:</strong> Per-hotel vehicle pass limits for guest management</li>
<li class=""><strong>Barcode/QR entry:</strong> PDF certificates with embedded QR codes for access passes</li>
<li class=""><strong>Live parking stats:</strong> Real-time occupancy dashboards with ApexCharts and count-up animations</li>
<li class=""><strong>Mobile scanner:</strong> HTML5 QR scanner for entry/exit checkpoint staff</li>
<li class=""><strong>WhatsApp + SMS:</strong> Twilio templated messaging with automatic fallback for undelivered WhatsApp messages</li>
<li class=""><strong>Multi-language:</strong> Full Arabic/English bilingual site with separate logos and RTL layout</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/raknye-carpass#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>five-service polyglot system</strong> with OxideAuth for authentication:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Public registration and information site (AR/EN)</td></tr><tr><td><strong>dashboard</strong> (Next.js)</td><td>Admin panel with RBAC and live stats</td></tr><tr><td><strong>api</strong> (Express.js)</td><td>REST API with MongoDB backend</td></tr><tr><td><strong>anpr-service</strong> (.NET 8)</td><td>ANPR camera integration for Hikvision cameras</td></tr><tr><td><strong>etl</strong> (Python)</td><td>Batch data pipeline for database migrations</td></tr><tr><td><strong>MongoDB</strong></td><td>Primary database</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="www--public-site">www — Public Site<a href="https://stackops.link/journal/raknye-carpass#www--public-site" class="hash-link" aria-label="Direct link to www — Public Site" title="Direct link to www — Public Site" translate="no">​</a></h2>
<p>Built with Next.js 16 App Router and MUI v7, supporting full Arabic/English bilingual with RTL layout using <code>react-intl</code> / <code>next-international</code>. The site features the Abu Dhabi Media font and separate Arabic and English logos.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pages">Pages<a href="https://stackops.link/journal/raknye-carpass#pages" class="hash-link" aria-label="Direct link to Pages" title="Direct link to Pages" translate="no">​</a></h3>
<ul>
<li class=""><strong>Car Pass Registration</strong> — Vehicle and driver registration form</li>
<li class=""><strong>Update CarPass</strong> — Modify existing registrations</li>
<li class=""><strong>Parking Registration</strong> — Parking pass purchase flow</li>
<li class=""><strong>Marjan Island Access</strong> — Event venue information</li>
<li class=""><strong>Celebration Guide</strong> — PDF event guides and schedules</li>
<li class=""><strong>Visitor Survey</strong> — Post-event feedback</li>
<li class=""><strong>Live Parking Updates</strong> — Real-time occupancy display</li>
<li class=""><strong>Public Display Screen</strong> — Large-format parking availability board for venue entry points</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dashboard--admin-panel">dashboard — Admin Panel<a href="https://stackops.link/journal/raknye-carpass#dashboard--admin-panel" class="hash-link" aria-label="Direct link to dashboard — Admin Panel" title="Direct link to dashboard — Admin Panel" translate="no">​</a></h2>
<p>The admin dashboard uses role-based views: <strong>Admin</strong>, <strong>Hotel</strong>, and <strong>Stats</strong> roles. Features include:</p>
<ul>
<li class=""><strong>Car pass management</strong> — View, approve, and manage all registrations</li>
<li class=""><strong>Hotel/quota management</strong> — Per-hotel vehicle pass quotas</li>
<li class=""><strong>Live parking stats</strong> — ApexCharts dashboards with count-up animations</li>
<li class=""><strong>Scan log viewer</strong> — Real-time entry/exit event logs</li>
<li class=""><strong>Barcode/QR scanner</strong> — Desktop and mobile scanning interfaces</li>
<li class=""><strong>Puck CMS</strong> — <code>@measured/puck</code> drag-and-drop page builder for custom admin layouts</li>
<li class=""><strong>Token management</strong> — Secure access token administration</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="anpr-service--net-8-camera-integration">anpr-service — .NET 8 Camera Integration<a href="https://stackops.link/journal/raknye-carpass#anpr-service--net-8-camera-integration" class="hash-link" aria-label="Direct link to anpr-service — .NET 8 Camera Integration" title="Direct link to anpr-service — .NET 8 Camera Integration" translate="no">​</a></h2>
<p>The ANPR service connects directly to <strong>Hikvision iDS-TCM403-B cameras</strong> for real-time license plate recognition. It handles:</p>
<ul>
<li class=""><strong>Scan intake</strong> — Receives camera scan data via XML/JSON/multipart</li>
<li class=""><strong>Plate registration</strong> — Programmatically adds authorized plates to camera allow lists</li>
<li class=""><strong>Traffic flow logic</strong> — Duplicates scan events across related parking locations (e.g., Marjan1 entry → Yanas exit) for accurate occupancy counts across the 10 parking zones</li>
<li class=""><strong>WhatsApp + SMS</strong> — Twilio-powered messaging with automatic SMS fallback when WhatsApp delivery fails</li>
<li class=""><strong>Message callbacks</strong> — Delivery status tracking for sent messages</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="etl--python-data-pipeline">etl — Python Data Pipeline<a href="https://stackops.link/journal/raknye-carpass#etl--python-data-pipeline" class="hash-link" aria-label="Direct link to etl — Python Data Pipeline" title="Direct link to etl — Python Data Pipeline" translate="no">​</a></h2>
<p>A batch ETL pipeline migrates data between database instances. It parses scan data from multiple formats (XML for Hikvision cameras, JSON, BSON, gate-specific) with configurable batch sizes and handles bulk hotel account imports from CSV files.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/raknye-carpass#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend (www)</strong></td><td>Next.js (App Router), React, TypeScript, MUI v7, Redux Toolkit</td></tr><tr><td><strong>Frontend (dashboard)</strong></td><td>Next.js, MUI v7, ApexCharts, Puck CMS</td></tr><tr><td><strong>API Backend</strong></td><td>Express.js, TypeScript, Mongoose (MongoDB)</td></tr><tr><td><strong>ANPR Service</strong></td><td>.NET 8 C#, ASP.NET Core Minimal API</td></tr><tr><td><strong>ETL Pipeline</strong></td><td>Python 3, PyMongo</td></tr><tr><td><strong>Database</strong></td><td>MongoDB</td></tr><tr><td><strong>Auth</strong></td><td>OxideAuth (JWT-based centralized auth)</td></tr><tr><td><strong>Messaging</strong></td><td>Twilio (WhatsApp + SMS fallback)</td></tr><tr><td><strong>Storage</strong></td><td>Azure Blob / Hetzner S3</td></tr><tr><td><strong>i18n</strong></td><td><code>react-intl</code> / <code>next-international</code> (EN/AR + RTL)</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>express</category>
            <category>dotnet</category>
            <category>csharp</category>
            <category>python</category>
            <category>mongodb</category>
            <category>mui</category>
            <category>typescript</category>
            <category>parking</category>
            <category>anpr</category>
            <category>iot</category>
            <category>qr-code</category>
            <category>bilingual</category>
        </item>
        <item>
            <title><![CDATA[Maven Platform — Bill of Quantities Management]]></title>
            <link>https://stackops.link/journal/maven-platform</link>
            <guid>https://stackops.link/journal/maven-platform</guid>
            <pubDate>Sun, 29 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A Bill of Quantities management platform for the construction and procurement industry. Manages projects, work orders, BOQs, work items, and checklists with a specialized .NET export engine   producing formatted Excel and PDF documents.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/maven-platform#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Maven Platform</strong> is a specialized Bill of Quantities (BOQ) management system built for the construction and procurement industry. It handles the entire BOQ workflow — from project creation and work order generation to work item classification, checklist management, and professional document export.</p>
<p>What sets Maven apart is its <strong>.NET BOQ Exporter</strong> — a dedicated microservice that generates commercial-grade multi-sheet Excel (XLSX) and PDF documents with professional formatting: table of contents, preambles, section sheets, page numbers, watermarks, and A4 print layouts. This is powered by Aspose.Cells, a licensed Excel generation engine.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/maven-platform#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Five-role RBAC:</strong> SuperAdmin, TeamManager, TeamLeader, QS, OfficeManager with entity-level permissions</li>
<li class=""><strong>BOQ Export Engine:</strong> Commercial-grade XLSX/PDF generation with Aspose.Cells — multi-sheet, table of contents, watermarks, A4 formatting</li>
<li class=""><strong>TanStack React Table:</strong> Performant data grids with sorting, filtering, and pagination for large BOQ datasets</li>
<li class=""><strong>Flexible query protocol:</strong> <code>$and</code>/<code>$or</code> JSON filters, population, pagination, fuzzy search, soft delete — all through a clean REST API</li>
<li class=""><strong>Drag-and-drop editing:</strong> DnD Kit for interactive reordering of work items and sections</li>
<li class=""><strong>QR code tracking:</strong> Generated QR codes on uploads and documents for traceability</li>
<li class=""><strong>Dual deployment mode:</strong> Kubernetes for staging, bare-metal VMs via systemd for production (with Cloudflare load balancing)</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/maven-platform#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>polyglot three-service system</strong> with OxideAuth for authentication:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Administrative dashboard</td></tr><tr><td><strong>api</strong> (Express.js)</td><td>REST API with 25 service files</td></tr><tr><td><strong>BOQExporter</strong> (.NET)</td><td>Professional XLSX/PDF document generation</td></tr><tr><td><strong>PostgreSQL</strong></td><td>Primary database</td></tr></tbody></table>
<p>The API uses five role types (SuperAdmin, TeamManager, TeamLeader, QS, OfficeManager) with fine-grained permissions on every entity. Auth is delegated to OxideAuth.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="www--dashboard">www — Dashboard<a href="https://stackops.link/journal/maven-platform#www--dashboard" class="hash-link" aria-label="Direct link to www — Dashboard" title="Direct link to www — Dashboard" translate="no">​</a></h2>
<p>The frontend is a Next.js App Router application with MUI v7, MUI X Pro (Tree View), and TanStack React Table for performant data grids. It supports English and Arabic with full RTL layout.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dashboard-sections">Dashboard Sections<a href="https://stackops.link/journal/maven-platform#dashboard-sections" class="hash-link" aria-label="Direct link to Dashboard Sections" title="Direct link to Dashboard Sections" translate="no">​</a></h3>
<ul>
<li class=""><strong>Projects &amp; Clients</strong> — Project lifecycle management and client relationships</li>
<li class=""><strong>Work Orders &amp; BOQs</strong> — The core BOQ workflow</li>
<li class=""><strong>Work Items</strong> — Classified line items within BOQs</li>
<li class=""><strong>Templates</strong> — Reusable BOQ templates for standardization</li>
<li class=""><strong>Checklists</strong> — QA/QC checklists with response tracking</li>
<li class=""><strong>Section Editor</strong> — BOQ section organization</li>
<li class=""><strong>Suppliers, Tenders &amp; Bids</strong> — Procurement workflow</li>
<li class=""><strong>Invoice</strong> — Billing and payment tracking</li>
<li class=""><strong>Variable Option Sets</strong> — Dynamic pricing configuration</li>
<li class=""><strong>Settings &amp; Users</strong> — Platform administration</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="rich-content-editing">Rich Content Editing<a href="https://stackops.link/journal/maven-platform#rich-content-editing" class="hash-link" aria-label="Direct link to Rich Content Editing" title="Direct link to Rich Content Editing" translate="no">​</a></h3>
<p>The dashboard includes TipTap for rich text editing (with color, highlighting, images, alignment) and DnD Kit for drag-and-drop reordering. Puck CMS (<code>@measured/puck</code>) provides a visual page builder for custom layouts.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="api--expressjs-backend">api — Express.js Backend<a href="https://stackops.link/journal/maven-platform#api--expressjs-backend" class="hash-link" aria-label="Direct link to api — Express.js Backend" title="Direct link to api — Express.js Backend" translate="no">​</a></h2>
<p>The Express.js API uses MikroORM for database access with 22 entity models and 22+ route sets. A <strong>flexible query protocol</strong> supports <code>$and</code>/<code>$or</code> JSON filters, population, pagination, fuzzy search, and soft deletion — giving the frontend powerful querying without exposing SQL.</p>
<p>All routes enforce OxideAuth-based RBAC with permissions like <code>maven.boq.create</code> and <code>maven.ui.admin</code>. The API also handles file uploads with drag-and-drop support, QR code generation for document tracking, and integration with the .NET export engine.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="boqexporter--net-8-export-engine">BOQExporter — .NET 8 Export Engine<a href="https://stackops.link/journal/maven-platform#boqexporter--net-8-export-engine" class="hash-link" aria-label="Direct link to BOQExporter — .NET 8 Export Engine" title="Direct link to BOQExporter — .NET 8 Export Engine" translate="no">​</a></h2>
<p>The BOQ Exporter is a .NET 8 ASP.NET Core Minimal API that uses Aspose.Cells (licensed) to generate professional BOQ documents:</p>
<ul>
<li class=""><strong>Multi-sheet XLSX:</strong> Cover page, table of contents, preambles, individual section sheets, summary sheet</li>
<li class=""><strong>Custom formatting:</strong> Fonts, colors, borders, print area, A4 portrait, page numbers, watermarks</li>
<li class=""><strong>PDF generation:</strong> Via SkiaSharp for non-editable distribution</li>
<li class=""><strong>Storage integration:</strong> Uploads generated files to Hetzner S3 and returns download URLs</li>
</ul>
<p>Endpoints: <code>POST /export-boq</code>, <code>POST /export-section</code>, <code>GET /ping</code>. The production Docker image includes LibreOffice Calc for server-side document format conversion.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/maven-platform#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (App Router), React, TypeScript, MUI v7, TanStack Table, ApexCharts</td></tr><tr><td><strong>Rich Editing</strong></td><td>TipTap (rich text), DnD Kit (drag-and-drop), Puck CMS (page builder)</td></tr><tr><td><strong>API Backend</strong></td><td>Express.js, TypeScript, MikroORM</td></tr><tr><td><strong>Export Engine</strong></td><td>.NET 8 C#, ASP.NET Core Minimal API, Aspose.Cells, SkiaSharp</td></tr><tr><td><strong>Database</strong></td><td>PostgreSQL</td></tr><tr><td><strong>Auth</strong></td><td>OxideAuth (JWT-based centralized auth)</td></tr><tr><td><strong>Storage</strong></td><td>Hetzner S3, Azure Blob (alternative)</td></tr><tr><td><strong>i18n</strong></td><td>English + Arabic with RTL</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>express</category>
            <category>dotnet</category>
            <category>csharp</category>
            <category>postgresql</category>
            <category>mui</category>
            <category>tanstack-table</category>
            <category>typescript</category>
            <category>construction</category>
            <category>procurement</category>
            <category>excel</category>
        </item>
        <item>
            <title><![CDATA[Event Hoster — Conference & Event Management Platform]]></title>
            <link>https://stackops.link/journal/event-app-demo</link>
            <guid>https://stackops.link/journal/event-app-demo</guid>
            <pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Full-stack event management platform for conference organizers. Supports multi-day events with speakers, agendas, attendee registration, ticket generation, QR code check-in, certificates, messaging, and feedback collection —   with bilingual English/Arabic support.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/event-app-demo#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Event Hoster</strong> is a complete event management platform designed for conference organizers. Originally built as a demo for a 3-day conference in Ras Al Khaimah, UAE, it has evolved into a full-featured system that handles every aspect of running a professional event — from the public marketing website to attendee check-in at the venue.</p>
<p>The platform supports English and Arabic (with RTL layout), role-based access control across seven user roles, and integrations for QR-based check-in, PDF certificate generation, ticket management, real-time messaging, and attendee feedback collection.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/event-app-demo#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Seven-role RBAC:</strong> admin, member, author, organizer, speaker, visitor — each with custom dashboard menus and permissions</li>
<li class=""><strong>QR-based check-in:</strong> Camera scanner at venue entrances + QR generation on tickets and certificates</li>
<li class=""><strong>PDF certificate generation:</strong> Dynamic certificates with attendee name, session hours, and verifiable QR codes</li>
<li class=""><strong>Multi-day agenda:</strong> 3-day schedule with hall-based filtering (A, B, C)</li>
<li class=""><strong>Bilingual support:</strong> Full English and Arabic interface with RTL layout and flag-based language switcher</li>
<li class=""><strong>Countdown timer:</strong> Eye-catching landing page countdown to event date</li>
<li class=""><strong>Feedback collection:</strong> 12-question satisfaction survey for post-event analytics</li>
<li class=""><strong>Notification system:</strong> In-app notifications for profile updates, registrations, and abstract reviews</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/event-app-demo#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>three-frontend split</strong> with a shared Strapi CMS backend:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Public event website (bilingual EN/AR)</td></tr><tr><td><strong>dashboard</strong> (Next.js)</td><td>Admin and user dashboard with RBAC</td></tr><tr><td><strong>api</strong> (Strapi)</td><td>Headless CMS + custom API routes</td></tr><tr><td><strong>PostgreSQL</strong></td><td>CMS database</td></tr></tbody></table>
<p>This three-app split creates clean separation: the public site is optimized for visitors browsing event details, while the dashboard is a full-featured SPA for organizers and attendees.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="www--public-event-website">www — Public Event Website<a href="https://stackops.link/journal/event-app-demo#www--public-event-website" class="hash-link" aria-label="Direct link to www — Public Event Website" title="Direct link to www — Public Event Website" translate="no">​</a></h2>
<p>The public-facing site serves as the conference's digital storefront. Built with Next.js 13 Pages Router, it uses MUI v5 for the component library and <code>react-intl</code> for bilingual English/Arabic with RTL support.</p>
<p>Landing page features a countdown timer to the event date. The agenda view displays a 3-day schedule filterable by hall (A/B/C). Speaker profiles show headshots and bios, and visitors can register, purchase tickets via Stripe, and verify certificates through a public lookup tool.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dashboard--admin--user-application">dashboard — Admin &amp; User Application<a href="https://stackops.link/journal/event-app-demo#dashboard--admin--user-application" class="hash-link" aria-label="Direct link to dashboard — Admin &amp; User Application" title="Direct link to dashboard — Admin &amp; User Application" translate="no">​</a></h2>
<p>The dashboard is a comprehensive management interface built with Next.js, Redux Toolkit for state, and FullCalendar for scheduling. Seven user roles each get customized navigation menus: <strong>admin</strong>, <strong>member</strong>, <strong>author</strong>, <strong>organizer</strong>, <strong>speaker</strong>, and <strong>visitor</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dashboard-capabilities">Dashboard Capabilities<a href="https://stackops.link/journal/event-app-demo#dashboard-capabilities" class="hash-link" aria-label="Direct link to Dashboard Capabilities" title="Direct link to Dashboard Capabilities" translate="no">​</a></h3>
<ul>
<li class=""><strong>QR code scanner</strong> — Camera-based attendee check-in using <code>html5-qrcode</code></li>
<li class=""><strong>Schedule management</strong> — FullCalendar with drag-and-drop event CRUD</li>
<li class=""><strong>Certificate generation</strong> — Dynamic PDF certificates with embedded QR codes using <code>pdf-lib</code></li>
<li class=""><strong>Ticket generation</strong> — QR-coded entry tickets for attendees</li>
<li class=""><strong>Chat/messaging</strong> — Real-time messaging between users with emoji picker</li>
<li class=""><strong>Attendance tracking</strong> — Per-day, per-hall records with online/in-person status</li>
<li class=""><strong>Blog management</strong> — CRUD for news articles</li>
<li class=""><strong>Registration management</strong> — Attendee approval and session abstract review</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend--strapi-cms">Backend — Strapi CMS<a href="https://stackops.link/journal/event-app-demo#backend--strapi-cms" class="hash-link" aria-label="Direct link to Backend — Strapi CMS" title="Direct link to Backend — Strapi CMS" translate="no">​</a></h2>
<p>Strapi manages all content through a rich data model: Event, Profile, Abstract, Blog, AttendRecord, FeedbackForm, Message, Notification, TicketType, and ProfileLog collections. Custom API endpoints provide profile lookups, admin queries, and attendance record operations.</p>
<p>Files are stored in Azure Blob Storage — particularly important for generated certificates and tickets that need durable, scalable storage. Emails are delivered through SendGrid in production, with a local MailDev instance for development testing.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/event-app-demo#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend (www)</strong></td><td>Next.js, React, TypeScript, MUI v5, <code>react-intl</code> (EN/AR + RTL)</td></tr><tr><td><strong>Frontend (dashboard)</strong></td><td>Next.js, Redux Toolkit, FullCalendar, Ant Design, <code>next-auth</code></td></tr><tr><td><strong>CMS / Backend</strong></td><td>Strapi (headless), PostgreSQL</td></tr><tr><td><strong>QR / Barcode</strong></td><td><code>html5-qrcode</code> for scanning, embedded QR in PDFs</td></tr><tr><td><strong>PDF Generation</strong></td><td><code>pdf-lib</code> for dynamic certificates and tickets</td></tr><tr><td><strong>Payments</strong></td><td>Stripe client-side integration</td></tr><tr><td><strong>Email</strong></td><td>SendGrid (prod), MailDev (dev)</td></tr><tr><td><strong>Storage</strong></td><td>Azure Blob Storage</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>strapi</category>
            <category>cms</category>
            <category>postgresql</category>
            <category>mui</category>
            <category>ant-design</category>
            <category>fullcalendar</category>
            <category>typescript</category>
            <category>events</category>
            <category>qr-code</category>
            <category>bilingual</category>
        </item>
        <item>
            <title><![CDATA[Terraform Labs]]></title>
            <link>https://stackops.link/journal/terraform-labs</link>
            <guid>https://stackops.link/journal/terraform-labs</guid>
            <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A collection of Terraform labs covering EC2, EKS, GitOps, SRE, static sites, serverless, and CI/CD on AWS. Pick a directory, `terraform init`, and go.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-index">Quick index<a href="https://stackops.link/journal/terraform-labs#quick-index" class="hash-link" aria-label="Direct link to Quick index" title="Direct link to Quick index" translate="no">​</a></h2>
<table><thead><tr><th>Directory</th><th>What it builds</th></tr></thead><tbody><tr><td><a href="https://stackops.link/journal/terraform-labs#basic-webserver" class=""><code>basic-webserver/</code></a></td><td>VPC + EC2 + Security Groups — bare-bones web hosting</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#eks" class=""><code>eks/</code></a></td><td>EKS cluster with managed node groups, ALB controller, autoscaler, metrics</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#gitops" class=""><code>gitops/</code></a></td><td>ArgoCD + Sealed Secrets — declarative cluster management</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#sre" class=""><code>sre/</code></a></td><td>Grafana LGTM stack (Loki, Mimir, Alloy) — observability for Kubernetes</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#static-website" class=""><code>static-website/</code></a></td><td>S3 + CloudFront + ACM + Route53 — static site with SSL and CDN</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#serverless-api" class=""><code>serverless-api/</code></a></td><td>API Gateway + Lambda — REST API with Python handlers</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#aws-ecs-ci-cd" class=""><code>aws-ecs-ci-cd/</code></a></td><td>ECS Fargate + CodePipeline — full-stack CI/CD (Django + React)</td></tr><tr><td><a href="https://stackops.link/journal/terraform-labs#pwa-ci-cd" class=""><code>pwa-ci-cd/</code></a></td><td>S3 + CloudFront + GitHub Actions — PWA deployment pipeline</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="basic-webserver">basic-webserver<a href="https://stackops.link/journal/terraform-labs#basic-webserver" class="hash-link" aria-label="Direct link to basic-webserver" title="Direct link to basic-webserver" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>A minimal AWS environment: a VPC with public subnets, security groups, an SSH key pair, and an EC2 instance bootstrapped with Nginx via user-data. The entry-point lab — used to learn Terraform + AWS fundamentals.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map">File map<a href="https://stackops.link/journal/terraform-labs#file-map" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">basic-webserver/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── vpc.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># VPC module (terraform-aws-modules/vpc/aws ~&gt; 5.0)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── sg.tf           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Security groups for HTTP (80) + SSH (22)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── ec2.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># EC2 instance + TLS private key + key pair</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── main.tf         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Provider + backend config</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── output.tf</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>VPC</strong> (<code>vpc.tf</code>) — uses the official <code>terraform-aws-modules/vpc/aws</code> module (v5). Two public subnets across <code>us-east-1a</code> and <code>us-east-1b</code>, one private subnet. No NAT gateway (cost-conscious). DNS hostnames and support enabled.</li>
<li class=""><strong>Security group</strong> (<code>sg.tf</code>) — ingress for port 80 (HTTP, 0.0.0.0/0) and port 22 (SSH, 0.0.0.0/0), standard egress all.</li>
<li class=""><strong>EC2</strong> (<code>ec2.tf</code>) — looks up the latest Ubuntu 22.04 AMI via data source, generates a 4096-bit RSA key pair with the <code>tls_private_key</code> resource, writes the private key to <code>keys/&lt;name&gt;.pem</code> with mode <code>0600</code>, and launches the instance in the first public subnet. User-data installs Nginx and writes a <code>Hello World from Terraform!</code> index page.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> basic-webserver/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform plan</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># SSH in (key written to keys/ directory)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> keys/my-key.pem ec2-user@</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">terraform output </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-raw</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> public_ip</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Or open the public IP in a browser to hit Nginx</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="eks">eks<a href="https://stackops.link/journal/terraform-labs#eks" class="hash-link" aria-label="Direct link to eks" title="Direct link to eks" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-1">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-1" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>A production-ready EKS cluster with managed node groups, IAM Roles for Service Accounts (IRSA) using the new pod identity agent, RBAC with three access tiers, and essential add-ons: AWS Load Balancer Controller, Cluster Autoscaler, and Metrics Server. Also includes example app manifests for HPA scaling tests and a Next.js static build deploy recipe.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-1">File map<a href="https://stackops.link/journal/terraform-labs#file-map-1" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">eks/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── eks.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># EKS cluster + IAM role + add-ons (pod identity, network flow monitoring)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── nodes.tf        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Managed node group (SPOT, t3.large, 1-10 nodes)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── vpc.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Custom VPC (no module — raw aws_vpc, aws_subnet, etc.)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── role.tf         </span><span class="token comment" style="color:rgb(98, 114, 164)"># IAM roles for service accounts (ALB controller, external-dns, etc.)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── trust.tf        </span><span class="token comment" style="color:rgb(98, 114, 164)"># OIDC trust relationship for IRSA</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── rbac.tf         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Three ClusterRoleBindings: admins, developers, viewers</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── users.tf        </span><span class="token comment" style="color:rgb(98, 114, 164)"># IAM users grouped into eks-admins / eks-developers / eks-viewers</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── autoscaler.tf   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Cluster Autoscaler via Helm + pod identity association</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── lbc.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># AWS Load Balancer Controller via Helm</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── metrics.tf      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Metrics Server via Helm</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── apps/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Example k8s manifests (HPA demo, Next.js static)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── policies/       </span><span class="token comment" style="color:rgb(98, 114, 164)"># IAM policy JSON documents</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── main.tf         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Provider + backend</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── providers.tf    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Provider aliases (aws + kubernetes + helm)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── output.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── version.tf</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-1">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-1" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>EKS control plane</strong> (<code>eks.tf</code>) — cluster with public endpoint, private-only subnet placement, API authentication mode, bootstrap admin permissions, and two add-ons: <code>aws-network-flow-monitoring-agent</code> and <code>eks-pod-identity-agent</code>. The pod identity agent enables <strong>EKS Pod Identity</strong> associations (the newer replacement for IRSA via OIDC).</li>
<li class=""><strong>Node group</strong> (<code>nodes.tf</code>) — single managed node group using SPOT capacity, <code>t3.large</code> instances, 1 desired / 0 min / 10 max. Lives in private subnets. <code>lifecycle { ignore_changes = [scaling_config[0].desired_size] }</code> so the autoscaler can adjust the count without Terraform drift. Commented-out launch template shows how to add custom user-data.</li>
<li class=""><strong>RBAC</strong> (<code>rbac.tf</code>) — three groups mapped to ClusterRoles: <code>eks-admins</code> → <code>cluster-admin</code>, <code>eks-developers</code> → <code>edit</code> (in <code>development</code> namespace), <code>eks-viewers</code> → <code>view</code>. IAM users are assigned to groups in <code>users.tf</code>.</li>
<li class=""><strong>Cluster Autoscaler</strong> (<code>autoscaler.tf</code>) — installed via Helm (v9.37.0, <code>kube-system</code> namespace). Uses an <strong>EKS Pod Identity association</strong> — the autoscaler service account is mapped to an IAM role with a custom policy (<code>policies/AWSClusterAutoScaler.json</code>) via <code>aws_eks_pod_identity_association</code>. This is the newer, cleaner approach vs. OIDC IRSA.</li>
<li class=""><strong>Load Balancer Controller</strong> (<code>lbc.tf</code>) — Helm install of <code>aws-load-balancer-controller</code>. Provisions ALBs/NLBs automatically from Kubernetes Ingress resources.</li>
<li class=""><strong>Metrics Server</strong> (<code>metrics.tf</code>) — Helm install. Required for HPA and <code>kubectl top</code>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start-1">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start-1" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> eks/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform plan</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Grab kubeconfig</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">aws eks update-kubeconfig </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--name</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">terraform output </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-raw</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> cluster_name</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Trigger HPA scaling test</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl apply </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> apps/app-hpa/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl port-forward </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> example svc/myapp </span><span class="token number">8080</span><span class="token plain">:8080</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> http://localhost:8080/api/cpu?index</span><span class="token operator">=</span><span class="token number">44</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Deploy a Next.js static build to S3 + CloudFront</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">aws s3 </span><span class="token function" style="color:rgb(80, 250, 123)">sync</span><span class="token plain"> out/ s3://</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">terraform output </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-raw</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> bucket_name</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--delete</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">aws cloudfront create-invalidation </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --distribution-id </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">terraform output </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-raw</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> cloudfront_id</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--paths</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/*"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="todo-from-readme">TODO (from README)<a href="https://stackops.link/journal/terraform-labs#todo-from-readme" class="hash-link" aria-label="Direct link to TODO (from README)" title="Direct link to TODO (from README)" translate="no">​</a></h3>
<ul>
<li class="">Add L4 LoadBalancer with Nginx Ingress</li>
<li class="">Install cert-manager for cluster-wide SSL</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gitops">gitops<a href="https://stackops.link/journal/terraform-labs#gitops" class="hash-link" aria-label="Direct link to gitops" title="Direct link to gitops" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-2">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-2" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>Bootstraps ArgoCD onto an EKS cluster and configures an ApplicationSet that discovers and syncs applications from a Git repository. Also sets up Sealed Secrets for encrypting sensitive values (Docker registry pull secrets) that can be safely committed to Git. Includes SSH-based private repo authentication for ArgoCD.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-2">File map<a href="https://stackops.link/journal/terraform-labs#file-map-2" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">gitops/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── argocd.tf       </span><span class="token comment" style="color:rgb(98, 114, 164)"># ArgoCD Helm install + ApplicationSet + SSH repo credentials</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── ns.tf           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Project namespaces (via kubernetes_namespace_v1)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── secrets.tf      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Sealed Secrets controller (Helm)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── secrets.md      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step-by-step for encrypting docker config with kubeseal</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── providers.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── apps/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Example ArgoCD Application manifests</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── gitops-example/ </span><span class="token comment" style="color:rgb(98, 114, 164)"># Reference repo structure: environments/&lt;env&gt;/&lt;app&gt;/</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── Makefile</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-2">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-2" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>ArgoCD install</strong> (<code>argocd.tf</code>) — deploys via Helm (chart <code>argo-cd</code> v10.1.4). Server gets <code>--insecure</code> flag for HTTP access during development.</li>
<li class=""><strong>SSH repo credentials</strong> — a <code>kubernetes_secret_v1</code> labeled <code>argocd.argoproj.io/secret-type: repository</code> stores an SSH private key (read from disk at <code>secrets/git_ssh.key</code>). ArgoCD uses this to clone the GitOps repo over SSH without a username/password.</li>
<li class=""><strong>ApplicationSet</strong> — a <code>kubernetes_manifest</code> that defines a Git directory generator. Anything under <code>environments/*/*</code> in the GitOps repo becomes an ArgoCD Application with automated sync (prune + self-heal enabled). Naming convention: <code>{{path[1]}}-{{path[2]}}</code>.</li>
<li class=""><strong>Sealed Secrets</strong> (<code>secrets.tf</code>) — deploys the Bitnami Sealed Secrets controller via Helm. The <code>secrets.md</code> file walks through encrypting a Docker <code>config.json</code> using <code>kubeseal</code> so private registry pull credentials can live in Git safely. The encrypted secret is then managed by Terraform.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start-2">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start-2" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Prerequisite — install Sealed Secrets controller</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">helm repo </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> sealed-secrets https://bitnami-labs.github.io/sealed-secrets</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">helm repo update</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">helm </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> sealed-secrets sealed-secrets/sealed-secrets </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--namespace</span><span class="token plain"> sealed-secrets --create-namespace</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Encrypt a docker config secret for Git</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl create secret generic regcred </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --from-file</span><span class="token operator">=</span><span class="token plain">.dockerconfigjson</span><span class="token operator">=</span><span class="token environment constant" style="color:rgb(189, 147, 249)">$HOME</span><span class="token plain">/.docker/config.json </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--type</span><span class="token operator">=</span><span class="token plain">kubernetes.io/dockerconfigjson </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--namespace</span><span class="token operator">=</span><span class="token plain">project </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --dry-run</span><span class="token operator">=</span><span class="token plain">client </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> yaml </span><span class="token operator">&gt;</span><span class="token plain"> local-regcred.secret.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">kubeseal </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--format</span><span class="token plain"> yaml </span><span class="token operator">&lt;</span><span class="token plain"> local-regcred.secret.yaml </span><span class="token operator">&gt;</span><span class="token plain"> docker-regcred.yaml </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --controller-namespace sealed-secrets </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --controller-name sealed-secrets</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Deploy gitops stack</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> gitops/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sre">sre<a href="https://stackops.link/journal/terraform-labs#sre" class="hash-link" aria-label="Direct link to sre" title="Direct link to sre" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-3">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-3" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>A full Grafana LGTM observability stack: <strong>Grafana</strong> for dashboards, <strong>Loki</strong> for log aggregation, <strong>Mimir</strong> for long-term metrics storage, and <strong>Grafana Alloy</strong> as the telemetry collector (metrics + logs). Plus <strong>Metrics Server</strong> and <strong>kube-state-metrics</strong>. Designed to run locally on Minikube with CSI-hostpath storage, or on EKS with S3 object storage for Loki chunks/ruler and Mimir blocks/alerts/ruler.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-3">File map<a href="https://stackops.link/journal/terraform-labs#file-map-3" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">sre/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── grafana.tf      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Grafana Helm install (v12.7.2)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── loki.tf         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Loki Helm install (v18.5.0) — S3-backed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── mimir.tf        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Mimir distributed Helm install (v6.2.0) — S3-backed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── alloy.tf        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Alloy Helm install (v1.10.1) — River config from templates</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── metrics_server.tf    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Metrics Server Helm install</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── state_metrics.tf     </span><span class="token comment" style="color:rgb(98, 114, 164)"># kube-state-metrics Helm install</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── s3.tf           </span><span class="token comment" style="color:rgb(98, 114, 164)"># 5 S3 buckets: loki_chunks, loki_ruler, mimir, mimir_alert, mimir_block, mimir_ruler</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── secrets.tf      </span><span class="token comment" style="color:rgb(98, 114, 164)"># S3 credentials as k8s secrets for Loki + Mimir</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── ns.tf           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Namespaces: monitoring, loki, mimir</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── node_init.tf    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Node label bootstrap</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── locals.tf       </span><span class="token comment" style="color:rgb(98, 114, 164)"># Shared locals (AWS region, namespace refs)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── values/         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Helm value overrides + Alloy River config templates</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── grafana.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── loki-ss.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── mimir.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── alloy.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── alloy/      </span><span class="token comment" style="color:rgb(98, 114, 164)"># River config: discovery.river, metrics.river, logs.river, loki.river</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── providers.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── version.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── Makefile        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Port-forward shortcuts</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-3">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-3" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>Alloy</strong> (<code>alloy.tf</code>) — the most interesting piece. Uses <code>templatefile()</code> to compose Alloy's River configuration from four template files: <code>discovery.river</code> (Kubernetes service discovery), <code>metrics.river</code> (scrape → Mimir), <code>logs.river</code> (collect → Loki), and <code>loki.river</code> (self-monitoring). The assembled config is passed as a Helm value. Alloy replaces the older Grafana Agent as the unified collector — one binary for metrics, logs, and traces.</li>
<li class=""><strong>Loki</strong> (<code>loki.tf</code>) — deployed with S3 storage backend for chunks and ruler. Values are templated with bucket names, AWS region, and credentials. Depends on the S3 buckets being created first.</li>
<li class=""><strong>Mimir</strong> (<code>mimir.tf</code>) — the <code>mimir-distributed</code> chart (v6.2.0-weekly.402). Uses four S3 buckets (blocks, alertmanager, ruler, and a general bucket). Also depends on an S3 credentials secret.</li>
<li class=""><strong>Grafana</strong> (<code>grafana.tf</code>) — pre-configured with Loki and Mimir as data sources via templatefile. The <code>grafana.yaml</code> values file references the cluster-internal service endpoints for both backends.</li>
<li class=""><strong>S3 buckets</strong> (<code>s3.tf</code>) — five buckets for Loki (chunks, ruler) and Mimir (blocks, alertmanager, ruler, general). Forces S3 over local storage when connected to AWS; defaults to CSI-hostpath volumes on Minikube.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="local-dev-minikube">Local dev (Minikube)<a href="https://stackops.link/journal/terraform-labs#local-dev-minikube" class="hash-link" aria-label="Direct link to Local dev (Minikube)" title="Direct link to Local dev (Minikube)" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Start 4-node cluster with CSI storage and Flannel CNI</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube start </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--nodes</span><span class="token operator">=</span><span class="token number">4</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--cni</span><span class="token operator">=</span><span class="token plain">flannel </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--addons</span><span class="token operator">=</span><span class="token plain">volumesnapshots,csi-hostpath-driver </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --extra-config</span><span class="token operator">=</span><span class="token plain">apiserver.storage-roles</span><span class="token operator">=</span><span class="token plain">true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Enable CSI as default storage class</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">enable</span><span class="token plain"> volumesnapshots</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">enable</span><span class="token plain"> csi-hostpath-driver</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons disable storage-provisioner</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons disable default-storageclass</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl patch storageclass csi-hostpath-sc </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Deploy the stack</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> sre/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Port-forward UIs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">make</span><span class="token plain"> port-forward</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Grafana  → http://localhost:5000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Alloy UI → http://localhost:5001</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Mimir UI → http://localhost:5002</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="helm-in-place-updates">Helm in-place updates<a href="https://stackops.link/journal/terraform-labs#helm-in-place-updates" class="hash-link" aria-label="Direct link to Helm in-place updates" title="Direct link to Helm in-place updates" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">helm upgrade loki grafana/loki </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--namespace</span><span class="token plain"> monitoring </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --reuse-values </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--set</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">loki.storage.s3.insecure</span><span class="token operator">=</span><span class="token plain">true</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="static-website">static-website<a href="https://stackops.link/journal/terraform-labs#static-website" class="hash-link" aria-label="Direct link to static-website" title="Direct link to static-website" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-4">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-4" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>A secure, CDN-accelerated static site: S3 bucket with static website hosting, CloudFront distribution with Origin Access Control (OAC), ACM SSL certificate, and Route53 DNS alias. The S3 bucket policy restricts access to CloudFront only (no public-read ACLs).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-4">File map<a href="https://stackops.link/journal/terraform-labs#file-map-4" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">static-website/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── s3.tf           </span><span class="token comment" style="color:rgb(98, 114, 164)"># S3 bucket + versioning + public access block + website config + bucket policy</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── cloudfront.tf   </span><span class="token comment" style="color:rgb(98, 114, 164)"># CloudFront distribution + OAC + custom error response</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── acm.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># ACM certificate + DNS validation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── dns.tf          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Route53 alias record → CloudFront</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── providers.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── output.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── version.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── apps/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Static site content (js, css, html)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── scripts/        </span><span class="token comment" style="color:rgb(98, 114, 164)"># S3 sync + CloudFront invalidation helpers</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── Makefile</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-4">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-4" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>S3</strong> (<code>s3.tf</code>) — bucket with versioning enabled, full public access block (no ACLs, no public policies), website config pointing to <code>index.html</code> / <code>error.html</code>. The bucket policy grants <code>s3:GetObject</code> only to the CloudFront distribution ARN via <code>AWS:SourceArn</code> condition. A commented-out legacy policy shows the older public-read approach that this replaced.</li>
<li class=""><strong>CloudFront</strong> (<code>cloudfront.tf</code>) — distribution with Origin Access Control (OAC, not the legacy OAI), <code>sigv4</code> signing, HTTPS redirect, and a custom 404 → 200 error response pointing to <code>/index.html</code> (SPA-friendly). Viewer certificate uses the ACM cert from <code>acm.tf</code>.</li>
<li class=""><strong>ACM</strong> (<code>acm.tf</code>) — certificate created in <code>us-east-1</code> (required for CloudFront) with DNS validation via Route53.</li>
<li class=""><strong>Route53</strong> (<code>dns.tf</code>) — alias A record pointing <code>infra.&lt;domain&gt;</code> to the CloudFront distribution.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start-3">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start-3" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> static-website/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Sync content and bust cache</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">aws s3 </span><span class="token function" style="color:rgb(80, 250, 123)">sync</span><span class="token plain"> ./apps/ s3://</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-bucket </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--delete</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">aws cloudfront create-invalidation --distribution-id </span><span class="token operator">&lt;</span><span class="token plain">id</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--paths</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/*"</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverless-api">serverless-api<a href="https://stackops.link/journal/terraform-labs#serverless-api" class="hash-link" aria-label="Direct link to serverless-api" title="Direct link to serverless-api" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-5">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-5" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>A REST API on API Gateway backed by Python Lambda functions. Uses the <code>mewa/apigateway-cors</code> module for CORS handling. Lambda handlers are skeleton methods — fill in your business logic.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-5">File map<a href="https://stackops.link/journal/terraform-labs#file-map-5" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">serverless-api/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── main.tf         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Root: wires api + lambda modules together</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── outputs.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── api/            </span><span class="token comment" style="color:rgb(98, 114, 164)"># API Gateway module (REST API, resources, methods, integrations)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── assets/         </span><span class="token comment" style="color:rgb(98, 114, 164)"># Cloud architecture diagram</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── lambda/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── handlers/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       └── http_methods.py   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Skeleton handlers: list, get, post, put, delete</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── lambda module/  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Lambda function + IAM role + permission</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-5">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-5" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>Modular design</strong> (<code>main.tf</code>) — the API and Lambda are separate Terraform modules. The root <code>main.tf</code> wires them by passing the API's execution ARN into the Lambda module, and each Lambda's invoke ARN back into the API module. CORS is handled by the <code>mewa/apigateway-cors/aws</code> community module (v2.0.1), applied to both the root resource and the <code>/resource</code> path.</li>
<li class=""><strong>Endpoints</strong> — six methods across two resources:</li>
</ul>
<table><thead><tr><th>Route</th><th>Methods</th><th>Lambda Handler</th></tr></thead><tbody><tr><td><code>/</code></td><td>GET, POST</td><td><code>index</code> (root)</td></tr><tr><td><code>/resource</code></td><td>GET</td><td><code>list</code></td></tr><tr><td><code>/resource</code></td><td>POST</td><td><code>post</code></td></tr><tr><td><code>/resource/{post_id}</code></td><td>GET</td><td><code>get</code></td></tr><tr><td><code>/resource/{post_id}</code></td><td>PUT</td><td><code>put</code></td></tr><tr><td><code>/resource/{post_id}</code></td><td>DELETE</td><td><code>delete</code></td></tr></tbody></table>
<ul>
<li class=""><strong>Lambda handlers</strong> — <code>http_methods.py</code> exports a <code>@app.route</code> decorator pattern. Responses use a <code>Response.format</code> class method for consistent JSON structure. No implementation — just the skeleton ready for business logic.</li>
<li class=""><strong>Providers</strong> — uses AWS provider <code>~&gt; 4.0.0</code>, plus <code>random</code> and <code>archive</code> for Lambda packaging.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start-4">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start-4" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> serverless-api/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> terraform.tfvars.example terraform.tfvars   </span><span class="token comment" style="color:rgb(98, 114, 164)"># fill in domain_name, tags</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform plan </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-out</span><span class="token plain"> plan</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Note: DNS propagation can take a couple of hours. If the first apply fails</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># due to resource creation ordering, retry — it usually works on the second run.</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="aws-ecs-ci-cd">aws-ecs-ci-cd<a href="https://stackops.link/journal/terraform-labs#aws-ecs-ci-cd" class="hash-link" aria-label="Direct link to aws-ecs-ci-cd" title="Direct link to aws-ecs-ci-cd" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-6">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-6" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>A full CI/CD infrastructure for a decoupled full-stack application: a Django GraphQL API backend on ECS Fargate behind an ALB with Auto Scaling, and a React frontend on S3/CloudFront. Both are deployed by independent AWS CodePipelines triggered from separate GitHub repos via CodeStar Connections. Includes a custom VPC, SSL via ACM, Route53 DNS, ECR image repository, and ALB access logs streamed to S3 (queryable via Athena).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-6">File map<a href="https://stackops.link/journal/terraform-labs#file-map-6" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">aws-ecs-ci-cd/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── main.tf              </span><span class="token comment" style="color:rgb(98, 114, 164)"># All resources (monolith — VPC, ECS, CodePipeline, S3, CloudFront, Route53)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── variables.tf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── terraform.tfvars.example   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Extensive var file: repos, ECR URI, domain, secrets, VPC CIDRs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── api/                 </span><span class="token comment" style="color:rgb(98, 114, 164)"># Backend ECS task def + service + ALB config</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── frontend/            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Frontend S3 + CloudFront config</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── pipelines/           </span><span class="token comment" style="color:rgb(98, 114, 164)"># CodePipeline definitions + buildspec files</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── api/             </span><span class="token comment" style="color:rgb(98, 114, 164)">#   Backend pipeline (build Docker → push ECR → deploy ECS)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── frontend/        </span><span class="token comment" style="color:rgb(98, 114, 164)">#   Frontend pipeline (build React → sync S3 → invalidate CloudFront)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── readme/              </span><span class="token comment" style="color:rgb(98, 114, 164)"># Screenshots + detailed how-to guides for every prerequisite</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-6">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-6" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>Decoupled pipelines</strong> — two independent CodePipelines. Pushing to the backend GitHub repo triggers the API pipeline (build Docker image, push to ECR, deploy to ECS). Pushing to the frontend GitHub repo triggers the frontend pipeline (build React, sync to S3, invalidate CloudFront). Each has its own buildspec with testing hooks.</li>
<li class=""><strong>ECS backend</strong> — Fargate launch type, ALB with Auto Scaling Group. The ECR repository URI is passed via <code>api_ecr_app_uri</code> variable. Docker build secrets (DB credentials, Django secret key, AWS keys, Docker Hub credentials) are injected at build time via <code>build_secrets</code> map.</li>
<li class=""><strong>Networking</strong> — VPC with 2 public + 2 private subnets across <code>us-east-1a</code> and <code>us-east-1b</code>, NAT gateway, internet gateway. All resources are in <code>us-east-1</code>.</li>
<li class=""><strong>DNS + SSL</strong> — Route53 hosted zone with <code>www</code> → root redirect. ACM certificate for HTTPS. ALB access logs go to S3 with Athena partitioning for querying.</li>
<li class=""><strong>Prerequisites are involved</strong> — the README has dedicated sections for each: Route53 domain, ACM certificate, Terraform backend S3 bucket, GitHub repos for frontend and backend (sample repos linked: <code>react-apollo</code> and <code>django-graphql</code>), CodeStar connection, ECR repository, RDS Postgres DB, and AWS access key credentials.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start-5">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start-5" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> aws-ecs-ci-cd/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> terraform.tfvars.example terraform.tfvars   </span><span class="token comment" style="color:rgb(98, 114, 164)"># fill in ALL variables</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Push to either repo's main branch → pipeline triggers automatically</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pwa-ci-cd">pwa-ci-cd<a href="https://stackops.link/journal/terraform-labs#pwa-ci-cd" class="hash-link" aria-label="Direct link to pwa-ci-cd" title="Direct link to pwa-ci-cd" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-builds-7">What it builds<a href="https://stackops.link/journal/terraform-labs#what-it-builds-7" class="hash-link" aria-label="Direct link to What it builds" title="Direct link to What it builds" translate="no">​</a></h3>
<p>Infrastructure and GitHub Actions workflows for a Progressive Web App with staging and production environments. Uses S3 + CloudFront for hosting, ACM for SSL, Route53 for DNS (including <code>www</code> → bare redirect). Terraform is isolated in a <code>terraform/</code> subdirectory, separate from the app code.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-map-7">File map<a href="https://stackops.link/journal/terraform-labs#file-map-7" class="hash-link" aria-label="Direct link to File map" title="Direct link to File map" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pwa-ci-cd/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── terraform/               </span><span class="token comment" style="color:rgb(98, 114, 164)"># Isolated Terraform config (separate from app code)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── app/                     </span><span class="token comment" style="color:rgb(98, 114, 164)"># PWA application source</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── assets/                  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Cloud architecture diagram</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── .github/workflows/       </span><span class="token comment" style="color:rgb(98, 114, 164)"># CI/CD actions</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── test.yml             </span><span class="token comment" style="color:rgb(98, 114, 164)">#   Runs on push to dev</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── deploy-staging.yml   </span><span class="token comment" style="color:rgb(98, 114, 164)">#   Tests + deploys to staging.domain.com on push to staging</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── deploy-prod.yml      </span><span class="token comment" style="color:rgb(98, 114, 164)">#   Tests + deploys to domain.com on push to main</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── README.md</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── README.md.pwa-pipeline   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pipeline-specific docs</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-7">How it works<a href="https://stackops.link/journal/terraform-labs#how-it-works-7" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h3>
<ul>
<li class=""><strong>Three-branch Git workflow</strong> — <code>dev</code> (test only) → <code>staging</code> (test + deploy to staging) → <code>main</code> (test + deploy to production). Each merge triggers the corresponding GitHub Action.</li>
<li class=""><strong>Dual environments</strong> — two sets of S3 buckets and CloudFront distributions: one for staging (<code>staging.domain.com</code>), one for production (<code>domain.com</code>). The <code>www</code> → root redirect bucket is a third S3 bucket configured for website redirect.</li>
<li class=""><strong>GitHub Actions</strong> — uses AWS CLI in the runner to sync <code>app/</code> to S3 and invalidate CloudFront. Secrets (AWS keys, bucket IDs, distribution IDs) come from Terraform outputs, manually set in GitHub repo settings.</li>
<li class=""><strong>Known issue</strong> — the CloudFront distribution for the <code>www</code> → root redirect bucket hits an upstream Terraform AWS provider bug (<a href="https://github.com/hashicorp/terraform-provider-aws/issues/24332" target="_blank" rel="noopener noreferrer" class="">#24332</a>). The origin must be manually patched in the AWS Console to point to the S3 website endpoint (<code>bucket.s3-website-us-east-1.amazonaws.com</code>) instead of the REST endpoint.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start-6">Quick start<a href="https://stackops.link/journal/terraform-labs#quick-start-6" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> pwa-ci-cd/terraform/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform plan </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-out</span><span class="token plain"> plan</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">terraform apply</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Grab outputs and set in GitHub Secrets:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   MAIN_CLOUDFRONT_DISTRIBUTION_ID  → main_cloudfront_dist_id</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   MAIN_S3_BUCKET_ID                → main_s3_bucket_id</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   STAGING_CLOUDFRONT_DISTRIBUTION_ID → staging_cloudfront_dist_id</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   STAGING_S3_BUCKET_ID             → staging_s3_bucket_id</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#   + AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Fix the redirect bucket origin manually in AWS Console (see known issue above)</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="local-sre-dev-setup">Local SRE dev setup<a href="https://stackops.link/journal/terraform-labs#local-sre-dev-setup" class="hash-link" aria-label="Direct link to Local SRE dev setup" title="Direct link to Local SRE dev setup" translate="no">​</a></h2>
<p>Used by the <code>sre/</code> lab; also referenced in the main README:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube start </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--nodes</span><span class="token operator">=</span><span class="token number">4</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--cni</span><span class="token operator">=</span><span class="token plain">flannel </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--addons</span><span class="token operator">=</span><span class="token plain">volumesnapshots,csi-hostpath-driver </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --extra-config</span><span class="token operator">=</span><span class="token plain">apiserver.storage-roles</span><span class="token operator">=</span><span class="token plain">true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">enable</span><span class="token plain"> volumesnapshots</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">enable</span><span class="token plain"> csi-hostpath-driver</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons disable storage-provisioner</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">minikube addons disable default-storageclass</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl patch storageclass csi-hostpath-sc </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'</span><br></div></code></pre></div></div>]]></content:encoded>
            <category>terraform</category>
            <category>aws</category>
            <category>kubernetes</category>
            <category>sre</category>
            <category>gitops</category>
            <category>ci-cd</category>
            <category>serverless</category>
            <category>iac</category>
        </item>
        <item>
            <title><![CDATA[Risk-V — 5-Stage Pipelined RISC-V Microarchitecture Study]]></title>
            <link>https://stackops.link/journal/riskv</link>
            <guid>https://stackops.link/journal/riskv</guid>
            <pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A structural, visual microarchitecture study of a complete 5-stage pipelined RISC-V RV32I processor built entirely from fundamental digital logic primitives in Logisim-evolution. Every component is visible and simulatable at the gate level, with full hazard detection, operand forwarding, branch prediction, and a 14-suite automated test framework. Includes comprehensive MkDocs documentation with detailed theory on pipeline hazards and forwarding.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/riskv#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Risk-V</strong> is not a software project — it is a structural, educational microarchitecture study of a complete <strong>5-stage pipelined RISC-V (RV32I) processor</strong> built in Logisim-evolution, a digital logic simulator. Every wire, every gate, and every pipeline register is visible and step-through-able.</p>
<p>The design covers the full classic 5-stage pipeline — Instruction Fetch, Decode, Execute, Memory Access, and Writeback — with hardware implementations of load-use hazard detection and stalling, operand forwarding (bypassing from EX/MEM and MEM/WB stages), and branch/jump resolution with pipeline flushes. An in-progress 4-way set-associative cache subsystem rounds out the memory hierarchy.</p>
<p>Complemented by a 22-page MkDocs Material documentation site and a Python-based automated test runner that validates 14 test suites against expected register state, the project is designed as both a reference implementation and a teaching tool.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/riskv#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Complete 5-stage RISC-V RV32I CPU:</strong> IF (Instruction Fetch) → ID (Decode + Register File) → EX (Execute + ALU) → MEM (Memory Access) → WB (Writeback)</li>
<li class=""><strong>Hardware Hazard Controller:</strong> Formal boolean logic for load-use hazard detection — <code>LoadUseHazard = ID_EX_MemRead AND (ID_EX_rd != 0) AND (ID_EX_rd == IF_ID_rs1 OR ID_EX_rd == IF_ID_rs2)</code> — triggering 1-cycle stalls with PC/IF_ID writes disabled</li>
<li class=""><strong>Operand Forwarding:</strong> Forwarding paths from EX/MEM and MEM/WB pipeline registers back to the ALU inputs, eliminating unnecessary stalls</li>
<li class=""><strong>Branch and Jump Resolution:</strong> Control hazards handled with pipeline flushes when branches are taken; jump target computed in the Decode stage</li>
<li class=""><strong>Full structural visibility:</strong> Every component built from Logisim primitives (AND/OR/NOT gates, multiplexers, registers) — no hidden abstractions</li>
<li class=""><strong>14 test suites:</strong> Automated testing via Logisim CLI covering alignment, branches, cache, control signals, CSR operations, pipeline flush behavior, multiply, and SLT instructions</li>
<li class=""><strong>In-progress cache subsystem:</strong> 4-way set-associative cache with pseudo-LRU replacement, cache FSM with write-through/write-back policies</li>
<li class=""><strong>RISC-V toolchain integration:</strong> <code>riscv64-unknown-elf-as</code> assembler integration via <code>compile_logisim.sh</code> that converts assembly listings to Logisim-compatible hex ROM files</li>
<li class=""><strong>Comprehensive documentation:</strong> MkDocs Material site with detailed write-ups on pipeline timing, hazard theory with truth tables, ALU control decoding, and component-by-component specifications</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/riskv#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cpu-datapath">CPU Datapath<a href="https://stackops.link/journal/riskv#cpu-datapath" class="hash-link" aria-label="Direct link to CPU Datapath" title="Direct link to CPU Datapath" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   IF    │──▶│   ID    │──▶│   EX    │──▶│   MEM   │──▶│   WB    │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│ PC+IMem │   │ Ctrl+RF │   │ ALU+Fwd │   │ DMem+Al │   │ WB Mux  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     │             │             │             │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  IF/ID reg     ID/EX reg     EX/MEM reg    MEM/WB reg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     │             │             │             │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     └──── Stall/Flush logic </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Hazard Controller</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> ────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              Forward Controller ──┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                       │           │ bypass paths</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                       └───────────┘</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="circuit-files">Circuit Files<a href="https://stackops.link/journal/riskv#circuit-files" class="hash-link" aria-label="Direct link to Circuit Files" title="Direct link to Circuit Files" translate="no">​</a></h3>
<table><thead><tr><th>Circuit</th><th>Contents</th></tr></thead><tbody><tr><td><code>RiskVCPU.circ</code></td><td>Top-level integrated 5-stage datapath</td></tr><tr><td><code>RiskVControl.circ</code></td><td>Main control, hazard, forwarding, branch, immediate gen</td></tr><tr><td><code>RiskVMemory.circ</code></td><td>Register File + Data Memory + load/store aligners</td></tr><tr><td><code>RiskVALU.circ</code></td><td>ALU + M-extension multiplier/divider logic</td></tr><tr><td><code>RiskVPipelineRegs.circ</code></td><td>IF/ID, ID/EX, EX/MEM, MEM/WB pipeline registers</td></tr><tr><td><code>RiskVCache.circ</code></td><td>4-way set-associative cache (in progress)</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/riskv#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Digital design</strong></td><td>Logisim-evolution (Java-based logic simulator)</td></tr><tr><td><strong>ISA</strong></td><td>RISC-V RV32I (32-bit base integer instruction set)</td></tr><tr><td><strong>Assembly</strong></td><td><code>riscv64-unknown-elf-as</code> (GNU assembler)</td></tr><tr><td><strong>Test framework</strong></td><td>Python 3 (<code>scripts/test_runner.py</code>), Logisim CLI</td></tr><tr><td><strong>Documentation</strong></td><td>MkDocs Material, Mermaid diagrams, MathJax</td></tr><tr><td><strong>CI/CD</strong></td><td>GitHub Actions (docs deploy on version tags)</td></tr><tr><td><strong>License</strong></td><td>MIT</td></tr></tbody></table>]]></content:encoded>
            <category>riscv</category>
            <category>cpu</category>
            <category>logisim</category>
            <category>digital-design</category>
            <category>microarchitecture</category>
            <category>pipeline</category>
            <category>hardware</category>
            <category>rv32i</category>
        </item>
        <item>
            <title><![CDATA[Clarity PMA — AI-Powered Project Manager Assistant]]></title>
            <link>https://stackops.link/journal/clarity</link>
            <guid>https://stackops.link/journal/clarity</guid>
            <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[An intelligent, local-first project management assistant that converts unstructured meeting notes and video transcripts into actionable work items. Uses Ollama for on-device LLM inference (no cloud dependency), generates structured JSON with Pydantic validation, and posts work items directly to Plane.so, Azure DevOps, GitHub Projects, or console output. Fully local and private — all AI processing stays on your machine.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/clarity#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Clarity PMA</strong> (Project Manager Assistant) is an AI-powered tool that turns messy meeting notes, video call transcripts, and brain-dump text files into structured, actionable work items — all running locally on your machine with zero data leaving your device.</p>
<p>It uses <strong>Ollama</strong> to run a large language model (default: Llama 3) directly on your hardware. A carefully crafted system prompt instructs the model to extract tasks, bugs, chores, and documentation items from unstructured text, outputting them as validated JSON. The parsed work items are saved locally and optionally pushed to your project management platform of choice — Plane.so, Azure DevOps, GitHub Projects, or just stdout.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/clarity#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Transcript-to-task conversion:</strong> Feed any <code>.txt</code> or <code>.vtt</code> transcript and receive structured work items with titles, descriptions, acceptance criteria, task breakdowns, types, and components</li>
<li class=""><strong>Local-first privacy:</strong> All LLM inference runs via Ollama on your own hardware — no API keys, no cloud dependency, no data exfiltration</li>
<li class=""><strong>Structured JSON output:</strong> Ollama-native JSON mode with <code>Pydantic</code> schema enforcement ensures consistently valid, well-typed outputs every time</li>
<li class=""><strong>Pydantic validation:</strong> <code>WorkItem</code> model validates title length, type enumeration, and required fields before any work item is created</li>
<li class=""><strong>Multi-platform posting:</strong> Push work items to Plane.so (REST API), Azure DevOps (SDK), GitHub Projects (ProjectsV2 API), or console output</li>
<li class=""><strong>Three prompt variants:</strong> Basic (simple extraction), Agile (user story format), and Engineering (technical task breakdown) — selectable via config</li>
<li class=""><strong>Docker Compose for Ollama:</strong> GPU-enabled NVIDIA Docker setup to run the LLM server in a container</li>
<li class=""><strong>Deterministic output:</strong> Temperature set to 0 in Ollama for reproducible, consistent results across runs</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/clarity#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">main.py</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  └── WorkflowManager </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">orchestrator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── IAgent </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">interface</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     └── OllamaAgent ─── connects to </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> Ollama server </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">JSON mode, </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">temp</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── IClient </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">interface</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     ├── PlaneClient    ─── Plane.so REST API</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     ├── AzureClient    ─── Azure DevOps SDK</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     ├── GithubClient   ─── GitHub Issues + ProjectsV2 API</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     └── ConsoleClient  ─── stdout </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">debug/testing</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── SystemPrompt         ─── </span><span class="token number">3</span><span class="token plain"> variants </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">A: basic, B: agile, C: engineering</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── WorkflowManagerParser ─── JSON → Pydantic WorkItem validation</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── Storage              ─── Read transcripts, save work items to disk</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── Config               ─── .env loader </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Ollama host, model, platform tokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └── Log                  ─── Custom logger </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">INFO + SUCCESS levels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="workitem-data-model">WorkItem Data Model<a href="https://stackops.link/journal/clarity#workitem-data-model" class="hash-link" aria-label="Direct link to WorkItem Data Model" title="Direct link to WorkItem Data Model" translate="no">​</a></h3>
<table><thead><tr><th>Field</th><th>Type</th><th>Constraints</th></tr></thead><tbody><tr><td><code>title</code></td><td>string</td><td>Max 100 chars, imperative style</td></tr><tr><td><code>description</code></td><td>string</td><td>WHAT + WHY narrative</td></tr><tr><td><code>acceptance_criteria</code></td><td>string[]</td><td>Verifiable conditions</td></tr><tr><td><code>task_breakdown</code></td><td>string[]</td><td>Implementation sub-tasks</td></tr><tr><td><code>task_type</code></td><td>enum</td><td>Task, Fix, Chore, Docs</td></tr><tr><td><code>component</code></td><td>string?</td><td>Optional module/area identifier</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/clarity#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Language</strong></td><td>Python 3</td></tr><tr><td><strong>LLM Runtime</strong></td><td>Ollama (local Llama 3 inference)</td></tr><tr><td><strong>Schema validation</strong></td><td>Pydantic v2</td></tr><tr><td><strong>API clients</strong></td><td><code>httpx</code>, <code>requests</code>, <code>azure-devops</code> SDK</td></tr><tr><td><strong>Config</strong></td><td><code>python-dotenv</code></td></tr><tr><td><strong>Container</strong></td><td>Docker Compose (NVIDIA GPU + Ollama)</td></tr><tr><td><strong>Formatting</strong></td><td><code>black</code></td></tr><tr><td><strong>License</strong></td><td>MIT</td></tr></tbody></table>]]></content:encoded>
            <category>python</category>
            <category>ai</category>
            <category>ollama</category>
            <category>llm</category>
            <category>project-management</category>
            <category>plane</category>
            <category>azure-devops</category>
            <category>github-projects</category>
        </item>
        <item>
            <title><![CDATA[Busa Woodward — Experience Engineering Corporate Site]]></title>
            <link>https://stackops.link/journal/busa-woodward</link>
            <guid>https://stackops.link/journal/busa-woodward</guid>
            <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Corporate website for Busa Woodward, a Dubai-based experience engineering firm operating at the intersection of strategy, design, and identity — working on mega-events, cultural districts, urban developments, and destination projects.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/busa-woodward#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Busa Woodward</strong> is a Dubai-based "Experience Engineering" firm that shapes cultural narratives and public experiences — from mega-events and cultural districts to urban developments and destination-building projects. Their corporate website is a fully CMS-driven Next.js application that presents the firm's studio, projects, labs, people, story, and insights through a rich visual experience.</p>
<p>Every page, every hero carousel, every testimonial, and every FAQ section is composed from <strong>block-based content</strong> managed in Directus — no content is hardcoded. When editors update content in the CMS, webhooks trigger Next.js ISR revalidation so changes go live immediately without a redeploy.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/busa-woodward#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Fully CMS-driven:</strong> All pages are dynamic block compositions from Directus — editors have complete control without touching code</li>
<li class=""><strong>WordPress migration:</strong> Extensive redirect map handles legacy WordPress URLs seamlessly</li>
<li class=""><strong>Complex hero carousel:</strong> Multi-category navigation with animated sidebar tabs and Embla smooth scrolling</li>
<li class=""><strong>Rich typography:</strong> 58 custom OTF font files self-hosted for branded visual identity</li>
<li class=""><strong>GitOps deployment:</strong> GitHub Actions builds the Docker image, updates the GitOps repository, and ArgoCD syncs to Kubernetes</li>
<li class=""><strong>SEO:</strong> Dynamic sitemap, per-page metadata from CMS, OpenGraph images</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/busa-woodward#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>Busa Woodward follows the <strong>headless CMS pattern</strong> — a single Next.js frontend talking directly to Directus via its SDK:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Public-facing corporate website with block-rendered pages</td></tr><tr><td><strong>directus</strong> (Headless CMS)</td><td>Content management with 28 collections</td></tr><tr><td><strong>PostgreSQL</strong></td><td>CMS database</td></tr></tbody></table>
<p>No separate API backend — the frontend fetches everything from Directus SDK calls. Three minimal Next.js API routes handle the contact form (AWS SES), ISR revalidation webhooks, and cache busting.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend--nextjs-app-router">Frontend — Next.js App Router<a href="https://stackops.link/journal/busa-woodward#frontend--nextjs-app-router" class="hash-link" aria-label="Direct link to Frontend — Next.js App Router" title="Direct link to Frontend — Next.js App Router" translate="no">​</a></h2>
<p>The frontend is built with Next.js App Router and styled with Tailwind CSS, featuring a custom purple theme. It ships with 58 self-hosted OTF font files across the Aktiv Grotesk and Gestura families.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-pages">Key Pages<a href="https://stackops.link/journal/busa-woodward#key-pages" class="hash-link" aria-label="Direct link to Key Pages" title="Direct link to Key Pages" translate="no">​</a></h3>
<ul>
<li class=""><strong>Home</strong> — Hero carousel with sidebar tabs, language-section typewriter effect, project highlights</li>
<li class=""><strong>Studio</strong> — Firm philosophy and approach</li>
<li class=""><strong>Projects</strong> — Case study gallery with filtering</li>
<li class=""><strong>Labs</strong> — Innovation and experimental work</li>
<li class=""><strong>Story</strong> — Brand narrative and history</li>
<li class=""><strong>People</strong> — Team profiles and leadership</li>
<li class=""><strong>Insights</strong> — Articles, thought leadership, and blog content</li>
<li class=""><strong>Expos</strong> — Exhibition showcase</li>
<li class=""><strong>Gallery</strong> — Visual portfolio</li>
<li class=""><strong>Contact</strong> — Contact form with AWS SES email delivery</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="notable-components">Notable Components<a href="https://stackops.link/journal/busa-woodward#notable-components" class="hash-link" aria-label="Direct link to Notable Components" title="Direct link to Notable Components" translate="no">​</a></h3>
<ul>
<li class=""><strong>HeroCarousel</strong> — Multi-category Embla carousel with color-coded sidebar tab navigation and autoplay</li>
<li class=""><strong>HomeLanguageSection</strong> — Typewriter text animation for taglines</li>
<li class=""><strong>FAQ accordion</strong> — Expandable Q&amp;A sections</li>
<li class=""><strong>Testimonials slider</strong> — Client testimonial carousel</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cms--directus-headless-backend">CMS — Directus Headless Backend<a href="https://stackops.link/journal/busa-woodward#cms--directus-headless-backend" class="hash-link" aria-label="Direct link to CMS — Directus Headless Backend" title="Direct link to CMS — Directus Headless Backend" translate="no">​</a></h2>
<p>Directus manages 28 collections including pages, projects, articles, team members, headers, footers, global settings, and reusable content blocks (hero sections, sliders, FAQs, testimonials, SEO metadata).</p>
<p>Content flows to the frontend through Directus SDK calls. When editors publish changes, Directus fires a webhook to <code>/api/revalidate</code>, triggering Next.js Incremental Static Regeneration — the site rebuilds affected pages without a full deployment.</p>
<p>Static assets (images, documents) are stored in a Hetzner S3 bucket and served through Bunny CDN (<code>cdt-busa-woodward.b-cdn.net</code>) for fast global delivery.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/busa-woodward#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (App Router), React, Tailwind CSS</td></tr><tr><td><strong>CMS</strong></td><td>Directus (headless), PostgreSQL</td></tr><tr><td><strong>Email</strong></td><td>AWS SES (contact form)</td></tr><tr><td><strong>Storage</strong></td><td>Hetzner S3 + Bunny CDN</td></tr><tr><td><strong>ISR</strong></td><td>Next.js on-demand revalidation via Directus webhooks</td></tr><tr><td><strong>Orchestration</strong></td><td>Docker, Kubernetes, ArgoCD</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>tailwind</category>
            <category>directus</category>
            <category>cms</category>
            <category>postgresql</category>
            <category>aws-ses</category>
            <category>corporate</category>
            <category>typescript</category>
        </item>
        <item>
            <title><![CDATA[Booking Clinic — Multi-Tenant Healthcare Platform]]></title>
            <link>https://stackops.link/journal/booking-clinic</link>
            <guid>https://stackops.link/journal/booking-clinic</guid>
            <pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A multi-tenant booking and CRM platform for healthcare clinics and appointment-based businesses. Shared admin dashboards for managing services, staff, locations, schedules, and bookings across multiple client workspaces, plus customizable public-facing booking websites.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/booking-clinic#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Booking Clinic</strong> is a multi-tenant SaaS platform that lets healthcare clinics and appointment-based businesses manage their entire booking workflow. Each client (tenant) gets their own workspace with customizable branding, services, staff profiles, locations, and scheduling — all managed through a shared admin dashboard. End customers can book appointments through personalized public booking pages, with deep-link URLs that securely embed tenant and provider context.</p>
<p>The platform handles everything from provider schedules (recurring weekly slots) to public booking tokens, appointment confirmations, and email notifications — powered by a dedicated Rust email microservice that dispatches templated messages through AWS SES.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/booking-clinic#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Multi-tenant isolation:</strong> Workspace-scoped entities via BaseWorkspaceEntity, per-tenant branding, settings, and users</li>
<li class=""><strong>Public booking with deep links:</strong> Cryptographically-secure time-limited tokens allow unauthenticated visitors to book appointments</li>
<li class=""><strong>Advanced scheduling:</strong> Recurring weekly slots via <code>daysOfWeek</code> JSON, templates for standardizing provider availability</li>
<li class=""><strong>Polyglot architecture:</strong> Node.js API for business logic, Next.js for the UI, and Rust for high-performance email dispatch — each in the right language for the job</li>
<li class=""><strong>Fine-grained RBAC:</strong> Permissions like <code>booking.serviceOfferingSlot.create</code> validated through OxideAuth on every request</li>
<li class=""><strong>Generic CRUD framework:</strong> 14 entities get full Create-Read-Update-Delete with RBAC through a single BaseModelRouter pattern</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/booking-clinic#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>Booking Clinic runs as a <strong>polyglot multi-service system</strong> orchestrated via Docker Compose or Kubernetes:</p>
<ul>
<li class=""><strong>www</strong> — Next.js administrative dashboard and public booking frontend</li>
<li class=""><strong>api</strong> — Express.js REST API with generic CRUD framework and RBAC</li>
<li class=""><strong>email</strong> — Rust/Axum microservice for event-driven email delivery</li>
<li class=""><strong>PostgreSQL</strong> — primary relational database for bookings and tenant data</li>
<li class=""><strong>Redis</strong> — caching layer and pub/sub event bus for email dispatch</li>
</ul>
<p>The API proxies all authentication through <strong>OxideAuth</strong>, CODATIVITY's centralized auth service, using service account JWTs. This keeps identity management separate from business logic.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="service-to-service-flow">Service-to-service flow<a href="https://stackops.link/journal/booking-clinic#service-to-service-flow" class="hash-link" aria-label="Direct link to Service-to-service flow" title="Direct link to Service-to-service flow" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Public user  →  www </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">booking form</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  →  api </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">validates token, creates booking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                         │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                    PostgreSQL </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">stores booking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                         │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                    Redis pub/sub </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">emits event</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                         │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                    email </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Rust</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> → AWS SES </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">sends confirmations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="services">Services<a href="https://stackops.link/journal/booking-clinic#services" class="hash-link" aria-label="Direct link to Services" title="Direct link to Services" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="www--nextjs-dashboard--public-booking">www — Next.js Dashboard + Public Booking<a href="https://stackops.link/journal/booking-clinic#www--nextjs-dashboard--public-booking" class="hash-link" aria-label="Direct link to www — Next.js Dashboard + Public Booking" title="Direct link to www — Next.js Dashboard + Public Booking" translate="no">​</a></h3>
<p>The frontend is a Next.js App Router application using MUI for the UI layer and FullCalendar for the scheduling interface. It serves two audiences through route groups:</p>
<ul>
<li class=""><strong>(auth)</strong> — login, registration, and auth flow pages</li>
<li class=""><strong>(dashboard)/[tenant]</strong> — admin dashboard with Schedule (FullCalendar), Bookings, Services, Providers, Locations, Users, Templates, Settings, Workspaces, and Notifications</li>
<li class=""><strong>(simple)</strong> — public-facing booking pages accessed via secure deep links</li>
</ul>
<p>Key frontend features include a compile-time typed service registry using a custom <code>useServiceSWR</code> hook that provides type-safe API calls and automatic caching.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="api--expressjs-rest-backend">api — Express.js REST Backend<a href="https://stackops.link/journal/booking-clinic#api--expressjs-rest-backend" class="hash-link" aria-label="Direct link to api — Express.js REST Backend" title="Direct link to api — Express.js REST Backend" translate="no">​</a></h3>
<p>The API is built on Express.js with TypeScript, using MikroORM for database access. It employs a <strong>generic BaseModelRouter pattern</strong> — 14 entity types (User, Workspace, Provider, ServiceOffering, Booking, etc.) each get automatic CRUD endpoints with RBAC enforcement.</p>
<p>Authentication goes through OxideAuth: the API validates incoming JWTs and enforces fine-grained permissions like <code>booking.serviceOfferingSlot.create</code> on every route. Storage backends are swappable between local, Azure Blob, and Hetzner S3 via configuration.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="email--rust-microservice">email — Rust Microservice<a href="https://stackops.link/journal/booking-clinic#email--rust-microservice" class="hash-link" aria-label="Direct link to email — Rust Microservice" title="Direct link to email — Rust Microservice" translate="no">​</a></h3>
<p>A dedicated email service written in Rust using the Axum framework. It listens on Redis pub/sub channels for events (booking created, cancelled, reminder triggers) and sends formatted emails via AWS SES. Templates use Handlebars/Tera for flexible HTML generation. Has its own PostgreSQL database for email tracking and logs.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/booking-clinic#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (App Router), React, MUI, FullCalendar, Emotion, Ant Design Icons</td></tr><tr><td><strong>API Backend</strong></td><td>Express.js, TypeScript, SWC, MikroORM</td></tr><tr><td><strong>Email Microservice</strong></td><td>Rust, Axum, SQLx, Redis pub/sub, AWS SES</td></tr><tr><td><strong>Databases</strong></td><td>PostgreSQL (primary + email), Redis (cache + pub/sub)</td></tr><tr><td><strong>Auth</strong></td><td>OxideAuth (JWT-based centralized auth)</td></tr><tr><td><strong>Storage</strong></td><td>Local / Azure Blob / Hetzner S3 (configurable)</td></tr><tr><td><strong>Orchestration</strong></td><td>Docker Compose (dev), Kubernetes + Kustomize + ArgoCD (prod)</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>express</category>
            <category>postgresql</category>
            <category>redis</category>
            <category>typescript</category>
            <category>fullcalendar</category>
            <category>healthcare</category>
            <category>saas</category>
            <category>booking</category>
        </item>
        <item>
            <title><![CDATA[The Craft Safari — E-Commerce Storefront]]></title>
            <link>https://stackops.link/journal/tcs-craft-safari</link>
            <guid>https://stackops.link/journal/tcs-craft-safari</guid>
            <pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A production e-commerce storefront for Indian handcrafted goods and artisan products. Headless WordPress + WooCommerce backend with a Next.js 15 frontend featuring three-layer caching, secure WooCommerce proxy, Geidea payment gateway (UAE), and 3D product visualization.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/tcs-craft-safari#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>The Craft Safari</strong> is a full-featured e-commerce storefront selling Indian handcrafted goods and artisan products — from home decor and textiles to craft tours and art atelier experiences. The store operates in the UAE market with AED pricing and Geidea payment processing.</p>
<p>The architecture is <strong>headless WooCommerce</strong> — WordPress handles the product catalog, orders, and blog content through its REST API, while Next.js 15 owns all rendering, caching, and the customer experience. A secure proxy layer keeps WooCommerce API credentials on the server, never exposing them to the browser.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/tcs-craft-safari#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Headless WooCommerce with secure proxy:</strong> WC credentials are server-side only — all API calls funnel through an allowlisted proxy route</li>
<li class=""><strong>Three-layer caching:</strong> ISR (10min), server memory (5min), TanStack Query (5s) — tiered for performance and freshness</li>
<li class=""><strong>Custom post types:</strong> Beyond standard WooCommerce — <code>home-banner</code>, <code>home-promotion</code>, <code>craft-tour</code>, <code>event</code>, <code>workshops</code> for rich content experiences</li>
<li class=""><strong>Bunny CDN media offloading:</strong> Custom WordPress plugin rewrites attachment URLs to CDN</li>
<li class=""><strong>Geidea payment integration:</strong> UAE-specific gateway supporting card payments and digital wallets in AED</li>
<li class=""><strong>3D model viewer:</strong> <code>@google/model-viewer</code> for interactive product visualization</li>
<li class=""><strong>Bootstrap 5 + SCSS:</strong> Extensive custom styling framework — not Tailwind — with page-level SCSS modules</li>
<li class=""><strong>Yoast SEO integration:</strong> WordPress SEO metadata consumed via <code>yoast_head_json</code> for Next.js <code>generateMetadata()</code></li>
<li class=""><strong>Dynamic sitemap:</strong> Fetches all products, categories, and blogs from WooCommerce at build time</li>
<li class=""><strong>Cookie consent + analytics:</strong> GDPR-compliant cookie banner with Google Analytics and Meta Pixel integration</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/tcs-craft-safari#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>single Next.js frontend with an external WooCommerce backend</strong>:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>E-commerce storefront with three-layer caching</td></tr><tr><td><strong>WordPress + WooCommerce</strong> (external)</td><td>Product catalog, orders, blog, custom post types</td></tr><tr><td><strong>Bunny CDN</strong></td><td>Media delivery with custom WordPress CDN plugin</td></tr></tbody></table>
<p>No database is managed in this repository — all product, order, and customer data lives in the external WordPress/WooCommerce instance at <code>admin.thecraftsafari.shop</code>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend--nextjs-app-router-with-bootstrap-5">Frontend — Next.js App Router with Bootstrap 5<a href="https://stackops.link/journal/tcs-craft-safari#frontend--nextjs-app-router-with-bootstrap-5" class="hash-link" aria-label="Direct link to Frontend — Next.js App Router with Bootstrap 5" title="Direct link to Frontend — Next.js App Router with Bootstrap 5" translate="no">​</a></h2>
<p>Built with Next.js 15 App Router, Bootstrap 5 + SCSS, and TanStack Query. Notably, this project uses Bootstrap/SCSS instead of Tailwind — with extensive custom styles in <code>public/scss/main.scss</code> and page-local <code>*.module.scss</code> files.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pages">Pages<a href="https://stackops.link/journal/tcs-craft-safari#pages" class="hash-link" aria-label="Direct link to Pages" title="Direct link to Pages" translate="no">​</a></h3>
<ul>
<li class=""><strong>Home</strong> — Hero, category grid, featured products, promotional banner, social proof</li>
<li class=""><strong>Shop</strong> — Product listing with filters, sorting, and search (Fuse.js fuzzy search)</li>
<li class=""><strong>Category</strong> — Category-specific product display</li>
<li class=""><strong>Product Detail</strong> — Full product page with PhotoSwipe gallery, 3D model viewer (<code>@google/model-viewer</code>), product attributes, and add-to-cart</li>
<li class=""><strong>Blog</strong> — WooCommerce blog posts</li>
<li class=""><strong>Travel Diaries</strong> — Custom post type for travel content</li>
<li class=""><strong>About, Contact</strong> — Brand story and contact form</li>
<li class=""><strong>Indian Crafts / Craft Tours / Art Atelier / Events</strong> — Custom post type pages</li>
<li class=""><strong>Checkout</strong> — Multi-step checkout with Geidea payment</li>
<li class=""><strong>Payment</strong> — Payment processing page</li>
<li class=""><strong>My Account</strong> — Orders, addresses, wishlist, profile management</li>
<li class=""><strong>Cart</strong> — Shopping cart with quantity management</li>
<li class=""><strong>15 modals</strong> — Ask Question, Color Compare, Login, Quick Add, Quick View, Share, Search, and more</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="woocommerce-secure-proxy">WooCommerce Secure Proxy<a href="https://stackops.link/journal/tcs-craft-safari#woocommerce-secure-proxy" class="hash-link" aria-label="Direct link to WooCommerce Secure Proxy" title="Direct link to WooCommerce Secure Proxy" translate="no">​</a></h2>
<p>All WooCommerce API calls flow through a single Next.js API route at <code>/api/wc</code>. A proxy utility determines which endpoints are public (GET products, categories, tags) vs. authorized (orders, customers, coupons) and enforces the appropriate access rules. The WooCommerce consumer key and secret remain server-side only — the browser never sees them.</p>
<p>Guest checkout is supported through carve-outs that allow anonymous order creation.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-layer-caching">Three-Layer Caching<a href="https://stackops.link/journal/tcs-craft-safari#three-layer-caching" class="hash-link" aria-label="Direct link to Three-Layer Caching" title="Direct link to Three-Layer Caching" translate="no">​</a></h2>
<p>The storefront implements a tiered caching strategy for performance:</p>
<ol>
<li class=""><strong>Next.js ISR</strong> — Pages revalidate every 10 minutes; <code>generateStaticParams()</code> pre-builds category and product pages at build time</li>
<li class=""><strong>Server memory cache</strong> — Products and categories cached for 5 minutes with a <code>setInterval</code> sweeper for invalidation</li>
<li class=""><strong>TanStack Query</strong> — Client-side cache with 5-second <code>staleTime</code> for instant UI updates</li>
</ol>
<p>This means category and product pages load instantly from static cache, while data freshness is maintained through multiple cache layers.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="media-delivery--bunny-cdn">Media Delivery — Bunny CDN<a href="https://stackops.link/journal/tcs-craft-safari#media-delivery--bunny-cdn" class="hash-link" aria-label="Direct link to Media Delivery — Bunny CDN" title="Direct link to Media Delivery — Bunny CDN" translate="no">​</a></h2>
<p>A custom WordPress PHP plugin rewrites all attachment URLs from the WordPress origin to Bunny CDN (<code>cdt-tcs.b-cdn.net</code>). This offloads media delivery to the CDN edge, reducing load on the WordPress server and improving image load times globally.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/tcs-craft-safari#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (App Router), React, TypeScript, Bootstrap 5 + SCSS</td></tr><tr><td><strong>Data Fetching</strong></td><td>TanStack Query, Axios</td></tr><tr><td><strong>E-commerce</strong></td><td>WooCommerce (headless, REST API)</td></tr><tr><td><strong>Payments</strong></td><td>Geidea (UAE payment gateway with card + digital wallet)</td></tr><tr><td><strong>CMS</strong></td><td>WordPress (headless, custom post types)</td></tr><tr><td><strong>Search</strong></td><td>Fuse.js (client-side fuzzy search)</td></tr><tr><td><strong>Images</strong></td><td>PhotoSwipe (gallery), <code>@google/model-viewer</code> (3D products)</td></tr><tr><td><strong>CDN</strong></td><td>Bunny CDN with custom WordPress offloading plugin</td></tr><tr><td><strong>Analytics</strong></td><td>Google Analytics, Meta Pixel</td></tr><tr><td><strong>CRM</strong></td><td>Zoho (newsletter subscriptions)</td></tr><tr><td><strong>SEO</strong></td><td>Yoast SEO (WordPress) consumed via <code>yoast_head_json</code></td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>woocommerce</category>
            <category>wordpress</category>
            <category>bootstrap</category>
            <category>scss</category>
            <category>typescript</category>
            <category>ecommerce</category>
            <category>stripe</category>
            <category>geidea</category>
            <category>bunny-cdn</category>
        </item>
        <item>
            <title><![CDATA[Champions Karate — Martial Arts Academy Platform]]></title>
            <link>https://stackops.link/journal/champions-karate</link>
            <guid>https://stackops.link/journal/champions-karate</guid>
            <pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Full-stack web application for a Dubai martial arts academy offering karate, kickboxing, self-defence, and fitness programs across five dojo locations. Public marketing website plus internal admin dashboard with class scheduling and contact management.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/champions-karate#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Champions Karate</strong> is a full-stack web platform for a martial arts academy operating across five locations in Dubai. The site serves two audiences: prospective students browsing the public marketing website, and academy staff managing the business through an internal admin dashboard.</p>
<p>Built as a single Next.js application with a Strapi headless CMS backend, the platform handles everything from class scheduling and blog management to contact form processing and file uploads. The admin dashboard provides a complete business management interface with Redux state management, role-based access control, and a class timing system that accommodates multiple programs, age groups, and locations.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/champions-karate#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Class scheduling system:</strong> Enum-based scheduling covering multiple class types, age groups, 5 locations, and 7 days — all configurable from the admin dashboard</li>
<li class=""><strong>Admin dashboard:</strong> Full MUI-themed business management interface with Redux state management</li>
<li class=""><strong>Contact form with reCAPTCHA:</strong> Google reCAPTCHA Enterprise verification for spam prevention</li>
<li class=""><strong>RTL/LTR layout support:</strong> Ready for Arabic with <code>stylis-plugin-rtl</code></li>
<li class=""><strong>ISR with 10-second revalidation:</strong> Static pages rebuild on content changes via Strapi webhooks</li>
<li class=""><strong>AWS S3 integration:</strong> File uploads stored in S3 for scalability</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/champions-karate#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>www + api split</strong> pattern with Strapi serving as both CMS and API:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Public marketing site + admin dashboard</td></tr><tr><td><strong>api</strong> (Strapi)</td><td>Headless CMS, custom API routes, content management</td></tr><tr><td><strong>PostgreSQL</strong></td><td>CMS database (production)</td></tr></tbody></table>
<p>The frontend communicates with Strapi through REST API calls at the <code>/cms</code> prefix. Strapi handles authentication via its built-in users-permissions plugin and provides content types for blog posts, contact forms, pages, class timings, profiles, and site settings.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend--nextjs-pages-router">Frontend — Next.js Pages Router<a href="https://stackops.link/journal/champions-karate#frontend--nextjs-pages-router" class="hash-link" aria-label="Direct link to Frontend — Next.js Pages Router" title="Direct link to Frontend — Next.js Pages Router" translate="no">​</a></h2>
<p>The frontend is a single Next.js 14 application using the Pages Router, styled with MUI v5 and enhanced with Framer Motion and AOS animations.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="public-site-pages">Public Site Pages<a href="https://stackops.link/journal/champions-karate#public-site-pages" class="hash-link" aria-label="Direct link to Public Site Pages" title="Direct link to Public Site Pages" translate="no">​</a></h3>
<ul>
<li class=""><strong>Home</strong> — Hero, program showcase, location finder, calls-to-action</li>
<li class=""><strong>About</strong> — Academy philosophy, instructors, facilities</li>
<li class=""><strong>Contact</strong> — Contact form with Google reCAPTCHA Enterprise verification</li>
<li class=""><strong>Classes</strong> — Program listings (Karate, Kickboxing, Self-Defence, Fitness, Tots, Private)</li>
<li class=""><strong>Dojo Locations</strong> — Five locations with maps</li>
<li class=""><strong>Holiday Camps</strong> — Seasonal program information</li>
<li class=""><strong>Blog</strong> — Articles and updates</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="admin-dashboard">Admin Dashboard<a href="https://stackops.link/journal/champions-karate#admin-dashboard" class="hash-link" aria-label="Direct link to Admin Dashboard" title="Direct link to Admin Dashboard" translate="no">​</a></h3>
<p>The dashboard is an MUI-themed SPA-within-Next.js, managed through Redux for state. It provides:</p>
<ul>
<li class=""><strong>Dashboard overview</strong> — Business metrics and activity</li>
<li class=""><strong>Blog management</strong> — CRUD for articles with rich content</li>
<li class=""><strong>Classes management</strong> — Schedule configuration for multiple programs, age groups, locations, and days</li>
<li class=""><strong>Contact forms</strong> — View and respond to inquiries</li>
<li class=""><strong>Page editor</strong> — Update site content without touching code</li>
<li class=""><strong>Settings</strong> — Site-wide configuration</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend--strapi-cms">Backend — Strapi CMS<a href="https://stackops.link/journal/champions-karate#backend--strapi-cms" class="hash-link" aria-label="Direct link to Backend — Strapi CMS" title="Direct link to Backend — Strapi CMS" translate="no">​</a></h2>
<p>Strapi manages all content through a REST API at <code>/cms</code>. Custom endpoints like <code>/cms/profiles/me</code> and <code>/cms/profiles/change-password</code> extend the built-in users-permissions system. Lifecycle hooks automatically create user profiles on registration. The <code>strapi-plugin-email-designer</code> plugin provides customizable email templates for automated communications.</p>
<p>Content types include Blog, ContactForm, Page, ClassTiming, Profile, and SiteSetting — giving non-technical staff full control over the website's content.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/champions-karate#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (Pages Router), React, TypeScript, MUI v5, Emotion, Framer Motion, AOS</td></tr><tr><td><strong>Calendar</strong></td><td>FullCalendar for class schedule visualization</td></tr><tr><td><strong>CMS / Backend</strong></td><td>Strapi (headless), PostgreSQL</td></tr><tr><td><strong>Auth</strong></td><td>Strapi users-permissions plugin</td></tr><tr><td><strong>Email</strong></td><td>Strapi Email Designer plugin</td></tr><tr><td><strong>CAPTCHA</strong></td><td>Google reCAPTCHA Enterprise</td></tr><tr><td><strong>Storage</strong></td><td>AWS S3 for uploads</td></tr><tr><td><strong>State</strong></td><td>Redux for admin dashboard state</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>strapi</category>
            <category>cms</category>
            <category>postgresql</category>
            <category>mui</category>
            <category>fullcalendar</category>
            <category>typescript</category>
            <category>martial-arts</category>
            <category>academy</category>
        </item>
        <item>
            <title><![CDATA[Multi-Strategy Deploy Pipeline]]></title>
            <link>https://stackops.link/journal/multi-strategy-deploy-pipeline</link>
            <guid>https://stackops.link/journal/multi-strategy-deploy-pipeline</guid>
            <pubDate>Sat, 08 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A reusable modular Bash deployment pipeline that builds Docker images, rolls out to Kubernetes for staging, and updates bare-metal VMs via SSH + systemd for production. Swap out the hostnames and image names and it works for any project.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>The pipeline lives in <code>references/scripts/</code> under the repo. It uses a <strong>module-loader pattern</strong> — <code>lib/index.sh</code> sources every <code>.sh</code> module in sorted order, guarded against double-includes. The entry point (<code>deploy.sh</code>) sources the loader, then calls into whichever modules it needs.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">references/scripts/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── deploy.sh              </span><span class="token comment" style="color:rgb(98, 114, 164)"># Entry point / orchestrator</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── lib/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── index.sh           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Auto-loader (sources all *.sh in lib/)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── helpers.sh         </span><span class="token comment" style="color:rgb(98, 114, 164)"># die(), dry-run, pushd/popd, timestamps</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── docker.sh          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Build &amp; push strategy per environment</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── kube.sh            </span><span class="token comment" style="color:rgb(98, 114, 164)"># kubectl apply (local or remote via SSH)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── systemd.sh         </span><span class="token comment" style="color:rgb(98, 114, 164)"># SSH + systemd image-bump for bare-metal</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── version.sh         </span><span class="token comment" style="color:rgb(98, 114, 164)"># .env / deploy.yaml / .version snapshot</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── git.sh             </span><span class="token comment" style="color:rgb(98, 114, 164)"># Git add + commit</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── help.sh            </span><span class="token comment" style="color:rgb(98, 114, 164)"># Usage text</span><br></div></code></pre></div></div>
<p>The loader pattern means <strong>you never need to manually source individual modules</strong> — adding a new <code>lib/*.sh</code> file automatically makes its functions available.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-the-loader-works-libindexsh">How the loader works (<code>lib/index.sh</code>)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#how-the-loader-works-libindexsh" class="hash-link" aria-label="Direct link to how-the-loader-works-libindexsh" title="Direct link to how-the-loader-works-libindexsh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">_LIB_DIR</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"$(cd "</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">dirname</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">${</span><span class="token string variable string variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">BASH_SOURCE</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">0</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$0}</span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">pwd</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">while IFS= read -r -d '' mod; do</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    case "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">basename</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$mod</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">" in index.sh) continue ;; esac</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    . "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$mod</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">done &lt; &lt;(find "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$_LIB_DIR</span><span class="token plain">" </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-maxdepth</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-type</span><span class="token plain"> f </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-name</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'*.sh'</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-print0</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sort</span><span class="token plain"> -z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>It finds its own directory (handles being sourced from anywhere), then globs every <code>.sh</code> file in the same folder — except itself — and sources them in deterministic alphabetical order. A <code>__LIB_INDEX_SH_SOURCED</code> guard variable prevents double-loading if index.sh is sourced twice in the same shell.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="environments--deployment-paths">Environments &amp; Deployment Paths<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#environments--deployment-paths" class="hash-link" aria-label="Direct link to Environments &amp; Deployment Paths" title="Direct link to Environments &amp; Deployment Paths" translate="no">​</a></h2>
<p>The pipeline supports three environments, each with a different rollout strategy:</p>
<table><thead><tr><th>Environment</th><th>Dockerfile</th><th>Version bump</th><th>Rollout strategy</th></tr></thead><tbody><tr><td><code>local</code></td><td><code>Dockerfile.local</code></td><td><code>.env</code> + <code>kubernetes/deploy.yaml</code> + <code>.version</code></td><td><code>kubectl apply</code> on local cluster</td></tr><tr><td><code>staging</code></td><td><code>Dockerfile.staging</code></td><td><code>.env</code> + <code>kubernetes/deploy.yaml</code> + <code>.version</code></td><td>SCP manifests to remote → <code>kubectl apply</code> via SSH</td></tr><tr><td><code>production</code></td><td><code>Dockerfile.prod</code></td><td><code>.env</code> only + <code>.version</code></td><td>SSH → edit <code>/etc/default/&lt;svc&gt;</code> → <code>systemctl restart</code></td></tr></tbody></table>
<p><strong>Staging uses Kubernetes.</strong> Manifests are SCP'd to a remote control node and applied with <code>kubectl apply -k</code> (Kustomize).</p>
<p><strong>Production uses bare-metal VMs.</strong> The pipeline SSHs directly into each host, atomically bumps the <code>IMAGE=</code> line in <code>/etc/default/&lt;service&gt;</code>, then runs <code>systemctl daemon-reload</code> + <code>restart</code>.</p>
<p>Each service directory must contain the corresponding <code>Dockerfile.*</code> or the build fails immediately. You define which directories constitute "services" inside <code>build_all_images()</code>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-reference--all-commands">Quick Reference — All Commands<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#quick-reference--all-commands" class="hash-link" aria-label="Direct link to Quick Reference — All Commands" title="Direct link to Quick Reference — All Commands" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Basic usage pattern</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token operator">&lt;</span><span class="token plain">version</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">env</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">options</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Local development cluster</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token number">1.4</span><span class="token plain">.0 </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Staging (Kubernetes via SSH)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token number">1.4</span><span class="token plain">.0 staging</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Production (bare-metal via systemd)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token number">1.4</span><span class="token plain">.0 production</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Dry-run: print what would happen, make no changes</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh --dry-run </span><span class="token number">1.4</span><span class="token plain">.0 staging</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">DRY_RUN</span><span class="token operator">=</span><span class="token number">1</span><span class="token plain"> ./deploy.sh </span><span class="token number">1.4</span><span class="token plain">.0 </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Show help</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--help</span><br></div></code></pre></div></div>
<p><strong>Positional arguments:</strong></p>
<ul>
<li class=""><code>version</code> — Semver tag used as the Docker image tag (e.g. <code>1.4.0</code>)</li>
<li class=""><code>env</code> — One of <code>local</code>, <code>staging</code>, <code>production</code></li>
</ul>
<p><strong>Options:</strong></p>
<ul>
<li class=""><code>-n</code>, <code>--dry-run</code> — Echo commands instead of executing them</li>
<li class=""><code>-h</code>, <code>--help</code>, <code>help</code> — Print usage and exit</li>
</ul>
<p><strong>Environment variable:</strong></p>
<ul>
<li class=""><code>DRY_RUN=1</code> — Same effect as <code>--dry-run</code></li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploy-to-kubernetes-local--staging">Deploy to Kubernetes (local / staging)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#deploy-to-kubernetes-local--staging" class="hash-link" aria-label="Direct link to Deploy to Kubernetes (local / staging)" title="Direct link to Deploy to Kubernetes (local / staging)" translate="no">​</a></h2>
<p>Both <code>local</code> and <code>staging</code> follow the same pattern, differing only in whether <code>kubectl</code> runs on the local machine or over SSH.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flow">Flow<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#flow" class="hash-link" aria-label="Direct link to Flow" title="Direct link to Flow" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">build_all_images  →  push_all_images </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">staging only</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">update_envs       →  bumps version </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> .env files</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    →  bumps image tags </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> deploy.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    →  writes .version snapshot</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">exec_kube         →  copies ./kubernetes/* to the target</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    →  kubectl apply </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-k</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Kustomize overlay</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-kubernetes-module-libkubesh">The Kubernetes module (<code>lib/kube.sh</code>)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#the-kubernetes-module-libkubesh" class="hash-link" aria-label="Direct link to the-kubernetes-module-libkubesh" title="Direct link to the-kubernetes-module-libkubesh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function-name function" style="color:rgb(80, 250, 123)">exec_kube</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">remote_host</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Remote host not specified."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"local"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        maybe </span><span class="token function" style="color:rgb(80, 250, 123)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-rf</span><span class="token plain"> ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/*</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        maybe </span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> ./kubernetes/* ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        maybe kubectl apply </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-k</span><span class="token plain"> ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--force</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        maybe </span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-rf</span><span class="token plain"> ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/*</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        maybe </span><span class="token function" style="color:rgb(80, 250, 123)">scp</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> ./kubernetes/* </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain">:~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        maybe </span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'kubectl apply -k ~/kubernetes/&lt;project&gt; --force'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><strong>Key points:</strong></p>
<ul>
<li class="">Uses Kustomize (<code>-k</code>) — the <code>./kubernetes/</code> directory should contain a <code>kustomization.yaml</code></li>
<li class="">For local: copies manifests to the target path and applies directly</li>
<li class="">For remote: SCPs manifests, then SSHs and applies</li>
<li class=""><code>--force</code> flag ensures resource conflicts are resolved</li>
<li class="">The target path (<code>~/kubernetes/&lt;project&gt;/</code>) is a project-specific value — change it to match your layout</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-staging-deploy">Example: staging deploy<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#example-staging-deploy" class="hash-link" aria-label="Direct link to Example: staging deploy" title="Direct link to Example: staging deploy" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token number">1.5</span><span class="token plain">.0 staging</span><br></div></code></pre></div></div>
<p>What happens:</p>
<ol>
<li class="">Builds each service with <code>Dockerfile.staging</code></li>
<li class="">Pushes images to your registry</li>
<li class="">Updates version strings in all <code>.env</code> files</li>
<li class="">Updates image tags in <code>kubernetes/deploy.yaml</code></li>
<li class="">Writes <code>.version</code> metadata file</li>
<li class="">SCPs manifests to the staging control host</li>
<li class="">Runs <code>kubectl apply -k</code> on the staging cluster</li>
<li class="">Git commits everything</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploy-to-ssh-hosts-production">Deploy to SSH Hosts (production)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#deploy-to-ssh-hosts-production" class="hash-link" aria-label="Direct link to Deploy to SSH Hosts (production)" title="Direct link to Deploy to SSH Hosts (production)" translate="no">​</a></h2>
<p>Production bypasses Kubernetes entirely — it uses <strong>SSH + systemd</strong> to update bare-metal VMs running services directly.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flow-1">Flow<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#flow-1" class="hash-link" aria-label="Direct link to Flow" title="Direct link to Flow" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">build_all_images  →  push_all_images</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">update_envs       →  bumps .env files </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">skips kubernetes/deploy.yaml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">systemd_update_remote_host   →  SSHs to each production </span><span class="token function" style="color:rgb(80, 250, 123)">host</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 →  edits </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">IMAGE</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> /etc/default/</span><span class="token operator">&lt;</span><span class="token plain">service</span><span class="token operator">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 →  systemctl daemon-reload</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 →  systemctl restart </span><span class="token operator">&lt;</span><span class="token plain">service</span><span class="token operator">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">git_commit</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-systemd-module-libsystemdsh">The systemd module (<code>lib/systemd.sh</code>)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#the-systemd-module-libsystemdsh" class="hash-link" aria-label="Direct link to the-systemd-module-libsystemdsh" title="Direct link to the-systemd-module-libsystemdsh" translate="no">​</a></h3>
<p>For each service listed in <code>systemd_update_remote_host()</code> on each production host:</p>
<ol>
<li class=""><strong>SSH into the host</strong> with <code>BatchMode=yes</code> (key-based auth)</li>
<li class=""><strong>Check</strong> <code>/etc/default/&lt;service&gt;</code> exists</li>
<li class=""><strong>Backup</strong> the env file: <code>sudo cp -a "$env_file" "${env_file}.bak"</code></li>
<li class=""><strong>Find</strong> the first <code>IMAGE=</code> line</li>
<li class=""><strong>Replace</strong> the tag portion (everything after <code>:</code>) with the new version</li>
<li class=""><strong>Write</strong> the update via <code>sudo tee</code> + atomic <code>mv</code></li>
<li class=""><strong>Systemctl sequence</strong>:<!-- -->
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl daemon-reload</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl restart </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${svc}</span><span class="token string" style="color:rgb(255, 121, 198)">.service"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl --no-pager </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--full</span><span class="token plain"> status </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${svc}</span><span class="token string" style="color:rgb(255, 121, 198)">.service"</span><br></div></code></pre></div></div>
</li>
</ol>
<p>The full update logic runs remotely via a heredoc — no scripts need to be pre-installed on the target host:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">maybye </span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${SSH_OPTS</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">@</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> -- </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$svc</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_tag</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token string" style="color:rgb(255, 121, 198)">'REMOTE'</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">set -euo pipefail</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">svc="$1"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">new_tag="$2"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># ... bump IMAGE=, daemon-reload, restart ...</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">REMOTE</span><br></div></code></pre></div></div>
<p><strong>SSH options used:</strong></p>
<ul>
<li class=""><code>BatchMode=yes</code> — never prompt for password (fails if key auth isn't set up)</li>
<li class=""><code>StrictHostKeyChecking=accept-new</code> — auto-accept first connection, fail on host-key mismatch</li>
<li class=""><code>ConnectTimeout=8</code> — fail fast if unreachable</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="customizing-hosts-and-services">Customizing hosts and services<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#customizing-hosts-and-services" class="hash-link" aria-label="Direct link to Customizing hosts and services" title="Direct link to Customizing hosts and services" translate="no">​</a></h3>
<p>The hostnames and service names are hard-coded near the top of <code>deploy.sh</code> and inside <code>systemd.sh</code>. Adapt these for your own project:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># deploy.sh — remote hosts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">STAGING_HOST</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"your-k8s-control-node"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">PRODUCTION_HOST_1</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"your-vm-1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">PRODUCTION_HOST_2</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"your-vm-2"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># systemd.sh — service names (must match /etc/default/&lt;name&gt;)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:rgb(189, 147, 249);font-style:italic">svc</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> your-api your-frontend your-worker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    _systemd_remote_update_one </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$svc</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_tag</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-production-deploy">Example: production deploy<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#example-production-deploy" class="hash-link" aria-label="Direct link to Example: production deploy" title="Direct link to Example: production deploy" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh </span><span class="token number">2.0</span><span class="token plain">.0 production</span><br></div></code></pre></div></div>
<p>What happens:</p>
<ol>
<li class="">Builds all images with <code>Dockerfile.prod</code></li>
<li class="">Pushes to registry</li>
<li class="">Updates <code>.env</code> files (skips <code>kubernetes/deploy.yaml</code> since <code>UPDATE_KUBE=false</code>)</li>
<li class="">Writes <code>.version</code></li>
<li class="">For each production host:<!-- -->
<ul>
<li class="">SSHs in, bumps <code>IMAGE=</code> in <code>/etc/default/&lt;service&gt;</code></li>
<li class="">Restarts the service via systemctl</li>
<li class="">Repeats for every service</li>
</ul>
</li>
<li class="">Git commits</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dry-run-system">The Dry-Run System<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#the-dry-run-system" class="hash-link" aria-label="Direct link to The Dry-Run System" title="Direct link to The Dry-Run System" translate="no">​</a></h2>
<p>Every destructive or state-changing operation is wrapped in <code>maybe</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function-name function" style="color:rgb(80, 250, 123)">maybe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> is_dry_run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[DRY-RUN] </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$*</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$@</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>This means <code>--dry-run</code> gives you a complete trace of every command that <em>would</em> execute — including <code>docker build</code>, <code>docker push</code>, <code>kubectl apply</code>, <code>ssh</code> commands, <code>sed</code> edits, and <code>git commit</code> — without actually touching anything.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">./deploy.sh --dry-run </span><span class="token number">1.4</span><span class="token plain">.0 staging</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Output:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># [DRY-RUN] docker build -f Dockerfile.staging . -t &lt;registry&gt;/&lt;image&gt;:1.4.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># [DRY-RUN] docker push &lt;registry&gt;/&lt;image&gt;:1.4.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># ...etc...</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="state-files-written">State Files Written<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#state-files-written" class="hash-link" aria-label="Direct link to State Files Written" title="Direct link to State Files Written" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="env--env-any-depth-2-file-matching"><code>.env</code> / <code>.env.*</code> (any depth-2 file matching)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#env--env-any-depth-2-file-matching" class="hash-link" aria-label="Direct link to env--env-any-depth-2-file-matching" title="Direct link to env--env-any-depth-2-file-matching" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">NEXT_PUBLIC_APP_VERSION</span><span class="token operator">=</span><span class="token number">1.4</span><span class="token plain">.0</span><br></div></code></pre></div></div>
<p>The env-var name (<code>NEXT_PUBLIC_APP_VERSION</code>) is Next.js-specific — change the <code>grep</code>/<code>sed</code> patterns in <code>version.sh</code> to match your framework.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="kubernetesdeployyaml-local--staging-only"><code>kubernetes/deploy.yaml</code> (local &amp; staging only)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#kubernetesdeployyaml-local--staging-only" class="hash-link" aria-label="Direct link to kubernetesdeployyaml-local--staging-only" title="Direct link to kubernetesdeployyaml-local--staging-only" translate="no">​</a></h3>
<p>All image tags <code>:X.Y.Z</code> are replaced with the new version via regex.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="version-repo-root--written-on-every-run"><code>.version</code> (repo root — written on every run)<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#version-repo-root--written-on-every-run" class="hash-link" aria-label="Direct link to version-repo-root--written-on-every-run" title="Direct link to version-repo-root--written-on-every-run" translate="no">​</a></h3>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key attr-name" style="color:rgb(241, 250, 140)">CURRENT_VERSION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">1.4.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">LAST_DEPLOY_ENV</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">staging</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">LAST_DEPLOY_AT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">2026-08-03T14:03:11+04:00</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">LAST_DEPLOY_USER</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">pierre</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">GIT_BRANCH</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">main</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">GIT_COMMIT_SHA</span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token value attr-value">a1b2c3d</span><br></div></code></pre></div></div>
<p>This file is sourced on the <em>next</em> run to provide the <code>OLD_VERSION</code> baseline.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="docker-build-strategy">Docker Build Strategy<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#docker-build-strategy" class="hash-link" aria-label="Direct link to Docker Build Strategy" title="Direct link to Docker Build Strategy" translate="no">​</a></h2>
<p>Each environment maps to a specific Dockerfile:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function-name function" style="color:rgb(80, 250, 123)">dockerfile_for_env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">      </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Dockerfile.local"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        staging</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Dockerfile.staging"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        production</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Dockerfile.prod"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        *</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">          die </span><span class="token string" style="color:rgb(255, 121, 198)">"Unknown env '</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">'"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">esac</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>The expected Dockerfiles per service directory — defined in <code>build_all_images()</code>:</p>
<table><thead><tr><th>Service directory</th><th>Required Dockerfiles</th></tr></thead><tbody><tr><td><code>api/</code></td><td><code>Dockerfile.local</code>, <code>Dockerfile.staging</code>, <code>Dockerfile.prod</code></td></tr><tr><td><code>www/</code></td><td><code>Dockerfile.local</code>, <code>Dockerfile.staging</code>, <code>Dockerfile.prod</code></td></tr><tr><td><code>worker/</code></td><td><code>Dockerfile.local</code>, <code>Dockerfile.staging</code>, <code>Dockerfile.prod</code></td></tr></tbody></table>
<p><strong>Image naming convention</strong> (customize <code>user</code> and image names in <code>build_all_images()</code>):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token operator">&lt;</span><span class="token plain">registry-user</span><span class="token operator">&gt;</span><span class="token plain">/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-api:</span><span class="token operator">&lt;</span><span class="token plain">version</span><span class="token operator">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">&lt;</span><span class="token plain">registry-user</span><span class="token operator">&gt;</span><span class="token plain">/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-frontend:</span><span class="token operator">&lt;</span><span class="token plain">version</span><span class="token operator">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">&lt;</span><span class="token plain">registry-user</span><span class="token operator">&gt;</span><span class="token plain">/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-worker:</span><span class="token operator">&lt;</span><span class="token plain">version</span><span class="token operator">&gt;</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="complete-file-reference">Complete File Reference<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#complete-file-reference" class="hash-link" aria-label="Direct link to Complete File Reference" title="Direct link to Complete File Reference" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploysh"><code>deploy.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#deploysh" class="hash-link" aria-label="Direct link to deploysh" title="Direct link to deploysh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/bin/bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-euo</span><span class="token plain"> pipefail</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SCRIPT_DIR</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"$(cd "</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">dirname</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">${</span><span class="token string variable string variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">BASH_SOURCE</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">0</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">pwd</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">REPO_ROOT="</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">cd</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$SCRIPT_DIR</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">/../.."</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">&amp;&amp;</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">pwd</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># load lib</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># shellcheck disable=SC1091</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">. "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$REPO_ROOT</span><span class="token plain">/lib/index.sh</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># --- REMOTE HOSTS (customize per project) ---</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">STAGING_HOST="</span><span class="token plain">your-k8s-control-node</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">PRODUCTION_HOST_1="</span><span class="token plain">your-vm-1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">PRODUCTION_HOST_2="</span><span class="token plain">your-vm-2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">parse_args() {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	DRY_RUN=0</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	while [[ </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$#</span><span class="token string" style="color:rgb(255, 121, 198)"> -gt 0 ]]; do</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		case "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">" in</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		-n | --dry-run)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			DRY_RUN=1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			shift</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			;;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		-h | --help | help)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			print_help</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			exit 0</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			;;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		-*)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			die "</span><span class="token plain">Unknown flag: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			;;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		*)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			break</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">			;;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		esac</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	done</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	NEW_VERSION="</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${1</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	shift || true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	ENV_NAME="</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${1</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	shift || true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	[ -z "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${NEW_VERSION</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">" ] &amp;&amp; die "</span><span class="token plain">Version tag not specified.</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	[ -z "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${ENV_NAME</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">" ] &amp;&amp; die "</span><span class="token plain">Environment not specified.</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	case "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">" in local | staging | production) ;; *) die "</span><span class="token plain">Invalid </span><span class="token function" style="color:rgb(80, 250, 123)">env</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'$ENV_NAME'</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"> Allowed: </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token operator">|</span><span class="token plain">staging</span><span class="token operator">|</span><span class="token plain">production.</span><span class="token string" style="color:rgb(255, 121, 198)">" ;; esac</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	if [ -n "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${1</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		die "</span><span class="token plain">Unknown argument: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">run() {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	parse_args "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$@</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	if [ -f .version ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		. .version</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		OLD_VERSION="</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${CURRENT_VERSION</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">0.0.0}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	else</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		OLD_VERSION="</span><span class="token number">0.0</span><span class="token plain">.0</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	echo "</span><span class="token plain">Old Version: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$OLD_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	echo "</span><span class="token plain">New Version: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	echo "</span><span class="token plain">Environment: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	echo "</span><span class="token plain">DRY_RUN</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${DRY_RUN}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	UPDATE_KUBE="</span><span class="token boolean">true</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	[ "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">" = "</span><span class="token plain">production</span><span class="token string" style="color:rgb(255, 121, 198)">" ] &amp;&amp; UPDATE_KUBE="</span><span class="token boolean">false</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	update_envs "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$OLD_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$UPDATE_KUBE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	if [ "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">" = "</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token string" style="color:rgb(255, 121, 198)">" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		echo "</span><span class="token operator">==</span><span class="token operator">&gt;</span><span class="token plain"> Local build selected</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		build_all_images "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		exec_kube "</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		echo "</span><span class="token plain">Local deploy complete.</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	if [ "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">" = "</span><span class="token plain">staging</span><span class="token string" style="color:rgb(255, 121, 198)">" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		echo "</span><span class="token operator">==</span><span class="token operator">&gt;</span><span class="token plain"> Staging build selected</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		build_all_images "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		push_all_images "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		exec_kube "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$STAGING_HOST</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		echo "</span><span class="token plain">Staging deploy complete.</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	if [ "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">" = "</span><span class="token plain">production</span><span class="token string" style="color:rgb(255, 121, 198)">" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		echo "</span><span class="token operator">==</span><span class="token operator">&gt;</span><span class="token plain"> Prod build selected</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		build_all_images "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		push_all_images "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		systemd_update_remote_host "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$PRODUCTION_HOST_1</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		systemd_update_remote_host "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$PRODUCTION_HOST_2</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">		echo "</span><span class="token plain">Production deploy complete.</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	git_commit "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$NEW_VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ENV_NAME</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	echo "</span><span class="token plain">Deployment complete</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">run "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$@</span><span class="token plain">"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libindexsh"><code>lib/index.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libindexsh" class="hash-link" aria-label="Direct link to libindexsh" title="Direct link to libindexsh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Loads all *.sh modules in this directory (except itself), once.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${__LIB_INDEX_SH_SOURCED</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">return</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">readonly</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">__LIB_INDEX_SH_SOURCED</span><span class="token operator">=</span><span class="token number">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">_LIB_DIR</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"$(cd "</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">dirname</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">${</span><span class="token string variable string variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">BASH_SOURCE</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">0</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$0}</span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">pwd</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">while IFS= read -r -d '' mod; do</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	case "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">basename</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$mod</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">" in</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	index.sh) continue ;;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	esac</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	# shellcheck source=/dev/null</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">	. "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$mod</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">done &lt; &lt;(find "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$_LIB_DIR</span><span class="token plain">" </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-maxdepth</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-type</span><span class="token plain"> f </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-name</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'*.sh'</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-print0</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sort</span><span class="token plain"> -z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libhelperssh"><code>lib/helpers.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libhelperssh" class="hash-link" aria-label="Direct link to libhelperssh" title="Direct link to libhelperssh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${TOP_PID</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$$}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">die</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">msg</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"${1:-"</span><span class="token plain">Unknown error</span><span class="token string" style="color:rgb(255, 121, 198)">"}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[ERROR] </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$msg</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${USAGE</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$USAGE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${TOP_PID</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">kill</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-0</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$TOP_PID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token function" style="color:rgb(80, 250, 123)">kill</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> </span><span class="token environment constant" style="color:rgb(189, 147, 249)">TERM</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$TOP_PID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">pushd_quiet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">d</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Changing working directory to ./</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$d</span><span class="token string" style="color:rgb(255, 121, 198)">..."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token function" style="color:rgb(80, 250, 123)">pushd</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$d</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Failed to enter </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$d</span><span class="token string" style="color:rgb(255, 121, 198)">."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">popd_quiet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token function" style="color:rgb(80, 250, 123)">popd</span><span class="token plain"> </span><span class="token operator">&amp;&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Failed to return to previous directory."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">is_dry_run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${DRY_RUN</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">0}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token number">1</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> TRUE </span><span class="token operator">|</span><span class="token plain"> on </span><span class="token operator">|</span><span class="token plain"> ON </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">yes</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> YES</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">return</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">return</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">esac</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">maybe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> is_dry_run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[DRY-RUN] </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$*</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$@</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">now_ts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token function" style="color:rgb(80, 250, 123)">date</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Iseconds</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libdockersh"><code>lib/docker.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libdockersh" class="hash-link" aria-label="Direct link to libdockersh" title="Direct link to libdockersh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">dockerfile_for_env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Dockerfile.local"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	staging</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Dockerfile.staging"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	production</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Dockerfile.prod"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Unknown env '</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">' for Dockerfile mapping (expected: local|staging|production)"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">esac</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">ensure_dockerfile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">df</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$df</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[ERROR] Required Dockerfile not found: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$df</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"         CWD: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">pwd</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"         Available Dockerfile* here:"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token function" style="color:rgb(80, 250, 123)">ls</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-1</span><span class="token plain"> Dockerfile* </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		die </span><span class="token string" style="color:rgb(255, 121, 198)">"Missing Dockerfile: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$df</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">docker_build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">repo</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">env</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$repo</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Repo required for docker build"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Env required for docker build"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">df</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">df</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">dockerfile_for_env </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	ensure_dockerfile </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$df</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Building image: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$repo</span><span class="token string" style="color:rgb(255, 121, 198)"> using </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$df</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> build </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$df</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$repo</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">docker_push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">repo</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$repo</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Repo required for docker push"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> push </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$repo</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># --- Customize: add/remove service directories and image names ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">build_all_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">version</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">env</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Version tag not specified."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Env tag not specified."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">user</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"your-registry-user"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	pushd_quiet </span><span class="token string" style="color:rgb(255, 121, 198)">"api"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	docker_build </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">/&lt;project&gt;-api:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	popd_quiet</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	pushd_quiet </span><span class="token string" style="color:rgb(255, 121, 198)">"www"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	docker_build </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">/&lt;project&gt;-frontend:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	popd_quiet</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	pushd_quiet </span><span class="token string" style="color:rgb(255, 121, 198)">"worker"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	docker_build </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">/&lt;project&gt;-worker:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	popd_quiet</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">push_all_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">version</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Version tag not specified."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">user</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"your-registry-user"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe docker_push </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">/&lt;project&gt;-api:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe docker_push </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">/&lt;project&gt;-frontend:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe docker_push </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">/&lt;project&gt;-worker:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libkubesh"><code>lib/kube.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libkubesh" class="hash-link" aria-label="Direct link to libkubesh" title="Direct link to libkubesh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/bin/bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">exec_kube</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">remote_host</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Remote host not specified."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"local"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		maybe </span><span class="token function" style="color:rgb(80, 250, 123)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-rf</span><span class="token plain"> ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/*</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		maybe </span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> ./kubernetes/* ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		maybe kubectl apply </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-k</span><span class="token plain"> ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--force</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		maybe </span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-rf</span><span class="token plain"> ~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/*</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		maybe </span><span class="token function" style="color:rgb(80, 250, 123)">scp</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> ./kubernetes/* </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain">:~/kubernetes/</span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		maybe </span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$remote_host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'kubectl apply -k ~/kubernetes/&lt;project&gt; --force'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libsystemdsh"><code>lib/systemd.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libsystemdsh" class="hash-link" aria-label="Direct link to libsystemdsh" title="Direct link to libsystemdsh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/bin/bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SSH_OPTS</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">-o </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">BatchMode</span><span class="token operator">=</span><span class="token plain">yes </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">StrictHostKeyChecking</span><span class="token operator">=</span><span class="token plain">accept-new </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">ConnectTimeout</span><span class="token operator">=</span><span class="token number">8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">_systemd_remote_update_one</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">host</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">svc</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">new_tag</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$3</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"INFO: running remote service update for </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${svc}</span><span class="token string" style="color:rgb(255, 121, 198)">.service on HOST: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${host}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe </span><span class="token function" style="color:rgb(80, 250, 123)">ssh</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${SSH_OPTS</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">@</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> -- </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$svc</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_tag</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token string" style="color:rgb(255, 121, 198)">'REMOTE'</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">set -euo pipefail</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">svc="$1"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">new_tag="$2"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">env_file="/etc/default/${svc}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">if [ ! -f "$env_file" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  echo "WARN: ${env_file} not found, skipping" &gt;&amp;2</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  exit 0</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">echo "INFO: making backup ${env_file}.bak"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">sudo cp -a "$env_file" "${env_file}.bak"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">line="$(grep -m1 -E '^IMAGE=' "$env_file" || true)"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">if [ -z "$line" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  echo "WARN: No IMAGE= in ${env_file}; skipping"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">else</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  image="${line#IMAGE=}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  if [[ "$image" == *:* ]]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    repo="${image%:*}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    new_image="${repo}:${new_tag}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  else</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    new_image="${image}:${new_tag}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  if [ "$image" = "$new_image" ]; then</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    echo "INFO: ${env_file} already at ${new_image}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  else</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    echo "UPDATE: ${env_file}  IMAGE=$image -&gt; IMAGE=$new_image"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    sudo awk -v newimg="IMAGE=${new_image}" '</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      BEGIN { done=0 }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        if (!done &amp;&amp; $0 ~ /^IMAGE=/) { print newimg; done=1 }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        else { print }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ' "$env_file" | sudo tee "${env_file}.tmp" &gt; /dev/null</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    sudo mv "${env_file}.tmp" "$env_file"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">fi</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">sudo systemctl daemon-reload</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">sudo systemctl restart "${svc}.service"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">sudo systemctl --no-pager --full status "${svc}.service" -n 0 || true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">echo "INFO: service reloaded ${svc}.service"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">REMOTE</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># --- Customize: list your systemd service names here ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">systemd_update_remote_host</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">host</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">new_tag</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:rgb(189, 147, 249);font-style:italic">svc</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-api </span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-frontend </span><span class="token operator">&lt;</span><span class="token plain">project</span><span class="token operator">&gt;</span><span class="token plain">-worker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		_systemd_remote_update_one </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$host</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$svc</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_tag</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libversionsh"><code>lib/version.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libversionsh" class="hash-link" aria-label="Direct link to libversionsh" title="Direct link to libversionsh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">_git_branch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> rev-parse --abbrev-ref HEAD </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"unknown"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">_git_sha</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> rev-parse </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--short</span><span class="token plain"> HEAD </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"unknown"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">_user_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">id</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-un</span><span class="token plain"> </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">whoami</span><span class="token plain"> </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"unknown"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">_write_version_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">new_version</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">env</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> ts user branch sha</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">ts</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">now_ts</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">user</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">_user_name</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">branch</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">_git_branch</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">sha</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">_git_sha</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Writing .version with:"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  CURRENT_VERSION=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  LAST_DEPLOY_ENV=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  LAST_DEPLOY_AT=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ts</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  LAST_DEPLOY_USER=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  GIT_BRANCH=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$branch</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  GIT_COMMIT_SHA=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$sha</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> is_dry_run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[DRY-RUN] &gt; .version (not writing)"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain">.version </span><span class="token operator">&lt;&lt;</span><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">CURRENT_VERSION=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">LAST_DEPLOY_ENV=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">LAST_DEPLOY_AT=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ts</span><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">LAST_DEPLOY_USER=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$user</span><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">GIT_BRANCH=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$branch</span><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">GIT_COMMIT_SHA=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$sha</span><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">update_envs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$@</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">old_version</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">new_version</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">env</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$3</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">update_kube</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${4</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">false}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> die </span><span class="token string" style="color:rgb(255, 121, 198)">"Usage: update_envs &lt;old_version&gt; &lt;new_version&gt; &lt;env&gt; [update_kube]"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Updating .env files with APP_VERSION=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)">..."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> </span><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">IFS</span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">''</span><span class="token plain"> env_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-q</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"^APP_VERSION="</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env_file</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">			maybe </span><span class="token function" style="color:rgb(80, 250, 123)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"s/^APP_VERSION=.*/APP_VERSION=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)">/"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env_file</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">			</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"  - updated: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env_file</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">find</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-mindepth</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-maxdepth</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-type</span><span class="token plain"> f </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-name</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">".env"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-name</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">".env.*"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> -print0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$update_kube</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> kubernetes/deploy.yaml </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">			</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Updating kubernetes/deploy.yaml image tags: :</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$old_version</span><span class="token string" style="color:rgb(255, 121, 198)"> -&gt; :</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)"> ..."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">			maybe </span><span class="token function" style="color:rgb(80, 250, 123)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-E</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"s/:[0-9]+\.[0-9]+\.[0-9]+/:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${new_version}</span><span class="token string" style="color:rgb(255, 121, 198)">/g"</span><span class="token plain"> kubernetes/deploy.yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">			</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[WARN] kubernetes/deploy.yaml not found; skipping."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">		</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	_write_version_file </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$new_version</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libgitsh"><code>lib/git.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libgitsh" class="hash-link" aria-label="Direct link to libgitsh" title="Direct link to libgitsh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">git_commit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">version</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">env</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"[INFO]: Running git commit for version </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)"> to </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe </span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	maybe </span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> commit </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-m</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"deploy: version </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$version</span><span class="token string" style="color:rgb(255, 121, 198)"> to </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$env</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="libhelpsh"><code>lib/help.sh</code><a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#libhelpsh" class="hash-link" aria-label="Direct link to libhelpsh" title="Direct link to libhelpsh" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">print_help</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">	</span><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token string" style="color:rgb(255, 121, 198)">'EOF'</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">Deploy Pipeline</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">USAGE</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  ./deploy.sh &lt;version&gt; &lt;env: local|staging|production&gt; [options]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">POSITIONAL ARGUMENTS</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  version                 Docker image tag to build/push/use (e.g. 1.4.0)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  env                     One of:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                            - local       : build all images with Dockerfile.local,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                                            update .env files, bump kubernetes/deploy.yaml,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                                            kubectl apply locally</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                            - staging     : build &amp; push images (Dockerfile.staging),</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                                            update .env files, bump kubernetes/deploy.yaml,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                                            kubectl apply on staging host via SSH</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                            - production  : build &amp; push images (Dockerfile.prod),</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                                            update .env files, update bare-metal hosts</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">                                            via SSH + systemd</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">OPTIONS</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  -n, --dry-run           Print what would happen without making any changes</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  -h, --help, help        Show this help and exit</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">BEHAVIOR</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  - Dockerfiles (required per service dir):</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        local       -&gt; Dockerfile.local</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        staging     -&gt; Dockerfile.staging</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        production  -&gt; Dockerfile.prod</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    The build will FAIL if the expected Dockerfile is missing.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  - Files/State changed:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      - .env / .env.*              : APP_VERSION=&lt;version&gt; is updated (if present)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      - kubernetes/deploy.yaml     : image tags :&lt;old&gt; -&gt; :&lt;version&gt; (local &amp; staging only)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      - .version                   : snapshot of the last run</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  - Remote hosts are hard-coded in the script — edit to match your infrastructure.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">ENV VARS</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  DRY_RUN=1                Same effect as --dry-run</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EXAMPLES</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  ./deploy.sh 1.4.0 local</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  ./deploy.sh 1.4.0 staging</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  ./deploy.sh 1.4.0 production</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  ./deploy.sh --dry-run 1.4.0 staging</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  DRY_RUN=1 ./deploy.sh 1.4.0 local</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EXIT CODES</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  0  success</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  1+ failure (missing args, missing Dockerfile, command error, etc.)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-decisions">Key Design Decisions<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#key-design-decisions" class="hash-link" aria-label="Direct link to Key Design Decisions" title="Direct link to Key Design Decisions" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="robust-script-relative-sourcing">Robust script-relative sourcing<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#robust-script-relative-sourcing" class="hash-link" aria-label="Direct link to Robust script-relative sourcing" title="Direct link to Robust script-relative sourcing" translate="no">​</a></h3>
<p>The deploy script uses <code>BASH_SOURCE</code> to resolve its own directory, so it works no matter where you invoke it from:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SCRIPT_DIR</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"$(cd "</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">dirname</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">${</span><span class="token string variable string variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">BASH_SOURCE</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">0</span><span class="token string variable string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">pwd</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">REPO_ROOT="</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">cd</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$SCRIPT_DIR</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">/../.."</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">&amp;&amp;</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">pwd</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">. "</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$REPO_ROOT</span><span class="token plain">/lib/index.sh"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="no-config-files--the-script-is-the-config">No config files — the script IS the config<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#no-config-files--the-script-is-the-config" class="hash-link" aria-label="Direct link to No config files — the script IS the config" title="Direct link to No config files — the script IS the config" translate="no">​</a></h3>
<p>Remote hosts, Docker registry users, and service names are all hard-coded directly in the scripts. This is intentional for a single-project monorepo — there's no indirection to chase when debugging. To adapt this for your project, edit the values in <code>deploy.sh</code> (hosts), <code>docker.sh</code> (images), and <code>systemd.sh</code> (service names).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="atomic-systemd-env-updates">Atomic systemd env updates<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#atomic-systemd-env-updates" class="hash-link" aria-label="Direct link to Atomic systemd env updates" title="Direct link to Atomic systemd env updates" translate="no">​</a></h3>
<p>The systemd module backs up <code>/etc/default/&lt;svc&gt;</code> before touching it, writes to a <code>.tmp</code> file, then <code>mv</code>s it into place. If the SSH connection drops mid-write, the original file is intact and the service keeps running on the old image.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="everything-is-dry-run-aware">Everything is dry-run-aware<a href="https://stackops.link/journal/multi-strategy-deploy-pipeline#everything-is-dry-run-aware" class="hash-link" aria-label="Direct link to Everything is dry-run-aware" title="Direct link to Everything is dry-run-aware" translate="no">​</a></h3>
<p>Every <code>docker build</code>, <code>docker push</code>, <code>kubectl apply</code>, <code>ssh</code>, <code>scp</code>, <code>sed</code>, and <code>git commit</code> goes through <code>maybe()</code>. A single <code>--dry-run</code> flag gives you complete visibility into what the pipeline will do before it touches anything.</p>]]></content:encoded>
            <category>bash</category>
            <category>docker</category>
            <category>kubernetes</category>
            <category>systemd</category>
            <category>deployment</category>
            <category>shell</category>
            <category>devops</category>
        </item>
        <item>
            <title><![CDATA[Orion Chain — Custom Blockchain in Rust]]></title>
            <link>https://stackops.link/journal/orion-chain</link>
            <guid>https://stackops.link/journal/orion-chain</guid>
            <pubDate>Fri, 23 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A feature-rich blockchain implementation written in Rust, featuring a custom TCP-based peer-to-peer communication protocol, ECDSA cryptographic signing (k256/p256 curves), RocksDB persistent storage, an Actix-web HTTP API, and a validator runtime for executing transactions. Designed as a full-node platform for decentralized applications with 55 source files across 7 modules.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/orion-chain#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Orion Chain</strong> is a blockchain platform built from the ground up in Rust. Unlike projects that fork existing chains, Orion implements its own custom P2P networking protocol over raw TCP, its own transaction pool and block validation logic, and its own validator runtime for transaction execution.</p>
<p>The codebase is structured into 7 cohesive modules — <code>api</code>, <code>core</code>, <code>crypto</code>, <code>network</code>, <code>rpc</code>, <code>state</code>, and <code>vm</code> — across 55 source files. It is designed as a full-node platform: a single <code>ChainNode</code> binary that runs an HTTP API (Actix-web), a P2P networking layer, and a blockchain validator loop, all backed by RocksDB for persistent storage.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/orion-chain#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Custom TCP P2P protocol:</strong> Nodes communicate over a custom binary protocol with message types encoded via <code>borsh</code> serialization — no gRPC, no libp2p — just raw TCP sockets with manual binary framing</li>
<li class=""><strong>ECDSA cryptography:</strong> Transaction signing and verification using both <code>k256</code> (secp256k1) and <code>p256</code> curves, with DER/PEM encoding support</li>
<li class=""><strong>RocksDB persistence:</strong> Block storage and chain state persisted to disk via RocksDB, with support for in-memory operation during testing</li>
<li class=""><strong>Block production:</strong> Validator loop runs every 5 seconds (configurable block time), proposing new blocks containing transactions from the mempool</li>
<li class=""><strong>Transaction pool:</strong> In-memory mempool with configurable size (default 50 transactions), fed by peer gossip and RPC submissions</li>
<li class=""><strong>Validator runtime:</strong> VM module that executes transactions against chain state, with a <code>ValidatorRuntime</code> that validates blocks before commitment</li>
<li class=""><strong>HTTP API:</strong> Actix-web server exposing REST endpoints for blocks, transactions, and chain state queries</li>
<li class=""><strong>RPC controller:</strong> Bridges peer TCP messages and client HTTP requests, routing <code>RpcHeader</code> enum variants (GetBlock, NewTx, CommitBlock, GetChainHeight, etc.) to appropriate handlers</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/orion-chain#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>Seven modules forming a complete blockchain node:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌────────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│               ChainNode                     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  orchestrates: TcpController, TxPool,       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  BlockValidator, Blockchain, RpcController  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────┬─────────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ┌───────────┼───────────┬──────────┬───────────┬──────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ▼           ▼           ▼          ▼           ▼          ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  api/       core/       crypto/   network/    rpc/       state/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">HTTP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">blockchain</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ECDSA</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">TCP P2P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">bridge</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">accounts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │           │           │          │           │          │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ▼           ▼           ▼          ▼           ▼          ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> Actix-web  BlockMgr    k256/p256  Node/Peer  Handlers   AcctMgr</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> Routes     StateMgr    Address    TxPool     Controller  Storage</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            RocksDB     Signature  Encoder                RocksDB</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            Validator   Hash       Message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            Runtime                Types</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/orion-chain#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Language</strong></td><td>Rust (edition 2021)</td></tr><tr><td><strong>HTTP API</strong></td><td>Actix-web 4.9, Actix-CORS</td></tr><tr><td><strong>P2P Networking</strong></td><td>Custom TCP protocol, <code>borsh</code> serialization</td></tr><tr><td><strong>Cryptography</strong></td><td><code>ecdsa</code> (k256/p256 curves), <code>sha256</code>, <code>rand</code></td></tr><tr><td><strong>Serialization</strong></td><td><code>borsh</code>, <code>serde</code>, <code>serde_json</code>, <code>bincode</code>, <code>hex</code></td></tr><tr><td><strong>Database</strong></td><td>RocksDB (<code>rocksdb</code> crate), <code>tempfile</code></td></tr><tr><td><strong>Async runtime</strong></td><td>Tokio (full features)</td></tr><tr><td><strong>Logging</strong></td><td><code>env_logger</code>, <code>log</code>, <code>pretty_env_logger</code></td></tr><tr><td><strong>Build</strong></td><td><code>cargo build --release</code>, <code>make build</code></td></tr><tr><td><strong>License</strong></td><td>MIT</td></tr></tbody></table>]]></content:encoded>
            <category>rust</category>
            <category>blockchain</category>
            <category>p2p</category>
            <category>tcp</category>
            <category>actix-web</category>
            <category>rocksdb</category>
            <category>cryptography</category>
            <category>ecdsa</category>
        </item>
        <item>
            <title><![CDATA[Codativity — Agency Corporate Website]]></title>
            <link>https://stackops.link/journal/codativity</link>
            <guid>https://stackops.link/journal/codativity</guid>
            <pubDate>Thu, 20 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Corporate website for CODATIVITY Software Solutions, a Dubai-based agency offering business software development, web applications, modernization services, and products like CodaExpo. CMS-driven with rich case study pages and   dedicated product landing pages.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/codativity#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Codativity</strong> is the corporate digital presence for CODATIVITY Software Solutions — a Dubai-based software development agency. The website is a headless CMS-powered Next.js application that showcases the agency's services, products, solutions, portfolio case studies, and thought leadership blog.</p>
<p>Every piece of content — from the homepage hero to project case studies — is managed through Strapi and statically generated via Next.js ISR. The site includes dedicated standalone landing pages for products like <strong>CodaExpo</strong> (event management platform) and solution pages like <strong>CodaPay</strong> (payment processing) and <strong>CodaWeb</strong> (web infrastructure).</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/codativity#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Fully CMS-driven:</strong> All content — including logos, branding, and case study sections — lives in Strapi</li>
<li class=""><strong>Rich case study system:</strong> Structured project detail pages with approach, challenge, solution, results sections for professional portfolio presentation</li>
<li class=""><strong>CodaExpo micro-site:</strong> Standalone product landing page within the same Next.js app with hero, features grid, FAQ accordion, and custom footer</li>
<li class=""><strong>GDPR compliance:</strong> Cookie consent banner for EU visitors</li>
<li class=""><strong>Dual GitOps pipeline:</strong> Both Azure DevOps and GitHub Actions CI/CD available</li>
<li class=""><strong>Dynamic SEO:</strong> Shared SEO component reused across all content types, dynamic sitemap</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/codativity#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>www + api split</strong> following the headless CMS pattern:</p>
<table><thead><tr><th>Service</th><th>Role</th></tr></thead><tbody><tr><td><strong>www</strong> (Next.js)</td><td>Public corporate website with ISR</td></tr><tr><td><strong>api</strong> (Strapi)</td><td>Headless CMS managing all content</td></tr><tr><td><strong>PostgreSQL</strong></td><td>CMS database</td></tr></tbody></table>
<p>The frontend fetches all dynamic content from Strapi's REST API. Static pages are generated at build time with ISR revalidation every 10 seconds, so CMS updates propagate nearly instantly.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend--nextjs-pages-router">Frontend — Next.js Pages Router<a href="https://stackops.link/journal/codativity#frontend--nextjs-pages-router" class="hash-link" aria-label="Direct link to Frontend — Next.js Pages Router" title="Direct link to Frontend — Next.js Pages Router" translate="no">​</a></h2>
<p>Built with Next.js 15 Pages Router, TypeScript, and MUI v5 for the component library. Animations are powered by Framer Motion and AOS, with Swiper and React Slick for carousels.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pages">Pages<a href="https://stackops.link/journal/codativity#pages" class="hash-link" aria-label="Direct link to Pages" title="Direct link to Pages" translate="no">​</a></h3>
<ul>
<li class=""><strong>Home</strong> — Hero, services overview, client logos, testimonials</li>
<li class=""><strong>Services</strong> — Four dedicated service pages (Business Software, Modernization, Web Apps, Product Development)</li>
<li class=""><strong>Solutions</strong> — CodaPay and CodaWeb solution pages</li>
<li class=""><strong>Products</strong> — CodaExpo landing page (standalone micro-site with hero, features, FAQ)</li>
<li class=""><strong>Our Work</strong> — Portfolio of client projects</li>
<li class=""><strong>Case Studies</strong> — Rich project detail pages with structured sections: approach, challenge, solution, results, benefits</li>
<li class=""><strong>Blog</strong> — Articles with categories and tags</li>
<li class=""><strong>About, Contact, Careers, Privacy Policy</strong></li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="case-study-system">Case Study System<a href="https://stackops.link/journal/codativity#case-study-system" class="hash-link" aria-label="Direct link to Case Study System" title="Direct link to Case Study System" translate="no">​</a></h3>
<p>Project detail pages are structured with dedicated CMS components for each section — approach, challenge, solution, challenges overview, benefits, and results. This gives every case study a professional, magazine-quality layout while keeping content completely editable through Strapi.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cms--strapi-backend">CMS — Strapi Backend<a href="https://stackops.link/journal/codativity#cms--strapi-backend" class="hash-link" aria-label="Direct link to CMS — Strapi Backend" title="Direct link to CMS — Strapi Backend" translate="no">​</a></h2>
<p>Strapi manages all content through a well-organized collection schema:</p>
<ul>
<li class=""><strong>GlobalSite</strong> — Singleton with site-wide settings, logo, and branding</li>
<li class=""><strong>Page</strong> — i18n-ready pages with block-based content composition</li>
<li class=""><strong>Project</strong> — Portfolio case studies with structured detail sections</li>
<li class=""><strong>Post / Category / Tag</strong> — Blog content with taxonomy</li>
<li class=""><strong>Testimonial</strong> — Client testimonials for homepage and project pages</li>
</ul>
<p>Shared components (SEO metadata, media embeds, approach sections, solution overviews, quotes, sliders) are reused across content types for consistency.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/codativity#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Frontend</strong></td><td>Next.js (Pages Router), React, TypeScript, MUI v5, Framer Motion, AOS, Swiper</td></tr><tr><td><strong>CMS</strong></td><td>Strapi (headless), PostgreSQL</td></tr><tr><td><strong>Email</strong></td><td>Nodemailer + SendGrid SMTP</td></tr><tr><td><strong>Internationalization</strong></td><td>i18n-ready content types (English base, expandable)</td></tr><tr><td><strong>Rendering</strong></td><td>SSG with ISR (10-second revalidation)</td></tr></tbody></table>]]></content:encoded>
            <category>nextjs</category>
            <category>react</category>
            <category>strapi</category>
            <category>cms</category>
            <category>postgresql</category>
            <category>mui</category>
            <category>framer-motion</category>
            <category>typescript</category>
            <category>agency</category>
            <category>corporate</category>
        </item>
        <item>
            <title><![CDATA[orOS — Monolithic 64-bit Kernel in Rust]]></title>
            <link>https://stackops.link/journal/oros-dev</link>
            <guid>https://stackops.link/journal/oros-dev</guid>
            <pubDate>Wed, 07 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A 64-bit operating system kernel written from scratch in freestanding Rust. Features custom interrupt handling (IDT, GDT, PIC), physical memory management with paging, multiple heap allocator implementations, a VGA text-mode driver with framebuffer support, and an async cooperative task executor with keyboard input. Built for x86_64-unknown-none using the Rust nightly toolchain.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/oros-dev#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>orOS</strong> is a 64-bit monolithic operating system kernel written entirely in Rust — no C, no assembly beyond what the bootloader provides. It runs in a <code>#![no_std]</code> freestanding environment, managing the full x86_64 CPU state without an underlying OS or standard library.</p>
<p>The project serves as a deep-dive into low-level systems programming: configuring the interrupt subsystem (GDT, IDT, PIC), setting up virtual memory with page tables, implementing multiple heap allocator strategies, driving the VGA text buffer and pixel-based framebuffer, and running a cooperative async task executor. It targets the <code>x86_64-unknown-none</code> triple and requires the Rust <strong>nightly</strong> toolchain.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/oros-dev#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Freestanding execution:</strong> <code>#![no_std]</code> + <code>#![no_main]</code> — no standard library, no host OS, bare-metal boot via GRUB2 or UEFI</li>
<li class=""><strong>Interrupt subsystem:</strong> Global Descriptor Table (GDT) with Task State Segment (TSS) for double-fault handling, Interrupt Descriptor Table (IDT) with handlers for breakpoint, double fault, page fault, timer (PIC IRQ0), and keyboard (PIC IRQ1)</li>
<li class=""><strong>Memory management:</strong> Page table setup via <code>OffsetPageTable</code>, physical frame allocation from bootloader memory regions, virtual memory mapping for heap pages</li>
<li class=""><strong>Three heap allocators:</strong> Bump allocator (linear), fixed-size block allocator (9 size classes with free lists), and linked-list allocator with free region merging — all behind <code>Locked&lt;T&gt;</code> using <code>spin::Mutex</code></li>
<li class=""><strong>VGA text-mode driver:</strong> Memory-mapped I/O to <code>0xb8000</code> (80×25 text mode), 16-color palette, line scrolling, thread-safe via spinlock + interrupt-disabled critical sections</li>
<li class=""><strong>Framebuffer output:</strong> Pixel-based writer using <code>noto-sans-mono-bitmap</code> font rasterizer, supporting RGB, BGR, and U8 pixel formats</li>
<li class=""><strong>Async task system:</strong> Cooperative executor with custom <code>TaskWaker</code>, <code>BTreeMap</code>-backed task registry, sleep-on-idle via <code>hlt</code>, and an async keyboard input stream</li>
<li class=""><strong>Custom test framework:</strong> <code>#![custom_test_frameworks]</code> with QEMU exit-code integration — tests run in the actual kernel on real hardware emulation</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/oros-dev#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>monolithic kernel</strong> organized into 7 subsystems:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌─────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│          init.rs </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">boot sequence</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  IDT → GDT → PIC → paging → allocator  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────┬──────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ┌──────────┼──────────┬──────────┬──────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ▼          ▼          ▼          ▼          ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> interrupts  memory     </span><span class="token function" style="color:rgb(80, 250, 123)">screen</span><span class="token plain">     task       port</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  gdt, idt   page tbl   vga        executor   serial</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  handlers    frame     frame-buf  keyboard   qemu </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  pic         bump      color</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              fixed     macros</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              linked</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/oros-dev#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Language</strong></td><td>Rust (nightly toolchain)</td></tr><tr><td><strong>Target triple</strong></td><td><code>x86_64-unknown-none</code></td></tr><tr><td><strong>Bootloader</strong></td><td><code>bootloader</code> crate (v0.11), GRUB2, UEFI (OVMF)</td></tr><tr><td><strong>Async runtime</strong></td><td>Custom cooperative executor, <code>futures-util</code></td></tr><tr><td><strong>Concurrency</strong></td><td><code>spin</code>, <code>crossbeam-queue</code> (lock-free)</td></tr><tr><td><strong>Keyboard</strong></td><td><code>pc-keyboard</code> (PS/2 scancode decoding), <code>x86_64</code></td></tr><tr><td><strong>Serial</strong></td><td><code>uart_16550</code> (COM1 at <code>0x3F8</code>)</td></tr><tr><td><strong>Font</strong></td><td><code>noto-sans-mono-bitmap</code> (pixel-perfect rendering)</td></tr><tr><td><strong>Build</strong></td><td>Cargo workspace: <code>os</code> (host launcher) + <code>oros-kernel</code></td></tr><tr><td><strong>License</strong></td><td>MIT</td></tr></tbody></table>]]></content:encoded>
            <category>rust</category>
            <category>osdev</category>
            <category>kernel</category>
            <category>x86-64</category>
            <category>no-std</category>
            <category>systems-programming</category>
            <category>vga</category>
            <category>interrupts</category>
            <category>async</category>
        </item>
        <item>
            <title><![CDATA[FastAPI ML Project — Car Damage Assessment Platform]]></title>
            <link>https://stackops.link/journal/fast-api-ml-project</link>
            <guid>https://stackops.link/journal/fast-api-ml-project</guid>
            <pubDate>Sun, 19 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A machine learning microservices platform for automated car accident damage assessment. Features a FastAPI backend, an asynchronous GPU-accelerated ML worker using Detectron2 for damage detection on car images, MongoDB persistence, Flask and React frontends, and a Celery task queue. Fully containerized with Docker Compose for NVIDIA GPU-accelerated inference.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/fast-api-ml-project#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>FastAPI ML Project</strong> (also branded as "Accident-AI") is a machine learning microservices platform that automates vehicle damage assessment from car accident images. Users upload photos of damaged vehicles, and the system's ML models — powered by <strong>Detectron2</strong> (Facebook Research) on an NVIDIA GPU — detect and classify damage across different car parts using blueprint mask overlays.</p>
<p>The platform is split across five containerized services: a <strong>FastAPI</strong> backend for business logic and case management, a <strong>GPU-accelerated ML worker</strong> running Detectron2 models, a <strong>Flask</strong> server-rendered frontend, a <strong>React</strong> (CoreUI) admin dashboard, and <strong>MongoDB</strong> for persistent storage. Celery with Redis handles asynchronous task distribution.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/fast-api-ml-project#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Automated damage detection:</strong> Detectron2-based computer vision models process car images and identify damaged areas using 18 car-part blueprint masks (bumper, fender, door, headlamp, etc.)</li>
<li class=""><strong>Two ML model versions:</strong> <code>model_service_v1.py</code> and <code>model_service_v2.py</code> (~2,500 lines each) — support for multiple detection architectures</li>
<li class=""><strong>Parallel image processing:</strong> ThreadPoolExecutor with 8 workers for concurrent image analysis</li>
<li class=""><strong>Full case management API:</strong> CRUD for accident cases, vehicle addition, top-view generation, and PDF report generation</li>
<li class=""><strong>JWT authentication:</strong> HS256-signed JWT tokens with bearer authentication and API key support</li>
<li class=""><strong>S3 storage integration:</strong> Images and reports stored in AWS S3 bucket (<code>accident-ai-uploads</code>)</li>
<li class=""><strong>Dual frontends:</strong> Flask (server-rendered Jinja2 + Bootstrap Creative) and React (CoreUI Free Admin Template)</li>
<li class=""><strong>GPU-accelerated Docker worker:</strong> NVIDIA CUDA 11.0.3 base image with Detectron2 compiled from source</li>
<li class=""><strong>Celery task queue:</strong> Asynchronous job distribution for long-running ML inference tasks</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/fast-api-ml-project#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">                  ┌──────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                  │   MongoDB    │  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">accident_ai_app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                  └──────┬───────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                         │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     ┌───────────────────┼───────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     │                   │                   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌────▼────────┐  ┌───────▼────────┐  ┌───────▼──────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  API Svc    │  │  Worker Svc    │  │  Flask Frontend  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  :5000      │◄─┤  :4000         │  │  :3000            │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  FastAPI    │  │  FastAPI + ML  │  │  Jinja2 + Bootstrap│</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  JWT Auth   │  │  Detectron2    │  │                   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└─────────────┘  │  GPU Inference │  └───────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                 └────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          ┌───────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          │  React Frontend   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          │  :3001            │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          │  CoreUI Admin     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          └────────┬──────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          ┌────────▼──────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          │     Nginx :80     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                          └───────────────────┘</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/fast-api-ml-project#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>API Framework</strong></td><td>FastAPI, Uvicorn</td></tr><tr><td><strong>ML Framework</strong></td><td>Detectron2 (Facebook Research), PyTorch 1.11</td></tr><tr><td><strong>Computer Vision</strong></td><td>OpenCV, NumPy, Pytesseract (OCR)</td></tr><tr><td><strong>GPU Runtime</strong></td><td>NVIDIA CUDA 11.0.3</td></tr><tr><td><strong>Database</strong></td><td>MongoDB (MongoEngine ODM)</td></tr><tr><td><strong>Task Queue</strong></td><td>Celery + Redis</td></tr><tr><td><strong>Auth</strong></td><td>JWT (HS256), bcrypt password hashing</td></tr><tr><td><strong>Storage</strong></td><td>AWS S3 (boto3)</td></tr><tr><td><strong>Frontend (Flask)</strong></td><td>Flask, Jinja2, Bootstrap Creative</td></tr><tr><td><strong>Frontend (React)</strong></td><td>React 18, Redux, CoreUI Admin Template, Chart.js</td></tr><tr><td><strong>PDF Generation</strong></td><td>wkhtmltopdf (PDFKit)</td></tr><tr><td><strong>Container</strong></td><td>Docker Compose (5 services), Nginx, NVIDIA GPU</td></tr><tr><td><strong>License</strong></td><td>MIT</td></tr></tbody></table>]]></content:encoded>
            <category>fastapi</category>
            <category>python</category>
            <category>machine-learning</category>
            <category>detectron2</category>
            <category>computer-vision</category>
            <category>docker</category>
            <category>mongodb</category>
            <category>react</category>
        </item>
        <item>
            <title><![CDATA[Rust Chess — Full-Stack Chess Application in Rust]]></title>
            <link>https://stackops.link/journal/rust-chess-project</link>
            <guid>https://stackops.link/journal/rust-chess-project</guid>
            <pubDate>Sun, 19 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A comprehensive full-stack chess application written entirely in Rust. The chess engine compiles to WebAssembly for high-performance in-browser move generation and validation, while the Actix-web backend provides real-time WebSocket communication for online multiplayer games, chat rooms, and session management. The frontend is built with Gatsby.js and React, consuming the WASM chess library for instant board evaluation.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/rust-chess-project#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Rust Chess</strong> is a complete chess application where every line of core logic is written in Rust. The project is organised as a monorepo with two sub-repositories: one for the <strong>chess engine + frontend</strong> and one for the <strong>multiplayer backend</strong>.</p>
<p>The chess engine compiles to <strong>WebAssembly</strong> via <code>wasm-pack</code>, then links into a <strong>Gatsby.js / React</strong> frontend through npm. This gives the browser native-speed move generation, validation, check/checkmate detection, castling, en passant, and pawn promotion — with zero JavaScript handling game state.</p>
<p>The backend is an <strong>Actix-web</strong> server built on <strong>WebSocket actors</strong>, providing real-time communication for online multiplayer, chat rooms, and session management. A text-based command protocol over WebSockets lets clients create games, join lobbies, and relay moves between opponents.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/rust-chess-project#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Full Rust chess engine compiles to WASM:</strong> Move generation, validation, check/checkmate detection, en passant, castling, and pawn promotion — all running at near-native speed in the browser</li>
<li class=""><strong>Strategy pattern for piece moves:</strong> Each piece type implements a <code>PieceMoveStrategy</code> trait, making the engine extensible and testable</li>
<li class=""><strong>Algebraic notation parser and writer:</strong> <code>MoveParser</code> / <code>MoveReader</code> / <code>MoveWriter</code> translate between standard chess notation and internal move results</li>
<li class=""><strong>Online multiplayer via WebSocket:</strong> Real-time game creation, joining, move relay, and opponent notification</li>
<li class=""><strong>Chat rooms and lobby system:</strong> Multiple themed rooms (mountain, ocean, sky, space), lobby for game discovery, and in-game chat</li>
<li class=""><strong>Session management with heartbeat:</strong> <code>WsSession</code> actors maintain client connections with ping/pong heartbeats and automatic timeout cleanup</li>
<li class=""><strong>Local single-player support:</strong> Full board playable offline against a local opponent</li>
<li class=""><strong>Self-info and user profiles:</strong> Sessions can query their own room/game state and list available games</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/rust-chess-project#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>two-repository architecture</strong> splitting the WASM frontend and the WebSocket backend:</p>
<table><thead><tr><th>Repository</th><th>Role</th></tr></thead><tbody><tr><td><strong>chess-frontend</strong></td><td>Rust chess engine (WASM) + Gatsby.js / React UI</td></tr><tr><td><strong>chess-api</strong></td><td>Actix-web WebSocket server + game/room/session management</td></tr></tbody></table>
<p>The chess engine lives inside the frontend repository: Rust source in <code>chess-frontend/src/</code> compiles to <code>pkg/</code> via <code>wasm-pack</code>. The Gatsby app in <code>chess-frontend/www/</code> links the WASM package via npm and calls into Rust bindings for all game logic.</p>
<p>The API server manages no game state of its own — it acts purely as a relay between connected clients. Each <code>WsSession</code> actor handles text commands (<code>/new-game</code>, <code>/join-game</code>, <code>/game-move</code>, etc.) and delegates to <code>ChatServer</code> and <code>GameManager</code> for room and game orchestration.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│               Browser                    │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Gatsby.js / React UI                    │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       │                                  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ▼                                  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  chess-lib </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">WASM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> — full chess engine    │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Board → MoveValidator → PieceStrategy   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────┬───────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               │ WebSocket</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│        chess-api </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Actix-web</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">             │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│                                          │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  WsSession actors                        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       │                                  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ▼                                  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  ChatServer → rooms, sessions, broadcast │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  GameManager → SessionGame state         │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="chess-engine-detail">Chess Engine Detail<a href="https://stackops.link/journal/rust-chess-project#chess-engine-detail" class="hash-link" aria-label="Direct link to Chess Engine Detail" title="Direct link to Chess Engine Detail" translate="no">​</a></h2>
<p>The chess engine is the heart of the frontend. Written in pure Rust and exposed to JavaScript via <code>wasm-bindgen</code>, it handles every aspect of the game:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="board-boardrs">Board (<code>board.rs</code>)<a href="https://stackops.link/journal/rust-chess-project#board-boardrs" class="hash-link" aria-label="Direct link to board-boardrs" title="Direct link to board-boardrs" translate="no">​</a></h3>
<p>An 8×8 grid of <code>Tile</code> structs, each holding an optional <code>Piece</code>. The board tracks:</p>
<ul>
<li class=""><strong>Last en passant coordinate</strong> — set when a pawn moves two squares, cleared after the next move if not captured</li>
<li class=""><strong>King castling state</strong> — a <code>KingCastleBoardState</code> struct tracking whether each side's king and rooks have moved</li>
<li class=""><strong>Tile highlighting</strong> — visual move suggestions computed by the engine and rendered by React</li>
</ul>
<p>The board implements <code>move_piece(from, to)</code> as the main entry point, which delegates to <code>handle_move_piece()</code> — a complex method handling all move types, checking validity, executing captures, and returning a <code>MoveResult</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pieces-pieces">Pieces (<code>pieces/</code>)<a href="https://stackops.link/journal/rust-chess-project#pieces-pieces" class="hash-link" aria-label="Direct link to pieces-pieces" title="Direct link to pieces-pieces" translate="no">​</a></h3>
<p>Each piece type gets its own strategy module implementing the <code>PieceMoveStrategy</code> trait:</p>
<ul>
<li class=""><strong><code>PawnMoveStrategy</code></strong> — forward moves, double-push from start rank, diagonal captures, en passant detection</li>
<li class=""><strong><code>RookMoveStrategy</code></strong> — sliding moves in four directions, blocked by first occupied square</li>
<li class=""><strong><code>KnightMoveStrategy</code></strong> — eight L-shaped moves, ignoring intervening pieces</li>
<li class=""><strong><code>BishopMoveStrategy</code></strong> — sliding diagonal moves</li>
<li class=""><strong><code>QueenMoveStrategy</code></strong> — combination of rook and bishop movement</li>
<li class=""><strong><code>KingMoveStrategy</code></strong> — single-step in any direction, plus castle validation via <code>KingCastleValidator</code></li>
</ul>
<p>The <code>StrategyBuilder</code> factory pattern creates the correct strategy from a piece's type at runtime.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="move-validation-strategyrs">Move Validation (<code>strategy.rs</code>)<a href="https://stackops.link/journal/rust-chess-project#move-validation-strategyrs" class="hash-link" aria-label="Direct link to move-validation-strategyrs" title="Direct link to move-validation-strategyrs" translate="no">​</a></h3>
<p>The <code>MoveValidator</code> struct runs multiple checks before any move is accepted:</p>
<ol>
<li class="">Is the target square in bounds?</li>
<li class="">Is the move within the piece's legal movement pattern?</li>
<li class="">Is the target occupied by a friendly piece?</li>
<li class="">For king moves: does this move expose the king to check?</li>
<li class="">For castling: are the path squares empty and not under attack?</li>
<li class="">For en passant: is the target coordinate the correct en passant square?</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="move-parsing-parserrs">Move Parsing (<code>parser.rs</code>)<a href="https://stackops.link/journal/rust-chess-project#move-parsing-parserrs" class="hash-link" aria-label="Direct link to move-parsing-parserrs" title="Direct link to move-parsing-parserrs" translate="no">​</a></h3>
<p><code>MoveReader::parse_move()</code> converts standard algebraic notation strings (e.g. <code>"Ng1f3"</code>, <code>"e5xd6+"</code>, <code>"0-0"</code>, <code>"b7b8=Q#"</code>) into <code>MoveResult</code> structs. <code>MoveWriter::write_move()</code> does the reverse, enabling the board state to be serialised into displayable move history.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-websocket-commands">Backend: WebSocket Commands<a href="https://stackops.link/journal/rust-chess-project#backend-websocket-commands" class="hash-link" aria-label="Direct link to Backend: WebSocket Commands" title="Direct link to Backend: WebSocket Commands" translate="no">​</a></h2>
<p>The API uses a text-based command protocol over WebSockets. Each command is prefixed with <code>/</code> and processed by the <code>WsSession::handle_command()</code> dispatch:</p>
<table><thead><tr><th>Command</th><th>Description</th></tr></thead><tbody><tr><td><code>/list-rooms</code></td><td>List available chat rooms</td></tr><tr><td><code>/list-users</code></td><td>List users in the current room</td></tr><tr><td><code>/join-room &lt;name&gt;</code></td><td>Join a chat room</td></tr><tr><td><code>/new-game</code></td><td>Create a new chess game (hosted under your username)</td></tr><tr><td><code>/join-game &lt;name&gt;</code></td><td>Join an existing game by name</td></tr><tr><td><code>/leave-game</code></td><td>Leave the current game, return to main room</td></tr><tr><td><code>/game-move &lt;notation&gt;</code></td><td>Send a move in algebraic notation to the opponent</td></tr><tr><td><code>/list-available-games</code></td><td>List joinable games (not yet started)</td></tr><tr><td><code>/list-all-games</code></td><td>List all games regardless of state</td></tr><tr><td><code>/delete-game</code></td><td>Delete a game you created</td></tr><tr><td><code>/self-info</code></td><td>Get your session profile (username, room, game)</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="game-lifecycle">Game Lifecycle<a href="https://stackops.link/journal/rust-chess-project#game-lifecycle" class="hash-link" aria-label="Direct link to Game Lifecycle" title="Direct link to Game Lifecycle" translate="no">​</a></h3>
<ol>
<li class="">Player A sends <code>/new-game</code> — a <code>SessionGame</code> is created with Player A as white</li>
<li class="">Player B sends <code>/join-game &lt;A's username&gt;</code> — joins as black, game status flips to "started"</li>
<li class="">Both players send <code>/game-move &lt;notation&gt;</code> to relay moves to each other</li>
<li class="">Either player sends <code>/leave-game</code> — opponent is notified, game is cleaned up if empty</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="session-management">Session Management<a href="https://stackops.link/journal/rust-chess-project#session-management" class="hash-link" aria-label="Direct link to Session Management" title="Direct link to Session Management" translate="no">​</a></h3>
<p>Each WebSocket connection spawns a <code>WsSession</code> actor. A heartbeat ping is sent every 5 seconds; if the client does not respond within 10 seconds, the session is disconnected and cleaned up — removed from its room and any active games.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-react--wasm-integration">Frontend: React + WASM Integration<a href="https://stackops.link/journal/rust-chess-project#frontend-react--wasm-integration" class="hash-link" aria-label="Direct link to Frontend: React + WASM Integration" title="Direct link to Frontend: React + WASM Integration" translate="no">​</a></h2>
<p>The frontend is a Gatsby.js (v5) single-page application with React 18 and TypeScript. It consumes the WASM chess library as an npm-linked package.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pages">Pages<a href="https://stackops.link/journal/rust-chess-project#pages" class="hash-link" aria-label="Direct link to Pages" title="Direct link to Pages" translate="no">​</a></h3>
<table><thead><tr><th>Route</th><th>Page</th><th>Purpose</th></tr></thead><tbody><tr><td><code>/</code></td><td><code>index.tsx</code></td><td>Landing page with sections for online play, local play, chat, and tech showcase</td></tr><tr><td><code>/game</code></td><td><code>game.tsx</code></td><td>Full-screen chessboard with move controls</td></tr><tr><td><code>/lobby</code></td><td><code>lobby.tsx</code></td><td>Game lobby — create/join online games</td></tr><tr><td><code>/chat</code></td><td><code>chat.tsx</code></td><td>Chat room interface with room management</td></tr><tr><td><code>/contact</code></td><td><code>contact.tsx</code></td><td>Contact page</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="context-providers">Context Providers<a href="https://stackops.link/journal/rust-chess-project#context-providers" class="hash-link" aria-label="Direct link to Context Providers" title="Direct link to Context Providers" translate="no">​</a></h3>
<ul>
<li class=""><strong><code>ConnectionContext</code></strong> — Manages the WebSocket connection, exposes <code>connect()</code>, <code>disconnect()</code>, <code>sendMoveMsg()</code>, <code>joinGame()</code>, <code>newGame()</code>, and session persistence via <code>sessionStorage</code></li>
<li class=""><strong><code>GameContext</code></strong> — Holds the WASM <code>Game</code> instance, manages player turn, move history, and online/local mode</li>
<li class=""><strong><code>BoardContext</code></strong> — Holds the WASM <code>Board</code> instance, handles tile selection, highlighting, and move execution</li>
<li class=""><strong><code>ChatContext</code></strong> — Message parsing and display for the chat interface</li>
<li class=""><strong><code>ModalContext</code></strong> — Controls the pawn promotion modal overlay</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-components">Key Components<a href="https://stackops.link/journal/rust-chess-project#key-components" class="hash-link" aria-label="Direct link to Key Components" title="Direct link to Key Components" translate="no">​</a></h3>
<table><thead><tr><th>Component</th><th>Description</th></tr></thead><tbody><tr><td><code>GameContainer</code></td><td>Renders the chessboard, move history, and controls</td></tr><tr><td><code>ChatContainer</code></td><td>Chat room with message list and input</td></tr><tr><td><code>LobbyContainer</code></td><td>Game lobby showing available and active games</td></tr><tr><td><code>ConnectControl</code></td><td>Username input and connect/disconnect button</td></tr><tr><td><code>PromotePieceModal</code></td><td>Piece selection modal for pawn promotion</td></tr><tr><td><code>TechnologySection</code></td><td>Showcases the Rust + WASM + Gatsby tech stack</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/rust-chess-project#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-chess-frontend">Frontend (<code>chess-frontend</code>)<a href="https://stackops.link/journal/rust-chess-project#frontend-chess-frontend" class="hash-link" aria-label="Direct link to frontend-chess-frontend" title="Direct link to frontend-chess-frontend" translate="no">​</a></h3>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Chess Engine</strong></td><td>Rust (compiled to WASM via <code>wasm-pack</code>)</td></tr><tr><td><strong>JS Bridge</strong></td><td><code>wasm-bindgen</code>, <code>serde-wasm-bindgen</code></td></tr><tr><td><strong>UI Framework</strong></td><td>Gatsby.js 5, React 18</td></tr><tr><td><strong>Language</strong></td><td>TypeScript 4</td></tr><tr><td><strong>Styling</strong></td><td>SCSS, Bootstrap 5, React Bootstrap, Emotion</td></tr><tr><td><strong>Build</strong></td><td><code>wasm-pack build</code> + <code>gatsby develop</code></td></tr><tr><td><strong>Serialisation</strong></td><td><code>bincode</code>, <code>serde</code>, <code>serde_json</code></td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-chess-api">Backend (<code>chess-api</code>)<a href="https://stackops.link/journal/rust-chess-project#backend-chess-api" class="hash-link" aria-label="Direct link to backend-chess-api" title="Direct link to backend-chess-api" translate="no">​</a></h3>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>HTTP/WS Server</strong></td><td>Actix-web 4, Actix-web-actors 4.1</td></tr><tr><td><strong>WebSocket</strong></td><td><code>actix-web-actors</code> WebSocket actors</td></tr><tr><td><strong>Concurrency</strong></td><td><code>Arc&lt;Mutex&lt;ChatServer&gt;&gt;</code>, <code>AtomicUsize</code></td></tr><tr><td><strong>Serialisation</strong></td><td><code>serde</code>, <code>serde_json</code></td></tr><tr><td><strong>Session IDs</strong></td><td><code>uuid</code> v4 with fast-rng</td></tr><tr><td><strong>CORS</strong></td><td><code>actix-cors</code> (permissive)</td></tr><tr><td><strong>Configuration</strong></td><td><code>dotenv</code> for .env-based host/port</td></tr><tr><td><strong>Logging</strong></td><td><code>env_logger</code>, <code>log</code></td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="development-workflow">Development Workflow<a href="https://stackops.link/journal/rust-chess-project#development-workflow" class="hash-link" aria-label="Direct link to Development Workflow" title="Direct link to Development Workflow" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="chess-engine-wasm-rebuild-on-change">Chess Engine (WASM rebuild on change)<a href="https://stackops.link/journal/rust-chess-project#chess-engine-wasm-rebuild-on-change" class="hash-link" aria-label="Direct link to Chess Engine (WASM rebuild on change)" title="Direct link to Chess Engine (WASM rebuild on change)" translate="no">​</a></h3>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> chess-frontend</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">watch</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> .gitignore </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"pkg/*"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"www/*"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"wasm-pack build"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-dev-server-hot-reloads-on-wasm-rebuild">Frontend Dev Server (hot-reloads on WASM rebuild)<a href="https://stackops.link/journal/rust-chess-project#frontend-dev-server-hot-reloads-on-wasm-rebuild" class="hash-link" aria-label="Direct link to Frontend Dev Server (hot-reloads on WASM rebuild)" title="Direct link to Frontend Dev Server (hot-reloads on WASM rebuild)" translate="no">​</a></h3>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> chess-frontend/www</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">npm</span><span class="token plain"> run </span><span class="token function" style="color:rgb(80, 250, 123)">watch</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Gatsby dev server + npm-watch on pkg/ changes</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-dev-server">Backend Dev Server<a href="https://stackops.link/journal/rust-chess-project#backend-dev-server" class="hash-link" aria-label="Direct link to Backend Dev Server" title="Direct link to Backend Dev Server" translate="no">​</a></h3>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> chess-api</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">watch</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-x</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"run"</span><br></div></code></pre></div></div>
<p>Editing Rust files triggers a <code>wasm-pack</code> rebuild of the chess engine, which in turn triggers a Gatsby restart since the linked npm package changes. Editing React/TypeScript files hot-reloads as normal through Gatsby's dev server.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="known-improvements--roadmap">Known Improvements &amp; Roadmap<a href="https://stackops.link/journal/rust-chess-project#known-improvements--roadmap" class="hash-link" aria-label="Direct link to Known Improvements &amp; Roadmap" title="Direct link to Known Improvements &amp; Roadmap" translate="no">​</a></h2>
<p>From the project's own <a href="https://github.com/subaquatic-pierre/chess-frontend" target="_blank" rel="noopener noreferrer" class="">README</a>, there is a clear list of planned improvements:</p>
<ol>
<li class=""><strong>Fix king castle validation</strong> — king should not be allowed to move to castle squares if the castle option is no longer valid</li>
<li class=""><strong>Refactor board move logic</strong> — the <code>handle_move_piece</code> method is too large and should be split into smaller, testable units</li>
<li class=""><strong>Split GameManager from ChatServer</strong> — introduce a dedicated <code>RoomManager</code> for room logic and keep <code>GameManager</code> focused on game state</li>
<li class=""><strong>Better client-side message updates</strong> — current polling-based approach for DOM updates should be replaced with proper reactive state management</li>
<li class=""><strong>Session persistence across page reloads</strong> — maintain session on the server with heartbeat checks, allowing clients to resume after a page refresh</li>
<li class=""><strong>User profile creation</strong> — proper user accounts with persistent profiles</li>
<li class=""><strong>Persistent game storage</strong> — save games in backend storage for long-running or paused games</li>
<li class=""><strong>Game timer</strong> — add chess clock functionality</li>
<li class=""><strong>Chat box between opponents</strong> — direct messaging during active games</li>
</ol>]]></content:encoded>
            <category>rust</category>
            <category>webassembly</category>
            <category>wasm</category>
            <category>gatsby</category>
            <category>react</category>
            <category>typescript</category>
            <category>actix-web</category>
            <category>websocket</category>
            <category>chess</category>
            <category>game</category>
            <category>multiplayer</category>
        </item>
        <item>
            <title><![CDATA[Z-Transfer — Decentralized File Sharing on 0Chain]]></title>
            <link>https://stackops.link/journal/ztransfer</link>
            <guid>https://stackops.link/journal/ztransfer</guid>
            <pubDate>Sun, 19 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A decentralized file-sharing web application built on the 0Chain (Zero Chain) blockchain. Features a Flask backend with a custom Python SDK for interacting with the blockchain, Reed-Solomon erasure-coded file sharding across storage nodes, BLS cryptographic signing, and a Bootstrap 5 frontend. Fully containerized with Docker Compose for both development and production.
]]></description>
            <content:encoded><![CDATA[
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://stackops.link/journal/ztransfer#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><strong>Z-Transfer</strong> is a file-sharing application that leverages the <strong>0Chain (Zero Chain)</strong> decentralized storage blockchain to provide secure, censorship-resistant file hosting. Users upload files through a Bootstrap 5 web interface; the application shards the files using Reed-Solomon erasure coding and distributes them across multiple storage nodes (blobbers) on the 0Chain network.</p>
<p>The project includes a custom <strong><code>zero_sdk</code></strong> Python library — a complete SDK for interacting with the 0Chain blockchain, handling wallet management, storage allocation, file sharding, and BLS cryptographic signing. Signatures are generated via a Node.js sidecar using <code>bls-wasm</code> on the BN254 elliptic curve.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-features">Key Features<a href="https://stackops.link/journal/ztransfer#key-features" class="hash-link" aria-label="Direct link to Key Features" title="Direct link to Key Features" translate="no">​</a></h2>
<ul>
<li class=""><strong>Decentralized file storage:</strong> Files are erasure-coded (4 data + 2 parity shards) and distributed across multiple blobber nodes on the 0Chain blockchain</li>
<li class=""><strong>Custom blockchain SDK:</strong> <code>zero_sdk</code> — a complete Python library for 0Chain interaction including <code>Wallet</code>, <code>Allocation</code>, <code>Network</code>, and <code>ConnectionBase</code> classes</li>
<li class=""><strong>BLS cryptographic signing:</strong> BN254 curve signatures via a Node.js <code>bls-wasm</code> subprocess bridge, with a Heroku fallback endpoint</li>
<li class=""><strong>File lifecycle management:</strong> Upload (shard, distribute, commit), download (reassemble from shards), and allocation management</li>
<li class=""><strong>Docker Compose dev/prod environments:</strong> Development with Flask hot-reload on SQLite; production with Gunicorn + PostgreSQL + Nginx</li>
<li class=""><strong>Responsive Bootstrap 5 UI:</strong> Jinja2 server-rendered templates with Font Awesome icons and vanilla JavaScript file upload with progress tracking</li>
<li class=""><strong>Email notifications:</strong> Flask-Mail integration with SMTP for sender/recipient notifications</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://stackops.link/journal/ztransfer#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>A <strong>three-tier containerized architecture</strong> with a custom blockchain SDK:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│              Nginx :80                   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│         </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">prod reverse proxy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">             │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────┬───────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────▼───────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│         Flask App </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">services/web/</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  / ──── upload form </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Jinja2 + Bootstrap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  /upload-files ──── POST handler         │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── saves to uploads/                  │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── emails sender/recipient            │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────┬───────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               │ uses</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────▼───────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       zero_sdk </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">app/zero_sdk/</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">           │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Wallet ── balance, sign, restore        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Allocation ── upload_file, download_file │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Network ── miner/sharder discovery      │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  ConnectionBase ── consensus engine      │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────┬───────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               │ signs via</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────▼───────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   JS Bridge </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">lib/bn254_signature_js/</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">    │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   bls-wasm ── BN254 BLS signatures       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://stackops.link/journal/ztransfer#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h2>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td><strong>Backend</strong></td><td>Python 3, Flask 2.0, Gunicorn</td></tr><tr><td><strong>Frontend</strong></td><td>Jinja2, Bootstrap 5, Font Awesome, vanilla JS</td></tr><tr><td><strong>Database</strong></td><td>SQLite (dev), PostgreSQL 13 (prod)</td></tr><tr><td><strong>Blockchain</strong></td><td>0Chain/Zus (beta network)</td></tr><tr><td><strong>Crypto</strong></td><td>BN254 BLS via <code>bls-wasm</code> (Node.js)</td></tr><tr><td><strong>Erasure coding</strong></td><td><code>reedsolo</code> (Reed-Solomon)</td></tr><tr><td><strong>Container</strong></td><td>Docker Compose (dev + prod), Nginx, Supervisor</td></tr><tr><td><strong>Email</strong></td><td>Flask-Mail, SMTP_SSL</td></tr><tr><td><strong>License</strong></td><td>MIT</td></tr></tbody></table>]]></content:encoded>
            <category>flask</category>
            <category>python</category>
            <category>blockchain</category>
            <category>zerochain</category>
            <category>decentralized-storage</category>
            <category>docker</category>
            <category>nginx</category>
            <category>postgresql</category>
        </item>
    </channel>
</rss>