mirror of
https://github.com/andwati/andwati.com.git
synced 2026-10-18 01:12:53 +00:00
remove cc banner. added llms.txt
This commit is contained in:
@@ -10,9 +10,11 @@ clean:
|
||||
rm -rfd ./$(BUILD_DIR)
|
||||
|
||||
build:
|
||||
node scripts/generate-llms.mjs
|
||||
zola build
|
||||
|
||||
serve:
|
||||
node scripts/generate-llms.mjs
|
||||
zola serve
|
||||
|
||||
update-zola:
|
||||
|
||||
@@ -10,7 +10,8 @@ echo "Extracting Zola..."
|
||||
tar -xf zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz
|
||||
|
||||
echo "Building site with Zola..."
|
||||
node scripts/generate-llms.mjs
|
||||
./zola build
|
||||
|
||||
echo "Build complete! Generated files:"
|
||||
ls -la public/
|
||||
ls -la public/
|
||||
|
||||
+3
-1
@@ -5,6 +5,8 @@
|
||||
"wrangler": "^4"
|
||||
},
|
||||
"scripts": {
|
||||
"deploy": "zola build && wrangler deploy"
|
||||
"generate:llms": "node scripts/generate-llms.mjs",
|
||||
"build": "npm run generate:llms && zola build",
|
||||
"deploy": "npm run build && wrangler deploy"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const config = fs.readFileSync(path.join(root, 'zola.toml'), 'utf8');
|
||||
const postsDir = path.join(root, 'content', 'posts');
|
||||
const output = path.join(root, 'static', 'llms.txt');
|
||||
|
||||
function tomlString(source, key) {
|
||||
const match = source.match(new RegExp(`^${key}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, 'm'));
|
||||
return match ? JSON.parse(match[1]) : undefined;
|
||||
}
|
||||
|
||||
function frontMatter(source) {
|
||||
return source.match(/^\+\+\+\s*\n([\s\S]*?)\n\+\+\+/)?.[1];
|
||||
}
|
||||
|
||||
function markdownFiles(directory) {
|
||||
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const location = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) return markdownFiles(location);
|
||||
return entry.isFile() && entry.name.endsWith('.md') ? [location] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function postUrl(file, metadata, baseUrl) {
|
||||
const relative = path.relative(postsDir, file).replaceAll(path.sep, '/');
|
||||
const defaultSlug = relative.endsWith('/index.md')
|
||||
? relative.slice(0, -'/index.md'.length)
|
||||
: relative.slice(0, -'.md'.length);
|
||||
const slug = tomlString(metadata, 'slug') ?? defaultSlug;
|
||||
return `${baseUrl}/posts/${slug}/`;
|
||||
}
|
||||
|
||||
const baseUrl = tomlString(config, 'base_url')?.replace(/\/$/, '');
|
||||
const siteTitle = new URL(baseUrl).hostname;
|
||||
const summary = tomlString(config, 'llms_description') ?? tomlString(config, 'description');
|
||||
|
||||
const posts = markdownFiles(postsDir)
|
||||
.filter((file) => path.basename(file) !== '_index.md')
|
||||
.map((file) => {
|
||||
const metadata = frontMatter(fs.readFileSync(file, 'utf8'));
|
||||
if (!metadata || /^draft\s*=\s*true$/m.test(metadata)) return null;
|
||||
|
||||
const title = tomlString(metadata, 'title');
|
||||
const description = tomlString(metadata, 'description');
|
||||
const date = metadata.match(/^date\s*=\s*(\d{4}-\d{2}-\d{2})/m)?.[1];
|
||||
if (!title || !date) return null;
|
||||
|
||||
return { title, description, date, url: postUrl(file, metadata, baseUrl) };
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => b.date.localeCompare(a.date));
|
||||
|
||||
const lines = [
|
||||
`# ${siteTitle}`,
|
||||
'',
|
||||
`> ${summary}`,
|
||||
'',
|
||||
'This site contains articles in English, often with commands, code samples, worked explanations, and material aimed at readers learning by doing.',
|
||||
'',
|
||||
'## Main pages',
|
||||
'',
|
||||
`- [Home](${baseUrl}/): Introduction and latest articles.`,
|
||||
`- [About](${baseUrl}/about/): About the author and contact information.`,
|
||||
`- [Posts](${baseUrl}/posts/): All published articles.`,
|
||||
`- [Tags](${baseUrl}/tags/): Articles grouped by topic.`,
|
||||
`- [Archive](${baseUrl}/archive/): Chronological article archive.`,
|
||||
'',
|
||||
'## Published articles',
|
||||
'',
|
||||
...posts.map(({ title, description, url }) =>
|
||||
`- [${title}](${url})${description ? `: ${description.replace(/\s+/g, ' ').trim()}` : ''}`,
|
||||
),
|
||||
'',
|
||||
'## Feeds and discovery',
|
||||
'',
|
||||
`- [RSS feed](${baseUrl}/rss.xml): Full site feed in RSS format.`,
|
||||
`- [JSON feed](${baseUrl}/feed.json): Full site feed in JSON format.`,
|
||||
`- [Sitemap](${baseUrl}/sitemap.xml): Machine-readable index of public pages.`,
|
||||
'',
|
||||
];
|
||||
|
||||
fs.writeFileSync(output, lines.join('\n'));
|
||||
console.log(`Generated ${path.relative(root, output)} with ${posts.length} published articles.`);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -48,19 +48,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Sync cookieconsent dark mode reactively with the site theme
|
||||
const syncCCTheme = () => {
|
||||
const ccMain = document.getElementById('cc-main');
|
||||
if (!ccMain) return;
|
||||
ccMain.classList.toggle('cc--darkmode', htmlElement.classList.contains('dark'));
|
||||
};
|
||||
new MutationObserver(syncCCTheme).observe(htmlElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
});
|
||||
// Initial sync in case banner is already mounted
|
||||
setTimeout(syncCCTheme, 100);
|
||||
|
||||
// Sync Utterances (comments iframe) theme with the site theme.
|
||||
// The iframe only reads its initial `theme` attribute once, so later
|
||||
// changes must go through postMessage per the utterances protocol.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# andwati.com
|
||||
|
||||
> Ian Andwati's personal blog about mathematics, computer science, binary exploitation, reverse engineering, capture-the-flag competitions, offensive security, Linux, and software development.
|
||||
|
||||
This site contains articles in English, often with commands, code samples, worked explanations, and material aimed at readers learning by doing.
|
||||
|
||||
## Main pages
|
||||
|
||||
- [Home](https://andwati.com/): Introduction and latest articles.
|
||||
- [About](https://andwati.com/about/): About the author and contact information.
|
||||
- [Posts](https://andwati.com/posts/): All published articles.
|
||||
- [Tags](https://andwati.com/tags/): Articles grouped by topic.
|
||||
- [Archive](https://andwati.com/archive/): Chronological article archive.
|
||||
|
||||
## Published articles
|
||||
|
||||
- [The One Thing Nobody Tells You About WiFi Hotspots on Arch Linux](https://andwati.com/posts/wifi-hotspot-arch/): A minimal Arch install is great until NetworkManager silently fails to find a binary you didn't know you needed
|
||||
- [From C to Machine Code: What Actually Runs](https://andwati.com/posts/from-c-to-machine-code/): Before diving into how to break a program, we need to understand what the computer is actually executing
|
||||
- [The Single Byte That Kills Your Exploit: Understanding Endianness](https://andwati.com/posts/the-single-byte-that-kills-your-exploit-understanding-endianness/): Problem: Computers speak different languages, like people
|
||||
- [What is pwn?](https://andwati.com/posts/what-is-pwn/): No not that one
|
||||
- [ScriptCTF 2025 Writeup](https://andwati.com/posts/scriptctf2025/): ScriptCTF 2025 Writeup
|
||||
- [mmuctf 2025 writeup](https://andwati.com/posts/mmuctf2025/): mmuctf 2025 challenges writeup
|
||||
- [cognitive inertia](https://andwati.com/posts/inertia/): inertia
|
||||
- [Export default and module.exports in Javascript](https://andwati.com/posts/javascript-exporting-modules/): Modules are the building blocks of modern JavaScript applications, allowing for the encapsulation of code into reusable components. Two primary systems govern the exportation of these modules: export default in ES6 and module.exports in CommonJS.
|
||||
- [Setting up Obsidian sync in Windows](https://andwati.com/posts/obsidian-sync/): Syncing Obsidian in Windows with Windows Task Scheduler with git
|
||||
- [A Fresh Coat Of Paint](https://andwati.com/posts/a-fresh-coat-of-paint/): Migrating to Zola
|
||||
- [What say you?](https://andwati.com/posts/utterances-comments/): Adding a comment system by utterances
|
||||
- [Subresource Integrity](https://andwati.com/posts/subresource-integrity/): Understanding Subresource Integrity
|
||||
- [Install PostgreSQL on Arch | Manjaro | Garuda Linux](https://andwati.com/posts/install-postgresql-15-on-arch-manjaro-garuda-linux/): Getting PostgreSQL up and running on Arch Linux-based distros
|
||||
- [Semantic Versioning(SemVer) and Conventional Commits](https://andwati.com/posts/semantic-version-and-conventional-commits/): Adopting best practices in your git and build workflows.
|
||||
- [Integrating Netlify CMS](https://andwati.com/posts/integrating-netlify/): Integrating netlify cms
|
||||
- [Django Secret Key Tutorial](https://andwati.com/posts/generate-django-secret-key/): Managing secrets
|
||||
- [Hello World](https://andwati.com/posts/hello-world/): Hello world
|
||||
- [Configure Vue.js app With Tailwind CSS](https://andwati.com/posts/vuejs-tailwind/): Tailwindcss in vuejs
|
||||
|
||||
## Feeds and discovery
|
||||
|
||||
- [RSS feed](https://andwati.com/rss.xml): Full site feed in RSS format.
|
||||
- [JSON feed](https://andwati.com/feed.json): Full site feed in JSON format.
|
||||
- [Sitemap](https://andwati.com/sitemap.xml): Machine-readable index of public pages.
|
||||
+6
-143
@@ -90,153 +90,16 @@
|
||||
</script>
|
||||
|
||||
{% if config.extra.google_analytics %}
|
||||
<!-- Google Analytics — loaded only after cookie consent -->
|
||||
<!-- Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ config.extra.google_analytics }}"></script>
|
||||
<script>
|
||||
// GA loader called by cookieconsent on consent
|
||||
function loadGoogleAnalytics() {
|
||||
if (window._gaLoaded) return;
|
||||
window._gaLoaded = true;
|
||||
var s = document.createElement('script');
|
||||
s.async = true;
|
||||
s.src = 'https://www.googletagmanager.com/gtag/js?id={{ config.extra.google_analytics }}';
|
||||
document.head.appendChild(s);
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
window.gtag = gtag;
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{ config.extra.google_analytics }}', { anonymize_ip: true });
|
||||
}
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{ config.extra.google_analytics }}', { anonymize_ip: true });
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<!-- CookieConsent v3 (self-hosted) — CSS deferred, banner doesn't need to paint immediately -->
|
||||
<link rel="preload" href="{{ get_url(path="css/cookieconsent.css") }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="{{ get_url(path="css/cookieconsent.css") }}"></noscript>
|
||||
<script defer src="{{ get_url(path="js/cookieconsent.umd.js") }}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof CookieConsent === 'undefined') return;
|
||||
|
||||
CookieConsent.run({
|
||||
cookie: { name: 'cc_cookie' },
|
||||
|
||||
// Mirror the site's html.dark class — banner switches with the theme toggle
|
||||
darkMode: ['class', 'dark'],
|
||||
|
||||
guiOptions: {
|
||||
consentModal: {
|
||||
layout: 'bar inline',
|
||||
position: 'bottom',
|
||||
flipButtons: false,
|
||||
equalWeightButtons: false
|
||||
},
|
||||
preferencesModal: {
|
||||
layout: 'box',
|
||||
position: 'right'
|
||||
}
|
||||
},
|
||||
|
||||
onConsent: function() {
|
||||
if (CookieConsent.acceptedCategory('analytics')) {
|
||||
if (typeof loadGoogleAnalytics === 'function') loadGoogleAnalytics();
|
||||
}
|
||||
},
|
||||
onChange: function() {
|
||||
if (CookieConsent.acceptedCategory('analytics')) {
|
||||
if (typeof loadGoogleAnalytics === 'function') loadGoogleAnalytics();
|
||||
}
|
||||
},
|
||||
|
||||
categories: {
|
||||
necessary: { enabled: true, readOnly: true },
|
||||
analytics: {
|
||||
enabled: false,
|
||||
readOnly: false,
|
||||
autoClear: { cookies: [{ name: /^(_ga|_gid|_gat)/ }] }
|
||||
}
|
||||
},
|
||||
|
||||
language: {
|
||||
default: 'en',
|
||||
translations: {
|
||||
en: {
|
||||
consentModal: {
|
||||
title: 'We use cookies',
|
||||
description: 'This site uses Google Analytics to understand how visitors interact with content. No personal data is sold or shared.',
|
||||
acceptAllBtn: 'Accept',
|
||||
acceptNecessaryBtn: 'Reject',
|
||||
showPreferencesBtn: 'Manage preferences'
|
||||
},
|
||||
preferencesModal: {
|
||||
title: 'Cookie preferences',
|
||||
acceptAllBtn: 'Accept all',
|
||||
acceptNecessaryBtn: 'Reject all',
|
||||
savePreferencesBtn: 'Save preferences',
|
||||
closeIconLabel: 'Close',
|
||||
sections: [
|
||||
{
|
||||
title: 'Strictly necessary',
|
||||
description: 'Required for the site to function. Cannot be disabled.',
|
||||
linkedCategory: 'necessary'
|
||||
},
|
||||
{
|
||||
title: 'Analytics',
|
||||
description: 'Google Analytics — helps understand which content is most useful. No personal data sold.',
|
||||
linkedCategory: 'analytics'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- CookieConsent: accent colors pulled from CSS custom properties -->
|
||||
<style>
|
||||
/* Light mode */
|
||||
#cc-main {
|
||||
--cc-btn-primary-bg: var(--accent-color);
|
||||
--cc-btn-primary-color: #ffffff;
|
||||
--cc-btn-primary-border-color: var(--accent-color);
|
||||
--cc-btn-primary-hover-bg: var(--accent-hover);
|
||||
--cc-btn-primary-hover-border-color: var(--accent-hover);
|
||||
--cc-toggle-on-bg: var(--accent-color);
|
||||
--cc-font-family: 'Source Serif 4', Georgia, serif;
|
||||
--cc-btn-border-radius: 2px;
|
||||
--cc-modal-border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Dark mode — synced with html.dark via darkMode: ['class', 'dark'] */
|
||||
#cc-main.cc--darkmode {
|
||||
--cc-bg: #1b1a17;
|
||||
--cc-primary-color: #e9e7de;
|
||||
--cc-secondary-color: #a5a49a;
|
||||
--cc-btn-primary-bg: var(--accent-color);
|
||||
--cc-btn-primary-color: #131311;
|
||||
--cc-btn-primary-border-color: var(--accent-color);
|
||||
--cc-btn-primary-hover-bg: var(--accent-hover);
|
||||
--cc-btn-primary-hover-border-color: var(--accent-hover);
|
||||
--cc-btn-secondary-bg: rgba(233,231,222,0.06);
|
||||
--cc-btn-secondary-color: #a5a49a;
|
||||
--cc-btn-secondary-border-color: rgba(233,231,222,0.1);
|
||||
--cc-btn-secondary-hover-bg: rgba(233,231,222,0.12);
|
||||
--cc-btn-secondary-hover-color: #e9e7de;
|
||||
--cc-separator-border-color: rgba(233,231,222,0.1);
|
||||
--cc-cookie-category-block-bg: rgba(233,231,222,0.04);
|
||||
--cc-cookie-category-block-border: rgba(233,231,222,0.08);
|
||||
--cc-toggle-on-bg: var(--accent-color);
|
||||
--cc-footer-bg: #131311;
|
||||
--cc-footer-border-color: rgba(233,231,222,0.08);
|
||||
}
|
||||
|
||||
/* Accent top border, no default box-shadow */
|
||||
#cc-main .cm,
|
||||
#cc-main .pm { box-shadow: none !important; }
|
||||
#cc-main .cm { border-top: 2px solid var(--accent-color) !important; }
|
||||
</style>
|
||||
|
||||
{% block head %}{% endblock head %}
|
||||
|
||||
<!-- Font preloads (eliminate FOUT on first paint) -->
|
||||
|
||||
@@ -18,6 +18,8 @@ lazy_async_image = true
|
||||
theme = "gruvbox-dark-medium"
|
||||
|
||||
[extra]
|
||||
llms_description = "Ian Andwati's personal blog about mathematics, computer science, binary exploitation, reverse engineering, capture-the-flag competitions, offensive security, Linux, and software development."
|
||||
|
||||
# Analytics
|
||||
google_analytics = "G-4HGRQ9QNPD"
|
||||
google_site_verification = ""
|
||||
|
||||
Reference in New Issue
Block a user