Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
569152a7be | ||
|
|
b2fc93c8e6 | ||
|
|
b239f61480 | ||
|
|
93fe924b74 |
@@ -425,8 +425,8 @@
|
|||||||
<strong>PRIMARY DESTINATIONS:</strong>
|
<strong>PRIMARY DESTINATIONS:</strong>
|
||||||
<span style="color: var(--accent-cyan); margin-left: 8px;">[1] Technical Blog (5 Articles)</span> •
|
<span style="color: var(--accent-cyan); margin-left: 8px;">[1] Technical Blog (5 Articles)</span> •
|
||||||
<span>[2] Forgejo Git Repos</span> •
|
<span>[2] Forgejo Git Repos</span> •
|
||||||
<span>[3] Public Cryptographic & PDF Tools</span> •
|
<!-- <span>[3] Public Cryptographic & PDF Tools</span> • -->
|
||||||
<span>[4] Cluster Telemetry</span>
|
<!-- <span>[4] Cluster Telemetry</span> -->
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
+28
-22
@@ -58,30 +58,30 @@ export default {
|
|||||||
url: `https://git.riverrooks.dev/venus`,
|
url: `https://git.riverrooks.dev/venus`,
|
||||||
external: true
|
external: true
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: "RSS Feed",
|
// label: "RSS Feed",
|
||||||
url: "/feed.xml"
|
// url: "/feed.xml"
|
||||||
}
|
// }
|
||||||
],
|
],
|
||||||
services: [
|
services: [
|
||||||
{
|
{
|
||||||
name: "Gitea",
|
name: "Gitea",
|
||||||
url: `https://git.riverrooks.dev/venus`,
|
url: `https://git.riverrooks.dev/venus`,
|
||||||
tag: `git.${siteDomain}:2222`,
|
tag: `git.${siteDomain}:443`,
|
||||||
desc: "Self-hosted Git code repositories."
|
desc: "Self-hosted Git code repositories."
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
name: "CyberChef",
|
// name: "CyberChef",
|
||||||
url: `https://chef.${siteDomain}`,
|
// url: `https://chef.${siteDomain}`,
|
||||||
tag: `chef.${siteDomain}:443`,
|
// tag: `chef.${siteDomain}:443`,
|
||||||
desc: "Industry tool for analyzing and decoding data."
|
// desc: "Industry tool for analyzing and decoding data."
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: "Aperisolve",
|
// name: "Aperisolve",
|
||||||
url: `https://aperisolve.${siteDomain}`,
|
// url: `https://aperisolve.${siteDomain}`,
|
||||||
tag: `aperisolve.${siteDomain}:443`,
|
// tag: `aperisolve.${siteDomain}:443`,
|
||||||
desc: "Basic image steganography suite."
|
// desc: "Basic image steganography suite."
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: "Streams",
|
name: "Streams",
|
||||||
url: `https://streams.${siteDomain}`,
|
url: `https://streams.${siteDomain}`,
|
||||||
@@ -90,16 +90,22 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Calibre",
|
name: "Calibre",
|
||||||
url: `https://library.${siteDomain}`,
|
url: `https://books.${siteDomain}`,
|
||||||
tag: `books.${siteDomain}:443`,
|
tag: `books.${siteDomain}:443`,
|
||||||
desc: `Calibre web frontend for managing and reading eBooks.`
|
desc: `Calibre web frontend for managing and reading eBooks.`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Whoami",
|
name: "Wireguard",
|
||||||
url: `https://whoami.${siteDomain}`,
|
url: `https://wg.${siteDomain}`,
|
||||||
tag: `whoami.${siteDomain}:443`,
|
tag: `wg.${siteDomain}:443`,
|
||||||
desc: `Simple service to display request headers and connection information.`
|
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: [
|
topics: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
|
|
||||||
<div class="footer-right">
|
<div class="footer-right">
|
||||||
<span>© {{ currentYear or "2026" }} {{ site.author }}</span>
|
<span>© {{ 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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
+3
-3
@@ -10,7 +10,7 @@ title: "River Rooks | Homelab Gateway"
|
|||||||
|
|
||||||
<div class="hero-landing-text">
|
<div class="hero-landing-text">
|
||||||
<p>
|
<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>
|
||||||
<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.
|
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>
|
<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> •
|
<a href="/dispatches/" style="color: var(--accent-cyan); text-decoration: none; margin-left: 8px;">[1] Technical Blog ({{ collections.posts.length }} Articles)</a> •
|
||||||
<a href="https://git.{{ site.domain }}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[2] Gitea Repos</a> •
|
<a href="https://git.{{ site.domain }}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[2] Gitea Repos</a> •
|
||||||
<span>[3] Public Tools</span> •
|
<a href="https://github.com/skoorrevir" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[3] GitHub Repos</a> •
|
||||||
<span>[4] Cluster Telemetry</span>
|
<a href="https://wg.{{ site.domain }}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none;">[4] Wireguard VPN</a> •
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user