history+outline: production deployment, nginx+systemd, build fixes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rokojori 2026-07-12 16:14:58 +02:00
parent 932e570ade
commit debaeb2b6f
6 changed files with 365 additions and 22 deletions

View File

@ -13,7 +13,7 @@
.wsnav-inner
{
width: 57em;
width: 65em;
margin: 0 auto;
padding: 0.45rem 1.5rem;
display: flex;

View File

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="3840"
height="2160"
viewBox="0 0 3840 2160"
version="1.1"
id="svg5"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
sodipodi:docname="roject.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#333333"
bordercolor="#404040"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#333333"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="0.20890503"
inkscape:cx="1969.7946"
inkscape:cy="1179.962"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g17" /><defs
id="defs2"><linearGradient
id="linearGradient12"
inkscape:collect="never"><stop
style="stop-color:#00bcf5;stop-opacity:1;"
offset="0"
id="stop12" /><stop
style="stop-color:#296af5;stop-opacity:0.65098041;"
offset="0.3475728"
id="stop14" /><stop
style="stop-color:#7800f5;stop-opacity:0;"
offset="1"
id="stop13" /></linearGradient><linearGradient
id="linearGradient9"
inkscape:collect="never"><stop
style="stop-color:#ff26a7;stop-opacity:1;"
offset="0"
id="stop9" /><stop
style="stop-color:#ff3426;stop-opacity:1;"
offset="1"
id="stop10" /></linearGradient><linearGradient
id="linearGradient7"
inkscape:collect="never"><stop
style="stop-color:#2636ff;stop-opacity:1;"
offset="0"
id="stop7" /><stop
style="stop-color:#29006f;stop-opacity:1;"
offset="1"
id="stop8" /></linearGradient><linearGradient
id="linearGradient5"
inkscape:collect="never"><stop
style="stop-color:#beffc5;stop-opacity:1;"
offset="0"
id="stop5" /><stop
style="stop-color:#26c5ff;stop-opacity:1;"
offset="0.53096259"
id="stop11" /><stop
style="stop-color:#0051eb;stop-opacity:1;"
offset="1"
id="stop6" /></linearGradient><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7940"><rect
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7942"
width="1440"
height="810"
x="0"
y="0" /></clipPath><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient5"
id="linearGradient6"
x1="2094.8511"
y1="824.97723"
x2="2175.8127"
y2="1376.4214"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.69112949,0,0,0.69112949,529.92586,259.08767)" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient7"
id="linearGradient8"
x1="2107.4609"
y1="790.479"
x2="2563.2524"
y2="1203.4646"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.69112949,0,0,0.69112949,529.92586,259.08767)" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient9"
id="linearGradient10"
x1="1966.5072"
y1="1228.4695"
x2="1874.9818"
y2="528.3299"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.69112949,0,0,0.69112949,529.92586,259.08767)" /><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter10"
x="-0.32921062"
y="-0.82302656"
width="1.6584212"
height="2.6460531"><feGaussianBlur
inkscape:collect="always"
stdDeviation="329.39922"
id="feGaussianBlur10" /></filter><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter11"
x="-0.017136762"
y="-0.0835418"
width="1.0342735"
height="1.1670836"><feGaussianBlur
inkscape:collect="always"
stdDeviation="11.456677"
id="feGaussianBlur11" /></filter><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter12"
x="-0.1102434"
y="-0.51284629"
width="1.2203709"
height="2.0256926"><feGaussianBlur
inkscape:collect="always"
stdDeviation="61.763442"
id="feGaussianBlur12" /></filter><radialGradient
inkscape:collect="never"
xlink:href="#linearGradient12"
id="radialGradient13"
cx="1008.5775"
cy="617.15338"
fx="1008.5775"
fy="617.15338"
r="749.229"
gradientTransform="matrix(3.747293,0,0,0.21523126,-1392.2413,742.81336)"
gradientUnits="userSpaceOnUse" /><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter16"
x="-0.32921062"
y="-0.82302656"
width="1.6584212"
height="2.6460531"><feGaussianBlur
inkscape:collect="always"
stdDeviation="329.39922"
id="feGaussianBlur16" /></filter></defs><g
inkscape:label="Content"
inkscape:groupmode="layer"
id="layer1"
style="fill:#ffffff;fill-opacity:1"><rect
style="fill:#000000;stroke-linecap:round;stroke-linejoin:round;fill-opacity:1"
id="rect1"
width="3840"
height="2160"
x="0"
y="0"
ry="7.9545546" /><g
id="g17"
transform="translate(38.057367,61.315595)"><ellipse
style="opacity:1;mix-blend-mode:normal;fill:#0a0066;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter10)"
id="path10"
cx="1834.5552"
cy="1143.0068"
rx="1200.6875"
ry="480.27499"
transform="matrix(0.69112949,0,0,0.69112949,566.63996,353.0411)" /><ellipse
style="opacity:1;mix-blend-mode:screen;fill:#005d78;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter16)"
id="ellipse16"
cx="1834.5552"
cy="1143.0068"
rx="1200.6875"
ry="480.27499"
transform="matrix(0.59256734,0,0,0.55929452,795.32629,304.17302)" /><g
id="g12"
style="opacity:1;filter:url(#filter12)"
transform="matrix(0.69112949,0,0,0.69112949,491.63996,291.80532)"><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:457.124px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke-width:11.4281;stroke-linecap:round;stroke-linejoin:round;filter:url(#filter11)"
x="1423.6522"
y="1196.2206"
id="text11"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,-55,0)"><tspan
sodipodi:role="line"
id="tspan11"
x="1423.6522"
y="1196.2206"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#000000;fill-opacity:1;stroke-width:11.4281">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:457.124px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke-width:11.4281;stroke-linecap:round;stroke-linejoin:round;filter:url(#filter11)"
x="1423.6522"
y="1196.2206"
id="text12"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,-4.2276373,0)"><tspan
sodipodi:role="line"
id="tspan12"
x="1423.6522"
y="1196.2206"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#000000;fill-opacity:1;stroke-width:11.4281">ROJECT</tspan></text></g><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:315.932px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:url(#linearGradient10);stroke-width:7.8983;stroke-linecap:round;stroke-linejoin:round"
x="1484.9595"
y="1087.6149"
id="text2"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan2"
x="1484.9595"
y="1087.6149"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:url(#linearGradient10);fill-opacity:1;stroke-width:7.8983">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:315.932px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:url(#linearGradient8);stroke-width:7.8983;stroke-linecap:round;stroke-linejoin:round"
x="1498.2201"
y="1087.0574"
id="text1"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan1"
x="1498.2201"
y="1087.0574"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:url(#linearGradient8);fill-opacity:1;stroke-width:7.8983">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:315.932px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:url(#linearGradient6);stroke-width:7.8983;stroke-linecap:round;stroke-linejoin:round"
x="1513.8538"
y="1085.8309"
id="text3"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan3"
x="1513.8538"
y="1085.8309"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:url(#linearGradient6);fill-opacity:1;stroke-width:7.8983">ROJECT</tspan></text><g
id="g16"
transform="translate(9.5737285,-66.875187)"><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:67.5143px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;opacity:1;fill:#0056f5;fill-opacity:1;stroke:none;stroke-width:16.8787;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="1480.9904"
y="1349.8354"
id="text15"><tspan
sodipodi:role="line"
id="tspan15"
x="1480.9904"
y="1349.8354"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#0056f5;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:67.5143px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;opacity:1;fill:#00f526;fill-opacity:1;stroke:none;stroke-width:16.8787;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="1480.9904"
y="1342.0404"
id="text16"><tspan
sodipodi:role="line"
id="tspan16"
x="1480.9904"
y="1342.0404"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#00f526;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:67.5143px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;opacity:1;fill:#00cbf5;fill-opacity:1;stroke:none;stroke-width:16.8787;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="1480.9904"
y="1344.5404"
id="text14"><tspan
sodipodi:role="line"
id="tspan14"
x="1480.9904"
y="1344.5404"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#00cbf5;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text></g></g></g></svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -35,7 +35,7 @@ body
.page
{
max-width: 40em;
max-width: 50em;
margin: 0 auto;
}

View File

@ -14,8 +14,9 @@
<p class="date">Sunday, 12 July 2026</p>
<h1>Roject — Session Summary</h1>
<p class="subtitle">
Full project directory restructure — source/, build/app/, build/data/,
source/pages/ with a copy-pages build step.
Full project directory restructure and first production deployment —
source/, build/app/, build/data/, source/pages/; Roject live at
roject.rokojori.com via nginx + systemd on Server A.
</p>
</header>
@ -64,6 +65,36 @@
</div>
</div>
<div class="card">
<h3>First production deployment — roject.rokojori.com</h3>
<p>
Roject deployed to Server A and served publicly at
<code>https://roject.rokojori.com</code>. Stack: Node.js 20,
<code>ts-node</code> running <code>source/server/index.ts</code> managed
by a systemd service, nginx as TLS-terminating reverse proxy with a
Let's Encrypt certificate, port 3000 kept closed at the IONOS firewall.
</p>
<p style="margin-top:0.75rem">
Several build gaps were discovered and fixed during the deploy:
component CSS files were never in source control (moved to
<code>source/components/</code>); vendor libraries were in the gitignored
<code>build/app/vendor/</code> (moved to <code>source/vendor/</code>);
<code>mkdirSync</code> calls in the server lacked <code>{ recursive: true }</code>
causing a crash on a fresh clone; and the library-ts browser
<code>tsconfig.roject.json</code> still referenced the old
<code>public/</code> outDir instead of <code>build/app/</code>.
The copy script was also expanded to copy all non-TS static assets
(CSS, SVG, etc.) from all frontend source directories.
</p>
<div class="tags">
<span class="tag">roject.rokojori.com</span>
<span class="tag">systemd</span>
<span class="tag">nginx</span>
<span class="tag">Let's Encrypt</span>
<span class="tag">Node.js 20</span>
</div>
</div>
</section>
<section>
@ -91,6 +122,30 @@
</p>
</div>
<div class="decision">
<strong>systemd over PM2</strong>
<p>
systemd is already present on every Linux server and integrates with
<code>journalctl</code> for log management. PM2 is easier for day-to-day
Node.js process management but adds an extra global dependency. For a
single-service deployment systemd is sufficient and keeps the server
dependency surface minimal.
</p>
</div>
<div class="decision">
<strong>Static assets belong in source/, not build/</strong>
<p>
CSS files, vendor libraries, and SVGs were historically placed directly
in <code>public/</code> (gitignored) and never tracked. A fresh server
clone exposes this immediately — nothing in <code>build/</code> exists
until the build runs, and the build can only copy what it finds in
<code>source/</code>. Moving all static assets to <code>source/</code>
makes them first-class source files and ensures a clean deploy from any
fresh clone.
</p>
</div>
<div class="decision">
<strong>Plain Node.js copy script, no npm dependency</strong>
<p>
@ -121,7 +176,14 @@
<code>source/library-ts/browser/tsconfig.roject.json</code> — outDir updated<br>
<code>package.json</code> — main and build script updated<br>
<code>.gitignore</code> — replaced data/, storage/, public/ with build/<br>
<code>source/server/</code> — all __dirname paths and library-ts imports updated
<code>source/server/</code> — all __dirname paths and library-ts imports updated<br>
<code>source/components/*.css</code> — moved from build/app/ into source control<br>
<code>source/vendor/</code> — vendor libs moved from build/app/ into source control<br>
<code>source/library-ts/browser/tsconfig.roject.json</code> — outDir fixed in submodule, committed and pushed<br>
<code>scripts/copy-pages.js</code> — expanded to copy all non-TS static assets from all frontend source dirs<br>
<code>source/server/db.ts</code>, <code>storage.ts</code> — mkdirSync updated to use <code>{ recursive: true }</code><br>
<code>/etc/systemd/system/roject.service</code> — new on Server A<br>
<code>/etc/nginx/sites-available/roject</code> — new on Server A
</p>
</div>
</section>

View File

@ -21,7 +21,7 @@
<div class="card">
<h3><a href="2026/07-July/12-Sunday/index.html">Sunday, 12 July 2026</a></h3>
<p>Full directory restructure — source/, build/app/, build/data/db/, build/data/storage/, source/pages/ with copy-pages build step.</p>
<p>Full directory restructure and first production deployment — source/, build/, source/pages/; Roject live at roject.rokojori.com via nginx + systemd.</p>
</div>
<div class="card">

View File

@ -10,8 +10,8 @@
<body>
<div class="page">
<div style="width:100%;height:260px;overflow:hidden;background:#000;margin-bottom:2rem;border-radius:8px;">
<img src="../../src/rojos/roject.svg" alt="Rojects"
<div style="width:100%;height:400px;overflow:hidden;background:#000;margin-bottom:2rem;border-radius:8px;">
<img src="../_assets_/roject.svg" alt="Rojects"
style="width:100%;height:100%;object-fit:cover;object-position:center;">
</div>
@ -71,6 +71,14 @@
A reusable <code>&lt;confirm-dialog&gt;</code> component replaces browser
<code>confirm()</code> for destructive actions (currently project deletion).
</p>
<p style="margin-top:0.75rem">
The app is deployed and publicly accessible at
<code>https://roject.rokojori.com</code> on Server A. nginx handles TLS
termination and reverse-proxies to the Node.js process managed by a
systemd service. Deployment is currently manual
(<code>git pull &amp;&amp; npm run build &amp;&amp; systemctl restart roject</code>);
CI automation is the remaining open item.
</p>
</div>
<div class="card">
@ -79,23 +87,19 @@
</div>
<div class="card">
<h3>1 — Production server + CI</h3>
<h3>1 — CI pipeline (server is live)</h3>
<p>
Deploy the Node.js/Express app to a publicly reachable server and wire up a
CI pipeline that builds, tests, and deploys automatically on push. The app's
file-based storage and no-database design make it straightforward to host on
any VPS: nginx as reverse proxy, PM2 or a systemd service for the Node.js
process. CI can run as a self-hosted Gitea/Forgejo Actions runner (matching
the existing self-hosted git remote) or mirror to GitHub Actions. This is
infrastructure work rather than feature work — it should be done once and early
because everything else ships faster and more safely once a real deployment
target and regression-catching pipeline exist.
The server is deployed and running at <code>https://roject.rokojori.com</code>
(nginx + systemd + Let's Encrypt on Server A). What remains is automating the
deploy on push: a Gitea Actions runner installed on Server A registers with the
repo on <code>community.rokojori.com</code> and runs a workflow that pulls,
builds, and restarts the service on every push to main. This is the last piece
that turns the manual deploy into a proper CI/CD pipeline.
</p>
<div class="tags">
<span class="tag">nginx</span>
<span class="tag">PM2 / systemd</span>
<span class="tag">Gitea Actions</span>
<span class="tag">VPS</span>
<span class="tag">Gitea Actions runner</span>
<span class="tag">act_runner</span>
<span class="tag">auto-deploy on push</span>
</div>
</div>