mirror of
https://github.com/anotherhadi/blog.git
synced 2026-07-07 01:02:34 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa86366b7f | |||
| 198112501a | |||
| 15f2b6c184 | |||
| 25fb5a4bf0 | |||
| d257a0f26e | |||
| 3dfbdcf970 | |||
| 930c3bf3bb | |||
| a055640fa8 | |||
| 9c0bbc4b77 |
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
"@lucide/astro": "^0.552.0",
|
"@lucide/astro": "^0.552.0",
|
||||||
"@tailwindcss/vite": "^4.2.4",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@types/bun": "^1.3.13",
|
"@types/bun": "^1.3.13",
|
||||||
"astro": "6.1.9",
|
"astro": "6.4.6",
|
||||||
"daisyui": "^5.5.19",
|
"daisyui": "^5.5.19",
|
||||||
"node-html-parser": "^7.1.0",
|
"node-html-parser": "^7.1.0",
|
||||||
"svelte": "^5.55.5",
|
"svelte": "^5.55.5",
|
||||||
|
|||||||
@@ -18,14 +18,19 @@ const latestPosts = sortedPosts.slice(0, 3);
|
|||||||
<div class="max-w-6xl mx-auto">
|
<div class="max-w-6xl mx-auto">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2>
|
<h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2>
|
||||||
<p class="text-lg text-base-content/70">
|
|
||||||
Thoughts, insights, and tutorials on cybersecurity, OSINT, and
|
|
||||||
technology.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{latestPosts.map((post) => <BlogCard post={post} />)}
|
{
|
||||||
|
latestPosts.map((post) => (
|
||||||
|
<BlogCard
|
||||||
|
displayBanner={false}
|
||||||
|
displayTags={false}
|
||||||
|
displayDate={false}
|
||||||
|
post={post}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-12">
|
<div class="text-center mt-12">
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { Image } from "astro:assets";
|
|||||||
import { formatDate } from "../utils/notes";
|
import { formatDate } from "../utils/notes";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
displayBanner: boolean;
|
||||||
|
displayDate: boolean;
|
||||||
|
displayTags: boolean;
|
||||||
post: CollectionEntry<"blog">;
|
post: CollectionEntry<"blog">;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,6 +17,8 @@ const { post } = Astro.props;
|
|||||||
<article
|
<article
|
||||||
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
|
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{ Astro.props.displayBanner && (
|
||||||
<figure class="aspect-video">
|
<figure class="aspect-video">
|
||||||
<Image
|
<Image
|
||||||
src={post.data.image}
|
src={post.data.image}
|
||||||
@@ -23,25 +28,27 @@ const { post } = Astro.props;
|
|||||||
height={400}
|
height={400}
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
|
)}
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
{ Astro.props.displayDate && (
|
||||||
<time class="text-sm text-base-content/60">
|
<time class="text-sm text-base-content/60">
|
||||||
{formatDate(post.data.publishDate)}
|
{formatDate(post.data.publishDate)}
|
||||||
</time>
|
</time>
|
||||||
|
)}
|
||||||
<h2 class="card-title hover:text-primary transition-colors">
|
<h2 class="card-title hover:text-primary transition-colors">
|
||||||
<a href={`/blog/${post.id}`}>{post.data.title}</a>
|
<a href={`/blog/${post.id}`}>{post.data.title}</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-base-content/80">{post.data.description}</p>
|
<p class="text-base-content/80">{post.data.description}</p>
|
||||||
{
|
|
||||||
post.data.tags && post.data.tags.length > 0 && (
|
{ Astro.props.displayTags && post.data.tags && post.data.tags.length > 0 && (
|
||||||
<div class="flex flex-wrap gap-2 mt-2">
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
{post.data.tags.map((tag) => (
|
{post.data.tags.map((tag) => (
|
||||||
<TagBadge tag={tag} />
|
<TagBadge tag={tag} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
<div class="card-actions justify-end mt-4">
|
<div class="card-actions justify-end mt-4">
|
||||||
<a href={`/blog/${post.id}`} class="btn btn-primary btn-sm">
|
<a href={`/blog/${post.id}`} class="btn btn-soft btn-primary btn-sm">
|
||||||
Read More
|
Read More
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ExternalLink, ChevronDown } from "@lucide/astro";
|
import { ExternalLink, ChevronDown } from "@lucide/astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
displayBanner: boolean;
|
||||||
repo: {
|
repo: {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -34,6 +35,7 @@ const hasMultiplePlatforms = platforms.length > 1;
|
|||||||
<article
|
<article
|
||||||
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
|
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
|
||||||
>
|
>
|
||||||
|
{ Astro.props.displayBanner && repo.banner_url && (
|
||||||
<figure class="aspect-video bg-base-200 overflow-hidden">
|
<figure class="aspect-video bg-base-200 overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={repo.banner_url}
|
src={repo.banner_url}
|
||||||
@@ -42,6 +44,7 @@ const hasMultiplePlatforms = platforms.length > 1;
|
|||||||
onerror="this.parentElement.style.display='none'"
|
onerror="this.parentElement.style.display='none'"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
|
)}
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title hover:text-primary transition-colors">
|
<h2 class="card-title hover:text-primary transition-colors">
|
||||||
@@ -83,7 +86,7 @@ const hasMultiplePlatforms = platforms.length > 1;
|
|||||||
<div
|
<div
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
class="btn btn-primary btn-sm gap-1"
|
class="btn btn-soft btn-primary btn-sm gap-1"
|
||||||
>
|
>
|
||||||
<ExternalLink class="size-4" />
|
<ExternalLink class="size-4" />
|
||||||
View Source
|
View Source
|
||||||
@@ -107,7 +110,7 @@ const hasMultiplePlatforms = platforms.length > 1;
|
|||||||
href={repo.html_url}
|
href={repo.html_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="btn btn-primary btn-sm gap-1"
|
class="btn btn-soft btn-primary btn-sm gap-1"
|
||||||
>
|
>
|
||||||
<ExternalLink class="size-4" />
|
<ExternalLink class="size-4" />
|
||||||
View on Gitea
|
View on Gitea
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { ArrowRight, FolderCode, Key, Rss } from "@lucide/astro";
|
import { FolderCode, Key, Rss } from "@lucide/astro";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import type { SocialLinks } from "../config";
|
import type { SocialLinks } from "../config";
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ interface Props {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
avatar: any;
|
avatar: any;
|
||||||
location?: string;
|
|
||||||
socialLinks?: SocialLinks;
|
socialLinks?: SocialLinks;
|
||||||
gpgKey?: string;
|
gpgKey?: string;
|
||||||
rssFeed?: string;
|
rssFeed?: string;
|
||||||
@@ -19,18 +18,17 @@ const {
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
avatar,
|
avatar,
|
||||||
location,
|
|
||||||
socialLinks,
|
socialLinks,
|
||||||
gpgKey,
|
gpgKey,
|
||||||
rssFeed,
|
rssFeed,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="hero min-h-[65vh]">
|
<section class="hero py-20">
|
||||||
<div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-8">
|
<div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-10">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<div
|
<div
|
||||||
class="w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2"
|
class="w-32 md:w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2"
|
||||||
>
|
>
|
||||||
<Image src={avatar} alt={name} />
|
<Image src={avatar} alt={name} />
|
||||||
</div>
|
</div>
|
||||||
@@ -40,8 +38,7 @@ const {
|
|||||||
Hi, I'm {name}
|
Hi, I'm {name}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl text-base-content/80 mb-2">{title}</p>
|
<p class="text-xl text-base-content/80 mb-2">{title}</p>
|
||||||
{location && <p class="text-base-content/60 mb-4">{location}</p>}
|
<p class="text-lg max-w-lg leading-relaxed mb-6">
|
||||||
<p class="text-lg leading-relaxed mb-6">
|
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
@@ -335,25 +332,6 @@ const {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="mt-12 flex flex-wrap gap-5">
|
|
||||||
<a href="/blog" class="btn btn-ghost gap-2">
|
|
||||||
Blog Posts
|
|
||||||
<ArrowRight class="size-4" />
|
|
||||||
</a>
|
|
||||||
<a href="/projects" class="btn btn-ghost gap-2">
|
|
||||||
Projects
|
|
||||||
<ArrowRight class="size-4" />
|
|
||||||
</a>
|
|
||||||
<a href="/notes" class="btn btn-ghost gap-2">
|
|
||||||
Infosec Notes
|
|
||||||
<ArrowRight class="size-4" />
|
|
||||||
</a>
|
|
||||||
<a href="/#contact" class="btn btn-ghost gap-2">
|
|
||||||
Contact Me
|
|
||||||
<ArrowRight class="size-4" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ interface Props {
|
|||||||
graphEdges: { from: string; to: string }[];
|
graphEdges: { from: string; to: string }[];
|
||||||
forwardLinks: CollectionEntry<"notes">[];
|
forwardLinks: CollectionEntry<"notes">[];
|
||||||
backlinks: CollectionEntry<"notes">[];
|
backlinks: CollectionEntry<"notes">[];
|
||||||
|
externalLinks: { url: string; label: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
|
const { entry, graphNodes, graphEdges, forwardLinks, backlinks, externalLinks } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
@@ -26,7 +27,7 @@ const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
|
|||||||
>
|
>
|
||||||
graph
|
graph
|
||||||
</p>
|
</p>
|
||||||
<NoteGraph client:visible nodes={graphNodes} edges={graphEdges} />
|
<NoteGraph client:load nodes={graphNodes} edges={graphEdges} />
|
||||||
{
|
{
|
||||||
graphNodes.length < 2 && (
|
graphNodes.length < 2 && (
|
||||||
<p class="font-mono text-[9px] text-base-content/20 text-center py-2">
|
<p class="font-mono text-[9px] text-base-content/20 text-center py-2">
|
||||||
@@ -90,6 +91,31 @@ const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
externalLinks.length > 0 && (
|
||||||
|
<div class="p-3 border-t border-base-300/40">
|
||||||
|
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">
|
||||||
|
external
|
||||||
|
</p>
|
||||||
|
<ul class="space-y-1">
|
||||||
|
{externalLinks.map(({ url, label }) => (
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate"
|
||||||
|
title={url}
|
||||||
|
>
|
||||||
|
↗ {label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<div class="px-4 pt-4 pb-1 border-t border-base-300/40 mt-auto">
|
<div class="px-4 pt-4 pb-1 border-t border-base-300/40 mt-auto">
|
||||||
<time
|
<time
|
||||||
datetime={entry.data.publishDate.toISOString()}
|
datetime={entry.data.publishDate.toISOString()}
|
||||||
|
|||||||
@@ -19,36 +19,6 @@
|
|||||||
|
|
||||||
let search = $state("");
|
let search = $state("");
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const navDrawer = document.getElementById("nav-drawer") as HTMLInputElement | null;
|
|
||||||
if (!navDrawer) return;
|
|
||||||
|
|
||||||
const lgQuery = window.matchMedia("(min-width: 1024px)");
|
|
||||||
|
|
||||||
const overlay = document.createElement("div");
|
|
||||||
overlay.style.cssText =
|
|
||||||
"position:fixed;inset:0 0 0 14rem;background:oklch(0% 0 0/.4);z-index:51;display:none;cursor:pointer";
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
overlay.style.display =
|
|
||||||
navDrawer.checked && !lgQuery.matches ? "block" : "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
navDrawer.addEventListener("change", update);
|
|
||||||
lgQuery.addEventListener("change", update);
|
|
||||||
overlay.addEventListener("click", () => {
|
|
||||||
navDrawer.checked = false;
|
|
||||||
navDrawer.dispatchEvent(new Event("change"));
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
navDrawer.removeEventListener("change", update);
|
|
||||||
lgQuery.removeEventListener("change", update);
|
|
||||||
overlay.remove();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function getCategory(n: Note): string {
|
function getCategory(n: Note): string {
|
||||||
if (n.data.category) return n.data.category;
|
if (n.data.category) return n.data.category;
|
||||||
const parts = n.id.split("/");
|
const parts = n.id.split("/");
|
||||||
@@ -95,25 +65,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<aside
|
<div class="flex flex-col flex-1 min-h-0">
|
||||||
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-full pt-2 lg:pt-0"
|
|
||||||
style="background: oklch(4% 0 0);"
|
|
||||||
>
|
|
||||||
<!-- Mobile close bar -->
|
|
||||||
<div class="lg:hidden flex items-center justify-between px-3 py-2 border-b border-base-300/40 shrink-0">
|
|
||||||
<span class="font-mono text-[10px] text-base-content/30 uppercase tracking-widest">nav</span>
|
|
||||||
<label
|
|
||||||
for="nav-drawer"
|
|
||||||
class="cursor-pointer text-base-content/30 hover:text-base-content/70 transition-colors p-1"
|
|
||||||
aria-label="close sidebar"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M18 6 6 18M6 6l12 12"/>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search -->
|
|
||||||
<div class="px-3 py-3 border-b border-base-300/40 shrink-0">
|
<div class="px-3 py-3 border-b border-base-300/40 shrink-0">
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-base-200/50 border border-base-300/40 focus-within:border-base-300/70 transition-colors"
|
class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-base-200/50 border border-base-300/40 focus-within:border-base-300/70 transition-colors"
|
||||||
@@ -128,7 +80,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nav -->
|
|
||||||
<nav class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-px">
|
<nav class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-px">
|
||||||
{#each categories as cat}
|
{#each categories as cat}
|
||||||
{@const catNotes = notes.filter(
|
{@const catNotes = notes.filter(
|
||||||
@@ -137,7 +88,6 @@
|
|||||||
{#if catNotes.length > 0 || !search}
|
{#if catNotes.length > 0 || !search}
|
||||||
{@const isFolder = notes.some((n) => n.id.includes("/") && getCategory(n) === cat)}
|
{@const isFolder = notes.some((n) => n.id.includes("/") && getCategory(n) === cat)}
|
||||||
<div>
|
<div>
|
||||||
<!-- Category header -->
|
|
||||||
<div class="flex items-center w-full">
|
<div class="flex items-center w-full">
|
||||||
<button
|
<button
|
||||||
onclick={() => toggle(cat)}
|
onclick={() => toggle(cat)}
|
||||||
@@ -172,7 +122,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notes list -->
|
|
||||||
{#if openCategories.includes(cat)}
|
{#if openCategories.includes(cat)}
|
||||||
<ul
|
<ul
|
||||||
class="ml-4 mt-0.5 pb-1 space-y-px"
|
class="ml-4 mt-0.5 pb-1 space-y-px"
|
||||||
@@ -200,4 +149,4 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
import NoteNavContent from "./NoteNavContent.svelte";
|
||||||
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
notes: CollectionEntry<"notes">[];
|
||||||
|
currentEntry?: CollectionEntry<"notes">;
|
||||||
|
currentCategory?: string;
|
||||||
|
categories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { notes, currentEntry, currentCategory, categories } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<aside
|
||||||
|
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-full"
|
||||||
|
style="background: oklch(4% 0 0);"
|
||||||
|
>
|
||||||
|
<NoteNavContent
|
||||||
|
client:load
|
||||||
|
notes={notes}
|
||||||
|
currentEntry={currentEntry}
|
||||||
|
currentCategory={currentCategory}
|
||||||
|
categories={categories}
|
||||||
|
/>
|
||||||
|
</aside>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
import { ArrowRight } from "@lucide/astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<section id="notes" class="py-10 px-4">
|
||||||
|
<div class="max-w-6xl mx-auto text-center">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-4xl font-bold mb-4">Infosec Notes</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<p class="text-base text-base-content/70 mb-6">
|
||||||
|
Cheatsheets and references on tools and techniques I use for CTFs and
|
||||||
|
pentesting.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="text-center mt-12">
|
||||||
|
<a href="/notes" class="btn btn-ghost gap-2">
|
||||||
|
Browse Notes
|
||||||
|
<ArrowRight class="size-4" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -10,14 +10,14 @@ const latestRepos = repos.slice(0, 3);
|
|||||||
<div class="max-w-6xl mx-auto">
|
<div class="max-w-6xl mx-auto">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-4xl font-bold mb-4">Check out my latest work</h2>
|
<h2 class="text-4xl font-bold mb-4">Check out my latest work</h2>
|
||||||
<p class="text-lg text-base-content/70">
|
|
||||||
I enjoy the challenge of reimagining existing programs & scripts in my
|
|
||||||
own unique way.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{latestRepos.map((repo) => <GiteaProjectCard repo={repo} />)}
|
{
|
||||||
|
latestRepos.map((repo) => (
|
||||||
|
<GiteaProjectCard displayBanner={false} repo={repo} />
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-12">
|
<div class="text-center mt-12">
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export interface SiteConfig {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
location: string;
|
|
||||||
socialLinks: SocialLinks;
|
socialLinks: SocialLinks;
|
||||||
gpgKey?: string;
|
gpgKey?: string;
|
||||||
rssFeed?: string;
|
rssFeed?: string;
|
||||||
@@ -39,7 +38,6 @@ export const siteConfig: SiteConfig = {
|
|||||||
description:
|
description:
|
||||||
"Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
|
"Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
|
||||||
avatar: "/avatar.png",
|
avatar: "/avatar.png",
|
||||||
location: "🇫🇷 France",
|
|
||||||
socialLinks: {
|
socialLinks: {
|
||||||
github: "https://github.com/anotherhadi",
|
github: "https://github.com/anotherhadi",
|
||||||
gitlab: "https://gitlab.com/anotherhadi_mirror",
|
gitlab: "https://gitlab.com/anotherhadi_mirror",
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: "GRUB Boot Bypass"
|
||||||
|
description: "Physical access techniques to get a root shell by editing GRUB boot parameters."
|
||||||
|
tags: ["linux", "grub", "physical-access", "privesc"]
|
||||||
|
publishDate: 2026-05-18
|
||||||
|
---
|
||||||
|
|
||||||
|
When GRUB is not password-protected, anyone with physical access can edit boot parameters and bypass authentication entirely.
|
||||||
|
|
||||||
|
At the GRUB menu, press **`e`** to edit the selected entry. Modify the line starting with `linux`, then press **`F10`** to boot.
|
||||||
|
|
||||||
|
## Techniques
|
||||||
|
|
||||||
|
### init=/bin/sh
|
||||||
|
|
||||||
|
Replaces the init process with a shell; drops directly into a root shell before any login prompt.
|
||||||
|
|
||||||
|
```
|
||||||
|
linux ... init=/bin/sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Filesystem is mounted read-only by default. Remount to make changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mount -o remount,rw /
|
||||||
|
```
|
||||||
|
|
||||||
|
### init=/bin/bash
|
||||||
|
|
||||||
|
Same as above but uses bash. Add `rw` on the `linux` line to mount read-write from the start:
|
||||||
|
|
||||||
|
```
|
||||||
|
linux ... rw init=/bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### rd.break (systemd)
|
||||||
|
|
||||||
|
Interrupts the boot process in the initramfs, before the real root filesystem is mounted. Useful for resetting the root password.
|
||||||
|
|
||||||
|
```
|
||||||
|
linux ... rd.break
|
||||||
|
```
|
||||||
|
|
||||||
|
From the initramfs shell:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mount -o remount,rw /sysroot
|
||||||
|
chroot /sysroot
|
||||||
|
passwd root
|
||||||
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
|
### single (single-user mode)
|
||||||
|
|
||||||
|
Boots into maintenance mode. On some distros this drops to a root shell without a password prompt (not Debian/Ubuntu).
|
||||||
|
|
||||||
|
```
|
||||||
|
linux ... single
|
||||||
|
```
|
||||||
|
|
||||||
|
### systemd.unit=rescue.target
|
||||||
|
|
||||||
|
systemd equivalent of single-user mode: minimal services, root shell.
|
||||||
|
|
||||||
|
```
|
||||||
|
linux ... systemd.unit=rescue.target
|
||||||
|
```
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
title: "Linux Privilege Escalation"
|
||||||
|
description: "Common misconfigurations and weaknesses to check when escalating privileges on Linux."
|
||||||
|
tags: ["linux", "privesc", "post-exploitation"]
|
||||||
|
publishDate: 2026-05-18
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sudo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -l
|
||||||
|
```
|
||||||
|
|
||||||
|
Check [GTFOBins](https://gtfobins.github.io) for any listed binary.
|
||||||
|
|
||||||
|
If `env_keep+=LD_PRELOAD` is set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# compile a shared lib that spawns a shell
|
||||||
|
gcc -fPIC -shared -o /tmp/shell.so shell.c -nostartfiles
|
||||||
|
sudo LD_PRELOAD=/tmp/shell.so <allowed_binary>
|
||||||
|
```
|
||||||
|
|
||||||
|
## SUID / SGID
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find / -user root -perm -4000 -ls 2>/dev/null # SUID
|
||||||
|
find / -group root -perm -2000 -ls 2>/dev/null # SGID
|
||||||
|
```
|
||||||
|
|
||||||
|
Check any non-standard binary on GTFOBins.
|
||||||
|
|
||||||
|
## Misconfiguration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# World-writable directories
|
||||||
|
find / -type d -perm -2 -ls 2>/dev/null
|
||||||
|
|
||||||
|
# World-writable files owned by root
|
||||||
|
find / -user root -perm -2 ! -type l -ls 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cron Jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /etc/crontab
|
||||||
|
ls -la /etc/cron.*
|
||||||
|
crontab -l
|
||||||
|
```
|
||||||
|
|
||||||
|
If a cron runs a script you can write to, replace its content:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'chmod +s /bin/bash' >> /path/to/script.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
If the cron uses a relative PATH and a directory is writable, drop a malicious binary earlier in `$PATH`.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
```bash
|
||||||
|
getcap -r / 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
Dangerous capabilities: `cap_setuid`, `cap_net_raw`, `cap_dac_override`.
|
||||||
|
Check [GTFOBins](https://gtfobins.github.io) for exploitation.
|
||||||
|
|
||||||
|
## Kernel Exploits
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uname -r
|
||||||
|
searchsploit linux kernel $(uname -r)
|
||||||
|
```
|
||||||
|
|
||||||
|
## LinPEAS / WinPEAS
|
||||||
|
|
||||||
|
Automated enumeration scripts to surface privesc vectors quickly.
|
||||||
|
|
||||||
|
- [LinPEAS (linux)](https://github.com/peass-ng/PEASS-ng/tree/master/linPEAS)
|
||||||
|
- [WinPEAS (windows)](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS)
|
||||||
@@ -5,8 +5,6 @@ tags: ["ftp", "network", "service"]
|
|||||||
publishDate: 2026-04-29
|
publishDate: 2026-04-29
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
FTP runs on **port 21** (control) and uses a secondary data channel (port 20 for active, ephemeral port for passive).
|
FTP runs on **port 21** (control) and uses a secondary data channel (port 20 for active, ephemeral port for passive).
|
||||||
Common implementations: vsftpd, ProFTPD, Pure-FTPd, FileZilla Server, IIS FTP.
|
Common implementations: vsftpd, ProFTPD, Pure-FTPd, FileZilla Server, IIS FTP.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
title: "NFS"
|
||||||
|
description: "Enumeration, mounting and privilege escalation techniques for NFS shares."
|
||||||
|
tags: ["nfs", "network", "service"]
|
||||||
|
publishDate: 2026-05-18
|
||||||
|
---
|
||||||
|
|
||||||
|
NFS (Network File System) runs on **port 2049** and allows remote filesystem mounting over the network.
|
||||||
|
Common on Linux/Unix environments. Access control is defined in `/etc/exports` on the server.
|
||||||
|
|
||||||
|
## Enumeration
|
||||||
|
|
||||||
|
### Nmap
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -sV -p 111,2049 $IP
|
||||||
|
nmap -p 111,2049 --script nfs-* $IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Key scripts:
|
||||||
|
|
||||||
|
- `nfs-showmount`: lists exported shares
|
||||||
|
- `nfs-ls`: lists files in shares
|
||||||
|
- `nfs-statfs`: retrieves disk stats
|
||||||
|
|
||||||
|
### List shares
|
||||||
|
|
||||||
|
```bash
|
||||||
|
showmount -e $IP
|
||||||
|
rpcinfo -p $IP
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mount
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir /mnt/nfs
|
||||||
|
mount -t nfs $IP:/share /mnt/nfs
|
||||||
|
mount -t nfs -o vers=2 $IP:/share /mnt/nfs # force NFSv2
|
||||||
|
umount /mnt/nfs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Privilege Escalation
|
||||||
|
|
||||||
|
### no_root_squash
|
||||||
|
|
||||||
|
If the share is exported with `no_root_squash`, the remote root user keeps root privileges on the share.
|
||||||
|
|
||||||
|
Check `/etc/exports` on the server (if readable):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /etc/exports
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
|
||||||
|
```
|
||||||
|
/share *(rw,no_root_squash)
|
||||||
|
```
|
||||||
|
|
||||||
|
If present, copy a SUID binary onto the share as root from your attacker machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp /bin/bash /mnt/nfs/bash
|
||||||
|
chmod +s /mnt/nfs/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Then execute it on the target with `-p` to keep the SUID effective UID:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/tmp/nfs/bash -p
|
||||||
|
```
|
||||||
|
|
||||||
|
### UID spoofing
|
||||||
|
|
||||||
|
NFS authenticates by UID. If you know a file is owned by UID 1001 on the server, impersonate it directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "import os; os.setuid(1001); os.system('/bin/bash')"
|
||||||
|
```
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
title: "Nmap"
|
||||||
|
description: "Host discovery, port scanning, service detection and NSE scripting"
|
||||||
|
tags: ["nmap", "network", "enumeration"]
|
||||||
|
publishDate: 2026-05-18
|
||||||
|
---
|
||||||
|
|
||||||
|
Nmap is a network scanner used for host discovery, port scanning, service/version detection, OS fingerprinting, and vulnerability scripting via NSE.
|
||||||
|
|
||||||
|
## Host Discovery
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -sn 192.168.1.0/24 # ping sweep, no port scan
|
||||||
|
nmap -sn -PR 192.168.1.0/24 # ARP ping (local network)
|
||||||
|
nmap -Pn $IP # skip host discovery, treat as up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Port Scanning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap $IP # top 1000 ports (SYN scan if root)
|
||||||
|
nmap -p 80,443,8080 $IP # specific ports
|
||||||
|
nmap -p 1-65535 $IP # all ports
|
||||||
|
nmap -p- $IP # shorthand for all ports
|
||||||
|
nmap -sU $IP # UDP scan
|
||||||
|
nmap -sU -sS $IP # UDP + SYN together
|
||||||
|
```
|
||||||
|
|
||||||
|
Scan types:
|
||||||
|
|
||||||
|
- `-sS`: SYN scan (stealth, requires root)
|
||||||
|
- `-sT`: TCP connect scan (no root needed)
|
||||||
|
- `-sU`: UDP scan
|
||||||
|
- `-sA`: ACK scan (firewall rule mapping)
|
||||||
|
- `-sN/sF/sX`: Null, FIN, Xmas (evasion, unreliable on Windows)
|
||||||
|
|
||||||
|
## Service & Version Detection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -sV $IP
|
||||||
|
nmap -sV --version-intensity 9 $IP # more aggressive probing
|
||||||
|
```
|
||||||
|
|
||||||
|
## OS Detection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -O $IP
|
||||||
|
nmap -O --osscan-guess $IP # guess if not confident
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aggressive Scan
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -A $IP # -sV -O --script=default --traceroute
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timing Templates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -T0 $IP # paranoid (IDS evasion, very slow)
|
||||||
|
nmap -T1 $IP # sneaky
|
||||||
|
nmap -T3 $IP # normal (default)
|
||||||
|
nmap -T4 $IP # aggressive (faster, good for CTFs)
|
||||||
|
nmap -T5 $IP # insane (may miss results)
|
||||||
|
```
|
||||||
|
|
||||||
|
## NSE Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap --script default $IP
|
||||||
|
nmap --script vuln $IP
|
||||||
|
nmap --script "ftp-*" $IP
|
||||||
|
nmap --script safe $IP
|
||||||
|
nmap --script $script --script-args user=$user,pass=$password $IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Common script categories: `auth`, `brute`, `default`, `discovery`, `dos`, `exploit`, `intrusive`, `safe`, `version`, `vuln`.
|
||||||
|
|
||||||
|
Scripts are located in `/usr/share/nmap/scripts/`.
|
||||||
|
|
||||||
|
## Output Formats
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -oN output.txt $IP # normal
|
||||||
|
nmap -oX output.xml $IP # XML
|
||||||
|
nmap -oG output.gnmap $IP # grepable
|
||||||
|
nmap -oA output $IP # all three at once
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Profiles
|
||||||
|
|
||||||
|
Quick full scan:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -p- -T4 --min-rate 5000 -sV -sC -oA full $IP
|
||||||
|
```
|
||||||
|
|
||||||
|
CTF/lab initial recon:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -sV -sC -p- --open $IP
|
||||||
|
```
|
||||||
|
|
||||||
|
UDP top ports:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap -sU --top-ports 100 $IP
|
||||||
|
```
|
||||||
@@ -5,8 +5,6 @@ tags: ["rdp", "network", "service"]
|
|||||||
publishDate: 2026-05-04
|
publishDate: 2026-05-04
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
RDP (Remote Desktop Protocol) runs on **port 3389** and provides a graphical remote session.
|
RDP (Remote Desktop Protocol) runs on **port 3389** and provides a graphical remote session.
|
||||||
Common on Windows servers and workstations.
|
Common on Windows servers and workstations.
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ tags: ["ssh", "network", "service"]
|
|||||||
publishDate: 2026-05-04
|
publishDate: 2026-05-04
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
SSH runs on **port 22** and provides an encrypted remote shell.
|
SSH runs on **port 22** and provides an encrypted remote shell.
|
||||||
Common implementations: OpenSSH, Dropbear, Bitvise.
|
Common implementations: OpenSSH, Dropbear, Bitvise.
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ tags: ["telnet", "network", "service"]
|
|||||||
publishDate: 2026-05-04
|
publishDate: 2026-05-04
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Telnet runs on **port 23** and transmits all data (including credentials) in **cleartext**.
|
Telnet runs on **port 23** and transmits all data (including credentials) in **cleartext**.
|
||||||
Common on embedded devices, legacy systems, routers, and IoT equipment.
|
Common on embedded devices, legacy systems, routers, and IoT equipment.
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ If the page has been taken down or modified, the cached version may still show t
|
|||||||
|
|
||||||
## Domain History
|
## Domain History
|
||||||
|
|
||||||
[VirusTotal](https://www.virustotal.com) shows the historical DNS records, subdomains, and associated IPs for any domain — useful when a site has moved or been taken down.
|
[VirusTotal](https://www.virustotal.com) shows the historical DNS records, subdomains, and associated IPs for any domaint useful when a site has moved or been taken down.
|
||||||
|
|
||||||
[ViewDNS.info](https://viewdns.info) covers WHOIS history, reverse IP, reverse MX, and port scans from a single interface.
|
[ViewDNS.info](https://viewdns.info) covers WHOIS history, reverse IP, reverse MX, and port scans from a single interface.
|
||||||
|
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ tweet_id = 1234567890123456789
|
|||||||
timestamp_ms = (tweet_id >> 22) + 1288834974657
|
timestamp_ms = (tweet_id >> 22) + 1288834974657
|
||||||
```
|
```
|
||||||
|
|
||||||
`1288834974657` is Twitter's custom epoch (Nov 4, 2010). Works on both tweet IDs and user IDs — useful to confirm account creation date without needing profile metadata.
|
`1288834974657` is Twitter's custom epoch (Nov 4, 2010). Works on both tweet IDs and user IDs: useful to confirm account creation date without needing profile metadata.
|
||||||
|
|
||||||
Several online converters exist if you don't want to do it manually — search "snowflake id decoder".
|
Several online converters exist if you don't want to do it manually: search "snowflake id decoder".
|
||||||
|
|
||||||
### Direct profile URL by ID
|
### Direct profile URL by ID
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
title: "Directory Discovery"
|
||||||
|
description: "Techniques and tools for discovering hidden directories and files on web servers."
|
||||||
|
tags: ["web", "enumeration", "discovery", "directory"]
|
||||||
|
publishDate: 2026-06-01
|
||||||
|
---
|
||||||
|
|
||||||
|
## FFUF
|
||||||
|
|
||||||
|
See also [FFUF](/notes/web/ffuf) for fuzzing-based directory discovery.
|
||||||
|
|
||||||
|
## Robots.txt
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s $url/robots.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sitemap.xml
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s $url/sitemap.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dirb
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dirb $url
|
||||||
|
```
|
||||||
|
|
||||||
|
## Spider - Katana
|
||||||
|
|
||||||
|
A spider is a tool that crawls a website and collects information about its
|
||||||
|
structure and content. It can be used to find hidden directories, files, and
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
katana -c 15 -p 15 -u $url > output
|
||||||
|
```
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: "FFUF"
|
||||||
|
description: "Reference and usage examples for ffuf, a fast web fuzzer for directories, endpoints and subdomains."
|
||||||
|
tags:
|
||||||
|
["web", "enumeration", "discovery", "subdomain", "directory", "bruteforce"]
|
||||||
|
publishDate: 2026-06-01
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fuff (or ffuf)** is a fast web fuzzer written in Go, mainly used in
|
||||||
|
cybersecurity to discover hidden directories, files, API endpoints, subdomains,
|
||||||
|
vhosts and more. Its speed and flexibility make it a must-have for pentesters
|
||||||
|
and bug bounty hunters.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Flags:
|
||||||
|
# -rate 50 -t 50 # Limit requests to 50 per second with 50 concurrent threads
|
||||||
|
# -X POST|GET|PUT # Set method
|
||||||
|
# -e .php,.asp,.bak,.db # Set the extension
|
||||||
|
# -recursion -recursion-depth 3 # Recursive fuzzing up to 3 levels deep
|
||||||
|
# -fc 404,500 # Exclude responses with status codes 404 and 500
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
ffuf -w wordlist.txt -u $url/FUZZ # Basic directory/file fuzzing using a wordlist
|
||||||
|
ffuf -w subdomains.txt -u https://FUZZ.$url # Subdomain fuzzing
|
||||||
|
ffuf -w vhosts.txt -u $url -H "Host: https://FUZZ.$url" # Virtual host fuzzing by modifying the Host header
|
||||||
|
ffuf -w wordlist.txt -u $url/page.php?FUZZ=value # GET parameter fuzzing in the query string
|
||||||
|
ffuf -w wordlist.txt -u $url/api -X POST -d 'FUZZ=value' # POST body parameter fuzzing
|
||||||
|
ffuf -w wordlist.txt -u $url/FUZZ -b 'session=abcdef' # Use a session cookie during fuzzing
|
||||||
|
ffuf -w headers.txt -u $url -H "X-Custom-Header: FUZZ" # HTTP header fuzzing
|
||||||
|
ffuf -w passwords.txt -X POST -u $url/login -d "username=admin&password=FUZZ" # Password brute-forcing for user "admin"
|
||||||
|
ffuf -w users.txt:USER -w passwords.txt:PASS -u "$url/login?username=USER&password=PASS" -mode pitchfork # Pitchfork mode: matches each line from both wordlists (USER[i], PASS[i])
|
||||||
|
ffuf -w users.txt:USER -w passwords.txt:PASS -u "$url/login?username=USER&password=PASS" -mode clusterbomb # Clusterbomb mode: tests every user with every password combination
|
||||||
|
```
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
title: "Subdomains Discovery"
|
||||||
|
description: "Methods and tools for enumerating subdomains of a target domain."
|
||||||
|
tags: ["web", "enumeration", "discovery", "subdomain"]
|
||||||
|
publishDate: 2026-06-01
|
||||||
|
---
|
||||||
|
|
||||||
|
## FFUF
|
||||||
|
|
||||||
|
See also [FFUF](/notes/web/ffuf) for fuzzing-based subdomain discovery.
|
||||||
|
|
||||||
|
## Google Dorking
|
||||||
|
|
||||||
|
Google dorks can surface subdomains indexed by Google without any active scanning.
|
||||||
|
|
||||||
|
```
|
||||||
|
site:*.$domain
|
||||||
|
site:*.$domain -www
|
||||||
|
site:*.$domain inurl:admin
|
||||||
|
site:*.$domain ext:php | ext:json | ext:xml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Certificate Transparency
|
||||||
|
|
||||||
|
CT logs record every TLS certificate ever issued for a domain. Querying them is
|
||||||
|
passive and reliable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "https://crt.sh/?q=%25.$domain&output=json" | jq '.[].name_value' | sort -u
|
||||||
|
```
|
||||||
|
|
||||||
|
Tools that aggregate CT logs:
|
||||||
|
|
||||||
|
- [crt.sh](https://crt.sh)
|
||||||
|
- [censys.io](https://search.censys.io)
|
||||||
|
|
||||||
|
## Passive DNS
|
||||||
|
|
||||||
|
Passive DNS databases store historical DNS resolutions collected from resolvers
|
||||||
|
worldwide; useful for finding subdomains that no longer resolve but once did.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Amass (passive mode, no active scanning)
|
||||||
|
amass enum -passive -d $domain
|
||||||
|
|
||||||
|
# subfinder (uses many passive sources)
|
||||||
|
subfinder -d $domain -silent
|
||||||
|
```
|
||||||
|
|
||||||
|
## DMARC
|
||||||
|
|
||||||
|
DMARC can reveal more domains associated with a target.
|
||||||
|
|
||||||
|
Go to `dmarc.live/info/$domain`, it allows you to find domains using the
|
||||||
|
same DMARC record.
|
||||||
|
|
||||||
|
## ASN & IP Ranges
|
||||||
|
|
||||||
|
Finding the ASN of a target exposes its entire IP range, which may contain
|
||||||
|
undiscovered subdomains or related infrastructure.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get ASN from an IP
|
||||||
|
whois $ip | grep -i "asn\|orgname\|origin"
|
||||||
|
|
||||||
|
# Get IP ranges from ASN
|
||||||
|
whois -h whois.radb.net -- '-i origin AS12345' | grep route
|
||||||
|
```
|
||||||
|
|
||||||
|
## Favicon Hash
|
||||||
|
|
||||||
|
A unique favicon can be fingerprinted to find other domains hosted by the same
|
||||||
|
organisation, including subdomains on non-standard ports.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compute the MMH3 hash of the favicon
|
||||||
|
python3 -c "
|
||||||
|
import requests, mmh3, base64
|
||||||
|
r = requests.get('https://$domain/favicon.ico')
|
||||||
|
h = mmh3.hash(base64.encodebytes(r.content))
|
||||||
|
print(h)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then search the hash on [Shodan](https://shodan.io): `http.favicon.hash:<hash>`
|
||||||
+229
-117
@@ -7,7 +7,7 @@
|
|||||||
"login_name": "",
|
"login_name": "",
|
||||||
"source_id": 0,
|
"source_id": 0,
|
||||||
"full_name": "Hadi",
|
"full_name": "Hadi",
|
||||||
"email": "anotherhadi@noreply.git.hadi.icu",
|
"email": "1+anotherhadi@noreply.git.hadi.icu",
|
||||||
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi",
|
"html_url": "https://git.hadi.icu/anotherhadi",
|
||||||
"language": "",
|
"language": "",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"fork": false,
|
"fork": false,
|
||||||
"template": false,
|
"template": false,
|
||||||
"mirror": true,
|
"mirror": true,
|
||||||
"size": 429945,
|
"size": 430163,
|
||||||
"language": "Nix",
|
"language": "Nix",
|
||||||
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages",
|
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi/nixy",
|
"html_url": "https://git.hadi.icu/anotherhadi/nixy",
|
||||||
@@ -47,13 +47,14 @@
|
|||||||
"stars_count": 0,
|
"stars_count": 0,
|
||||||
"forks_count": 0,
|
"forks_count": 0,
|
||||||
"watchers_count": 1,
|
"watchers_count": 1,
|
||||||
|
"branch_count": 1,
|
||||||
"open_issues_count": 0,
|
"open_issues_count": 0,
|
||||||
"open_pr_counter": 0,
|
"open_pr_counter": 0,
|
||||||
"release_counter": 0,
|
"release_counter": 0,
|
||||||
"default_branch": "main",
|
"default_branch": "main",
|
||||||
"archived": false,
|
"archived": false,
|
||||||
"created_at": "2026-03-30T17:31:04+02:00",
|
"created_at": "2026-03-30T17:31:04+02:00",
|
||||||
"updated_at": "2026-04-23T09:52:11+02:00",
|
"updated_at": "2026-05-07T09:47:53+02:00",
|
||||||
"archived_at": "1970-01-01T01:00:00+01:00",
|
"archived_at": "1970-01-01T01:00:00+01:00",
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"admin": false,
|
"admin": false,
|
||||||
@@ -90,7 +91,7 @@
|
|||||||
"internal": false,
|
"internal": false,
|
||||||
"mirror_interval": "8h0m0s",
|
"mirror_interval": "8h0m0s",
|
||||||
"object_format_name": "sha1",
|
"object_format_name": "sha1",
|
||||||
"mirror_updated": "2026-04-23T11:17:25+02:00",
|
"mirror_updated": "2026-05-07T16:22:32+02:00",
|
||||||
"topics": [
|
"topics": [
|
||||||
"dotfiles",
|
"dotfiles",
|
||||||
"hyprland",
|
"hyprland",
|
||||||
@@ -106,6 +107,220 @@
|
|||||||
},
|
},
|
||||||
"banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png"
|
"banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "anotherhadi",
|
||||||
|
"login_name": "",
|
||||||
|
"source_id": 0,
|
||||||
|
"full_name": "Hadi",
|
||||||
|
"email": "1+anotherhadi@noreply.git.hadi.icu",
|
||||||
|
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
||||||
|
"html_url": "https://git.hadi.icu/anotherhadi",
|
||||||
|
"language": "",
|
||||||
|
"is_admin": false,
|
||||||
|
"last_login": "0001-01-01T00:00:00Z",
|
||||||
|
"created": "2026-03-30T17:21:50+02:00",
|
||||||
|
"restricted": false,
|
||||||
|
"active": false,
|
||||||
|
"prohibit_login": false,
|
||||||
|
"location": "127.0.0.1",
|
||||||
|
"website": "https://hadi.icu",
|
||||||
|
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
|
||||||
|
"visibility": "public",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 0,
|
||||||
|
"starred_repos_count": 0,
|
||||||
|
"username": "anotherhadi"
|
||||||
|
},
|
||||||
|
"name": "usbguard-tui",
|
||||||
|
"full_name": "anotherhadi/usbguard-tui",
|
||||||
|
"description": "A terminal UI for managing USB devices via usbguard. TUI built with golang & bubbletea.",
|
||||||
|
"empty": false,
|
||||||
|
"private": false,
|
||||||
|
"fork": false,
|
||||||
|
"template": false,
|
||||||
|
"mirror": true,
|
||||||
|
"size": 425,
|
||||||
|
"language": "Go",
|
||||||
|
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui/languages",
|
||||||
|
"html_url": "https://git.hadi.icu/anotherhadi/usbguard-tui",
|
||||||
|
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui",
|
||||||
|
"link": "",
|
||||||
|
"ssh_url": "gitea@git.hadi.icu:anotherhadi/usbguard-tui.git",
|
||||||
|
"clone_url": "https://git.hadi.icu/anotherhadi/usbguard-tui.git",
|
||||||
|
"original_url": "https://github.com/anotherhadi/usbguard-tui",
|
||||||
|
"website": "",
|
||||||
|
"stars_count": 0,
|
||||||
|
"forks_count": 0,
|
||||||
|
"watchers_count": 1,
|
||||||
|
"branch_count": 1,
|
||||||
|
"open_issues_count": 0,
|
||||||
|
"open_pr_counter": 0,
|
||||||
|
"release_counter": 0,
|
||||||
|
"default_branch": "main",
|
||||||
|
"archived": false,
|
||||||
|
"created_at": "2026-04-30T17:40:42+02:00",
|
||||||
|
"updated_at": "2026-05-06T14:42:02+02:00",
|
||||||
|
"archived_at": "1970-01-01T01:00:00+01:00",
|
||||||
|
"permissions": {
|
||||||
|
"admin": false,
|
||||||
|
"push": false,
|
||||||
|
"pull": true
|
||||||
|
},
|
||||||
|
"has_code": true,
|
||||||
|
"has_issues": true,
|
||||||
|
"internal_tracker": {
|
||||||
|
"enable_time_tracker": true,
|
||||||
|
"allow_only_contributors_to_track_time": true,
|
||||||
|
"enable_issue_dependencies": true
|
||||||
|
},
|
||||||
|
"has_wiki": true,
|
||||||
|
"has_pull_requests": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"projects_mode": "all",
|
||||||
|
"has_releases": true,
|
||||||
|
"has_packages": true,
|
||||||
|
"has_actions": false,
|
||||||
|
"ignore_whitespace_conflicts": false,
|
||||||
|
"allow_merge_commits": false,
|
||||||
|
"allow_rebase": false,
|
||||||
|
"allow_rebase_explicit": false,
|
||||||
|
"allow_squash_merge": false,
|
||||||
|
"allow_fast_forward_only_merge": false,
|
||||||
|
"allow_rebase_update": false,
|
||||||
|
"allow_manual_merge": true,
|
||||||
|
"autodetect_manual_merge": false,
|
||||||
|
"default_delete_branch_after_merge": false,
|
||||||
|
"default_merge_style": "merge",
|
||||||
|
"default_allow_maintainer_edit": false,
|
||||||
|
"avatar_url": "",
|
||||||
|
"internal": false,
|
||||||
|
"mirror_interval": "8h0m0s",
|
||||||
|
"object_format_name": "sha1",
|
||||||
|
"mirror_updated": "2026-05-07T12:42:33+02:00",
|
||||||
|
"topics": [
|
||||||
|
"bubbletea",
|
||||||
|
"tui",
|
||||||
|
"usbguard"
|
||||||
|
],
|
||||||
|
"licenses": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"mirrors": {
|
||||||
|
"github": "https://github.com/anotherhadi/usbguard-tui",
|
||||||
|
"gitlab": "https://gitlab.com/anotherhadi_mirror/usbguard-tui"
|
||||||
|
},
|
||||||
|
"banner_url": "https://git.hadi.icu/anotherhadi/usbguard-tui/raw/branch/main/.github/assets/banner.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "anotherhadi",
|
||||||
|
"login_name": "",
|
||||||
|
"source_id": 0,
|
||||||
|
"full_name": "Hadi",
|
||||||
|
"email": "1+anotherhadi@noreply.git.hadi.icu",
|
||||||
|
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
||||||
|
"html_url": "https://git.hadi.icu/anotherhadi",
|
||||||
|
"language": "",
|
||||||
|
"is_admin": false,
|
||||||
|
"last_login": "0001-01-01T00:00:00Z",
|
||||||
|
"created": "2026-03-30T17:21:50+02:00",
|
||||||
|
"restricted": false,
|
||||||
|
"active": false,
|
||||||
|
"prohibit_login": false,
|
||||||
|
"location": "127.0.0.1",
|
||||||
|
"website": "https://hadi.icu",
|
||||||
|
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
|
||||||
|
"visibility": "public",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 0,
|
||||||
|
"starred_repos_count": 0,
|
||||||
|
"username": "anotherhadi"
|
||||||
|
},
|
||||||
|
"name": "blog",
|
||||||
|
"full_name": "anotherhadi/blog",
|
||||||
|
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
|
||||||
|
"empty": false,
|
||||||
|
"private": false,
|
||||||
|
"fork": false,
|
||||||
|
"template": false,
|
||||||
|
"mirror": true,
|
||||||
|
"size": 3863,
|
||||||
|
"language": "Nix",
|
||||||
|
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
|
||||||
|
"html_url": "https://git.hadi.icu/anotherhadi/blog",
|
||||||
|
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
|
||||||
|
"link": "",
|
||||||
|
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
|
||||||
|
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
|
||||||
|
"original_url": "https://github.com/anotherhadi/blog",
|
||||||
|
"website": "https://hadi.icu",
|
||||||
|
"stars_count": 0,
|
||||||
|
"forks_count": 0,
|
||||||
|
"watchers_count": 1,
|
||||||
|
"branch_count": 1,
|
||||||
|
"open_issues_count": 0,
|
||||||
|
"open_pr_counter": 0,
|
||||||
|
"release_counter": 0,
|
||||||
|
"default_branch": "main",
|
||||||
|
"archived": false,
|
||||||
|
"created_at": "2026-03-30T17:39:47+02:00",
|
||||||
|
"updated_at": "2026-05-04T17:03:05+02:00",
|
||||||
|
"archived_at": "1970-01-01T01:00:00+01:00",
|
||||||
|
"permissions": {
|
||||||
|
"admin": false,
|
||||||
|
"push": false,
|
||||||
|
"pull": true
|
||||||
|
},
|
||||||
|
"has_code": true,
|
||||||
|
"has_issues": true,
|
||||||
|
"internal_tracker": {
|
||||||
|
"enable_time_tracker": true,
|
||||||
|
"allow_only_contributors_to_track_time": true,
|
||||||
|
"enable_issue_dependencies": true
|
||||||
|
},
|
||||||
|
"has_wiki": true,
|
||||||
|
"has_pull_requests": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"projects_mode": "all",
|
||||||
|
"has_releases": true,
|
||||||
|
"has_packages": true,
|
||||||
|
"has_actions": false,
|
||||||
|
"ignore_whitespace_conflicts": false,
|
||||||
|
"allow_merge_commits": false,
|
||||||
|
"allow_rebase": false,
|
||||||
|
"allow_rebase_explicit": false,
|
||||||
|
"allow_squash_merge": false,
|
||||||
|
"allow_fast_forward_only_merge": false,
|
||||||
|
"allow_rebase_update": false,
|
||||||
|
"allow_manual_merge": true,
|
||||||
|
"autodetect_manual_merge": false,
|
||||||
|
"default_delete_branch_after_merge": false,
|
||||||
|
"default_merge_style": "merge",
|
||||||
|
"default_allow_maintainer_edit": false,
|
||||||
|
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
|
||||||
|
"internal": false,
|
||||||
|
"mirror_interval": "8h0m0s",
|
||||||
|
"object_format_name": "sha1",
|
||||||
|
"mirror_updated": "2026-05-07T17:22:32+02:00",
|
||||||
|
"topics": [
|
||||||
|
"blog",
|
||||||
|
"cybersecurity",
|
||||||
|
"portfolio"
|
||||||
|
],
|
||||||
|
"licenses": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"mirrors": {
|
||||||
|
"github": "https://github.com/anotherhadi/blog",
|
||||||
|
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
|
||||||
|
},
|
||||||
|
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"owner": {
|
"owner": {
|
||||||
@@ -114,7 +329,7 @@
|
|||||||
"login_name": "",
|
"login_name": "",
|
||||||
"source_id": 0,
|
"source_id": 0,
|
||||||
"full_name": "Hadi",
|
"full_name": "Hadi",
|
||||||
"email": "anotherhadi@noreply.git.hadi.icu",
|
"email": "1+anotherhadi@noreply.git.hadi.icu",
|
||||||
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi",
|
"html_url": "https://git.hadi.icu/anotherhadi",
|
||||||
"language": "",
|
"language": "",
|
||||||
@@ -141,7 +356,7 @@
|
|||||||
"fork": false,
|
"fork": false,
|
||||||
"template": false,
|
"template": false,
|
||||||
"mirror": true,
|
"mirror": true,
|
||||||
"size": 676,
|
"size": 681,
|
||||||
"language": "Svelte",
|
"language": "Svelte",
|
||||||
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages",
|
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi/iknowyou",
|
"html_url": "https://git.hadi.icu/anotherhadi/iknowyou",
|
||||||
@@ -154,6 +369,7 @@
|
|||||||
"stars_count": 0,
|
"stars_count": 0,
|
||||||
"forks_count": 0,
|
"forks_count": 0,
|
||||||
"watchers_count": 1,
|
"watchers_count": 1,
|
||||||
|
"branch_count": 2,
|
||||||
"open_issues_count": 0,
|
"open_issues_count": 0,
|
||||||
"open_pr_counter": 0,
|
"open_pr_counter": 0,
|
||||||
"release_counter": 0,
|
"release_counter": 0,
|
||||||
@@ -197,7 +413,7 @@
|
|||||||
"internal": false,
|
"internal": false,
|
||||||
"mirror_interval": "8h0m0s",
|
"mirror_interval": "8h0m0s",
|
||||||
"object_format_name": "sha1",
|
"object_format_name": "sha1",
|
||||||
"mirror_updated": "2026-04-23T14:17:25+02:00",
|
"mirror_updated": "2026-05-07T20:02:34+02:00",
|
||||||
"topics": [
|
"topics": [
|
||||||
"osint",
|
"osint",
|
||||||
"osint-tool"
|
"osint-tool"
|
||||||
@@ -211,112 +427,6 @@
|
|||||||
},
|
},
|
||||||
"banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png"
|
"banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"owner": {
|
|
||||||
"id": 1,
|
|
||||||
"login": "anotherhadi",
|
|
||||||
"login_name": "",
|
|
||||||
"source_id": 0,
|
|
||||||
"full_name": "Hadi",
|
|
||||||
"email": "anotherhadi@noreply.git.hadi.icu",
|
|
||||||
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi",
|
|
||||||
"language": "",
|
|
||||||
"is_admin": false,
|
|
||||||
"last_login": "0001-01-01T00:00:00Z",
|
|
||||||
"created": "2026-03-30T17:21:50+02:00",
|
|
||||||
"restricted": false,
|
|
||||||
"active": false,
|
|
||||||
"prohibit_login": false,
|
|
||||||
"location": "127.0.0.1",
|
|
||||||
"website": "https://hadi.icu",
|
|
||||||
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
|
|
||||||
"visibility": "public",
|
|
||||||
"followers_count": 0,
|
|
||||||
"following_count": 0,
|
|
||||||
"starred_repos_count": 0,
|
|
||||||
"username": "anotherhadi"
|
|
||||||
},
|
|
||||||
"name": "blog",
|
|
||||||
"full_name": "anotherhadi/blog",
|
|
||||||
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
|
|
||||||
"empty": false,
|
|
||||||
"private": false,
|
|
||||||
"fork": false,
|
|
||||||
"template": false,
|
|
||||||
"mirror": true,
|
|
||||||
"size": 3009,
|
|
||||||
"language": "Nix",
|
|
||||||
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
|
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi/blog",
|
|
||||||
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
|
|
||||||
"link": "",
|
|
||||||
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
|
|
||||||
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
|
|
||||||
"original_url": "https://github.com/anotherhadi/blog",
|
|
||||||
"website": "https://hadi.icu",
|
|
||||||
"stars_count": 0,
|
|
||||||
"forks_count": 0,
|
|
||||||
"watchers_count": 1,
|
|
||||||
"open_issues_count": 0,
|
|
||||||
"open_pr_counter": 0,
|
|
||||||
"release_counter": 0,
|
|
||||||
"default_branch": "main",
|
|
||||||
"archived": false,
|
|
||||||
"created_at": "2026-03-30T17:39:47+02:00",
|
|
||||||
"updated_at": "2026-04-11T17:35:02+02:00",
|
|
||||||
"archived_at": "1970-01-01T01:00:00+01:00",
|
|
||||||
"permissions": {
|
|
||||||
"admin": false,
|
|
||||||
"push": false,
|
|
||||||
"pull": true
|
|
||||||
},
|
|
||||||
"has_code": true,
|
|
||||||
"has_issues": true,
|
|
||||||
"internal_tracker": {
|
|
||||||
"enable_time_tracker": true,
|
|
||||||
"allow_only_contributors_to_track_time": true,
|
|
||||||
"enable_issue_dependencies": true
|
|
||||||
},
|
|
||||||
"has_wiki": true,
|
|
||||||
"has_pull_requests": false,
|
|
||||||
"has_projects": true,
|
|
||||||
"projects_mode": "all",
|
|
||||||
"has_releases": true,
|
|
||||||
"has_packages": true,
|
|
||||||
"has_actions": false,
|
|
||||||
"ignore_whitespace_conflicts": false,
|
|
||||||
"allow_merge_commits": false,
|
|
||||||
"allow_rebase": false,
|
|
||||||
"allow_rebase_explicit": false,
|
|
||||||
"allow_squash_merge": false,
|
|
||||||
"allow_fast_forward_only_merge": false,
|
|
||||||
"allow_rebase_update": false,
|
|
||||||
"allow_manual_merge": true,
|
|
||||||
"autodetect_manual_merge": false,
|
|
||||||
"default_delete_branch_after_merge": false,
|
|
||||||
"default_merge_style": "merge",
|
|
||||||
"default_allow_maintainer_edit": false,
|
|
||||||
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
|
|
||||||
"internal": false,
|
|
||||||
"mirror_interval": "8h0m0s",
|
|
||||||
"object_format_name": "sha1",
|
|
||||||
"mirror_updated": "2026-04-23T11:17:26+02:00",
|
|
||||||
"topics": [
|
|
||||||
"blog",
|
|
||||||
"cybersecurity",
|
|
||||||
"portfolio"
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"mirrors": {
|
|
||||||
"github": "https://github.com/anotherhadi/blog",
|
|
||||||
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
|
|
||||||
},
|
|
||||||
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"owner": {
|
"owner": {
|
||||||
@@ -325,7 +435,7 @@
|
|||||||
"login_name": "",
|
"login_name": "",
|
||||||
"source_id": 0,
|
"source_id": 0,
|
||||||
"full_name": "Hadi",
|
"full_name": "Hadi",
|
||||||
"email": "anotherhadi@noreply.git.hadi.icu",
|
"email": "1+anotherhadi@noreply.git.hadi.icu",
|
||||||
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi",
|
"html_url": "https://git.hadi.icu/anotherhadi",
|
||||||
"language": "",
|
"language": "",
|
||||||
@@ -352,7 +462,7 @@
|
|||||||
"fork": false,
|
"fork": false,
|
||||||
"template": false,
|
"template": false,
|
||||||
"mirror": true,
|
"mirror": true,
|
||||||
"size": 530,
|
"size": 535,
|
||||||
"language": "Nix",
|
"language": "Nix",
|
||||||
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages",
|
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi/default-creds",
|
"html_url": "https://git.hadi.icu/anotherhadi/default-creds",
|
||||||
@@ -365,6 +475,7 @@
|
|||||||
"stars_count": 0,
|
"stars_count": 0,
|
||||||
"forks_count": 0,
|
"forks_count": 0,
|
||||||
"watchers_count": 1,
|
"watchers_count": 1,
|
||||||
|
"branch_count": 1,
|
||||||
"open_issues_count": 0,
|
"open_issues_count": 0,
|
||||||
"open_pr_counter": 0,
|
"open_pr_counter": 0,
|
||||||
"release_counter": 0,
|
"release_counter": 0,
|
||||||
@@ -408,7 +519,7 @@
|
|||||||
"internal": false,
|
"internal": false,
|
||||||
"mirror_interval": "8h0m0s",
|
"mirror_interval": "8h0m0s",
|
||||||
"object_format_name": "sha1",
|
"object_format_name": "sha1",
|
||||||
"mirror_updated": "2026-04-23T11:17:25+02:00",
|
"mirror_updated": "2026-05-07T17:12:33+02:00",
|
||||||
"topics": [
|
"topics": [
|
||||||
"cybersecurity",
|
"cybersecurity",
|
||||||
"cybersecurity-tools",
|
"cybersecurity-tools",
|
||||||
@@ -432,7 +543,7 @@
|
|||||||
"login_name": "",
|
"login_name": "",
|
||||||
"source_id": 0,
|
"source_id": 0,
|
||||||
"full_name": "Hadi",
|
"full_name": "Hadi",
|
||||||
"email": "anotherhadi@noreply.git.hadi.icu",
|
"email": "1+anotherhadi@noreply.git.hadi.icu",
|
||||||
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
|
||||||
"html_url": "https://git.hadi.icu/anotherhadi",
|
"html_url": "https://git.hadi.icu/anotherhadi",
|
||||||
"language": "",
|
"language": "",
|
||||||
@@ -472,6 +583,7 @@
|
|||||||
"stars_count": 0,
|
"stars_count": 0,
|
||||||
"forks_count": 0,
|
"forks_count": 0,
|
||||||
"watchers_count": 1,
|
"watchers_count": 1,
|
||||||
|
"branch_count": 1,
|
||||||
"open_issues_count": 0,
|
"open_issues_count": 0,
|
||||||
"open_pr_counter": 0,
|
"open_pr_counter": 0,
|
||||||
"release_counter": 0,
|
"release_counter": 0,
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ const sortedPosts = blogPosts.sort(
|
|||||||
) : (
|
) : (
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{sortedPosts.map((post) => (
|
{sortedPosts.map((post) => (
|
||||||
<BlogCard post={post} />
|
<BlogCard
|
||||||
|
displayBanner={true}
|
||||||
|
displayTags={true}
|
||||||
|
displayDate={true}
|
||||||
|
post={post}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,26 +3,31 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
import Hero from "../components/Hero.astro";
|
import Hero from "../components/Hero.astro";
|
||||||
import Projects from "../components/Projects.astro";
|
import Projects from "../components/Projects.astro";
|
||||||
import Blog from "../components/Blog.astro";
|
import Blog from "../components/Blog.astro";
|
||||||
|
import Notes from "../components/Notes.astro";
|
||||||
import Contact from "../components/Contact.astro";
|
import Contact from "../components/Contact.astro";
|
||||||
import { siteConfig } from "../config";
|
import { siteConfig } from "../config";
|
||||||
import avatar from "../../public/avatar.jpg";
|
import avatar from "../../public/avatar.jpg";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={`Another Hadi`} description={siteConfig.description}>
|
<Layout title={`Another Hadi`} description={siteConfig.description}>
|
||||||
<main>
|
<main class="px-10">
|
||||||
<Hero
|
<Hero
|
||||||
name={siteConfig.name}
|
name={siteConfig.name}
|
||||||
title={siteConfig.title}
|
title={siteConfig.title}
|
||||||
description={siteConfig.description}
|
description={siteConfig.description}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
location={siteConfig.location}
|
|
||||||
socialLinks={siteConfig.socialLinks}
|
socialLinks={siteConfig.socialLinks}
|
||||||
gpgKey={siteConfig.gpgKey}
|
gpgKey={siteConfig.gpgKey}
|
||||||
rssFeed={siteConfig.rssFeed}
|
rssFeed={siteConfig.rssFeed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<hr class="border-base-300/30 max-w-6xl mx-auto" />
|
||||||
<Blog />
|
<Blog />
|
||||||
|
<hr class="border-base-300/30 max-w-6xl mx-auto" />
|
||||||
|
<Notes />
|
||||||
|
<hr class="border-base-300/30 max-w-6xl mx-auto" />
|
||||||
<Projects />
|
<Projects />
|
||||||
|
<hr class="border-base-300/30 max-w-6xl mx-auto" />
|
||||||
<Contact />
|
<Contact />
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { getCollection, render } from "astro:content";
|
|||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "../../layouts/Layout.astro";
|
||||||
import { List, PanelRight } from "@lucide/astro";
|
import { List, PanelRight } from "@lucide/astro";
|
||||||
import NoteTOC from "../../components/NoteTOC.astro";
|
import NoteTOC from "../../components/NoteTOC.astro";
|
||||||
import NoteNavSidebar from "../../components/NoteNavSidebar.svelte";
|
import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
|
||||||
import NoteGraphSidebar from "../../components/NoteGraphSidebar.astro";
|
import NoteGraphSidebar from "../../components/NoteGraphSidebar.astro";
|
||||||
import NoteVars from "../../components/NoteVars.svelte";
|
import NoteVars from "../../components/NoteVars.svelte";
|
||||||
import { getCategory, extractLinks, extractHeadings } from "../../utils/notes";
|
import { getCategory, extractLinks, extractExternalLinks } from "../../utils/notes";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const notes = await getCollection("notes");
|
const notes = await getCollection("notes");
|
||||||
@@ -17,7 +17,7 @@ export async function getStaticPaths() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { entry } = Astro.props;
|
const { entry } = Astro.props;
|
||||||
const { Content } = await render(entry);
|
const { Content, headings: astroHeadings } = await render(entry);
|
||||||
|
|
||||||
const allNotes = await getCollection("notes");
|
const allNotes = await getCollection("notes");
|
||||||
const sortedNotes = allNotes.sort((a, b) =>
|
const sortedNotes = allNotes.sort((a, b) =>
|
||||||
@@ -60,12 +60,18 @@ const noteVars = [
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const headings = extractHeadings(entry.body ?? "");
|
const headings = astroHeadings.map((h) => ({ depth: h.depth, text: h.text, id: h.slug }));
|
||||||
|
const externalLinks = extractExternalLinks(entry.body ?? "");
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 768px) {
|
||||||
.drawer.lg\:drawer-open > .drawer-side,
|
.drawer.md\:drawer-open > .drawer-side {
|
||||||
|
top: 3rem;
|
||||||
|
height: calc(100vh - 3rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1280px) {
|
||||||
.drawer.xl\:drawer-open > .drawer-side {
|
.drawer.xl\:drawer-open > .drawer-side {
|
||||||
top: 3rem;
|
top: 3rem;
|
||||||
height: calc(100vh - 3rem);
|
height: calc(100vh - 3rem);
|
||||||
@@ -78,11 +84,11 @@ const headings = extractHeadings(entry.body ?? "");
|
|||||||
description={entry.data.description}
|
description={entry.data.description}
|
||||||
>
|
>
|
||||||
<main class="max-w-screen-2xl mx-auto">
|
<main class="max-w-screen-2xl mx-auto">
|
||||||
<div class="drawer lg:drawer-open min-h-[calc(100vh-3rem)]">
|
<div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
|
||||||
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
|
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
|
||||||
|
|
||||||
<div class="drawer-content flex min-h-[calc(100vh-3rem)] min-w-0">
|
<div class="drawer-content flex min-h-[calc(100vh-3rem)] min-w-0">
|
||||||
<div class="drawer drawer-end xl:drawer-open w-full">
|
<div class="drawer drawer-end xl:drawer-open w-full" id="right-drawer">
|
||||||
<input id="graph-drawer" type="checkbox" class="drawer-toggle" />
|
<input id="graph-drawer" type="checkbox" class="drawer-toggle" />
|
||||||
|
|
||||||
<div class="drawer-content flex flex-col min-w-0">
|
<div class="drawer-content flex flex-col min-w-0">
|
||||||
@@ -117,7 +123,7 @@ const headings = extractHeadings(entry.body ?? "");
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label
|
<label
|
||||||
for="nav-drawer"
|
for="nav-drawer"
|
||||||
class="btn btn-ghost btn-xs lg:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
|
class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
|
||||||
>
|
>
|
||||||
<List size={11} />
|
<List size={11} />
|
||||||
nav
|
nav
|
||||||
@@ -126,7 +132,7 @@ const headings = extractHeadings(entry.body ?? "");
|
|||||||
<label
|
<label
|
||||||
for="graph-drawer"
|
for="graph-drawer"
|
||||||
id="graph-toggle"
|
id="graph-toggle"
|
||||||
class="btn btn-ghost btn-xs font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
|
class="btn btn-ghost btn-xs xl:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
|
||||||
title="Toggle graph"
|
title="Toggle graph"
|
||||||
>
|
>
|
||||||
<PanelRight size={11} />
|
<PanelRight size={11} />
|
||||||
@@ -206,25 +212,26 @@ const headings = extractHeadings(entry.body ?? "");
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-side z-40">
|
<div class="drawer-side z-[60]">
|
||||||
<label
|
<label
|
||||||
for="graph-drawer"
|
for="graph-drawer"
|
||||||
aria-label="close sidebar"
|
aria-label="close sidebar"
|
||||||
class="drawer-overlay xl:hidden"></label>
|
class="drawer-overlay"></label>
|
||||||
<NoteGraphSidebar
|
<NoteGraphSidebar
|
||||||
entry={entry}
|
entry={entry}
|
||||||
graphNodes={graphNodes}
|
graphNodes={graphNodes}
|
||||||
graphEdges={graphEdges}
|
graphEdges={graphEdges}
|
||||||
forwardLinks={forwardLinks}
|
forwardLinks={forwardLinks}
|
||||||
backlinks={backlinks}
|
backlinks={backlinks}
|
||||||
|
externalLinks={externalLinks}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-side z-50">
|
<div class="drawer-side z-[70]">
|
||||||
|
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
<NoteNavSidebar
|
<NoteNavSidebar
|
||||||
client:load
|
|
||||||
notes={sortedNotes}
|
notes={sortedNotes}
|
||||||
currentEntry={entry}
|
currentEntry={entry}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
@@ -288,51 +295,7 @@ const headings = extractHeadings(entry.body ?? "");
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initXlGraphToggle() {
|
|
||||||
const graphDrawer = document.getElementById(
|
|
||||||
"graph-drawer",
|
|
||||||
) as HTMLInputElement | null;
|
|
||||||
if (!graphDrawer) return;
|
|
||||||
|
|
||||||
const outerDrawer = graphDrawer.closest<HTMLElement>(".drawer.drawer-end");
|
|
||||||
const xlQuery = window.matchMedia("(min-width: 1280px)");
|
|
||||||
const STORAGE_KEY = "notes-graph-sidebar";
|
|
||||||
|
|
||||||
function setXlSidebar(open: boolean) {
|
|
||||||
if (!outerDrawer) return;
|
|
||||||
if (open) {
|
|
||||||
outerDrawer.classList.add("xl:drawer-open");
|
|
||||||
} else {
|
|
||||||
outerDrawer.classList.remove("xl:drawer-open");
|
|
||||||
}
|
|
||||||
localStorage.setItem(STORAGE_KEY, open ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
const graphToggle = document.getElementById("graph-toggle");
|
|
||||||
graphToggle?.addEventListener("click", (e) => {
|
|
||||||
if (!xlQuery.matches) return;
|
|
||||||
e.preventDefault();
|
|
||||||
setXlSidebar(!outerDrawer?.classList.contains("xl:drawer-open"));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (xlQuery.matches) {
|
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
|
||||||
// Open by default unless user explicitly closed it
|
|
||||||
setXlSidebar(saved !== "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
xlQuery.addEventListener("change", (e) => {
|
|
||||||
if (!e.matches) {
|
|
||||||
outerDrawer?.classList.remove("xl:drawer-open");
|
|
||||||
} else {
|
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
|
||||||
setXlSidebar(saved !== "0");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("astro:page-load", () => {
|
document.addEventListener("astro:page-load", () => {
|
||||||
injectHeadingAnchors();
|
injectHeadingAnchors();
|
||||||
initXlGraphToggle();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "../../layouts/Layout.astro";
|
||||||
import NoteNavSidebar from "../../components/NoteNavSidebar.svelte";
|
import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
|
||||||
import { getCategory } from "../../utils/notes";
|
import { getCategory } from "../../utils/notes";
|
||||||
import { List } from "@lucide/astro";
|
import { List } from "@lucide/astro";
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ if (!categoryNotes) {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 768px) {
|
||||||
.drawer.lg\:drawer-open > .drawer-side {
|
.drawer.md\:drawer-open > .drawer-side {
|
||||||
top: 3rem;
|
top: 3rem;
|
||||||
height: calc(100vh - 3rem);
|
height: calc(100vh - 3rem);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ if (!categoryNotes) {
|
|||||||
description={`Notes on ${category}.`}
|
description={`Notes on ${category}.`}
|
||||||
>
|
>
|
||||||
<main class="max-w-screen-2xl mx-auto">
|
<main class="max-w-screen-2xl mx-auto">
|
||||||
<div class="drawer lg:drawer-open min-h-[calc(100vh-3rem)]">
|
<div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
|
||||||
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
|
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
|
||||||
|
|
||||||
<div class="drawer-content flex flex-col min-w-0">
|
<div class="drawer-content flex flex-col min-w-0">
|
||||||
@@ -74,7 +74,7 @@ if (!categoryNotes) {
|
|||||||
</div>
|
</div>
|
||||||
<label
|
<label
|
||||||
for="nav-drawer"
|
for="nav-drawer"
|
||||||
class="btn btn-ghost btn-xs lg:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
|
class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
|
||||||
>
|
>
|
||||||
<List size={11} />
|
<List size={11} />
|
||||||
nav
|
nav
|
||||||
@@ -163,9 +163,9 @@ if (!categoryNotes) {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-side z-50">
|
<div class="drawer-side z-[70]">
|
||||||
|
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
<NoteNavSidebar
|
<NoteNavSidebar
|
||||||
client:load
|
|
||||||
notes={allNotes}
|
notes={allNotes}
|
||||||
currentCategory={category}
|
currentCategory={category}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import repos from "../../data/repos.json";
|
|||||||
) : (
|
) : (
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{repos.map((repo) => (
|
{repos.map((repo) => (
|
||||||
<GiteaProjectCard repo={repo} />
|
<GiteaProjectCard displayBanner={true} repo={repo} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-2
@@ -36,6 +36,13 @@
|
|||||||
--noise: 0;
|
--noise: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drawer-side > aside > astro-island {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.btn:not(.btn-circle):not(.btn-square) {
|
.btn:not(.btn-circle):not(.btn-square) {
|
||||||
@apply rounded-lg;
|
@apply rounded-lg;
|
||||||
}
|
}
|
||||||
@@ -50,7 +57,9 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--color-base-content);
|
color: var(--color-base-content);
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
transition: background 0.15s ease, border-color 0.15s ease;
|
transition:
|
||||||
|
background 0.15s ease,
|
||||||
|
border-color 0.15s ease;
|
||||||
margin-block: 0.25rem;
|
margin-block: 0.25rem;
|
||||||
}
|
}
|
||||||
.link-card::after {
|
.link-card::after {
|
||||||
@@ -60,7 +69,9 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
transition:
|
||||||
|
opacity 0.15s ease,
|
||||||
|
transform 0.15s ease;
|
||||||
transform: translate(-4px, 4px);
|
transform: translate(-4px, 4px);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,34 @@ function slugify(text: string): string {
|
|||||||
.replace(/ +/g, "-");
|
.replace(/ +/g, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractExternalLinks(body: string): { url: string; label: string }[] {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const links: { url: string; label: string }[] = [];
|
||||||
|
|
||||||
|
// Markdown: [label](https://...)
|
||||||
|
const mdRe = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
|
||||||
|
let m;
|
||||||
|
while ((m = mdRe.exec(body)) !== null) {
|
||||||
|
if (!seen.has(m[2])) {
|
||||||
|
seen.add(m[2]);
|
||||||
|
links.push({ url: m[2], label: m[1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML: <a href="https://...">...</a> — use h4 content as label if present, else href host
|
||||||
|
const htmlRe = /<a\s[^>]*href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
|
||||||
|
while ((m = htmlRe.exec(body)) !== null) {
|
||||||
|
const url = m[1];
|
||||||
|
if (seen.has(url)) continue;
|
||||||
|
seen.add(url);
|
||||||
|
const h4 = m[2].match(/<h4[^>]*>([\s\S]*?)<\/h4>/);
|
||||||
|
const label = h4 ? h4[1].trim() : new URL(url).hostname;
|
||||||
|
links.push({ url, label });
|
||||||
|
}
|
||||||
|
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
export function extractLinks(body: string): string[] {
|
export function extractLinks(body: string): string[] {
|
||||||
const re = /\(\/notes\/([^)#\s]+)(?:#[^)\s]*)?\)/g;
|
const re = /\(\/notes\/([^)#\s]+)(?:#[^)\s]*)?\)/g;
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user