Skip to content

Commit 500334a

Browse files
committed
Add /guides section; move SNMP + Docker how-tos out of /blog
New /guides route (flat /guides/<slug>) for evergreen guides/reference, split from /blog (dated posts) and /comparisons (vendor tables). - Move #18 (snmp-network-topology-mapping) and #19 (visualize-docker- containers-network) from /blog to /guides; 301 redirects from old URLs - Repoint internal links; move #18 screenshots to /guides/ - Footer: add Guides; move Discoverable Services + Security to Product - Narrow docs /guides/* redirect catch-all to explicit per-slug recovery so it doesn't shadow the new marketing /guides pages - Sitemap: add /guides section and pages
1 parent 3418ed3 commit 500334a

14 files changed

Lines changed: 713 additions & 22 deletions

src/lib/blog/automated-network-documentation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ None of this requires installing agents on endpoints. A single scanner on the ne
4545

4646
## How Automated Discovery Works, Step by Step
4747

48-
The protocols above sound abstract until you see how they fit together. If you want to see exactly what that looks like without a tool, here's [how to map network topology with SNMP by hand](/blog/snmp-network-topology-mapping). Here's what actually happens when an automated documentation tool scans your network:
48+
The protocols above sound abstract until you see how they fit together. If you want to see exactly what that looks like without a tool, here's [how to map network topology with SNMP by hand](/guides/snmp-network-topology-mapping). Here's what actually happens when an automated documentation tool scans your network:
4949

5050
**Step 1: Find live hosts.** The scanner sends ARP requests across your subnets. Every device that responds reveals its IP address and MAC address. This is fast (a /24 subnet takes seconds) and catches everything from servers to printers to IoT devices that don't respond to ping.
5151

src/lib/blog/network-documentation-template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ I did what any good homelabber would do - went the technical overkill route and
7777

7878
If you've spent any time on r/selfhosted, you've seen those beautiful hand-drawn network diagrams. They're great for learning and sharing, but they have the same problem as templates. The moment you change something, the diagram is wrong. I wanted that same utility, but alive. Something that stays current without you thinking about it.
7979

80-
[Scanopy](/) deploys a lightweight daemon on your network. It discovers devices and [over 200 services](/services), maps connections [via SNMP](/blog/snmp-network-topology-mapping), and generates an interactive topology map that stays current with scheduled scans. It even [visualizes Docker containers](/blog/visualize-docker-containers-network) and what they're running. No spreadsheets. No manual entry. The documentation stays current because it's generated from the actual network, not from someone's memory.
80+
[Scanopy](/) deploys a lightweight daemon on your network. It discovers devices and [over 200 services](/services), maps connections [via SNMP](/guides/snmp-network-topology-mapping), and generates an interactive topology map that stays current with scheduled scans. It even [visualizes Docker containers](/guides/visualize-docker-containers-network) and what they're running. No spreadsheets. No manual entry. The documentation stays current because it's generated from the actual network, not from someone's memory.
8181

8282
Deploy the daemon, point it at your subnets, and you get a live network map in minutes. Scanopy runs scheduled scans, so the map stays current as your network changes. New device? It shows up on the next scan.
8383

src/lib/components/Footer.svelte

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@
7373
<li>
7474
<a href="/changelog" class="text-sm text-gray-400 hover:text-white">Changelog</a>
7575
</li>
76+
<li>
77+
<a href="/services" class="text-sm text-gray-400 hover:text-white"
78+
>Discoverable Services</a
79+
>
80+
</li>
81+
<li>
82+
<a href="/docs/reference/security" class="text-sm text-gray-400 hover:text-white"
83+
>Security</a
84+
>
85+
</li>
7686
</ul>
7787
</div>
7888

@@ -83,19 +93,12 @@
8393
<li>
8494
<a href="/docs" class="text-sm text-gray-400 hover:text-white">Documentation</a>
8595
</li>
86-
<li><a href="/blog" class="text-sm text-gray-400 hover:text-white">Blog</a></li>
87-
<li>
88-
<a href="/comparisons" class="text-sm text-gray-400 hover:text-white">Comparisons</a>
89-
</li>
9096
<li>
91-
<a href="/services" class="text-sm text-gray-400 hover:text-white"
92-
>Discoverable Services</a
93-
>
97+
<a href="/guides" class="text-sm text-gray-400 hover:text-white">Guides</a>
9498
</li>
99+
<li><a href="/blog" class="text-sm text-gray-400 hover:text-white">Blog</a></li>
95100
<li>
96-
<a href="/docs/reference/security" class="text-sm text-gray-400 hover:text-white"
97-
>Security</a
98-
>
101+
<a href="/comparisons" class="text-sm text-gray-400 hover:text-white">Comparisons</a>
99102
</li>
100103
</ul>
101104
</div>

src/lib/blog/snmp-network-topology-mapping.md renamed to src/lib/guides/snmp-network-topology-mapping.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,13 @@ The difference is scale and repetition. Scanopy does this across your entire net
246246

247247
Start with a single device. Here is the same SNMP data you walked by hand, the system identity from Step 1 and the interface list from Step 2, pulled and structured automatically for switch-core-01:
248248

249-
![Scanopy host detail for switch-core-01: the SNMP System Information panel with the C2960 system description, system OID 1.3.6.1.4.1.9.1.1208, and location, the same fields the System MIB walk returned in Step 1.](/blog/snmp-network-topology-mapping/host-modal-details.png)
249+
![Scanopy host detail for switch-core-01: the SNMP System Information panel with the C2960 system description, system OID 1.3.6.1.4.1.9.1.1208, and location, the same fields the System MIB walk returned in Step 1.](/guides/snmp-network-topology-mapping/host-modal-details.png)
250250

251-
![Scanopy interface list for switch-core-01: GigabitEthernet0/1 to 0/3 and Vlan10, with Gi0/1 expanded to show ifName, ifType, speed, admin and operational status, and the alias "Uplink to switch-access-01", the same fields the Interface MIB walk returned in Step 2.](/blog/snmp-network-topology-mapping/host-modal-interfaces.png)
251+
![Scanopy interface list for switch-core-01: GigabitEthernet0/1 to 0/3 and Vlan10, with Gi0/1 expanded to show ifName, ifType, speed, admin and operational status, and the alias "Uplink to switch-access-01", the same fields the Interface MIB walk returned in Step 2.](/guides/snmp-network-topology-mapping/host-modal-interfaces.png)
252252

253253
Now the part that took real work in Step 6, correlating neighbors into a topology, done for you across a whole network. This is an L2 (physical) view of a small datacenter:
254254

255-
![Scanopy L2 physical topology view: a switch (dc-switch-01) with labeled trunk and VLAN 20 server ports linked to a firewall, a Proxmox hypervisor, a Docker host, and an HAProxy load balancer, each link showing the port speed and MAC address.](/blog/snmp-network-topology-mapping/l2.png)
255+
![Scanopy L2 physical topology view: a switch (dc-switch-01) with labeled trunk and VLAN 20 server ports linked to a firewall, a Proxmox hypervisor, a Docker host, and an HAProxy load balancer, each link showing the port speed and MAC address.](/guides/snmp-network-topology-mapping/l2.png)
256256

257257
Everything on that map traces back to an OID you queried by hand. The port labels (Firewall uplink, Proxmox uplink, Docker host uplink) are `ifAlias` values from the Interface MIB. The 10G speeds come from `ifHighSpeed`, the MAC addresses from `ifPhysAddress`, and the lines between ports are LLDP neighbor data. The down ports are collapsed into a single group off `ifOperStatus`. Same data, same MIBs, correlated and drawn automatically, then re-walked on every scan so it never goes stale.
258258

src/lib/blog/visualize-docker-containers-network.md renamed to src/lib/guides/visualize-docker-containers-network.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,4 @@ Either way, detection picks up containers wherever they run, so a mixed homelab
9191

9292
Scanopy is not a container manager. It will not start, stop, or deploy anything, and it is not trying to replace Portainer, Komodo, or your orchestrator. Those tools manage containers. Scanopy shows you where your containers sit on the network and what they connect to. They work well side by side: manage in your container tool, see the topology in Scanopy. It is a documentation tool, and it stays in that lane on purpose.
9393

94-
If you are mapping the rest of your gear too, the same scan that finds your containers also walks your switches and routers over SNMP. Here is the deeper version of that: [exploring your network topology with SNMP](/blog/snmp-network-topology-mapping). And Scanopy fingerprints [the services running on each host](/services), so a host is not just an IP, it is "the box running Postgres, Nginx, and twelve containers."
94+
If you are mapping the rest of your gear too, the same scan that finds your containers also walks your switches and routers over SNMP. Here is the deeper version of that: [exploring your network topology with SNMP](/guides/snmp-network-topology-mapping). And Scanopy fingerprints [the services running on each host](/services), so a host is not just an IP, it is "the box running Postgres, Nginx, and twelve containers."

src/routes/guides/+page.server.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { marked } from 'marked';
2+
3+
// Guides / reference content lives at /guides. The product docs guides are separate, under
4+
// /docs/guides; static/_redirects has explicit per-slug recovery for old /guides/<docs-slug> links.
5+
interface Resource {
6+
title: string;
7+
description: string;
8+
date: string;
9+
keyword: string;
10+
slug: string;
11+
content: string;
12+
}
13+
14+
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
15+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
16+
if (!match) {
17+
return { frontmatter: {}, body: content };
18+
}
19+
20+
const frontmatter: Record<string, string> = {};
21+
match[1].split('\n').forEach((line) => {
22+
const [key, ...valueParts] = line.split(':');
23+
if (key && valueParts.length) {
24+
frontmatter[key.trim()] = valueParts
25+
.join(':')
26+
.trim()
27+
.replace(/^["'](.*)["']$/, '$1');
28+
}
29+
});
30+
31+
return { frontmatter, body: match[2] };
32+
}
33+
34+
export async function load() {
35+
const resourceFiles = import.meta.glob('/src/lib/guides/*.md', {
36+
query: '?raw',
37+
import: 'default'
38+
});
39+
40+
const resources: Resource[] = [];
41+
42+
for (const [path, loader] of Object.entries(resourceFiles)) {
43+
const raw = (await loader()) as string;
44+
const { frontmatter, body } = parseFrontmatter(raw);
45+
46+
const slug = frontmatter.slug || path.split('/').pop()?.replace('.md', '') || '';
47+
48+
resources.push({
49+
title: frontmatter.title || '',
50+
description: frontmatter.description || '',
51+
date: frontmatter.date || '',
52+
keyword: frontmatter.keyword || '',
53+
slug,
54+
content: await marked.parse(body)
55+
});
56+
}
57+
58+
resources.sort((a, b) => {
59+
if (!a.date || !b.date) return 0;
60+
return new Date(b.date).getTime() - new Date(a.date).getTime();
61+
});
62+
63+
return { resources };
64+
}

src/routes/guides/+page.svelte

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<script lang="ts">
2+
import { BookOpen } from 'lucide-svelte';
3+
4+
interface Resource {
5+
title: string;
6+
description: string;
7+
date: string;
8+
keyword: string;
9+
slug: string;
10+
}
11+
12+
interface PageData {
13+
resources: Resource[];
14+
}
15+
16+
let { data }: { data: PageData } = $props();
17+
18+
function formatDate(dateStr: string): string {
19+
if (!dateStr) return '';
20+
const date = new Date(dateStr + 'T12:00:00');
21+
return date.toLocaleDateString('en-US', {
22+
year: 'numeric',
23+
month: 'long',
24+
day: 'numeric'
25+
});
26+
}
27+
</script>
28+
29+
<svelte:head>
30+
<title>Guides - Scanopy</title>
31+
<meta
32+
name="description"
33+
content="In-depth guides and reference on network documentation: automated discovery, topology mapping, integrations, and compliance."
34+
/>
35+
<link rel="canonical" href="https://scanopy.net/guides" />
36+
37+
<meta property="og:title" content="Guides - Scanopy" />
38+
<meta
39+
property="og:description"
40+
content="In-depth guides and reference on network documentation: automated discovery, topology mapping, integrations, and compliance."
41+
/>
42+
<meta property="og:type" content="website" />
43+
<meta property="og:url" content="https://scanopy.net/guides" />
44+
45+
<meta property="og:image" content="https://scanopy.net/social.webp" />
46+
47+
<meta name="twitter:card" content="summary_large_image" />
48+
<meta name="twitter:title" content="Guides - Scanopy" />
49+
<meta
50+
name="twitter:description"
51+
content="In-depth guides and reference on network documentation: automated discovery, topology mapping, integrations, and compliance."
52+
/>
53+
<meta name="twitter:image" content="https://scanopy.net/social.webp" />
54+
55+
{@html `<script type="application/ld+json">${JSON.stringify({
56+
'@context': 'https://schema.org',
57+
'@type': 'CollectionPage',
58+
name: 'Guides - Scanopy',
59+
description:
60+
'In-depth guides and reference on network documentation: automated discovery, topology mapping, integrations, and compliance.',
61+
url: 'https://scanopy.net/guides',
62+
isPartOf: {
63+
'@type': 'WebSite',
64+
name: 'Scanopy',
65+
url: 'https://scanopy.net'
66+
},
67+
mainEntity: {
68+
'@type': 'ItemList',
69+
itemListElement: data.resources.map((resource, i) => ({
70+
'@type': 'ListItem',
71+
position: i + 1,
72+
url: `https://scanopy.net/guides/${resource.slug}`,
73+
name: resource.title
74+
}))
75+
}
76+
})}</script>`}
77+
</svelte:head>
78+
79+
<section class="py-20">
80+
<div class="container mx-auto max-w-3xl px-4">
81+
<div class="mb-12 text-center">
82+
<div class="mb-4 flex justify-center">
83+
<BookOpen class="h-12 w-12 text-blue-400" />
84+
</div>
85+
<h1 class="mb-4 text-4xl font-bold text-white lg:text-5xl">Guides</h1>
86+
<p class="text-xl text-gray-400">
87+
In-depth, evergreen guides on documenting, mapping, and understanding your network.
88+
</p>
89+
</div>
90+
91+
{#if data.resources.length === 0}
92+
<div class="text-center">
93+
<p class="text-gray-400">No guides yet. Check back soon!</p>
94+
</div>
95+
{:else}
96+
<div class="space-y-8">
97+
{#each data.resources as resource (resource.slug)}
98+
<article
99+
class="rounded-lg border border-gray-800 p-6 transition-colors hover:border-gray-700"
100+
>
101+
<a href="/guides/{resource.slug}" class="block">
102+
{#if resource.date}
103+
<time class="text-sm text-gray-500" datetime={resource.date}>
104+
{formatDate(resource.date)}
105+
</time>
106+
{/if}
107+
<h2 class="mt-1 text-2xl font-bold text-white hover:text-blue-400">
108+
{resource.title}
109+
</h2>
110+
{#if resource.description}
111+
<p class="mt-2 text-gray-400">{resource.description}</p>
112+
{/if}
113+
</a>
114+
</article>
115+
{/each}
116+
</div>
117+
{/if}
118+
</div>
119+
</section>

0 commit comments

Comments
 (0)