Compare commits

..

10 commits

16 changed files with 644 additions and 11 deletions

1
.gitignore vendored
View file

@ -24,6 +24,7 @@ _site
# nix packages
.direnv
result
# Environment variables
.env

7
_data/site.js Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
// Set SITE_URL environment variable to build for different domains:
// SITE_URL=https://blog.jan-leila.com pnpm run build
// SITE_URL=https://volpe.jan-leila.com pnpm run build
// SITE_URL=http://your-onion-address.onion pnpm run build
url: process.env.SITE_URL || "http://localhost:8080"
};

View file

@ -3,9 +3,47 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }} | Volpe</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/prism.css">
<title>Volpe{% if title %} | {{ title }}{% endif %}</title>
{% if description %}
<meta name="description" content="{{ description }}">
{% endif %}
{# Critical CSS inlined for faster initial render #}
<style>
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--text-color: #333;
--background-color: #fff;
--border-color: #ddd;
--code-background: #f5f5f5;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--background-color);
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
header { margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border-color); }
header nav a { color: var(--primary-color); text-decoration: none; font-weight: bold; font-size: 1.2rem; }
main { min-height: calc(100vh - 200px); }
h1, h2, h3 { color: var(--primary-color); margin: 1.5rem 0 1rem; }
h1 { font-size: 2rem; }
footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border-color); text-align: center; color: #666; font-size: 0.9rem; }
</style>
{# Preload full stylesheet with cache-busted filename #}
<link rel="preload" href="{{ 'style.css' | cssHash }}" as="style">
<link rel="stylesheet" href="{{ 'style.css' | cssHash }}">
{# Defer prism.css - only needed for syntax highlighting #}
<link rel="preload" href="{{ 'prism.css' | cssHash }}" as="style">
<link rel="stylesheet" href="{{ 'prism.css' | cssHash }}" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="{{ 'prism.css' | cssHash }}"></noscript>
</head>
<body>
<header>
@ -19,7 +57,12 @@
</main>
<footer>
<p>&copy; {{ page.date.getFullYear() }} Volpe</p>
<div>
<a href="https://cyberia.click/prev.cgi?source=lambda"><- prev</a>
<a href="https://cyberia.click">cyberia webring</a>
<a href="https://cyberia.click/next.cgi?source=lambda">next -></a>
</div>
<div>&copy; {{ page.date.getFullYear() }} Volpe</div>
</footer>
</body>
</html>

View file

@ -4,8 +4,26 @@ const markdownItFootnote = require("markdown-it-footnote");
const markdownItMermaid = require('markdown-it-mermaid').default
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");
const { DateTime } = require("luxon");
const cssHashCache = {};
const getCssHash = (cssFile) => {
if (cssHashCache[cssFile]) return cssHashCache[cssFile];
const cssPath = path.join(__dirname, "css", cssFile);
try {
const content = fs.readFileSync(cssPath, "utf-8");
const hash = crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
cssHashCache[cssFile] = hash;
return hash;
} catch (e) {
console.warn(`Could not hash CSS file: ${cssFile}`);
return null;
}
};
const extractTags = (content, mdInstance) => {
if (!content) return [];
@ -36,7 +54,7 @@ const getPostTags = (post, mdInstance) => {
}
const isReleased = (post) => {
return post.data.unreleased !== true;
return post.data.released !== false;
}
const markdownItHashtag = (md) => {
@ -220,7 +238,39 @@ module.exports = (eleventyConfig) => {
return tagMap;
});
eleventyConfig.addPassthroughCopy("css");
// Cache busting filter: returns hashed CSS filename
eleventyConfig.addFilter("cssHash", (cssFile) => {
const hash = getCssHash(cssFile);
const ext = path.extname(cssFile);
const base = path.basename(cssFile, ext);
return `/css/${base}.${hash}${ext}`;
});
eleventyConfig.on("eleventy.before", async () => {
const cssDir = path.join(__dirname, "css");
const outputCssDir = path.join(__dirname, "_site", "css");
if (!fs.existsSync(outputCssDir)) {
fs.mkdirSync(outputCssDir, { recursive: true });
}
const cssFiles = fs.readdirSync(cssDir).filter(f => f.endsWith(".css"));
for (const cssFile of cssFiles) {
const hash = getCssHash(cssFile);
const ext = path.extname(cssFile);
const base = path.basename(cssFile, ext);
const hashedName = `${base}${hash == null ? '' : `.${hash}`}${ext}`;
fs.copyFileSync(
path.join(cssDir, cssFile),
path.join(outputCssDir, hashedName)
);
}
});
eleventyConfig.addPassthroughCopy("robots.txt");
eleventyConfig.ignores.add("README.md");
return {
dir: {

View file

@ -1,5 +1,5 @@
{
description = "A Nix-flake-based Node.js development environment";
description = "Volpe Blog";
inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.*.tar.gz";
@ -19,9 +19,46 @@
pkgs = import nixpkgs {inherit overlays system;};
});
in {
packages = forEachSupportedSystem ({pkgs}: {
default = pkgs.callPackage ./nix/package.nix {
siteUrl = "https://blog.jan-leila.com";
};
blog = pkgs.callPackage ./nix/package.nix {
siteUrl = "https://blog.jan-leila.com";
};
volpe = pkgs.callPackage ./nix/package.nix {
siteUrl = "https://volpe.jan-leila.com";
};
});
devShells = forEachSupportedSystem ({pkgs}: {
default = pkgs.mkShell {
packages = with pkgs; [node2nix nodejs pnpm sqlite];
packages = with pkgs; [
nodejs
nodePackages.pnpm
];
};
});
nixosConfigurations.volpe = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
{nixpkgs.overlays = overlays;}
./nix/configuration.nix
];
};
# Deployment helper - use with: nix run .#deploy
apps = forEachSupportedSystem ({pkgs}: {
deploy = {
type = "app";
program = toString (pkgs.writeShellScript "deploy-volpe" ''
set -e
echo "Building and deploying to cyberian@69.61.19.180..."
nixos-rebuild switch --flake .#volpe \
--target-host cyberian@69.61.19.180 \
--sudo
'');
};
});
};

View file

@ -1,8 +1,13 @@
---
layout: base.njk
title: Blog
description: Welcome to my website! I write about tech, politics, food, and hobby projects. Stay a while, make some friends, learn something, and help your neighbors.
---
<section class="intro">
<p>{{ description }}</p>
</section>
<h1>Blog Posts</h1>
<ul class="post-list">

42
nix/configuration.nix Normal file
View file

@ -0,0 +1,42 @@
{...}: {
imports = [
./hardware-configuration.nix
./module.nix
];
nix.settings.experimental-features = ["nix-command" "flakes"];
nix.settings.trusted-users = ["root" "cyberian"];
swapDevices = [
{
device = "/swapfile";
size = 2 * 1024; # 2GB
}
];
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/vda";
system.stateVersion = "24.11";
users.users.cyberian = {
isNormalUser = true;
extraGroups = ["wheel"];
};
security.sudo.wheelNeedsPassword = false;
services.qemuGuest.enable = true;
services.acpid.enable = true;
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
services.volpe = {
enable = true;
domain = "blog.jan-leila.com";
extraDomains = ["volpe.jan-leila.com"];
enableACME = true;
acmeEmail = "leyla@jan-leila.com";
};
}

View file

@ -0,0 +1,30 @@
# Do not modify this file! It was generated by 'nixos-generate-config'
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = ["ata_piix" "virtio_pci" "floppy" "sr_mod" "virtio_blk"];
boot.initrd.kernelModules = [];
boot.kernelModules = ["kvm-amd"];
boot.extraModulePackages = [];
fileSystems."/" = {
device = "/dev/disk/by-uuid/1195bb4c-ddcb-4ad6-8109-d28170f169b1";
fsType = "ext4";
};
swapDevices = [];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

83
nix/module.nix Normal file
View file

@ -0,0 +1,83 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.services.volpe;
mkPkg = domain:
pkgs.callPackage ./package.nix {
siteUrl = "https://${domain}";
};
allDomains = [cfg.domain] ++ cfg.extraDomains;
mkVirtualHost = domain: {
root = "${mkPkg domain}";
forceSSL = cfg.enableACME;
enableACME = cfg.enableACME;
locations."/" = {
tryFiles = "$uri $uri/ /index.html";
};
# Cache static assets (CSS, JS, images) for 1 year with immutable
locations."~* \\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$" = {
extraConfig = ''
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
'';
};
};
in {
options.services.volpe = {
enable = lib.mkEnableOption "volpe blog";
domain = lib.mkOption {
type = lib.types.str;
description = "Primary domain name for nginx virtual host.";
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Additional domain names, each gets its own virtualHost.";
};
enableACME = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to enable ACME (Let's Encrypt) for SSL certificates.";
};
acmeEmail = lib.mkOption {
type = lib.types.str;
default = "";
description = "Email address for ACME certificate registration.";
};
};
config = lib.mkIf cfg.enable {
services.nginx = {
enable = true;
recommendedTlsSettings = cfg.enableACME;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
# Create a virtualHost for each domain
virtualHosts = lib.listToAttrs (map (domain: {
name = domain;
value = mkVirtualHost domain;
})
allDomains);
};
security.acme = lib.mkIf cfg.enableACME {
acceptTerms = true;
defaults.email = cfg.acmeEmail;
};
networking.firewall.allowedTCPPorts = [80 443];
};
}

48
nix/package.nix Normal file
View file

@ -0,0 +1,48 @@
{
lib,
stdenv,
nodejs_latest,
pnpm_10,
fetchPnpmDeps,
pnpmConfigHook,
siteUrl,
}: let
nodejs = nodejs_latest;
pnpm = pnpm_10;
in
stdenv.mkDerivation (finalAttrs: {
pname = "volpe";
version = "1.0.0";
src = lib.cleanSource ./..;
nativeBuildInputs = [
nodejs
pnpm
pnpmConfigHook
];
# fetchPnpmDeps creates the offline store
pnpmDeps = fetchPnpmDeps {
inherit (finalAttrs) pname version src;
hash = "sha256-AiyDVGSxlfdqzuei0N0F3UOXlQVztxqyU7gBkZbUqOI=";
fetcherVersion = 3; # pnpm store version
};
buildPhase = ''
runHook preBuild
SITE_URL="${siteUrl}" pnpm build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r _site/* $out/
runHook postInstall
'';
})

View file

@ -6,6 +6,7 @@ pagination:
resolve: keys
permalink: /post/{{ slugData }}/
layout: base.njk
excludeFromSitemap: true
---
{% set posts = collections.postsBySlug[slugData] %}

View file

@ -0,0 +1,74 @@
---
released: false
---
Richard David Wolff about socialists and socialism
https://www.youtube.com/watch?v=_9lSwELJsGs&t=486
The Politics Of The Legend Of Korra
https://www.youtube.com/playlist?list=PLPVfWSlNRVc5H-66tMKGa_Zb2i7C08agd
Beyond the Genderbread Person - The Failures of Biological Sex
https://www.youtube.com/watch?v=TcOhfOrz0HM
What The %$@! Is A Nonprofit!?
https://youtu.be/DYy9OL-VBck
The Alt-Right Playbook
https://youtube.com/playlist?list=PLJA_jUddXvY7v0VkYRbANnTnzkA_HMFtQ
the original Mario Bros.
https://www.youtube.com/watch?v=NYZOngyZvaI
Capital
https://www.marxists.org/archive/marx/works/download/pdf/Capital-Volume-I.pdf
The Conquest of Bread
https://theanarchistlibrary.org/library/petr-kropotkin-the-conquest-of-bread/
The Problem with r/anti-work: What is Anti-work?
https://www.youtube.com/watch?v=28D5K28J8qM
The Truth About Declawing Your Cat
http://www.youtube.com/watch?v=gFeC3lM02sc#t=478
Cities Skylines: Power, Politics, & Planning
https://youtube.com/playlist?list=PLwkSQD3vqK1S1NiHIxxF2g_Uy-LbbcR84
3 Crummy Jobs That Radicalized Me, But I Was The Boss.
https://www.youtube.com/watch?v=YG2YzI_T9U0
Decentralized Social Networks vs the Trolls
https://joinmastodon.org/servers https://www.youtube.com/watch?v=yZoASOyfvGQ
Solution for the Left? Gramsci, Mouffe and Laclau on Hegemony!
https://www.youtube.com/watch?v=FCF_woTIxrk
Palestine
https://www.youtube.com/watch?v=3xottY-7m3k
brown eye blue eye, Jane Elliott
https://www.youtube.com/watch?v=jPZEJHJPwIw
Don't Talk to the Police
https://www.youtube.com/watch?v=d-7o9xYp7eE
Unauthorized Bread: Real rebellions involve jailbreaking IoT toasters
https://arstechnica.com/gaming/2020/01/unauthorized-bread-a-near-future-tale-of-refugees-and-sinister-iot-appliances/
The Year Without Sunshine - Uncanny Magazine
https://www.uncannymagazine.com/article/the-year-without-sunshine/
Gutter Oil: The Real Story
https://www.youtube.com/watch?v=G43wJ7YyWzM
Lawyer. Passport. Locksmith. Gun.
https://www.youtube.com/watch?v=6ihrGNGesfI
Accomplices Not Allies
https://www.indigenousaction.org/wp-content/uploads/Accomplices-Not-Allies-print.pdf
Don't Get Distracted
https://calebhearth.com/dont-get-distracted
TODO: book recommendations

View file

@ -5,6 +5,7 @@ description: "A blog post describing posts that I would like to make. Serves par
## project ideas
- Tail scale, style, layer 3, personal connection, network and identity service. (reticulum might be this?)
- package deployment for the reticulum style thing and distributed hosting via hashes of thr project and having overlays
- What I want to make quark into
- Signal can't ensure the other person deletes messages and it should stop pretending it does.
- IMS system for co-ops.
- Meal Prep and Planning Service.
@ -32,7 +33,23 @@ description: "A blog post describing posts that I would like to make. Serves par
- stop making objects with dynamic keys
## slice of life
- I have a love hate relationship with Typescript
- Leyla's texting and communication preferences.
- I like the lambda functions
- I love union types
- I hate the ecosystem
- Why are things mutable
- Algebraic effects please
- So much legacy BS
- Leyla's texting and communication preferences
- nice websites
- https://dontasktoask.com/
- https://xyproblem.info/
- https://nohello.net/en/
- https://modalzmodalzmodalz.com/#content
- What are you trying to say with a message
- Please avoid emoji
- I hate reactions
- Have an idea of what you want me to do with information in mind
- In person is better then over text
- OSM is cool and you should use it
- how to get started
- overpass queries for community
@ -61,6 +78,52 @@ description: "A blog post describing posts that I would like to make. Serves par
- Fettuccine Alfredo
- White people Sushi
- Enchiladas
- Recipes
- Bread machine bread
- Brownies
- Chicken broth powder
- Dal
- English Muffins
- White girl Yang Zhou Fried Rice
- White girl MaPo Tofu
- Jam
- Apple
- Strawberry
- Raspberry rhubarb
- Pear
- Stock
- Stuffing
- Soffritto
- Granola
- Pancakes
- Pizza Dough
- Pizza Sauce
- Souffle
- Sweet Corn
- Tapioca Pudding
- Tater Tot Hotdish
- Tikka Massala
- Tofu Scramble
- Smash Burgers
- Waffels
- Pancakes
- Tortellini
- Angle food cake
- banana Bread
- Cha
- Chilli
- Chorizo
- Creamy Mash Potatoes
- Madeleines
- Mochi
- Milk Shake
- Mozzarella
- Seitan
- Snickerdoodles
- Soft boiled egg
- Spice Cake
- Sushi Rice
- Apple Upside down cake
- Drink preparation
- Kombucha making
- Cider making
@ -101,4 +164,4 @@ description: "A blog post describing posts that I would like to make. Serves par
- We shouldn't have speed limits, we score vehicles based on how they can strike a pedestrian or biker, and then limit the newtons that a vehicle can have based on the reaction time another person would have to react to their speed
- National payment service like europe
- digital right like the GDPR and ban DRM
- government provided 0 interest rate loans for emergency expenses

View file

@ -88,7 +88,7 @@ const getPostCreatedAt = (data) => {
module.exports = {
layout: "post.njk",
tags: ["posts"],
unreleased: false,
released: true,
eleventyComputed: {
title: (data) => {
if (data.title && data.title !== data.page.fileSlug) {

134
robots.txt Normal file
View file

@ -0,0 +1,134 @@
User-agent: *
Allow: /
User-agent: AddSearchBot
User-agent: AI2Bot
User-agent: AI2Bot-DeepResearchEval
User-agent: Ai2Bot-Dolma
User-agent: aiHitBot
User-agent: amazon-kendra
User-agent: Amazonbot
User-agent: AmazonBuyForMe
User-agent: Andibot
User-agent: Anomura
User-agent: anthropic-ai
User-agent: Applebot
User-agent: Applebot-Extended
User-agent: atlassian-bot
User-agent: Awario
User-agent: bedrockbot
User-agent: bigsur.ai
User-agent: Bravebot
User-agent: Brightbot 1.0
User-agent: BuddyBot
User-agent: Bytespider
User-agent: CCBot
User-agent: Channel3Bot
User-agent: ChatGLM-Spider
User-agent: ChatGPT Agent
User-agent: ChatGPT-User
User-agent: Claude-SearchBot
User-agent: Claude-User
User-agent: Claude-Web
User-agent: ClaudeBot
User-agent: Cloudflare-AutoRAG
User-agent: CloudVertexBot
User-agent: cohere-ai
User-agent: cohere-training-data-crawler
User-agent: Cotoyogi
User-agent: Crawl4AI
User-agent: Crawlspace
User-agent: Datenbank Crawler
User-agent: DeepSeekBot
User-agent: Devin
User-agent: Diffbot
User-agent: DuckAssistBot
User-agent: Echobot Bot
User-agent: EchoboxBot
User-agent: FacebookBot
User-agent: facebookexternalhit
User-agent: Factset_spyderbot
User-agent: FirecrawlAgent
User-agent: FriendlyCrawler
User-agent: Gemini-Deep-Research
User-agent: Google-CloudVertexBot
User-agent: Google-Extended
User-agent: Google-Firebase
User-agent: Google-NotebookLM
User-agent: GoogleAgent-Mariner
User-agent: GoogleOther
User-agent: GoogleOther-Image
User-agent: GoogleOther-Video
User-agent: GPTBot
User-agent: iAskBot
User-agent: iaskspider
User-agent: iaskspider/2.0
User-agent: IbouBot
User-agent: ICC-Crawler
User-agent: ImagesiftBot
User-agent: imageSpider
User-agent: img2dataset
User-agent: ISSCyberRiskCrawler
User-agent: Kangaroo Bot
User-agent: KlaviyoAIBot
User-agent: KunatoCrawler
User-agent: laion-huggingface-processor
User-agent: LAIONDownloader
User-agent: LCC
User-agent: LinerBot
User-agent: Linguee Bot
User-agent: LinkupBot
User-agent: Manus-User
User-agent: meta-externalagent
User-agent: Meta-ExternalAgent
User-agent: meta-externalfetcher
User-agent: Meta-ExternalFetcher
User-agent: meta-webindexer
User-agent: MistralAI-User
User-agent: MistralAI-User/1.0
User-agent: MyCentralAIScraperBot
User-agent: netEstate Imprint Crawler
User-agent: NotebookLM
User-agent: NovaAct
User-agent: OAI-SearchBot
User-agent: omgili
User-agent: omgilibot
User-agent: OpenAI
User-agent: Operator
User-agent: PanguBot
User-agent: Panscient
User-agent: panscient.com
User-agent: Perplexity-User
User-agent: PerplexityBot
User-agent: PetalBot
User-agent: PhindBot
User-agent: Poggio-Citations
User-agent: Poseidon Research Crawler
User-agent: QualifiedBot
User-agent: QuillBot
User-agent: quillbot.com
User-agent: SBIntuitionsBot
User-agent: Scrapy
User-agent: SemrushBot-OCOB
User-agent: SemrushBot-SWA
User-agent: ShapBot
User-agent: Sidetrade indexer bot
User-agent: Spider
User-agent: TavilyBot
User-agent: TerraCotta
User-agent: Thinkbot
User-agent: TikTokSpider
User-agent: Timpibot
User-agent: TwinAgent
User-agent: VelenPublicWebCrawler
User-agent: WARDBot
User-agent: Webzio-Extended
User-agent: webzio-extended
User-agent: wpbot
User-agent: WRTNBot
User-agent: YaK
User-agent: YandexAdditional
User-agent: YandexAdditionalBot
User-agent: YouBot
User-agent: ZanistaBot
Disallow: /

15
sitemap.njk Normal file
View file

@ -0,0 +1,15 @@
---
permalink: /sitemap.xml
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in collections.all %}
{%- if page.url and page.url != "/sitemap.xml" and not page.data.excludeFromSitemap %}
<url>
<loc>{{ site.url }}{{ page.url }}</loc>
<lastmod>{{ (page.data.updatedAt or page.data.createdAt or page.date) | isoDate }}</lastmod>
</url>
{%- endif %}
{%- endfor %}
</urlset>