Compare commits

...
4 Commits
Author SHA1 Message Date
venus 569152a7be fix: fixed broken nav links 2026-10-06 16:16:13 -05:00
venus b2fc93c8e6 fix: fixed some copy and broken links 2026-10-06 16:11:35 -05:00
venus b239f61480 fix: removed a typo and some old code 2026-10-06 16:06:22 -05:00
venus 93fe924b74 test: added obsidian test 2026-10-06 16:00:36 -05:00
5 changed files with 196 additions and 28 deletions
+2 -2
View File
@@ -425,8 +425,8 @@
<strong>PRIMARY DESTINATIONS:</strong>
<span style="color: var(--accent-cyan); margin-left: 8px;">[1] Technical Blog (5 Articles)</span> &bull;
<span>[2] Forgejo Git Repos</span> &bull;
<span>[3] Public Cryptographic &amp; PDF Tools</span> &bull;
<span>[4] Cluster Telemetry</span>
<!-- <span>[3] Public Cryptographic &amp; PDF Tools</span> &bull; -->
<!-- <span>[4] Cluster Telemetry</span> -->
</div>
</section>
+28 -22
View File
@@ -58,30 +58,30 @@ export default {
url: `https://git.riverrooks.dev/venus`,
external: true
},
{
label: "RSS Feed",
url: "/feed.xml"
}
// {
// label: "RSS Feed",
// url: "/feed.xml"
// }
],
services: [
{
name: "Gitea",
url: `https://git.riverrooks.dev/venus`,
tag: `git.${siteDomain}:2222`,
tag: `git.${siteDomain}:443`,
desc: "Self-hosted Git code repositories."
},
{
name: "CyberChef",
url: `https://chef.${siteDomain}`,
tag: `chef.${siteDomain}:443`,
desc: "Industry tool for analyzing and decoding data."
},
{
name: "Aperisolve",
url: `https://aperisolve.${siteDomain}`,
tag: `aperisolve.${siteDomain}:443`,
desc: "Basic image steganography suite."
},
// {
// name: "CyberChef",
// url: `https://chef.${siteDomain}`,
// tag: `chef.${siteDomain}:443`,
// desc: "Industry tool for analyzing and decoding data."
// },
// {
// name: "Aperisolve",
// url: `https://aperisolve.${siteDomain}`,
// tag: `aperisolve.${siteDomain}:443`,
// desc: "Basic image steganography suite."
// },
{
name: "Streams",
url: `https://streams.${siteDomain}`,
@@ -90,16 +90,22 @@ export default {
},
{
name: "Calibre",
url: `https://library.${siteDomain}`,
url: `https://books.${siteDomain}`,
tag: `books.${siteDomain}:443`,
desc: `Calibre web frontend for managing and reading eBooks.`
},
{
name: "Whoami",
url: `https://whoami.${siteDomain}`,
tag: `whoami.${siteDomain}:443`,
desc: `Simple service to display request headers and connection information.`
name: "Wireguard",
url: `https://wg.${siteDomain}`,
tag: `wg.${siteDomain}:443`,
desc: `Self-hosted Wireguard VPN server.(Requires login)`
}
// {
// name: "Whoami",
// url: `https://whoami.${siteDomain}`,
// tag: `whoami.${siteDomain}:443`,
// desc: `Simple service to display request headers and connection information.`
// }
],
topics: [
{
+1 -1
View File
@@ -8,6 +8,6 @@
<div class="footer-right">
<span>&copy; {{ currentYear or "2026" }} {{ site.author }}</span>
<a href="https://git.{{ site.domain }}" target="_blank" rel="noopener noreferrer" class="footer-link">Source Forge ↗</a>
<a href="https://git.{{ site.domain }}" target="_blank" rel="noopener noreferrer" class="footer-link">Source Code ↗</a>
</div>
</footer>
+3 -3
View File
@@ -10,7 +10,7 @@ title: "River Rooks | Homelab Gateway"
<div class="hero-landing-text">
<p>
I am River Rooks, a cybersecurity student and the University of Alabama. This website is the public-facing gateway to my homelab, which hosts a variety of services and tools for personal and professional use. Here, you can find my technical blog and a collection of public tools that host.
I am River Rooks, a cyber security student at the University of Alabama. This website is the public-facing gateway to my homelab, which hosts a variety of services and tools for personal and professional use. Here, you can find my technical blog and a collection of public tools that host.
</p>
<p>
I deploy and maintain a variety of services in my homelab for research, development, and learning purposes. This includes web applications, databases, and other tools that I use to enhance my skills in cybersecurity and DevOps.
@@ -23,8 +23,8 @@ title: "River Rooks | Homelab Gateway"
<strong>PRIMARY DESTINATIONS:</strong>
<a href="/dispatches/" style="color: var(--accent-cyan); text-decoration: none; margin-left: 8px;">[1] Technical Blog ({{ collections.posts.length }} Articles)</a> &bull;
<a href="https://git.{{ site.domain }}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[2] Gitea Repos</a> &bull;
<span>[3] Public Tools</span> &bull;
<span>[4] Cluster Telemetry</span>
<a href="https://github.com/skoorrevir" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[3] GitHub Repos</a> &bull;
<a href="https://wg.{{ site.domain }}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[4] Wireguard VPN</a> &bull;
</div>
</section>
+162
View File
@@ -0,0 +1,162 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
const pluginPath = path.resolve("/home/venus/Documents/Notes/.obsidian/plugins/blog-publisher/main.js");
// Pure helper function to be tested and exported/used by plugin
export function insertOrUpdateFrontmatterProperty(content, key, value) {
if (typeof content !== "string") return "";
const fmRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
const match = content.match(fmRegex);
if (match) {
let fmBlock = match[1];
const keyRegex = new RegExp(`^${key}:.*$`, "m");
if (value === null || value === undefined) {
// Remove property if value is null
fmBlock = fmBlock.replace(new RegExp(`^${key}:.*\\r?\\n?`, "m"), "");
} else if (keyRegex.test(fmBlock)) {
// Replace existing property
fmBlock = fmBlock.replace(keyRegex, `${key}: "${value}"`);
} else {
// Append property to frontmatter
fmBlock = `${fmBlock}\n${key}: "${value}"`;
}
return content.replace(fmRegex, `---\n${fmBlock.trim()}\n---`);
} else if (value) {
return `---\n${key}: "${value}"\n---\n\n${content}`;
}
return content;
}
export function computePublicUrl(serverUrl, postPath) {
const base = (serverUrl || "").replace(/\/+$/, "");
const p = (postPath || "").startsWith("/") ? postPath : `/${postPath || ""}`;
return `${base}${p}`;
}
test("Obsidian Plugin: computePublicUrl normalizes slashes correctly", () => {
assert.equal(
computePublicUrl("https://riverrooks.dev", "/posts/test-note/"),
"https://riverrooks.dev/posts/test-note/"
);
assert.equal(
computePublicUrl("https://riverrooks.dev/", "/posts/test-note/"),
"https://riverrooks.dev/posts/test-note/"
);
assert.equal(
computePublicUrl("https://riverrooks.dev///", "posts/test-note/"),
"https://riverrooks.dev/posts/test-note/"
);
assert.equal(
computePublicUrl("http://localhost:8080", "/posts/test-note/"),
"http://localhost:8080/posts/test-note/"
);
});
test("Obsidian Plugin: insertOrUpdateFrontmatterProperty adds public-url when missing", () => {
const original = `---
title: "Sample Note"
date: 2026-09-23
publish: true
---
# Content here`;
const updated = insertOrUpdateFrontmatterProperty(
original,
"public-url",
"https://riverrooks.dev/posts/sample-note/"
);
assert.match(
updated,
/public-url:\s*"https:\/\/riverrooks\.dev\/posts\/sample-note\/"/,
"Must insert public-url into frontmatter"
);
assert.match(updated, /title:\s*"Sample Note"/, "Preserves title");
assert.match(updated, /publish:\s*true/, "Preserves publish flag");
assert.match(updated, /# Content here/, "Preserves body content");
});
test("Obsidian Plugin: insertOrUpdateFrontmatterProperty updates existing public-url", () => {
const original = `---
title: "Updated Note"
date: 2026-09-23
public-url: "https://old.domain/posts/sample-note/"
---
Body`;
const updated = insertOrUpdateFrontmatterProperty(
original,
"public-url",
"https://riverrooks.dev/posts/updated-note/"
);
assert.match(
updated,
/public-url:\s*"https:\/\/riverrooks\.dev\/posts\/updated-note\/"/,
"Must update public-url to new URL"
);
assert.doesNotMatch(
updated,
/old\.domain/,
"Old URL must be replaced"
);
});
test("Obsidian Plugin: insertOrUpdateFrontmatterProperty removes public-url on unpublish", () => {
const original = `---
title: "Unpublished Note"
date: 2026-09-23
public-url: "https://riverrooks.dev/posts/sample-note/"
---
Body`;
const updated = insertOrUpdateFrontmatterProperty(original, "public-url", null);
assert.doesNotMatch(updated, /public-url:/, "Must remove public-url when value is null");
assert.match(updated, /title:\s*"Unpublished Note"/, "Preserves other frontmatter");
});
test("Obsidian Plugin: Adversarial edge cases for frontmatter property insertion", () => {
// 1. Empty content
assert.equal(insertOrUpdateFrontmatterProperty("", "public-url", "https://url"), "---\npublic-url: \"https://url\"\n---\n\n");
// 2. Non-string inputs
assert.equal(insertOrUpdateFrontmatterProperty(null, "public-url", "https://url"), "");
assert.equal(insertOrUpdateFrontmatterProperty(undefined, "public-url", "https://url"), "");
// 3. Note without frontmatter
const plain = "# Just a header\nSome text.";
const result = insertOrUpdateFrontmatterProperty(plain, "public-url", "https://url");
assert.match(result, /^---\npublic-url:\s*"https:\/\/url"\n---/);
});
test("Obsidian Plugin: main.js in vault includes public-url insertion and recursion guard", () => {
assert.ok(fs.existsSync(pluginPath), `Plugin main.js must exist at ${pluginPath}`);
const pluginContent = fs.readFileSync(pluginPath, "utf-8");
// 1. Must handle public-url
assert.match(pluginContent, /public-url/i, "main.js must reference 'public-url'");
// 2. Must guard against infinite loops during frontmatter updates
assert.match(
pluginContent,
/isUpdatingFrontmatter/i,
"main.js must maintain a guard to prevent metadataCache recursion loops"
);
// 3. Must use processFrontMatter or frontmatter updater
assert.match(
pluginContent,
/processFrontMatter|updateFrontmatter/i,
"main.js must implement frontmatter updating logic"
);
});