the home site for me: also iteration 3 or 4 of my site

Compare changes

Choose any two refs to compare.

Changed files
+4905 -1762
.github
images
content
hooks
sass
scripts
static
badges
blog
adding-a-copy-button
analyzing-implications-of-online-safety-legislation
atuin
degraded-zpool-proxmox
garmin-vivoactive-homeassistant
hilton-tomfoolery
install-truenas-core-proxmox
mega
monaspace-vs-code-install
my-animations
my-life-story-with-tech
remove-exif-git-hook
spherical-ray-diagrams
ssd-removal-mbp-2017
tangled-sync
js
now
pfp
pfps
tags
accessibility
archival
atproto
biography
cool-stuff
essays
fancy
hilton
homelab
meta
mildrant
nix
physics
project
reverse-engineering
shell
teardown
tool
tutorial
yap-fest
verify
syntaxes
templates
tools
.github/images/preview.webp

This is a binary file and will not be displayed.

+4 -1
.gitignore
···
public
+
.zola-build
node_modules
-
.env
+
.env
+
.crush
+
.DS_Store
+1 -1
README.md
···
Huge thanks to [Speyll/anemone](https://github.com/Speyll/anemone) for the template that helped me understand [Zola](https://www.getzola.org/)
-
This site's theme is based off of the awesome project [Speyll/suCSS/](https://github.com/) with my own flavoring on top and the code theme is based off of [uncomfyhalomacro/catppuccin-zola](https://github.com/uncomfyhalomacro/catppuccin-zola) modified to work with `data-theme`.
+
This site's theme is based off of the awesome project [Speyll/suCSS/](https://github.com/) with my own flavoring on top and the code theme is based off of [uncomfyhalomacro/catppuccin-zola](https://github.com/uncomfyhalomacro/catppuccin-zola) modified to work with `data-theme` (and then removed again lol).
<p align="center">
<img src="https://raw.githubusercontent.com/taciturnaxolotl/carriage/main/.github/images/line-break.svg" />
+241
bun.lock
···
+
{
+
"lockfileVersion": 1,
+
"configVersion": 1,
+
"workspaces": {
+
"": {
+
"dependencies": {
+
"dotenv": "^16.4.7",
+
"glob": "^13.0.0",
+
},
+
"devDependencies": {
+
"@types/bun": "latest",
+
"puppeteer": "^23.6.0",
+
},
+
},
+
},
+
"packages": {
+
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+
+
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+
+
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
+
+
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
+
+
"@puppeteer/browsers": ["@puppeteer/browsers@2.6.1", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg=="],
+
+
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
+
+
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
+
+
"@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="],
+
+
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
+
+
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
+
+
"b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
+
+
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
+
+
"bare-fs": ["bare-fs@4.5.2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw=="],
+
+
"bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
+
+
"bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="],
+
+
"bare-stream": ["bare-stream@2.7.0", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="],
+
+
"bare-url": ["bare-url@2.3.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="],
+
+
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+
"basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="],
+
+
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
+
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
+
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+
+
"chromium-bidi": ["chromium-bidi@0.11.0", "", { "dependencies": { "mitt": "3.0.1", "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA=="],
+
+
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+
"cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="],
+
+
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
+
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
+
+
"devtools-protocol": ["devtools-protocol@0.0.1367902", "", {}, "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg=="],
+
+
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+
+
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+
+
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
+
+
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
+
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
+
+
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
+
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
+
+
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
+
+
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
+
+
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
+
+
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
+
+
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
+
+
"glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
+
+
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+
+
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
+
+
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
+
+
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
+
+
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+
+
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
+
+
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
+
+
"lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
+
+
"minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
+
+
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
+
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
+
+
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+
"pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
+
+
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
+
+
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
+
+
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
+
+
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
+
+
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
+
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
+
+
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
+
+
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
+
+
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
+
+
"puppeteer": ["puppeteer@23.11.1", "", { "dependencies": { "@puppeteer/browsers": "2.6.1", "chromium-bidi": "0.11.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1367902", "puppeteer-core": "23.11.1", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw=="],
+
+
"puppeteer-core": ["puppeteer-core@23.11.1", "", { "dependencies": { "@puppeteer/browsers": "2.6.1", "chromium-bidi": "0.11.0", "debug": "^4.4.0", "devtools-protocol": "0.0.1367902", "typed-query-selector": "^2.12.0", "ws": "^8.18.0" } }, "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg=="],
+
+
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+
+
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
+
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
+
+
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
+
+
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
+
+
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
+
+
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+
"tar-fs": ["tar-fs@3.1.1", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="],
+
+
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
+
+
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
+
+
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
+
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+
"typed-query-selector": ["typed-query-selector@2.12.0", "", {}, "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg=="],
+
+
"unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="],
+
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
+
+
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
+
+
"zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
+
+
"proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
+
}
+
}
compressedfav.jpg

This is a binary file and will not be displayed.

+1
config.toml
···
highlight_code = true
highlight_theme = "css"
+
extra_syntaxes_and_themes = ["syntaxes"]
[slugify]
paths = "on"
+25 -13
content/_index.md
···
+++
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin: 2rem;">
-
<img src="https://cachet.dunkirk.sh/users/U062UG485EE/r" alt="kieran with a white and gray spotted kitten with a grainy background and star dust" width="512" height="512" class="u-photo"/>
+
<img src="/pfps/fall.jpg" alt="kieran wearing a robotics sweatshirt and standing in front of a tree with fall leaves" width="512" height="512" class="u-photo"/>
{{ is() }}
</div>
# About me
-
Erlo! My name is Kieran Klukas and i'm a homeschooled coder who is {{ age(length=0) }} years old and loves film making, fpv, and typescript :)
+
Erlo! My name is Kieran Klukas i'm {{ age(length=0) }} years old and love cyber, anything with micro-controllers, obscure languages, nix :nix:, and yummy food :)
+
+
> flake.nix
+
+
```nix
+
{
+
description = "a short bit about me";
-
> init.ts
-
```ts
-
const kieran = {
-
name: "kieran klukas",
-
age: {{ age(length=2 comma=true) }}
-
education: ["Homeschooled", "Dual Enrollment"],
-
favFoods: ["lo mein", "bacon fried rice", "pretty much any meat"]
+
outputs = { self, ... }:
+
let
+
kieran = rec {
+
name = "Kieran Klukas";
+
pronouns = "he/him";
+
aliases = [ "taciturnaxolotl" "krn" ];
+
location = "Westerville, Ohio, USA";
+
hobbies = [ "frc" "ctfs" "random side projects"];
+
};
+
in
+
{
+
inherit kieran;
+
};
}
```
-
this site has page hits (<code id="visits">0</code> and counting) via [abacus](https://jasoncameron.dev/abacus/) but they are completely anonymous and just http requests!
+
this site has page hits (<code id="visits">0</code> and counting) via [abacus](https://jasoncameron.dev/abacus/) but they are completely anonymous and just http requests so no sketchy analytics here!
# Want to talk to me?
-
Do you want to hire me for a project? If you just have a question or want to talk I'll still answer (admittedly more slowly ^-^).
+
I'm open to projects or just random questions! Feel free to reach out with any of the following or anything on [/verify](/verify)
-
- Email: [me@dunkirk.sh](mailto:me@dunkirk.sh)
+
- Email: [kieran@dunkirk.sh](mailto:kieran@dunkirk.sh)
- Hackclub Slack: [@krn](https://hackclub.slack.com/team/U062UG485EE) (only if you are a highschooler or younger; [join here](https://hackclub.com/slack/))
-
- If you just want to know when I make a new post then you can subscribe to the [rss](rss.xml) feed ^-^
+
- If you just want to know when I make a new post then you can subscribe to the [:rss:](rss.xml) feed
+17 -17
content/blog/2023-07-10_install-truenas-core-proxmox.md
···
<!-- more -->
-
{{ img(id="https://cloud-f19fn8u8j-hack-club-bot.vercel.app/0image.png" alt="screenshot of the vault vm in proxmox" caption="my active vault storing 1.8TB of old projects") }}
+
![screenshot of the vault vm in proxmox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/81f100961a1c4033c0c50c9192b48521e968a8e8_0image.png){caption="my active vault storing 1.8TB of old projects"}
## Introduction
···
Sign-in to Proxmox and upload your ISO to the local storage or, download the file directly from the link using the built-in ISO fetcher.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/Ww212rUDQ_Ms9P2WhJMwz.png" alt="download iso tool in proxmox") }}
+
![download iso tool in proxmox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/45ce25b0bd1accf6a31004e0d71dc92783151266_Ww212rUDQ_Ms9P2WhJMwz.png)
Next to create the VM, the only thing that needs to be changed from the defaults is the memory, which I set to `8192 MB` (8 GB).
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/bDT9VdIMMG1LWvv1RwNKl.png" alt="create a vm modal in proxmox") }}
+
![create a vm modal in proxmox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/1e5d72e4a50907ad23fc1f24a7163cb4898f9bd2_bDT9VdIMMG1LWvv1RwNKl.png)
Now finish creating the VM and click on the VM after it is created. Go to options and enable start at boot.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/ImxOHWJNuRL3yiF12cQfe.png" alt="start at boot checkbox") }}
+
![start at boot checkbox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/81a9d9897274eaac53e0d2cc3ff7075cb4ba8cdd_ImxOHWJNuRL3yiF12cQfe.png)
Next, we need to pass through the physical drives to the VM. Open a terminal on the Proxmox server (use the built-in terminal or ssh in) and run the following command. Only run the part after the #.
···
Now find your VM_ID, mine is 102.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/gwjgFbI5IrnJSTLTB0PeX.png" alt="vm list in proxmox") }}
+
![vm list in proxmox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/ac8724875dd07e84903cfe0004a435041a21c35c_gwjgFbI5IrnJSTLTB0PeX.png)
Run the following command, replacing the VM_ID and DISK_ID with yours.
···
Here is how it appears in Proxmox:
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/QBpmqflEHmPiUHbd8JVk2.png" alt="hardware page of the vm in proxmox") }}
+
![hardware page of the vm in proxmox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/c313b32c8c099ea8644663e3d796c12a861ac8e8_QBpmqflEHmPiUHbd8JVk2.png)
If everything went well, then you can start your VM now. After it finishes booting up, you will get the screen below. Make sure Install/Upgrade is selected and hit enter.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/UFqhrRdD3GkP1_No5lWaj.png" alt="truenas startup screen") }}
+
![truenas startup screen](https://hc-cdn.hel1.your-objectstorage.com/s/v3/0eb213f7699f79c389328cdc9f591bcde475e4fe_UFqhrRdD3GkP1_No5lWaj.png)
You will then get this screen, use space to select the first drive and hit enter.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/xD5QxmFtHxw10p624FgwM.png" alt="destination media screen") }}
+
![destination media screen](https://hc-cdn.hel1.your-objectstorage.com/s/v3/c53f8a58f746d0ae045882c9563d83219ab9f29f_xD5QxmFtHxw10p624FgwM.png)
Hit enter one last time and enter your password.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/MZy3mN1cXBaicgVolVYs5.png" alt="confirm erase page") }}
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/KWq2P7Iok9LThOF5Xoj6l.png" alt="repeat password page") }}
+
![confirm erase page](https://hc-cdn.hel1.your-objectstorage.com/s/v3/a0f2b37568b394eaff15e1b487ab68bbc3736cd1_MZy3mN1cXBaicgVolVYs5.png)
+
![repeat password page](https://hc-cdn.hel1.your-objectstorage.com/s/v3/0bccd9e726a027cf96ab98a8b313ea8c710b46f5_KWq2P7Iok9LThOF5Xoj6l.png)
Select BIOS, as this is the default mode for Proxmox VMs.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/RfXwEGx6oug1vVF3UZuCj.png" alt="boot via bios or via uefi screen") }}
+
![boot via bios or via uefi screen](https://hc-cdn.hel1.your-objectstorage.com/s/v3/7f52c721a7fcb2542552d997224f7d02c3ebba9b_RfXwEGx6oug1vVF3UZuCj.png)
After about five to ten minutes, the installation process will finish and the VM will ask you to remove installation media and reboot.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/mFEH-FHY10H7NUAvYi0aE.png" alt="installation succeded message") }}
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/JPXkEQJgBmeATEE40HHpr.png" alt="hardware screen in proxmox") }}
+
![installation succeded message](https://hc-cdn.hel1.your-objectstorage.com/s/v3/f28005427ff5ea328550a079dcb7a65d07f09c95_mFEH-FHY10H7NUAvYi0aE.png)
+
![hardware screen in proxmox](https://hc-cdn.hel1.your-objectstorage.com/s/v3/acd7be1e3ac4903269c9af7e808287a94eb96398_JPXkEQJgBmeATEE40HHpr.png)
Select the installation media and remove it with the top button, go back to the console and hit enter, which will take you back to the main menu. On the main menu, select reboot with the arrow keys and hit enter.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/IfvdMuF6AVU_f0-_rngqq.png" alt="power options screen in truenas") }}
+
![power options screen in truenas](https://hc-cdn.hel1.your-objectstorage.com/s/v3/52eb8ec8c018c185d3a0361de58be8351b61fa79_IfvdMuF6AVU_f0-_rngqq.png)
Once the machine restarts, it will display an IP address in the console.
-
{{ img(id="https://cloud-pur64l07h-hack-club-bot.vercel.app/0image.png" alt="ip address displayed in proxmox console") }}
+
![ip address displayed in proxmox console](https://hc-cdn.hel1.your-objectstorage.com/s/v3/80f56ad99e3e9fa8a997798096214dd5592c40ac_0image.png)
Upon connecting to the IP address, you will get this screen. Use the root username and the password, previously configured, to login.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/ghvCsvwAJMudUCUGvcnCu.png" alt="truenas web ui signin page") }}
+
![truenas web ui signin page](https://hc-cdn.hel1.your-objectstorage.com/s/v3/5211ac631426d7ffb4add091c4308f460b5ef746_ghvCsvwAJMudUCUGvcnCu.png)
Once logged in, I updated the system using the button on the home screen.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/nrBop3a9ilvuc7h-0WPEG.png" alt="check for updates button in the truenas web ui") }}
+
![check for updates button in the truenas web ui](https://hc-cdn.hel1.your-objectstorage.com/s/v3/392e592dd232096380a29f4e769b80eb290fee31_nrBop3a9ilvuc7h-0WPEG.png)
I chose not to save the configuration file when prompted, proceeded to install the updates, and rebooted.
+9 -9
content/blog/2023-08-04_garmin-vivoactive-homeassistant.md
···
<!-- more -->
-
{{ img(id="https://cloud-au4cbwyfl-hack-club-bot.vercel.app/0img_3051.jpg" alt="a garmin watch with the apicall app open to a spotify page" caption="I can control spotify from my watch via api hooks how bout you?") }}
+
![a garmin watch with the apicall app open to a spotify page](https://hc-cdn.hel1.your-objectstorage.com/s/v3/3893acb0d518216e8051bb59f602748ffde69a68_0img_3051.jpg){caption="I can control spotify from my watch via api hooks how bout you?"}
This widget interested me because it allowed me to call any webhook I wanted utilizing the onboard Wi-Fi as well as through the Connect IQ app. This was a very important feature for me because I canโ€™t get the app to run on LineageOS as it keeps asking for the location permission even though it was already granted.
···
Now for the Google Assistant SDK / APICall / Home Assistant tutorial. The first thing you want to do is follow this guide, [Google Assistant SDK - Home Assistant](https://www.home-assistant.io/integrations/google_assistant_sdk#configuration), to install the Assistant SDK. Once you have completed that, go to Settings / Automations & Services.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/Yha1bUhOH_iuWK30QR0F1.png" alt="arrow pointing to settings in home assistant") }}
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/RR0VzZqsU7uTxiNlqVGum.png" alt="arrow pointing to Automations & Services in home assistant") }}
+
![arrow pointing to settings in home assistant](https://hc-cdn.hel1.your-objectstorage.com/s/v3/24972b625b42a5348d22eebcd2116df317fa7a99_Yha1bUhOH_iuWK30QR0F1.png)
+
![arrow pointing to Automations & Services in home assistant](https://hc-cdn.hel1.your-objectstorage.com/s/v3/91729592f4a9cee25691c1e013244b5f3fb0d22b_RR0VzZqsU7uTxiNlqVGum.png)
This is where you can create the action that you want to trigger with your smartwatch. The first thing you need to do is to create a new automation. Save and name the automation you just created. Now add a trigger, scroll to the bottom of the list and select webhook. If done successfully, it will look like the image below.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/VqiM4d3wncM9BuoDR_FW7.png" alt="creating a new webhook in home assistant") }}
+
![creating a new webhook in home assistant](https://hc-cdn.hel1.your-objectstorage.com/s/v3/fe82d82d970e7560e285e4606b5f17d842ab4c6b_VqiM4d3wncM9BuoDR_FW7.png)
Now add an action. I decided to use the media player to play a song on Spotify. Also go back to the webhook section and click the settings icon next to the webhook ID. Change the settings to reflect below screenshot.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/Xh3BtyMxA1MhI0rHuo3WG.png" alt="editing the webook in home assistant to allow GET queries") }}
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/rAbDGMrBS5fcGo7AzPT-O.png" alt="adding a play media block to the webhook") }}
+
![editing the webook in home assistant to allow GET queries](https://hc-cdn.hel1.your-objectstorage.com/s/v3/53c6ef6999ab4a636983cfcc879b75c4d7ba0375_Xh3BtyMxA1MhI0rHuo3WG.png)
+
![adding a play media block to the webhook](https://hc-cdn.hel1.your-objectstorage.com/s/v3/382d74c38d3323197636f0af125d3a5faff13e7f_rAbDGMrBS5fcGo7AzPT-O.png)
Now for the fun part. Download [APICall](https://apps.garmin.com/en-US/apps/ac9a81ab-a52d-41b3-8c14-940a9de37544) onto your Garmin smartwatch and go to the configuration section for the app.
> Note: Iโ€™ll be using Garmin Express on my MacBook, but you can also use the Garmin Connect app on a phone.
-
{{ img(id="https://cloud-hsopd7dwj-hack-club-bot.vercel.app/0image.png" alt="garmin express app homepage on desktop") }}
+
![garmin express app homepage on desktop](https://hc-cdn.hel1.your-objectstorage.com/s/v3/37f7a72276cc3c1bc2575833cb4f663ce23bbbd6_0image.png)
If you are using Garmin Express, then you can access the app settings by selecting the 3 dots next to the app. You will have 36 possible API calls that you can enter.
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/-lSqNObL3TGNk0VQc8xOq.png" alt="ApiCall settings page" caption="Yes that formatting is atrocious but it works at least!") }}
+
![ApiCall settings page](https://hc-cdn.hel1.your-objectstorage.com/s/v3/b98ab669cd8549519876b95d99af1d8edebb0f28_-lSqNObL3TGNk0VQc8xOq.png){caption="Yes that formatting is atrocious but it works at least!"}
> webhooks
```ts
···
These are the actions that I configured for my watch so far. To customize for your API calls you need to change the `deviceName`, `actionName`, and `url` fields. The `method` and `headers` need to stay the same across all actions. If you want to add an icon to that action, then you can configure that with the `actionIcon` field. A table with the possible icons is included below, sourced from APICallโ€™s [documentation](https://apicall.dumesnil.net/documentation_en.html).
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/119m02PEgn6_wcNGtCnjM.png" alt="ApiCall icons") }}
+
![ApiCall icons](https://hc-cdn.hel1.your-objectstorage.com/s/v3/e957a6b2b01133d35ccfe189e0466049a58da3bb_119m02PEgn6_wcNGtCnjM.png)
In conclusion, you can use APICall to trigger actions in home assistant from your Garmin smartwatch. I hope this tutorial proved to be useful, and have a great rest of your day (or night).
+1 -1
content/blog/2023-11-01_censorship-or-protection.md
···
<!-- more -->
-
{{ img(id="https://cloud-quuwed8n2-hack-club-bot.vercel.app/0image.png" alt="child looking out window" caption="Law makers keeping producing new โ€œonline safety billsโ€ but do they really help?") }}
+
![child looking out window](https://hc-cdn.hel1.your-objectstorage.com/s/v3/5523fdca5f558bc9ff49b4053e02cd1ded0c6e43_0image.png){caption="Law makers keeping producing new โ€œonline safety billsโ€ but do they really help?"}
In the last few years, we have seen a wave of โ€œonline safety billsโ€ created by lawmakers that will ostensibly help protect children online. The US has the Protecting Kids on Social Media Act (PKSMA, S.1291) and the Kids Online Safety Act (KOSA, S.1409) while in the UK they have the Online Safety Bill (OSB). The main feature that all of these bills have in common is the censorship of online content for minors. The Electronic Frontier Foundation (EFF) has raised concerns over KOSA, saying, โ€œThe bill requires all websites, apps, and online platforms to filter and block legal speechโ€ (Mullin). These bills raise an important questionโ€“should the government regulate the online activities of children, or should that responsibility lie solely with parents?
+2 -2
content/blog/2023-11-10_monaspace-vs-code-install.md
···
<!-- more -->
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/KuOAwCEm9ypWEemv60Qs7.png" alt="monaspace font in action" caption="This font is so pretty and has so many features its amazing. It's main downside is to work it takes to set it up.") }}
+
![monaspace font in action](https://hc-cdn.hel1.your-objectstorage.com/s/v3/7abefc1fcefa49d2ab5aac9afbafa1da41259382_KuOAwCEm9ypWEemv60Qs7.png){caption="This font is so pretty and has so many features its amazing. It's main downside is to work it takes to set it up."}
## 1. Download and install the Monaspace font:
···
- You will find this option under _Editor: Font Family_ in the user preferences
-
{{ img(id="https://assets.vrite.io/64974cb888e8beebeb2c925b/v0cMm5jcwHEgrvtBv4Syx.png" alt="the available varients of the font") }}
+
![the available varients of the font](https://hc-cdn.hel1.your-objectstorage.com/s/v3/107040ab22c6ccfaa53c82bd5a328168f5d2d119_v0cMm5jcwHEgrvtBv4Syx.png)
Next enable font ligatures in the settings.json with following snippet:
+8 -8
content/blog/2024-08-03_ssd-removal-mbp-2017.md
···
<!-- more -->
-
{{ img(id="https://cloud-owp7vmln1-hack-club-bot.vercel.app/0img_1846_1_.jpg" alt="MacBook proprietary blade SSD" caption="it really was a rather sleek design; shame that apple got rid of it in favor of soldered on storage") }}
+
![MacBook proprietary blade SSD](https://hc-cdn.hel1.your-objectstorage.com/s/v3/d294f113bf415a0d1c544fbf3c2d0f4286d892a6_0img_1846_1_.jpg){caption="it really was a rather sleek design; shame that apple got rid of it in favor of soldered on storage"}
I eventually decided to just try and remove the SSD from the MacBook and see if there was a way to recover any files from it (spoiler: there kinda is, but it's annoying) but I couldn't find any guide online and iFixit had nothing. So I decided to just try and yolo it and see if I could figure it out on my own, and surprisingly I actually managed to do it! Turns out, the process isn't that hard! I'll take you through the steps I took so that if you want to do this, it's much less of a hassle.
## Guide
1. the first thing you need to do is to remove the screws from the back of your MacBook. This will use a P5 Pentalobe driver, which I believe you can buy from iFixit as well as several other companies on Amazon.
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/1img_1838.jpg", alt="Removing the screws") }}
+
![Removing the screws](https://hc-cdn.hel1.your-objectstorage.com/s/v3/ed2c207dcd77b9dede8496aa40959042fca082ba_1img_1838.jpg)
1. next you need to crack open the shell of the MacBook by prying under the front (on the side where the MacBook opens). It's pretty helpful to have a suction cup or something to lift it up a bit so you can get your prying tool underneath (I used a flat plastic prying tool I got from the battery repair kit for this MacBook, but a guitar pick or credit card would probably also work)
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/2img_1839.jpg", alt="using a suction cup to lift the back shell") }}
+
![using a suction cup to lift the back shell](https://hc-cdn.hel1.your-objectstorage.com/s/v3/faf7ece0437ca9ae232361dc1305c7012c676d88_2img_1839.jpg)
1. now once you've got the back slightly opened up just run around the edge of the shell prying up on it until the front and two sides are free then just pull forward at a slight (15ish degree?) angle, and it should slide right out.
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/3img_1840.jpg", alt="the opened MacBook") }}
+
![the opened MacBook](https://hc-cdn.hel1.your-objectstorage.com/s/v3/0b55d2aed8ecda90aa39091bc5dacd72c2c5aab0_3img_1840.jpg)
1. once it's open, locate the silver metal block looking thing; this is your SSD
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/4img_1841.jpg", alt="the SSD") }}
+
![the SSD](https://hc-cdn.hel1.your-objectstorage.com/s/v3/acc1ae05460bf0792959c684e5104d225d309b5e_4img_1841.jpg)
1. now using a T5 Torx driver (why couldn't you just use one type of screws apple ๐Ÿ˜ญ; be more like framework) you need to unscrew the two screws on either side of the front of the SSD
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/7img_1844.jpg", alt="the screws") }}
+
![the screws](https://hc-cdn.hel1.your-objectstorage.com/s/v3/f04ad8221ae080dba828e8e0cad9492ae2691118_7img_1844.jpg)
1. now comes the slightly scary part (for me at least) you need to lift the black tape that's covering the top of the SSD (don't worry the SSD will be fine)
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/8img_1845.jpg", alt="the removed tape on the SSD") }}
+
![the removed tape on the SSD](https://hc-cdn.hel1.your-objectstorage.com/s/v3/d63a97738c3378a1813a7c59263c37b2e4120470_8img_1845.jpg)
1. now just slightly pull on the SSD (again at a slight angle) and it should pop right out!
-
{{ img(id="https://cloud-nw5fqpqfw-hack-club-bot.vercel.app/9img_1846.jpg", alt="the SSD out of the MacBook") }}
+
![the SSD out of the MacBook](https://hc-cdn.hel1.your-objectstorage.com/s/v3/85632b1b9443498a770aae1687b6ff594aad79ff_9img_1846.jpg)
## Postlog and notes
+46 -1
content/blog/2024-10-11_example_post.md
···
{{/* img(id="https://url.com/image.png" alt="alt text" caption="this can be ommited if you want or added! It's optional :)") */}}
```
-
{{ img(id="https://cloud-owp7vmln1-hack-club-bot.vercel.app/0img_1846_1_.jpg" alt="MacBook proprietary blade SSD" caption="it really was a rather sleek design; shame that apple got rid of it in favor of soldered on storage") }}
+
![MacBook proprietary blade SSD](https://hc-cdn.hel1.your-objectstorage.com/s/v3/d294f113bf415a0d1c544fbf3c2d0f4286d892a6_0img_1846_1_.jpg){caption="it really was a rather sleek design; shame that apple got rid of it in favor of soldered on storage"}
+
+
You can also display multiple images side-by-side using the `imgs` shortcode with comma-separated URLs:
+
+
```terra
+
{{/* imgs(id="https://url.com/image1.png, https://url.com/image2.png" alt="alt text 1, alt text 2" caption="optional caption for both images") */}}
+
```
+
+
!![the copyright section](https://hc-cdn.hel1.your-objectstorage.com/s/v3/c509aeaac769c3e5b99d5a7d320cc4a759db4ff5_img_8880.jpeg)[the ssh section](https://hc-cdn.hel1.your-objectstorage.com/s/v3/ed400c26ddfa37ab4a9ef4fd5a506f2dcc1bcfbb_img_8879.jpeg){caption="side by side images from the remarkable tutorial"}
### Videos
···
---
But these should be used sparingly, if at all.
+
+
You can also use emojis inline from the hackclub slack like this :yay:! This is just done by writing `:emoji:` and it gets progressively enhanced with a bit of js as long as the emoji is in cachet!
+
+
## Callouts
+
+
Callouts are a great way to draw attention to important information. They come in several types:
+
+
### Info Callout
+
+
> [!INFO]
+
> This is an info callout! Use this for general information that readers should be aware of.
+
+
### Warning Callout
+
+
> [!WARNING]
+
> This is a warning callout! Use this to alert readers about potential issues or things to watch out for.
+
+
### Danger Callout
+
+
> [!DANGER]
+
> This is a danger callout! Use this for critical information that could cause problems if ignored.
+
+
### Tip Callout
+
+
> [!TIP]
+
> This is a tip callout! Use this to share helpful hints and best practices.
+
+
### Note Callout
+
+
> [!NOTE]
+
> This is a note callout! Use this for additional context or side information.
+
+
### Custom Title
+
+
{% callout(type="info", title="Custom Title Here") %}
+
You can also customize the title of any callout by adding a `title` parameter!
+
{% end %}
+5 -5
content/blog/2024-10-13_hilton_tomfoolery.md
···
I'm connecting over WireGuard, so I fired up mitmproxy with `mitmweb --mode wireguard` on my laptop. Connecting via WireGuard theoretically is pretty simple; all I need to do is to scan a qr code and connect. Unfortunately, the hotel Wi-Fi seems to be oddly segmented, and I can't access the WireGuard server or ping my laptop from my phone. I'm going to try firing up a hot spot on my dad's phone and see if that allows me to talk to my phone.
-
{{ img(id="https://cloud-ryjlxhb9r-hack-club-bot.vercel.app/2install_profile.png" alt="screenshot of the root certificate install process" caption="You have to dig through several menus to trust it") }}
+
![screenshot of the root certificate install process](https://hc-cdn.hel1.your-objectstorage.com/s/v3/29c856921e88c31bfc2e2d73d09d287ce4aa149a_2install_profile.png){caption="You have to dig through several menus to trust it"}
I messed with getting my laptop to connect to my dad's phone, but it kept refusing for some reason. My next idea is to ngrok the WireGuard tunnel, which ended up failing because ngrok doesn't support UDP. Finally, after an embarrassingly long time, I realized that I could simply use `ngrok tcp 8080` and the HTTP proxy server built into mitmproxy instead. After installing the root certificate and trusting it in the iPhone settings, we were good to go!
···
## Locks
-
{{ img(id="https://cloud-ryjlxhb9r-hack-club-bot.vercel.app/0hotel-key.png" alt="screenshot of the hotel digital key" caption="What it looks like in the app") }}
+
![screenshot of the hotel digital key](https://hc-cdn.hel1.your-objectstorage.com/s/v3/06dcbd4c5166d7f17e31027b687f32539d68aa44_0hotel-key.png){caption="What it looks like in the app"}
When using the unlock button, it made a request to this URL: `https://smetric.hilton.com/b/ss/hiltonglobalprod/10/IOSN030200030900/s65425920` with a payload of a URL encoded form.
···
## Wrap up
-
{{ img(id="https://cloud-ryjlxhb9r-hack-club-bot.vercel.app/1bluetooth-scan.png" alt="screenshot of bluetooth scan" caption="The bluetooth scan of (what i believe is) the lift") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/4e9bfb28c266eb29cea1568cedd3573be2ba1f97_1bluetooth-scan.png" alt="screenshot of bluetooth scan" caption="The bluetooth scan of (what i believe is) the lift") }}
I tried running a Bluetooth scan to see if I could find the locks, but nothing popped out as being a likely culprit. I did however find an interesting set of 3 Bluetooth devices named "clearsky smart fleet" which upon research seems to be scissor lifts / construction equipment made by a company called [JLG](https://smartfleet.jlg.com/) which is quite interesting. That would make sense, however, as I saw several scissor lifts outside the hotel on my way in.
-
{{ img(id="https://cloud-1asinv8kn-hack-club-bot.vercel.app/0img_2781.jpg" alt="image of JLG lift" caption="The same (probably) JLG lift in the wild!") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/993ad810e42289ad3aaefa4093ede271a4ee1d12_0img_2781.jpg" alt="image of JLG lift" caption="The same (probably) JLG lift in the wild!") }}
By the time I'm writing this it's 6:41, and I need to eat breakfast, so I'll probably finish this post in the car this afternoon. Overall this was a fascinating experiment and while I sadly did fail at unlocking doors from my laptop I do feel more confident with reverse engineering app requests now! The next step would probably be to grab the app bundle and try to decompile it looking for the URLs we saw, but I don't have a mac on me, and I've never done that before. Next post?
Taking inspiration from the [LOWโ†TECH MAGAZINE](https://solar.lowtechmagazine.com/) I will be taking any questions / comments about this article via email and then posting them here to my site! If you have a question or comment, feel free to email me at [me@dunkirk.sh](mailto://me@dunkirk.sh). Now to go eat breakfast :)
-
{{ img(id="https://cloud-1asinv8kn-hack-club-bot.vercel.app/3img_2777.jpg" alt="image of my hotel breakfast" caption="A delicious waffle, mildy warm bacon, and under seasoned potatoes.") }}
+
![image of my hotel breakfast](https://hc-cdn.hel1.your-objectstorage.com/s/v3/c974178c62fc836657a1c5e61cac90596c13a3bd_3img_2777.jpg){caption="A delicious waffle, mildy warm bacon, and under seasoned potatoes."}
+2 -2
content/blog/2024-10-23_hilton_decompilation.md
···
<!-- more -->
-
{{ img(id="https://cloud-glc3mgu9t-hack-club-bot.vercel.app/0image.png" alt="screenshot of the nix packages entry" caption="prepackaged for nix; always a good sign") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/4e667b8066044667ea63d5ec44222aef97dc764c_0image.png" alt="screenshot of the nix packages entry" caption="prepackaged for nix; always a good sign") }}
I was able to download the apk from the [apkcombo.com](https://apkcombo.com/downloader/#package=com.hilton.android.hhonors) website by simply inputing the play store URL so we were off to a good start. Apktool was already in [nix packages](https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=apktool) so we didn't have to do anything fancy there. One `pkgs.unstable.apktool` and a `sudo nixos-rebuild switch` latter and we were ready to go. Then I waited another 2 days lol. Finally in the hotel room (again crunched on time; why do I never seem to learn?) I was able to decompile the apk and start looking around.
-
{{ img(id="https://cloud-qh7hbvivt-hack-club-bot.vercel.app/0image.png" alt="screenshot of the successful decompilation process" caption="all nicely decompiled") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/55f3ffe6a3f8130fc7f389d5d151660364e99d93_0image.png" alt="screenshot of the successful decompilation process" caption="all nicely decompiled") }}
I started uploading the decompiled app to github ([taciturnaxolotl/hilton-honors](https://github.com/taciturnaxolotl/hilton-honors)) which was incredibly slow and then started poking around the app. The first thing I noticed was quite a few files with firebase in the name as well as several play store properties files. All of them seemed to follow the same pattern of having a `version`, `client`, and then file specific client key.
+2 -2
content/blog/2025-01-01_spotify-to-apple-music.md
···
<!-- more -->
-
{{ img(id="https://cloud-r47l8h2er-hack-club-bot.vercel.app/0img_3821.jpg" alt="screenshot of the apple music app saying welcome to apple music" caption="the proper horror this should/does instill ๐Ÿ’€") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/f17f56ea1780c37519a4f2cc5d866124acfe476e_0img_3821.jpg" alt="screenshot of the apple music app saying welcome to apple music" caption="the proper horror this should/does instill ๐Ÿ’€") }}
## Shortcut Time
I haven't played around with apple shortcuts near enough but I know that they can be quite powerful (case in point [eieio.games](https://eieio.games/blog/doom-in-the-ios-photos-app/)). I looked to see whether spotify had a shortcut to get songs out first but didn't find anything (come on spotify!) but then when I checked Apple Music it expectedly had quite a few options. One of the options is add to playlist which when I tested it initially with the share sheet as input could take a spotify url. That got me thinking; why can't I just import a file of urls on new lines? Turns out that's exactly what you can do. If you start with a file as the input and then bring it to a split text block then you can route that directly to the add songs block! Whats even better is that you don't even need some fancy looping system, you can simply dump thousands of songs into it and it takes care of it super easily.
-
{{ img(id="https://cloud-pbd6jl8ws-hack-club-bot.vercel.app/0img_3824.png" alt="screenshot of the shortcut" caption="if you want to try it yourself you could build the shortcut from scratch or you can use the link below") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/b05061099d67aa2297e991a074dd6e95bd33096d_0img_3824.png" alt="screenshot of the shortcut" caption="if you want to try it yourself you could build the shortcut from scratch or you can use the link below") }}
Now the second part of the puzzle was exporting the liked playlist. I really didn't want to mess with the slack api and registering an oauth app but then I remembered that you can simple just hit control + a to select songs in the desktop app ๐Ÿคฆ and turns out if you copy it then it literally just chucks it all into your clipboard as spotify links on newlines. A quick `vi test.txt` and sending the file to myself over slack latter I could simply select the song file and use the share sheet to import it. It took a solid 35 seconds to import but gave a nice progress bar up top!
+4 -4
content/blog/2025-01-31_my-life-story-with-tech.md
···
Hi! My name is Kieran, and I've been interested in / involved with cybersecurity and programming since I first started using a laptop at 10! I started out with a raspberry pi 3b+ which taught me how to use debian as well as the basics of creating and maintaining databases and web services. I moved on to an ubuntu laptop about a year latter and started using my raspberry pi as a home server to run small websites on our local lan. Soon I wanted to share them with others and expose them to the internet, so I learned how to use dns and port forwarding and then how to secure the server to prevent attacks with tools like fail2ban!
-
{{ img(id="https://cloud-bwc8bo8f2-hack-club-bot.vercel.app/0image_from_ios.jpg" alt="2 boxes of electronics sitting on a closet shelf" caption="I still have that same rpi today! It's joined with all the random tech bits in two enormously heavy bins in my closet") }}
+
![2 boxes of electronics sitting on a closet shelf](https://hc-cdn.hel1.your-objectstorage.com/s/v3/b237edcfbdefc74f6296d157203ba539187a1e24_0image_from_ios.jpg){caption="I still have that same rpi today! It's joined with all the random tech bits in two enormously heavy bins in my closet"}
Over the next 2 years, I systematically read every single book in the tech section of my local library and became interested in white-hat hacking. I taught myself how to use kali linux and metasploit with the help of many web searches and had quite a bit of fun rooting and then sideloading custom payloads onto our families set of kindle fires (I was eventually restricted to just playing with just one but I did make a home security system with all of them once). I figured out wireshark and started playing with wifi protocals but eventually reached the limit of what I could figure out on my own and took a quick detour of two years to learn blender and build my first computer.
I became interested in home labs and self hosting services around 14 and bought an old workstation off ebay which combined with my set of 3 rasberry pis and several old laptops (and one old pentium tower that I found on the side of the road) made quite a nice playground for deploying my own services. Half a year later I had to pick it all up and move up north which was quite the adventure; my services got completely messed up during the move, and it took my a week or so tinkering with everything to get it back to a stable state.
-
{{ img(id="https://cloud-p1nlhyynq-hack-club-bot.vercel.app/0contributions-graph.gif" alt="gif of my github contributions graph 2021-2025" caption="2021-2022 is mainly just unity and hugo sites lol; I really started seriously using it and doing contributions to other projects 2023-2025. You can also see where I broke my wrist in January of 2025") }}
+
![gif of my github contributions graph 2021-2025](https://hc-cdn.hel1.your-objectstorage.com/s/v3/bf06e9d57d41dd75e328b8898cfe04ef2f30a3f3_0contributions-graph.gif){caption="2021-2022 is mainly just unity and hugo sites lol; I really started seriously using it and doing contributions to other projects 2023-2025. You can also see where I broke my wrist in January of 2025"}
After the move, I became quite interested in front end development and started making quite a few websites and various random coding projects. If you look on my GitHub contributions graph ([github.com/taciturnaxolotl](https://github.com/taciturnaxolotl), you can see it go from a lightly speckled grid in 2021 and 2022 to a much more solid commit streak in 2023. I only had one week when I didn't code anything and that was the second week of the year :) Toward the end of that year I started learning about hardware design and made my first PCB! I also joined a wonderful community called hackclub where I met a ton of amazing teenagers who were also interested in tech just like me! I joined an FRC robotics team in January of the next year and had a blast designing, building, and programming a custom meter square, 150 lb, industrial robot to compete in that year's game!
-
{{ img(id="https://github.com/thepurplebubble/brand/blob/main/public/logo.png?raw=true" alt="purple bubble logo" caption="I loved working on purple bubble ๐Ÿ’– i worked with some pretty incredible people and learned a ton. ik know yall are probably reading this when rss drops it so ๐Ÿซถ") }}
+
![purple bubble logo](https://hc-cdn.hel1.your-objectstorage.com/s/v3/a9e816e5c00a7bdd8e8f0dc8ad2180dfe9792aa5_logo.png_raw_true){caption="I loved working on purple bubble ๐Ÿ’– i worked with some pretty incredible people and learned a ton. ik know yall are probably reading this when rss drops it so ๐Ÿซถ"}
During that same time I also started a 501(c)3 named Purple Bubble with friends that I had met through Hackclub focused on making a secure, cost-effective, and privacy preserving messaging protocol. We drafted a specification and poured many, many hours into planning and developing the protocol over the next year but eventual realized that the messaging protocol space is *incredibly* hard and that there were innate flaws in our protocol that would compromise the security of the app (We couldn't find a good way to anonymize connections to a network of server's while also providing zero metadata transfer of messages between servers; we had originally planned for the protocol to be zero trust federated, but this proved to be a challenge that, no matter how hard we kept thinking and talking about it, we couldn't find a solution too). I learned a huge amount about organizing a group of people and running an organization through that experience and made some wonderful friends, so it wasn't entirely in vain.
My latest project and biggest learning experience in both security and development has been building a time tracking server for coding called Hackatime. It is fully compatible with the popular wakatime.org, which allows it to leverage the hundreds of existing extensions for tracking time spent coding in almost every popular IDE and editor. I made this as a part of an event Hackclub ran called High Seas where they encouraged high school students to make cool projects by giving out awesome prizes for time spent coding (you had to "ship" your project where it would get voted on by the other four thousand teens participating and then via a custom ELO system convert your hours into "doubloons" that could be redeemed for prizes like framework laptops, soldering irons, McMaster Car credits, and many others. If you want to learn more about it, the website is [highseas.hackclub.com](https://highseas.hackclub.com)). In order to track the time of the thousands of teenagers participating, I created this server which was handling thousands of users an hour and hundreds of requests a second. I learned how to scale the server and database and learned an incredible amount that only comes at scale. At one point I got an email that the database bill had increased so much over the previous month that we were going to hit both the `$1k` hard limit and then a `$4k` limit that I had placed on the monthly bill, expecting never to hit it. The team hosting the database (Cockroach DB) graciously offered to reduce our bill down to only `$500` which was incredible. There were many more instances where things broke, or where I discovered security issues that made me grow an insane amount in my knowledge of how to fix things and really pushed me out of my comfort zone. (If you want to take a look at the github repo it is at [github.com/hackclub/hackatime](https://github.com/hackclub/hackatime) and the hosted version is at [waka.hackclub.com](https;//waka.hackclub.com) with a live hours counted tracker)
-
{{ img(id="https://cloud-qaa4875b8-hack-club-bot.vercel.app/0image.png" alt="the cockroach charges in hcb" caption="The price really sky rocketed as we started using it in prod ๐Ÿ˜‚") }}
+
![the cockroach charges in hcb](https://hc-cdn.hel1.your-objectstorage.com/s/v3/23f88382d4fabf9d9f3481c176f5eb5722a188b4_0image.png){caption="The price really sky rocketed as we started using it in prod ๐Ÿ˜‚"}
I'm still trying to figure out what exactly I want to major in, and I'm pretty solidly split between Comp Sci with a cybersecurity focus and Computer/Electrical Engineering. I'm hoping that this camp can help make that decision a bit more clear and give me a better understanding of what getting a major in Cyber Security would be like!
+4 -4
content/blog/2025-02-02_degraded-zpool-proxmox.md
···
<!-- more -->
-
{{ img(id="https://cloud-n6m4bt2xl-hack-club-bot.vercel.app/2image.png" alt="the zpool reporting a downed disk" caption="That really scared the pants off me when I first saw it ๐Ÿ˜‚") }}
+
![the zpool reporting a downed disk](https://hc-cdn.hel1.your-objectstorage.com/s/v3/e54fd32f9a72ef35d310cb3cdc299b297c87baea_2image.png){caption="That really scared the pants off me when I first saw it ๐Ÿ˜‚"}
## Actually fixing it
First I had to find the affected disk physically in my case. Because I was stupid I didn't bother to label them but thankfully the serial numbers of the drives are stuck to them with a sticker so that wasn't terrible.
-
{{ img(id="https://cloud-pi335w1l0-hack-club-bot.vercel.app/0image_from_ios.jpg" alt="chick-fil-a macaroni and cheese with 2 nuggets and some ketchup" caption="(By this point I had spent 30 minutes moaning so I went to lunch)") }}
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/a6512def9bbeedbc1315a8ee58c92fbfb9e4d169_0image_from_ios.jpg" alt="chick-fil-a macaroni and cheese with 2 nuggets and some ketchup" caption="(By this point I had spent 30 minutes moaning so I went to lunch)") }}
Now we can run `lsblk -o +MODEL,SERIAL` to find the serial number of our new drive.
···
ata-ST3750640NS_3QD0BN6V
```
-
{{ img(id="https://cloud-d0bjeue06-hack-club-bot.vercel.app/0image_from_ios.jpg" alt="chick-fil-a macaroni and cheese with 2 nuggets and some ketchup" caption="My case situation is a bit of a mess and I'm using old 7200rpm server drives for pretty much everything; the dream is a 3 drive 2 TB each m.2 nvme ssd setup, maybe someday ๐Ÿคท") }}
+
![chick-fil-a macaroni and cheese with 2 nuggets and some ketchup](https://hc-cdn.hel1.your-objectstorage.com/s/v3/f539cc5cb4e40b768f4b7bc6dc719467e438c6ed_0image_from_ios.jpg){caption="My case situation is a bit of a mess and I'm using old 7200rpm server drives for pretty much everything; the dream is a 3 drive 2 TB each m.2 nvme ssd setup, maybe someday ๐Ÿคท"}
We are going to go with the first id so no we move on to the zfs part. Running `zpool status vault-of-the-eldunari` we can get the status of the pool:
···
We can add our new disk with `zpool replace vault-of-the-eldunari 9201394420428878514 ata-ST3750640NS_3QD0BN6V` but first we wipe the disk from proxmox under the disks tab on our proxmox node to make sure its all clean before we restore the pool after we do that we also initalize a new gpt table. Now we are ready to replace the disk. Running this command can take quite a while and it doesn't output anything so sit tight. After waiting a few minutes proxmox reported that resilvering would take 1:49 minutes and it was 5% done already! I hope this helped at least one other person but I'm mainly writing this to remind myself how to do this when it inevitably happens again :)
-
{{ img(id="https://cloud-n6m4bt2xl-hack-club-bot.vercel.app/0image.png" alt="the zpool reporting a downed disk" caption="It's slow but faster then I expected for HDDs") }}
+
![the zpool reporting a downed disk](https://hc-cdn.hel1.your-objectstorage.com/s/v3/8cc1c0d1717abacbc29d634004b14ec7475de0f2_0image.png){caption="It's slow but faster then I expected for HDDs"}
+1 -1
content/blog/2025-02-15_remove-exif-git-hook.md
···
I started with the naive method of just having a `.git/hooks/pre-commit` file that would run `exiftool` on the input but after realizing that hooks placed there wouldn't be synced to the repo decided that wasn't the best way. I moved to using a script that would symlink files from the `hooks` directory to `.git/hooks`. It worked moderately well but due to the fact that I used (yes I feel the shame admitting this [:uw_embarrassed:](https://cachet.dunkirk.sh/emojis/uw_embarrassed/r)) `#!/bin/bash` instead of `#!/usr/bin/env bash`. Not realizing my mistake and believing it to be related to the symlink I found [this stack overflow](https://stackoverflow.com/questions/4592838/symbolic-link-to-a-hook-in-git/#:~:text=While%20you%20can%20use%20symbolic%20links) answer which taught me that you can use `git config core.hooksPath hooks` to move the hooks directory to `./hooks` in the root of your repo! After doing that and it still not working (i feel very dense writing this lol) I finally realized that the shebang was wrong and then it worked!
-
{{ img(id="https://cdn.hackclubber.dev/slackcdn/9049d20038cc3058acee1bbe58c5ac3f.png" alt="the commit hook finally working!" caption="phew") }}
+
![the commit hook finally working!](https://hc-cdn.hel1.your-objectstorage.com/s/v3/f2ba3f2dbad8c67eccc42ddbb51bc7128f85d049_9049d20038cc3058acee1bbe58c5ac3f.png){caption="phew"}
Is there anything at all to learn from this? Well yes actually! You can use the script below and the `git config core.hooksPath hooks` setting to scrub your own images!
+16 -20
content/blog/2025-03-14_my-animations.md
···
{{ youtube(id="O7SYcdUM8mI", caption="2021.01.27 first jelly jar") }}
-
{{ img(id="https://cdn.hack.pet/slackcdn/2f6bd410317b341df20d8349771bb716.jpg" alt="tesla in a showroom with fire jets" caption="2021.02.10 tesla showroom") }}
+
![tesla in a showroom with fire jets](https://hc-cdn.hel1.your-objectstorage.com/s/v3/24338fe6379f23bebeda87ab6b0868c4a2890460_2f6bd410317b341df20d8349771bb716.jpg){caption="2021.02.10 tesla showroom"}
{{ youtube(id="7Ozt7WcVwt0", caption="2021.09.27 Chalet a la Tagia minecraft animation") }}
-
{{ img(id="https://cdn.hackclubber.dev/slackcdn/90335a1f835749fe219a677a24aedd02.png" alt="cube diorama" caption="2021.12.15 cube diorama") }}
+
![cube diorama](https://hc-cdn.hel1.your-objectstorage.com/s/v3/7ba5e3e478447e92bb4dc5091c99081c7294745c_90335a1f835749fe219a677a24aedd02.png){caption="2021.12.15 cube diorama"}
{{ youtube(id="O5iHoFwKQuE", caption="2021.12.17 creature walk cycle test") }}
···
{{ youtube(id="Gy0K-Gi95Jg", caption="2022.01.22 lost music visualization") }}
-
{{ img(id="https://cdn.hack.pet/slackcdn/c1759999d78c2a99312b5b34562c6f72.png" alt="ice sphere" caption="2022.01.24 ice icosphere") }}
+
![ice sphere](https://hc-cdn.hel1.your-objectstorage.com/s/v3/ed654ffc15801a163f4996e011371f433d66d8f6_c1759999d78c2a99312b5b34562c6f72.png){caption="2022.01.24 ice icosphere"}
-
{{ img(id="https://cdn.hackclubber.dev/slackcdn/c9587628d15c605e99a3a72769bad60a.png" alt="glass jar with marbles" caption="2022.01.27 marble jar") }}
+
![glass jar with marbles](https://hc-cdn.hel1.your-objectstorage.com/s/v3/766558e98ab81bd262f26e0650507669f88bbdb6_c9587628d15c605e99a3a72769bad60a.png){caption="2022.01.27 marble jar"}
{{ youtube(id="ue-hy7w1-JE", caption="2022.02.08 firefly particle sim") }}
···
{{ youtube(id="BGJbmXqCD5M", caption="2022.03.16 molecular plexus") }}
-
{{ img(id="https://cdn.fluff.pw/slackcdn/57287dfc2f5eb7d6effe43f7ebbde030.png" alt="twisted torus with flattened sphere in the center" caption="2022.03.16 twisted torus") }}
+
![twisted torus with flattened sphere in the center](https://hc-cdn.hel1.your-objectstorage.com/s/v3/67268702bc70adbfef7c9a2aaca996901e766737_57287dfc2f5eb7d6effe43f7ebbde030.png){caption="2022.03.16 twisted torus"}
{{ youtube(id="yT37oZmd4hc", caption="2022.03.17 hex tunnel") }}
{{ youtube(id="3SQN0L0wbhU", caption="2022.03.23 wavy strips motion effects") }}
-
{{ img(id="https://cdn.fluff.pw/slackcdn/b47952a40fd7695b9667357fb0c8386b.png" alt="airship far bottom" caption="2022.03.31 airship") }}
+
![airship far bottom](https://hc-cdn.hel1.your-objectstorage.com/s/v3/f74e7ccb0591b5f3d1b2cff6c773fa88653aa6a8_b47952a40fd7695b9667357fb0c8386b.png){caption="2022.03.31 airship"}
-
{{ img(id="https://cdn.hackclubber.dev/slackcdn/52c9ccd4378fdac717a5a9f1b924000d.png" alt="airship far side") }}
+
![airship far side](https://hc-cdn.hel1.your-objectstorage.com/s/v3/1dddf1a9c6d8fcf5f907fd534e7a1cd1a679d1b9_52c9ccd4378fdac717a5a9f1b924000d.png)
-
{{ img(id="https://cdn.fluff.pw/slackcdn/4138d497e2deb1f9c7369762e08e5a7c.png" alt="airship front top") }}
+
![airship front top](https://hc-cdn.hel1.your-objectstorage.com/s/v3/d25c0834922bb1283bf8e994e2e550127f93aa31_4138d497e2deb1f9c7369762e08e5a7c.png)
-
{{ img(id="https://cdn.hack.pet/slackcdn/bac28fe85728b6d009c4e520ccff3140.png" alt="airship front cab") }}
+
![airship front cab](https://hc-cdn.hel1.your-objectstorage.com/s/v3/552636643eee62271818fed80b6e365de91aca05_bac28fe85728b6d009c4e520ccff3140.png)
-
{{ img(id="https://cdn.hack.pet/slackcdn/963198d64f001f55d703cdc65a257e98.png" alt="airship front side") }}
+
![airship front side](https://hc-cdn.hel1.your-objectstorage.com/s/v3/a62a9da89cff18aeeb95f1cc84027f86fc43cde9_963198d64f001f55d703cdc65a257e98.png)
-
{{ img(id="https://cdn.hack.pet/slackcdn/d3c7021428757b3bd607ffdbcc9daa7f.png" alt="minecraft village front door with villager" caption="2022.04.06 viking village") }}
+
![minecraft village front door with villager](https://hc-cdn.hel1.your-objectstorage.com/s/v3/966ccc3514057cbee79f1fa0b91750e909cbbce0_d3c7021428757b3bd607ffdbcc9daa7f.png){caption="2022.04.06 viking village"}
-
{{ img(id="https://cdn.hackclubber.dev/slackcdn/55aff1a73336f1262e625a667ca4d7f8.png" alt="minecraft village from across a small lake with a skeleton and witch" caption="2022.04.06 mountain lake village") }}
+
![minecraft village from across a small lake with a skeleton and witch](https://hc-cdn.hel1.your-objectstorage.com/s/v3/58c5f094932910f8f011a36c710ca6c7ac51396f_55aff1a73336f1262e625a667ca4d7f8.png){caption="2022.04.06 mountain lake village"}
{{ youtube(id="gPRrt_0NMKE", caption="2022.04.07 rolling balls motion effects") }}
{{ youtube(id="hHIv2yO9DvU", caption="2022.04.29 cloth sim") }}
-
{{ img(id="https://cdn.hack.ngo/slackcdn/ab7015d03697d62ebedad011161b40db.gif" alt="clay animation of cubes within cubes looping endlessly" caption="2022.05.02 infinite cubes") }}
-
{{ youtube(id="zqyv7GBTLGA", caption="2022.05.30 fire handwriting") }}
-
{{ img(id="https://cdn.fluff.pw/slackcdn/42e90de7ab09d863c735a6ca74069fdc.png" alt="a cylinder with a bunch of bumps on it" caption="2022.06.24 the cylinder") }}
-
-
{{ img(id="https://cdn.hack.ngo/slackcdn/dc691e3fe9382467a01821aa966e253b.jpg" alt="a castle made out of mud with a mud giant and ufo" caption="2022.07.15 mud castle") }}
+
![a cylinder with a bunch of bumps on it](https://hc-cdn.hel1.your-objectstorage.com/s/v3/36eaad616bad2d70d5f066d6039b191ff26cfea2_42e90de7ab09d863c735a6ca74069fdc.png){caption="2022.06.24 the cylinder"}
{{ youtube(id="XVyMUROofZ8", caption="2022.07.21 the iconic donut") }}
···
{{ youtube(id="zRlgWbW1Qcw", caption="2022.08.01 mirror physics") }}
-
{{ img(id="https://cdn.hack.pet/slackcdn/b5876d1491b397c1a80fb8d7411fd627.png" alt="the earth from space" caption="2022.08.30 the earth") }}
+
![the earth from space](https://hc-cdn.hel1.your-objectstorage.com/s/v3/d30ec95b2224c93cf244bb65f6bc9fda1c458149_b5876d1491b397c1a80fb8d7411fd627.png){caption="2022.08.30 the earth"}
-
{{ img(id="https://cdn.hack.pet/slackcdn/bb000a699c4aa41413a282a31cfb0f59.png" alt="11 glowing pendulums swinging in a flowing curve" caption="2022.08.31 glowing pendulums") }}
+
![11 glowing pendulums swinging in a flowing curve](https://hc-cdn.hel1.your-objectstorage.com/s/v3/e30d3004c23540bf048cd5bbb3f5c98336db456f_bb000a699c4aa41413a282a31cfb0f59.png){caption="2022.08.31 glowing pendulums"}
-
{{ img(id="https://cdn.hack.pet/slackcdn/f1cdd1a422c0a3280541f3bbab4f662e.png" alt="an orange flower in a flower pot with skyline dirt" caption="2022.10.22 orange flower") }}
+
![an orange flower in a flower pot with skyline dirt](https://hc-cdn.hel1.your-objectstorage.com/s/v3/a5c2d4885ccaaa95628344bedf0fd7fb433034db_f1cdd1a422c0a3280541f3bbab4f662e.png){caption="2022.10.22 orange flower"}
## 2023
+3 -1
content/blog/2025-04-24_atuin.md
···
Here's the basic scaffolding you'll need for your Nix configuration:
> configuration.nix
+
```nix
{ config, pkgs, ... }:
···
and then the home-manager bit
> shell.nix in home manager
+
```nix
{ config, pkgs, ... }:
···
Now saving the secrets with agenix is not particularly tricky you just have to know that this is an option. Run `agenix -e atuin-session.age` and then paste in the session from `~/.local/share/atuin/session` and then instead of just saving like normal you need to run `:set binary` and then `:set noeol` and then you can save the file like normal.
-
Anyways now i'm enjoying my stats and it's on to the next project (proly [serif.blue](https://tangled.sh/@dunkirk.sh/serif) ๐Ÿ‘€)
+
Anyways now i'm enjoying my stats and it's on to the next project (proly [serif.blue](https://tangled.org/@dunkirk.sh/serif) ๐Ÿ‘€)
```
> atuin stats
+19
content/blog/2025-10-24_github-phishing.md
···
+
+++
+
title = "Novel phishing tactic using github notifications"
+
date = 2025-10-24
+
slug = "github-phishing"
+
description = "the creators certainly didn't execute this very well"
+
+
[taxonomies]
+
tags = ["phishing"]
+
+++
+
+
I received an email yesterday at `19:45 EST` titled `[yccombinator/-notification] Y-Combinator W2026 | $15M Y-Combinator & GitHub (Issue #126)`. From a quick glance it was easy to tell that it was a phising email funneling people to `https://y-comblnator.com/apply`. They did at least try to disguise the link but then there is a ton of whitespace and you can see that they tagged 32 github users including mine.
+
+
<!-- more -->
+
+
{{ img(id="https://hc-cdn.hel1.your-objectstorage.com/s/v3/47a842d35a86d6ac16d717b40ee69f2f801ff852_screenshot_2025-09-23_at_21.23.19.png" alt="a screenshot of the email" caption="I've never seen something simultaniously this stupid and (as far as i can tell) novel") }}
+
+
Like most phishing emails I doubt most people would fall for this but if you were moving quickly and not thinking straight maybe you could fall for this?
+
+
Cloudflare has blocked the site due to phishing by now (13:17 Sept 24th) which is a shame since I would have loved to dig into the site a bit.
+15 -1
content/pfp.md
···
# 2025
-
February 27th to Present
+
November 11th till present
+
+
![kieran with a robotics sweatshirt and fall leaves behind](/pfps/fall.webp)
+
+
September 13th till November 11th
+
+
![kieran with his hands framing his face](/pfps/hands.jpg)
+
+
June to September 13th
+
+
dynamically updating varient of the starry background one with the cat; the background would change with the time.
+
+
{{ bluesky(post="https://bsky.app/profile/serif.blue/post/3lqncouklcc2e") }}
+
+
February 27th to June
![kieran with an orange cast in a polaroid over a pinkish background](/pfps/instsqc-rat-pfp.webp)
+12 -12
content/verify.md
···
# domains / email
-
I personally own and control this domain ([dunkirk.sh](https://dunkirk.sh)) as well as [kieranklukas.com](https://kieranklukas.com) and maintain email addresses on both domains. I also just got [serif.blue](https://serif.blue) which I'm super excited about!
+
I personally own and control this domain ([dunkirk.sh](https://dunkirk.sh)) as well as [kieranklukas.com](https://kieranklukas.com) and maintain email addresses on both domains. I also just got [serif.blue](https://serif.blue) which I'm super excited about! I have a redirect from [kieran.klukas.net](https://kieran.klukas.net) to dunkirk.sh that is also maintained.
-
If you want to contact me, via email to `me@dunkirk.sh` or `me@kieranklukas.com` are both good places to find me. I also send email from `kieran@hackclub.com` and ~`kieran@purplebubble.org`~.
+
If you want to contact me, via email to `kieran@dunkirk.sh`. I also send email from `kieran@hackclub.com` and ~`kieran@purplebubble.org`~.
# accounts
-
* Keyoxide: [aspe:keyoxide.org:QMHCMT55EODYTEBQ5C7QOAFN6A](https://keyoxide.org/aspe:keyoxide.org:QMHCMT55EODYTEBQ5C7QOAFN6A)
-
* Github: [@taciturnaxolotl](https://github.com/taciturnaxolotl) (formerly @kcoderhtml)
-
* [Hackclub Slack](https://hackclub.com/slack/): [@krn](https://hackclub.slack.com/team/U062UG485EE) (display name changes quite often though) with userID `U062UG485EE`
-
* Bluesky: [@dunkirk.sh](https://bsky.app/profile/dunkirk.sh)
-
* Mastodon: [@taciturnaxolotl@social.dino.icu](https://social.dino.icu/@taciturnaxolotl)
-
* Youtube: [@kieran.rambles](https://www.youtube.com/@kieran.rambles)
-
* Matrix: ~[@kieran:dumpsterfire.icu](https://matrix.to/#/@kieran.matrix.dumpsterfire.icu)~ or [@sclacker:matrix.org](https://matrix.to/#/@sclacker:matrix.org) (i'm active on here once in a blue moon so this isn't a great way to contact me urgently)
-
* Signal: `verox.89`
-
* Phone #: *Do you really think i'm going to publicly share that?*
+
- Keyoxide: [aspe:keyoxide.org:QMHCMT55EODYTEBQ5C7QOAFN6A](https://keyoxide.org/aspe:keyoxide.org:QMHCMT55EODYTEBQ5C7QOAFN6A)
+
- Github: [@taciturnaxolotl](https://github.com/taciturnaxolotl) (formerly @kcoderhtml)
+
- [Hackclub Slack](https://hackclub.com/slack/): [@krn](https://hackclub.slack.com/team/U062UG485EE) (display name changes quite often though) with userID `U062UG485EE`
+
- Bluesky: [@dunkirk.sh](https://bsky.app/profile/dunkirk.sh)
+
- Mastodon: [@taciturnaxolotl@social.dino.icu](https://social.dino.icu/@taciturnaxolotl)
+
- Youtube: [@kieran.rambles](https://www.youtube.com/@kieran.rambles)
+
- Matrix: ~[@kieran:dumpsterfire.icu](https://matrix.to/#/@kieran.matrix.dumpsterfire.icu)~ or [@sclacker:matrix.org](https://matrix.to/#/@sclacker:matrix.org) (i'm active on here once in a blue moon so this isn't a great way to contact me urgently)
+
- Signal: `verox.89`
# keys
> SSH
-
```ssh
+
+
```pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= me@dunkirk.sh
```
+31 -1
hooks/pre-commit
···
# Check if exiftool is installed
if ! command -v exiftool &> /dev/null; then
-
echo "Error: exiftool is not installed. Please install it." >&2
+
echo "Error: exiftool is not installed. Please install it." >&2
exit 1
fi
+
# Flag to track if we found any draft files
+
found_draft=0
+
+
# First pass: check for draft files
+
while read -r file; do
+
case "$file" in
+
*.md)
+
# Check if file contains draft = true within +++ header section
+
awk '
+
/^\+\+\+$/ { inblock = !inblock }
+
inblock && /draft = true/ { found = 1 }
+
END { exit(found ? 0 : 1) }
+
' "$file"
+
if [ $? -eq 0 ]; then
+
echo "Error: Draft file detected: $file" >&2
+
echo "Please remove draft status or unstage this file before committing." >&2
+
found_draft=1
+
fi
+
;;
+
*)
+
;;
+
esac
+
done < <(git diff --cached --name-only --diff-filter=ACMR)
+
+
# Exit if we found any draft files
+
if [ $found_draft -eq 1 ]; then
+
exit 1
+
fi
+
+
# Second pass: process images
while read -r file; do
case "$file" in
*.jpg|*.jpeg|*.png|*.gif|*.tiff|*.bmp)
+19
package.json
···
+
{
+
"name": "zera",
+
"type": "module",
+
"scripts": {
+
"dev": "bun run scripts/dev.ts",
+
"serve": "bun run scripts/dev.ts",
+
"build": "bun run scripts/build.ts",
+
"preprocess": "bun run scripts/preprocess.ts",
+
"gen-og": "bun run scripts/genOG.ts"
+
},
+
"dependencies": {
+
"dotenv": "^16.4.7",
+
"glob": "^13.0.0"
+
},
+
"devDependencies": {
+
"@types/bun": "latest",
+
"puppeteer": "^23.6.0"
+
}
+
}
+3 -2
sass/css/_copy-button.scss
···
}
}
-
pre.z-code:not(.pre-container):not(.pre-container pre) {
+
pre.z-code:not(.pre-container pre) {
position: relative;
border: none;
background-color: var(--accent);
padding: 0.4em;
border-bottom: 5px solid var(--bg-light);
+
border-radius: 7px 7px 10px 10px;
code {
border-radius: 0.3rem;
display: block;
overflow-x: auto;
padding: 1em;
-
background-color: var(--bg-light);
+
background-color: var(--nightshade-violet);
margin: 0;
border: none;
box-shadow: none;
+32
sass/css/_emoji-inline.scss
···
+
.emoji-inline--wrapper {
+
vertical-align: baseline;
+
height: auto;
+
position: relative;
+
overflow: visible;
+
vertical-align: top;
+
object-fit: contain;
+
align-items: center;
+
display: inline-flex;
+
overflow: hidden;
+
width: 1.375rem;
+
height: 1.375rem;
+
}
+
+
.emoji-inline--wrapper img {
+
object-fit: contain;
+
position: absolute;
+
top: 50%;
+
overflow: hidden;
+
width: 1.375rem !important;
+
height: 1.375rem !important;
+
margin-top: -0.6875rem;
+
margin-left: 0 !important;
+
margin-right: 0 !important;
+
margin-bottom: 0 !important;
+
border: none !important;
+
border-radius: 0 !important;
+
padding: 0 !important;
+
opacity: 1 !important;
+
max-width: 1.375rem !important;
+
display: inline !important;
+
}
+109
sass/css/_lightbox.scss
···
+
#lightbox {
+
display: none;
+
position: fixed;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 100%;
+
background-color: rgba(0, 0, 0, 0.8);
+
z-index: 9999;
+
justify-content: center;
+
align-items: center;
+
}
+
+
.lightbox-content {
+
position: relative;
+
max-width: 90%;
+
max-height: 90%;
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
justify-content: center;
+
}
+
+
#lightbox-img {
+
max-width: 100%;
+
max-height: 80vh;
+
object-fit: contain;
+
border: none;
+
padding: 0;
+
margin: 0;
+
background: transparent;
+
border-radius: 0;
+
}
+
+
.lightbox-controls {
+
display: flex;
+
gap: 2rem;
+
margin-top: 1rem;
+
align-items: center;
+
}
+
+
.lightbox-close {
+
position: fixed;
+
top: 20px;
+
right: 20px;
+
font-size: 40px;
+
color: var(--text);
+
background: transparent !important;
+
border: none;
+
cursor: pointer;
+
padding: 0;
+
line-height: 1;
+
-webkit-tap-highlight-color: transparent;
+
transition: color 120ms ease, transform 300ms ease;
+
}
+
+
.lightbox-close:hover {
+
background: transparent !important;
+
color: var(--accent);
+
background-color: transparent !important;
+
transform: rotate(90deg);
+
}
+
+
.lightbox-close:focus {
+
background: transparent !important;
+
background-color: transparent !important;
+
}
+
+
.lightbox-prev,
+
.lightbox-next {
+
font-size: 30px;
+
color: var(--text);
+
background: transparent !important;
+
border: none;
+
cursor: pointer;
+
padding: 0.5rem 1rem;
+
user-select: none;
+
-webkit-tap-highlight-color: transparent;
+
transition: color 120ms ease;
+
}
+
+
.lightbox-prev:hover,
+
.lightbox-next:hover {
+
background: transparent !important;
+
color: var(--accent);
+
background-color: transparent !important;
+
}
+
+
.lightbox-prev:focus,
+
.lightbox-next:focus {
+
background: transparent !important;
+
background-color: transparent !important;
+
}
+
+
@media only screen and (max-width: 720px) {
+
.lightbox-close {
+
top: 10px;
+
right: 10px;
+
}
+
}
+
+
.img-container {
+
cursor: pointer;
+
transition: opacity 120ms ease;
+
}
+
+
.img-container:hover {
+
opacity: 0.9;
+
}
+2 -1
sass/css/main.scss
···
@use "mods";
@use "copy-button";
-
@use "theme-toggle";
@use "view-transitions";
+
@use "emoji-inline";
+
@use "lightbox";
+238 -168
sass/css/mods.css
···
#nav-bar {
-
padding: 0.625rem 0 0 0;
-
display: flex;
-
flex-direction: row;
-
gap: 0.25rem;
-
flex-wrap: wrap;
-
justify-content: flex-end;
-
align-items: center;
-
align-content: flex-end;
+
padding: 0.625rem 0 0 0;
+
display: flex;
+
flex-direction: row;
+
flex-wrap: wrap;
+
justify-content: flex-end;
+
align-items: center;
+
align-content: flex-end;
+
}
+
+
#nav-bar a {
+
text-decoration: none;
+
color: var(--link);
+
padding: 0 0.25rem;
+
border-radius: 0.25rem;
+
transition: all 120ms ease;
+
position: relative;
+
font-weight: 600;
+
}
+
+
#nav-bar a:hover {
+
color: var(--accent);
+
background-color: color-mix(in oklab, var(--accent) 15%, transparent);
+
}
+
+
#nav-bar a.active {
+
color: var(--link-visited);
+
background-color: color-mix(in oklab, var(--accent) 20%, transparent);
+
margin: 0 0.15rem;
}
#footer-container {
-
display: flex;
-
flex-direction: column;
-
justify-content: center;
-
align-items: center;
-
text-align: center;
-
padding-bottom: 0.5rem;
+
display: flex;
+
flex-direction: column;
+
justify-content: center;
+
align-items: center;
+
text-align: center;
+
padding-bottom: 0.5rem;
}
#footer-container p {
-
margin: 0;
+
margin: 0;
}
.accent-data {
-
color: var(--accent-dark);
+
color: var(--accent-dark);
}
.tags-data {
-
display: flex;
-
flex-direction: row;
-
flex-wrap: wrap;
-
justify-content: flex-end;
-
align-items: flex-start;
-
align-content: flex-end;
-
gap: 0.25rem;
+
display: flex;
+
flex-direction: row;
+
flex-wrap: wrap;
+
justify-content: flex-end;
+
align-items: flex-start;
+
align-content: flex-end;
+
gap: 0.25rem;
}
.title-list li {
-
margin-bottom: 0.375rem;
+
margin-bottom: 0.375rem;
}
/* icons settings */
.icons {
-
width: 1.3rem;
-
height: 1.3rem;
-
aspect-ratio: 1 / 1;
-
display: inline-block;
-
vertical-align: middle;
-
color: var(--text);
-
fill: var(--text);
-
background-color: transparent;
-
cursor: pointer;
+
width: 1.3rem;
+
height: 1.3rem;
+
aspect-ratio: 1 / 1;
+
display: inline-block;
+
vertical-align: middle;
+
color: var(--text);
+
fill: var(--text);
+
background-color: transparent;
+
cursor: pointer;
}
.icons:hover {
-
background-color: transparent;
-
color: var(--accent);
+
background-color: transparent;
+
color: var(--accent);
}
/* footnotes */
.footnote-definition {
-
margin: 0 0 0 0.125rem;
+
margin: 0 0 0 0.125rem;
}
.footnote-definition-label {
-
color: var(--accent);
+
color: var(--accent);
}
.footnote-definition p {
-
display: inline;
-
margin: 0.625rem 0 0 0.625rem;
+
display: inline;
+
margin: 0.625rem 0 0 0.625rem;
}
/* general classes */
.no-style {
-
padding: 0;
-
margin: 0;
-
border: none;
-
border-radius: 0;
+
padding: 0;
+
margin: 0;
+
border: none;
+
border-radius: 0;
}
.no-style:hover {
-
background-color: transparent;
-
color: var(--accent);
+
background-color: transparent;
+
color: var(--accent);
}
.center {
-
text-align: center;
+
text-align: center;
}
-
.center img {
-
display: block;
-
margin: 1rem auto;
+
.center .img-container {
+
margin: 1rem auto;
}
.center figcaption {
-
text-align: center;
+
text-align: center;
}
.float-right {
-
float: right;
+
float: right;
}
.float-left {
-
float: left;
+
float: left;
}
-
div > code,
+
div>code,
li code,
p code {
-
padding: 0.1em 0.3em 0.1em 0.3em;
-
color: var(--text-dark);
-
background-color: var(--bg-light);
+
padding: 0.1em 0.3em 0.1em 0.3em;
+
color: var(--text-dark);
+
background-color: var(--bg-light);
}
pre {
-
border-top-left-radius: 0;
+
border-top-left-radius: 0;
}
blockquote {
-
padding-top: 0.2rem;
-
padding-bottom: 0.2rem;
+
padding-top: 0.2rem;
+
padding-bottom: 0.2rem;
}
blockquote:has(+ pre) {
-
display: inline-block;
-
border: none;
-
font-family: "code", monospace !important;
-
font-size: 0.8rem;
-
font-weight: 600;
-
margin: 0;
-
margin-bottom: 0.2rem;
-
margin-block: 0 0;
-
border-top-left-radius: 0.2em;
-
border-top-right-radius: 0.2em;
-
padding-left: 0.75rem;
-
padding-right: 0.75rem;
-
padding-top: 0.25rem;
-
padding-bottom: 0.25rem;
-
position: relative;
-
background-color: var(--accent);
+
display: inline-block;
+
border: none;
+
font-family: "code", monospace !important;
+
font-size: 0.8rem;
+
font-weight: 600;
+
margin: 0;
+
margin-bottom: 0.2rem;
+
margin-block: 0 0;
+
border-top-left-radius: 0.2em;
+
border-top-right-radius: 0.2em;
+
padding-left: 0.75rem;
+
padding-right: 0.75rem;
+
padding-top: 0.25rem;
+
padding-bottom: 0.25rem;
+
position: relative;
+
background-color: var(--accent);
}
blockquote:has(+ pre) p {
-
margin: 0;
-
color: var(--accent-text);
+
margin: 0;
+
color: var(--accent-text);
}
blockquote:has(+ pre) p::selection {
-
background: var(--pink-puree);
+
background: var(--pink-puree);
}
.yt-embed {
-
width: 100%;
-
display: flex;
-
flex-direction: column;
-
justify-content: center;
-
align-content: center;
-
text-align: center;
+
width: 100%;
+
display: flex;
+
flex-direction: column;
+
justify-content: center;
+
align-content: center;
+
text-align: center;
}
.yt-embed iframe {
-
width: 100%;
-
aspect-ratio: 16 / 9;
-
align-self: center;
+
width: 100%;
+
aspect-ratio: 16 / 9;
+
align-self: center;
}
.yt-embed figcaption {
-
margin-top: 1rem;
-
text-align: center;
+
margin-top: 1rem;
+
text-align: center;
}
#elr table {
-
border-style: none;
+
border-style: none;
}
#elr td {
-
border-style: dashed;
+
border-style: dashed;
}
img.avatar {
-
width: 24px;
-
height: 24px;
-
aspect-ratio: 1 / 1;
-
border-radius: 50%;
-
vertical-align: middle;
-
display: inline-block;
-
border: none;
-
padding: 0;
-
margin: 0;
+
width: 24px;
+
height: 24px;
+
aspect-ratio: 1 / 1;
+
border-radius: 50%;
+
vertical-align: middle;
+
display: inline-block;
+
border: none;
+
padding: 0;
+
margin: 0;
}
cite {
-
display: inline-flex;
-
align-items: center;
-
vertical-align: middle;
+
display: inline-flex;
+
align-items: center;
+
vertical-align: middle;
}
cite a {
-
display: inline-flex;
-
align-items: center;
+
display: inline-flex;
+
align-items: center;
}
cite a img.avatar {
-
margin-right: 5px;
+
margin-right: 5px;
}
.image-gallery {
-
display: flex;
-
flex-direction: column;
-
gap: 0.5rem;
-
padding-top: 0.5rem;
+
display: flex;
+
flex-direction: column;
+
gap: 0.5rem;
+
padding-top: 0.5rem;
}
.image-gallery img {
-
max-width: 100%;
-
border-radius: 0.25rem;
-
padding: 0;
-
margin: 0;
+
max-width: 100%;
+
border-radius: 0.25rem;
+
padding: 0;
+
margin: 0;
}
.side-by-side {
-
display: flex;
-
flex-direction: row;
+
display: flex;
+
flex-direction: row;
}
.side-by-side img {
-
max-width: calc(50% - 0.25rem);
+
max-width: calc(50% - 0.25rem);
}
.gallery-grid {
-
display: grid;
-
grid-template-columns: repeat(2, 1fr);
-
gap: 0.1rem;
+
display: grid;
+
grid-template-columns: repeat(2, 1fr);
+
gap: 0.1rem;
}
.gallery-grid img {
-
width: 100%;
-
height: auto;
-
object-fit: cover;
+
width: 100%;
+
height: auto;
+
object-fit: cover;
}
.inlined-bubbles {
-
width: auto;
-
pointer-events: none;
-
display: block;
-
margin-top: 10px;
-
text-align: center;
+
width: auto;
+
pointer-events: none;
+
display: block;
+
margin-top: 10px;
+
text-align: center;
}
.bubble {
-
padding: 0.7em 1.2em 0.7em 1.2em;
-
background: var(--accent);
-
border-bottom: 5px solid var(--bg-light);
-
border-radius: 7px 7px 10px 10px;
-
font-size: 1rem;
-
font-weight: bold;
-
color: var(--accent-text);
-
display: inline-block;
-
text-align: center;
-
transition:
-
transform 0.3s ease,
-
opacity 0.3s ease;
+
padding: 0.7em 1.2em 0.7em 1.2em;
+
background: var(--accent);
+
border-bottom: 5px solid var(--bg-light);
+
border-radius: 7px 7px 10px 10px;
+
font-size: 1rem;
+
font-weight: bold;
+
color: var(--accent-text);
+
display: inline-block;
+
text-align: center;
+
transition:
+
transform 0.3s ease,
+
opacity 0.3s ease;
}
@keyframes bubbleIn {
-
0% {
-
opacity: 0;
-
transform: translateY(10px) scale(0.95);
-
}
-
100% {
-
opacity: 1;
-
transform: translateY(0) scale(1);
-
}
+
0% {
+
opacity: 0;
+
transform: translateY(10px) scale(0.95);
+
}
+
+
100% {
+
opacity: 1;
+
transform: translateY(0) scale(1);
+
}
}
@media (prefers-reduced-motion: no-preference) {
-
.bubble.animate-in {
-
animation: bubbleIn 0.3s ease-out forwards;
-
}
+
.bubble.animate-in {
+
animation: bubbleIn 0.3s ease-out forwards;
+
}
}
.bubble a {
-
color: var(--accent-text);
+
color: var(--accent-text);
+
text-decoration: none;
}
#time-ago {
-
font-weight: normal;
-
font-size: 0.8rem;
+
font-weight: normal;
+
font-size: 0.8rem;
+
}
+
+
.badge-row {
+
display: flex;
+
flex-wrap: wrap;
+
justify-content: center;
+
gap: 8px;
+
padding-bottom: 0.5rem;
+
}
+
+
.badge-row img {
+
display: inline-block;
+
border: none;
+
border-radius: 0;
+
box-shadow: none;
+
max-width: 100%;
+
height: auto;
+
margin: 0;
+
padding: 0;
+
}
+
+
.badge-row a {
+
display: inline-flex;
+
align-items: center;
+
}
+
+
.img-group {
+
display: flex;
+
flex-direction: row;
+
gap: 1rem;
+
max-width: 100%;
+
justify-content: center;
+
align-items: flex-start;
}
-
:root {
-
--nightshade-violet: oklch(0.27 0.0242 287.67);
-
--purple-night: oklch(27.58% 0.0203 289.13);
-
--dark-crushed-grape: oklch(74.02% 0.0756 311.96);
-
--light-crushed-grape: oklch(73.48% 0.1008 284.99);
-
--reseda-green: oklch(62.33% 0.0475 126.94);
-
--earth-yellow: oklch(86.49% 0.018 73.05);
-
--sunset: oklch(0.86 0.0213 73.05);
-
--ultra-violet: oklch(42.21% 0.0676 297.45);
-
--rose-quartz: oklch(65.32% 0.0585 311.96);
-
--pink-puree: oklch(75.65% 0.0555 290.76);
-
--lavendar-breeze: oklch(91.06% 0.0223 290.76);
-
--purple-gray: oklch(25.63% 0.0002 290.76);
-
--alice-blue: oklch(95.38% 0.0118 239.91);
+
.img-group .img-container {
+
background-color: var(--accent);
+
border-bottom: 4px solid var(--bg-light);
+
border-radius: 7px 7px 10px 10px;
+
padding: 0.35rem;
+
margin: 1rem 0;
+
line-height: 0;
}
+
+
.img-group img {
+
max-width: 100%;
+
height: auto;
+
border-radius: 0.35rem;
+
}
+
+
:root {
+
--nightshade-violet: oklch(0.27 0.0242 287.67);
+
--purple-night: oklch(27.58% 0.0203 289.13);
+
--red-crushed-grape: oklch(0.6829 0.1189 296.74);
+
--dark-crushed-grape: oklch(0.6261 0.1289 284.99);
+
--light-crushed-grape: oklch(0.7727 0.094 296.74);
+
--reseda-green: oklch(62.33% 0.0475 126.94);
+
--earth-yellow: oklch(86.49% 0.018 73.05);
+
--sunset: oklch(0.86 0.0213 73.05);
+
--ultra-violet: oklch(42.21% 0.0676 297.45);
+
--rose-quartz: oklch(65.32% 0.0585 311.96);
+
--pink-puree: oklch(75.65% 0.0555 290.76);
+
--lavendar-breeze: oklch(91.06% 0.0223 290.76);
+
--purple-gray: oklch(25.63% 0.0002 290.76);
+
--alice-blue: oklch(95.38% 0.0118 239.91);
+
}
+415 -377
sass/css/suCSS.css
···
:root,
::backdrop {
-
/* set sans-serif & mono fonts */
-
--sans-font:
-
-apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
-
"Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica,
-
"Helvetica Neue", sans-serif;
-
--serif-font:
-
Superclarendon, "Bookman Old Style", "URW Bookman", "URW Bookman L",
-
"Georgia Pro", Georgia, serif;
-
--mono-font:
-
ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
-
"DejaVu Sans Mono", monospace;
-
--standard-border-radius: 5px;
-
-
/* default colors */
-
--bg: var(--earth-yellow);
-
--noise-strength: 0.3;
-
--bg-light: #cbcdcd;
-
--text: #41474e;
-
--text-light: #686764;
-
--text-dark: #20252b;
-
--accent: oklch(35.55% 0.0754 60.09);
-
--accent-dark: #56412bc5;
-
--accent-text: #dfd1bc;
-
--border: #646868;
-
--link: var(--accent);
-
--selection: color-mix(in oklab, var(--accent), var(--earth-yellow) 50%);
-
}
-
-
/* theme media queries */
-
@media (prefers-color-scheme: dark) {
-
:root,
-
::backdrop {
-
color-scheme: dark;
-
--bg: var(--purple-night);
-
--noise-strength: 0.15;
-
--bg-light: var(--ultra-violet);
-
--text: var(--lavendar-breeze);
-
--text-light: var(--pink-puree);
-
--text-dark: oklch(80.28% 0.0111 204.11);
-
--accent: var(--rose-quartz);
-
--accent-dark: var(--dark-crushed-grape);
-
--accent-text: var(--purple-gray);
-
--link: var(--light-crushed-grape);
-
--border: var(--pink-puree);
-
--selection: color-mix(
-
in oklab,
-
var(--accent),
-
var(--purple-night) 50%
-
);
-
}
-
}
-
-
[data-theme="dark"] {
-
color-scheme: dark;
-
--bg: var(--purple-night);
-
--noise-strength: 0.15;
-
--bg-light: var(--ultra-violet);
-
--text: var(--lavendar-breeze);
-
--text-light: var(--pink-puree);
-
--text-dark: oklch(80.28% 0.0111 204.11);
-
--accent: var(--rose-quartz);
-
--accent-dark: var(--dark-crushed-grape);
-
--accent-text: var(--purple-gray);
-
--link: var(--light-crushed-grape);
-
--border: var(--pink-puree);
-
--selection: color-mix(in oklab, var(--accent), var(--purple-night) 50%);
-
}
-
-
@media (prefers-color-scheme: light) {
-
:root,
-
::backdrop {
-
color-scheme: light;
-
--bg: var(--earth-yellow);
-
--noise-strength: 0.15;
-
--bg-light: #cbcdcd;
-
--text: #41474e;
-
--text-light: #686764;
-
--accent: #58310ac5;
-
--accent-dark: #e08f67;
-
--accent-text: #dfd1bc;
-
--border: #646868;
-
--link: var(--accent);
-
--selection: color-mix(
-
in oklab,
-
var(--accent),
-
var(--earth-yellow) 50%
-
);
-
}
-
}
+
/* set sans-serif & mono fonts */
+
--sans-font:
+
-apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
+
"Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica,
+
"Helvetica Neue", sans-serif;
+
--serif-font:
+
Superclarendon, "Bookman Old Style", "URW Bookman", "URW Bookman L",
+
"Georgia Pro", Georgia, serif;
+
--mono-font:
+
ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
+
"DejaVu Sans Mono", monospace;
+
--standard-border-radius: 5px;
-
[data-theme="light"] {
-
/* default (light) theme */
-
color-scheme: light;
-
--bg: var(--earth-yellow);
-
--noise-strength: 0.22;
-
--bg-light: var(--reseda-green);
-
--text: #41474e;
-
--text-light: #686764;
-
--text-dark: #20252b;
-
--accent: #58310ac5;
-
--accent-dark: #56412bc5;
-
--accent-text: #dfd1bc;
-
--border: #646868;
-
--link: var(--accent);
-
--selection: color-mix(in oklab, var(--accent), var(--earth-yellow) 50%);
+
/* default colors */
+
color-scheme: dark;
+
--bg: var(--purple-night);
+
--noise-strength: 0.15;
+
--bg-light: var(--ultra-violet);
+
--text: var(--lavendar-breeze);
+
--text-light: var(--pink-puree);
+
--text-dark: oklch(80.28% 0.0111 204.11);
+
--accent: var(--rose-quartz);
+
--accent-dark: var(--dark-crushed-grape);
+
--accent-text: var(--purple-gray);
+
--link: var(--light-crushed-grape);
+
--link-visited: var(--red-crushed-grape);
+
--border: var(--pink-puree);
+
--selection: color-mix(in oklab, var(--accent), var(--purple-night) 50%);
}
::selection,
::-moz-selection {
-
color: var(--bg);
-
background: var(--selection);
+
color: var(--bg);
+
background: var(--selection);
}
/* chromium scrollbars */
::-webkit-scrollbar {
-
width: 8px;
-
height: 8px;
-
overflow: visible;
+
width: 8px;
+
height: 8px;
+
overflow: visible;
}
+
::-webkit-scrollbar-thumb {
-
background: var(--accent);
-
width: 12px;
+
background: var(--accent);
+
width: 12px;
}
+
::-webkit-scrollbar-track {
-
background: var(--bg-light);
+
background: var(--bg-light);
}
/* firefox scrollbars */
* {
-
scrollbar-color: var(--accent) var(--bg-light);
-
scrollbar-width: auto;
+
scrollbar-color: var(--accent) var(--bg-light);
+
scrollbar-width: auto;
}
html {
-
color-scheme: light dark;
-
font-family: var(--mono-font);
-
scroll-behavior: smooth;
+
color-scheme: light dark;
+
font-family: var(--mono-font);
+
scroll-behavior: smooth;
}
body {
-
min-height: 100svh;
-
color: var(--text);
-
background: var(--bg);
-
position: relative;
-
font-size: 1rem;
-
display: grid;
-
grid-template-columns: 1fr min(45rem, 90%) 1fr;
-
grid-template-rows: auto 1fr auto;
-
grid-row-gap: 0.625rem;
+
min-height: 100svh;
+
color: var(--text);
+
background: var(--bg);
+
position: relative;
+
font-size: 1rem;
+
display: grid;
+
grid-template-columns: 1fr min(45rem, 90%) 1fr;
+
grid-template-rows: auto 1fr auto;
+
grid-row-gap: 0.625rem;
}
-
body > * {
-
grid-column: 2;
+
+
body>* {
+
grid-column: 2;
}
-
body > footer {
-
color: var(--text-light);
-
font-size: 0.875;
+
body>footer {
+
color: var(--text-light);
+
font-size: 0.875;
}
/* Format headers */
h1 {
-
font-size: 2rem;
+
font-size: 2rem;
}
+
h2 {
-
font-size: 1.75rem;
+
font-size: 1.75rem;
}
+
h3 {
-
font-size: 1.5rem;
+
font-size: 1.5rem;
}
+
h4 {
-
font-size: 1.25rem;
+
font-size: 1.25rem;
}
+
h5 {
-
font-size: 1rem;
+
font-size: 1rem;
}
+
h6 {
-
font-size: 0.75rem;
+
font-size: 0.75rem;
}
h1,
···
h4,
h5,
h6 {
-
margin: 0.5em 0 0.5em 0;
-
padding: 0.22em 0.4em 0.22em 0.4em;
-
border-radius: 0.1em;
-
background-color: var(--accent);
-
border-bottom: 5px solid var(--bg-light);
-
border-radius: 7px 7px 10px 10px;
-
color: var(--accent-text);
-
width: fit-content;
+
margin: 0.5em 0 0.5em 0;
+
padding: 0.22em 0.4em 0.22em 0.4em;
+
background-color: var(--accent);
+
border-bottom: 5px solid var(--bg-light);
+
border-radius: 0.2em 0.2em 0.27em 0.27em;
+
color: var(--accent-text);
+
width: fit-content;
}
/* Fix line height when title wraps */
h1,
h2,
h3 {
-
line-height: 1.1;
+
line-height: 1.1;
}
@media only screen and (max-width: 720px) {
-
h1 {
-
font-size: 1.5rem;
-
}
-
h2 {
-
font-size: 1.25rem;
-
}
-
h3 {
-
font-size: 1rem;
-
}
-
h4 {
-
font-size: 0.75rem;
-
}
-
h5 {
-
font-size: 0.5rem;
-
}
-
h6 {
-
font-size: 0.25rem;
-
}
+
h1 {
+
font-size: 1.5rem;
+
}
+
+
h2 {
+
font-size: 1.25rem;
+
}
+
+
h3 {
+
font-size: 1rem;
+
}
+
+
h4 {
+
font-size: 0.75rem;
+
}
+
+
h5 {
+
font-size: 0.5rem;
+
}
+
+
h6 {
+
font-size: 0.25rem;
+
}
}
p {
-
margin: 1rem 0;
+
margin: 1rem 0;
}
/* format links */
-
a,
+
a {
+
color: var(--link);
+
text-decoration: none;
+
font-weight: 600;
+
transition: color 120ms ease;
+
}
+
a:visited {
-
text-decoration: none;
-
font-weight: bold;
-
border-radius: 0.125rem;
-
color: var(--accent-dark);
+
color: var(--link-visited);
}
-
a {
-
color: var(--link);
+
a:hover,
+
a:focus-visible {
+
color: var(--accent);
+
outline: none;
}
-
a:hover {
-
text-decoration: underline wavy;
+
a:visited:hover,
+
a:visited:focus-visible {
+
color: var(--accent);
}
/* format lists */
ul {
-
list-style: none;
-
margin-top: 0.25rem;
-
margin-bottom: 0.25rem;
+
list-style: none;
+
margin-top: 0.25rem;
+
margin-bottom: 0.25rem;
}
ol {
-
list-style-type: decimal;
-
margin-top: 0.25rem;
-
margin-bottom: 0.25rem;
+
list-style-type: decimal;
+
margin-top: 0.25rem;
+
margin-bottom: 0.25rem;
}
li {
-
margin-bottom: 0.125rem;
+
margin-bottom: 0.125rem;
}
ul li::marker {
-
content: "* ";
-
color: var(--accent);
-
font-size: 1.1rem;
+
content: "* ";
+
color: var(--accent);
+
font-size: 1.1rem;
}
ol li::marker {
-
color: var(--accent);
+
color: var(--accent);
}
ol li:hover::marker {
-
font-weight: 700;
-
color: var(--link);
+
font-weight: 700;
+
color: var(--link);
}
/* Use flexbox to allow items to wrap, as needed */
-
header > nav ul,
-
header > nav ol {
-
display: flex;
-
flex-direction: row;
-
flex-wrap: wrap;
-
align-content: space-around;
-
justify-content: right;
-
list-style-type: none;
-
margin: 0.5rem 0 0 0;
-
padding: 0;
-
gap: 1rem;
+
header>nav ul,
+
header>nav ol {
+
display: flex;
+
flex-direction: row;
+
flex-wrap: wrap;
+
align-content: space-around;
+
justify-content: right;
+
list-style-type: none;
+
margin: 0.5rem 0 0 0;
+
padding: 0;
+
gap: 1rem;
}
/* List items are inline elements, make them behave more like blocks */
-
header > nav ul li,
-
header > nav ol li {
-
display: inline-block;
+
header>nav ul li,
+
header>nav ol li {
+
display: inline-block;
}
/* Consolidate box styling */
aside,
details,
progress {
-
background-color: var(--bg-light);
-
border-radius: var(--standard-border-radius);
+
background-color: var(--bg-light);
+
border-radius: var(--standard-border-radius);
}
aside {
-
font-size: 1rem;
-
width: 35%;
-
padding: 0 10px;
-
margin-inline-start: 10px;
-
float: right;
+
font-size: 1rem;
+
width: 35%;
+
padding: 0 10px;
+
margin-inline-start: 10px;
+
float: right;
}
+
*[dir="rtl"] aside {
-
float: left;
+
float: left;
}
/* make aside full-width on mobile */
@media only screen and (max-width: 720px) {
-
aside {
-
width: 100%;
-
float: none;
-
margin-inline-start: 0;
-
}
+
aside {
+
width: 100%;
+
float: none;
+
margin-inline-start: 0;
+
}
}
details {
-
padding: 0.5rem;
+
padding: 0.5rem;
}
summary {
-
cursor: pointer;
-
font-weight: bold;
-
word-break: break-all;
+
cursor: pointer;
+
font-weight: bold;
+
word-break: break-all;
}
-
details[open] > summary + * {
-
margin-top: 0;
+
details[open]>summary+* {
+
margin-top: 0;
}
-
details[open] > summary {
-
margin-bottom: 0.5rem;
+
details[open]>summary {
+
margin-bottom: 0.5rem;
}
-
details[open] > :last-child {
-
margin-bottom: 0;
+
details[open]> :last-child {
+
margin-bottom: 0;
}
/* Format tables */
table {
-
border-collapse: collapse;
-
margin: 1.5rem 0;
-
display: block;
-
overflow-x: auto;
-
white-space: nowrap;
+
border-collapse: collapse;
+
margin: 1.5rem 0;
+
display: block;
+
overflow-x: auto;
+
white-space: nowrap;
}
td,
th {
-
border: 1px solid var(--border);
-
text-align: start;
-
padding: 0.5rem;
+
border: 1px solid var(--border);
+
text-align: start;
+
padding: 0.5rem;
}
th {
-
background-color: var(--bg-light);
-
font-weight: bold;
+
background-color: var(--bg-light);
+
font-weight: bold;
}
tr:nth-child(even) {
-
background-color: var(--bg-light);
+
background-color: var(--bg-light);
}
table caption {
-
text-align: left;
-
font-weight: bold;
-
margin: 0 0 0.4rem 1rem;
+
text-align: left;
+
font-weight: bold;
+
margin: 0 0 0.4rem 1rem;
}
/* format forms */
fieldset {
-
border: 1px dashed var(--accent);
-
border-radius: var(--standard-border-radius);
+
border: 1px dashed var(--accent);
+
border-radius: var(--standard-border-radius);
}
-
fieldset > legend {
-
color: var(--accent);
+
fieldset>legend {
+
color: var(--accent);
}
textarea,
···
input,
button,
.button {
-
font-size: inherit;
-
font-family: inherit;
-
padding: 0.25rem;
-
border-radius: var(--standard-border-radius);
-
box-shadow: none;
-
max-width: 100%;
-
display: inline-block;
+
font-size: inherit;
+
font-family: inherit;
+
padding: 0.25rem;
+
border-radius: var(--standard-border-radius);
+
box-shadow: none;
+
max-width: 100%;
+
display: inline-block;
}
textarea,
select,
input {
-
color: var(--text);
-
background-color: var(--bg);
-
border: 1px dashed var(--border);
+
color: var(--text);
+
background-color: var(--bg);
+
border: 1px dashed var(--border);
}
label {
-
display: block;
+
display: block;
}
fieldset label {
-
margin: 0 0 0.3rem 0;
+
margin: 0 0 0.3rem 0;
}
textarea {
-
max-width: 43.5rem;
-
resize: both;
+
max-width: 43.5rem;
+
resize: both;
}
textarea:not([cols]) {
-
width: 100%;
+
width: 100%;
}
@media only screen and (max-width: 720px) {
-
textarea,
-
select,
-
input {
-
width: 100%;
-
}
+
+
textarea,
+
select,
+
input {
+
width: 100%;
+
}
}
/* format buttons */
···
input[type="reset"],
input[type="button"],
label[type="button"] {
-
border: 1px solid var(--accent);
-
background-color: var(--accent);
-
color: var(--accent-text);
-
padding: 0.5rem 0.9rem;
-
text-decoration: none;
-
line-height: normal;
+
border: 1px solid var(--accent);
+
background-color: var(--accent);
+
color: var(--accent-text);
+
padding: 0.5rem 0.9rem;
+
text-decoration: none;
+
line-height: normal;
}
.button[aria-disabled="true"],
···
textarea:disabled,
select:disabled,
button[disabled] {
-
cursor: not-allowed;
-
background-color: var(--bg-light);
-
border-color: var(--bg-light);
-
color: var(--text-light);
+
cursor: not-allowed;
+
background-color: var(--bg-light);
+
border-color: var(--bg-light);
+
color: var(--text-light);
}
input[type="range"] {
-
padding: 0;
-
color: var(--accent);
+
padding: 0;
+
color: var(--accent);
}
abbr[title] {
-
cursor: help;
-
text-decoration-line: underline;
-
text-decoration-style: dotted;
+
cursor: help;
+
text-decoration-line: underline;
+
text-decoration-style: dotted;
}
button:enabled:hover,
···
input[type="reset"]:enabled:hover,
input[type="button"]:enabled:hover,
label[type="button"]:hover {
-
background-color: var(--accent-dark);
-
border-color: var(--accent-dark);
-
cursor: pointer;
+
background-color: var(--accent-dark);
+
border-color: var(--accent-dark);
+
cursor: pointer;
}
.button:focus-visible,
button:focus-visible:where(:enabled),
-
input:enabled:focus-visible:where(
-
[type="submit"],
-
[type="reset"],
-
[type="button"]
-
) {
-
outline: 2px solid var(--accent);
-
outline-offset: 1px;
+
input:enabled:focus-visible:where([type="submit"],
+
[type="reset"],
+
[type="button"]) {
+
outline: 2px solid var(--accent);
+
outline-offset: 1px;
}
/* checkbox and radio button style */
input[type="checkbox"],
input[type="radio"] {
-
vertical-align: middle;
-
position: relative;
-
width: min-content;
-
width: 14px;
-
height: 14px;
+
vertical-align: middle;
+
position: relative;
+
width: 14px;
+
height: 14px;
}
-
input[type="checkbox"] + label,
-
input[type="radio"] + label {
-
display: inline-block;
+
input[type="checkbox"]+label,
+
input[type="radio"]+label {
+
display: inline-block;
}
input[type="radio"] {
-
border-radius: 100%;
+
border-radius: 100%;
}
input[type="checkbox"]:checked,
input[type="radio"]:checked {
-
background-color: var(--accent);
+
background-color: var(--accent);
}
@media only screen and (max-width: 720px) {
-
textarea,
-
select,
-
input {
-
width: 100%;
-
}
+
+
textarea,
+
select,
+
input {
+
width: 100%;
+
}
}
input[type="color"] {
-
height: 2.5rem;
-
padding: 0.2rem;
+
height: 2.5rem;
+
padding: 0.2rem;
}
input[type="file"] {
-
border: 0;
+
border: 0;
}
/* misc body elements */
hr {
-
border: 1px dashed var(--accent);
-
margin: 0.5rem 0 0.5rem 0;
+
border: 1px dashed var(--accent);
+
margin: 0.5rem 0 0.5rem 0;
}
mark {
-
padding: 0 0.25em 0 0.25em;
-
border-radius: var(--standard-border-radius);
-
background-color: var(--accent);
-
color: var(--bg);
+
padding: 0 0.25em 0 0.25em;
+
border-radius: var(--standard-border-radius);
+
background-color: var(--accent);
+
color: var(--bg);
}
mark a {
-
color: var(--link);
+
color: var(--link);
}
img,
video,
iframe[src^="https://www.youtube-nocookie.com"],
-
iframe[src^="https://www.youtube.com"]
-
{
-
max-width: 90%;
-
margin: 1rem;
-
height: auto;
-
border: dashed 2px var(--accent);
-
border-radius: 15px;
-
opacity: 0.95;
+
iframe[src^="https://www.youtube.com"] {
+
max-width: 100%;
+
height: auto;
+
border-radius: 0.35rem;
+
}
+
+
.img-container {
+
background-color: var(--accent);
+
border-bottom: 4px solid var(--bg-light);
+
border-radius: 7px 7px 10px 10px;
+
padding: 0.35rem;
+
margin: 1rem;
+
display: inline-block;
+
max-width: 90%;
}
figure {
-
margin: 0;
-
display: block;
-
overflow-x: auto;
+
margin: 0;
+
display: block;
+
overflow-x: auto;
}
figcaption {
-
text-align: left;
-
font-size: 0.875rem;
-
color: var(--text-light);
-
margin: 0 0 1rem 1rem;
+
text-align: left;
+
font-size: 0.875rem;
+
color: var(--text-light);
+
margin: 0 0 1rem 1rem;
}
blockquote {
-
margin: 0 0 0 1.25rem;
-
padding: 0.5rem 0 0 0.5rem;
-
border-inline-start: 0.375rem solid var(--accent);
-
color: var(--text-light);
-
font-style: italic;
+
margin: 0 0 0 1.25rem;
+
padding: 0.5rem 0 0 0.5rem;
+
border-inline-start: 0.375rem solid var(--accent);
+
color: var(--text-light);
+
font-style: italic;
+
}
+
+
/* Callout styles */
+
.callout {
+
margin: 1.5rem 0;
+
padding: 1rem;
+
border-left: 0.25rem solid;
+
border-radius: 0.25rem;
+
background-color: var(--bg-light);
+
}
+
+
.callout-title {
+
display: flex;
+
align-items: center;
+
gap: 0.5rem;
+
margin-bottom: 0.5rem;
+
font-size: 1rem;
+
}
+
+
.callout-icon {
+
display: inline-flex;
+
width: 1.25rem;
+
height: 1.25rem;
+
flex-shrink: 0;
+
}
+
+
.callout-icon svg {
+
width: 100%;
+
height: 100%;
+
}
+
+
.callout-content {
+
font-style: normal;
+
color: var(--text);
+
}
+
+
.callout-content p:first-child {
+
margin-top: 0;
+
}
+
+
.callout-content p:last-child {
+
margin-bottom: 0;
+
}
+
+
.callout-blue {
+
border-color: #8aadf4;
+
}
+
+
.callout-blue .callout-icon {
+
color: #8aadf4;
+
}
+
+
.callout-yellow {
+
border-color: #eed49f;
+
}
+
+
.callout-yellow .callout-icon {
+
color: #eed49f;
+
}
+
+
.callout-red {
+
border-color: #ed8796;
+
}
+
+
.callout-red .callout-icon {
+
color: #ed8796;
+
}
+
+
.callout-green {
+
border-color: #a6da95;
+
}
+
+
.callout-green .callout-icon {
+
color: #a6da95;
+
}
+
+
.callout-gray {
+
border-color: #6e738d;
+
}
+
+
.callout-gray .callout-icon {
+
color: #6e738d;
}
+
p:has(cite) {
-
text-align: right;
-
font-size: 0.875rem;
-
color: var(--text-light);
-
font-weight: 600;
+
text-align: right;
+
font-size: 0.875rem;
+
color: var(--text-light);
+
font-weight: 600;
}
cite::before {
-
content: "โ€” ";
+
content: "โ€” ";
}
dt {
-
color: var(--text-light);
+
color: var(--text-light);
}
code,
···
pre span,
kbd,
samp {
-
font-family: var(--mono-font);
+
font-family: var(--mono-font);
}
pre {
-
border: 1px solid var(--accent);
-
max-height: 30rem;
-
padding: 0.625rem;
-
overflow-x: auto;
-
font-style: monospace;
+
border: 1px solid var(--accent);
+
max-height: 30rem;
+
padding: 0.625rem;
+
overflow-x: auto;
+
font-style: monospace;
+
}
+
+
/* Allow wrapping for specific code blocks (e.g., SSH keys) */
+
pre[data-lang="pub"],
+
pre.wrap {
+
white-space: pre-wrap;
+
word-break: break-all;
+
overflow-x: visible;
}
p code,
li code,
div code {
-
padding: 0 0.125rem 0 0.125rem;
-
border-radius: 3px;
-
color: var(--bg);
-
background-color: var(--text);
+
padding: 0 0.125rem 0 0.125rem;
+
border-radius: 3px;
+
color: var(--bg);
+
background-color: var(--text);
}
pre code {
-
padding: 0;
-
border-radius: 0;
-
color: inherit;
-
background-color: inherit;
+
padding: 0;
+
border-radius: 0;
+
color: inherit;
+
background-color: inherit;
}
iframe {
-
max-width: 90%;
+
max-width: 90%;
}
/* progress bars */
progress {
-
width: 100%;
+
width: 100%;
}
progress:indeterminate {
-
background-color: var(--bg-light);
+
background-color: var(--bg-light);
}
progress::-webkit-progress-bar {
-
border-radius: var(--standard-border-radius);
-
background-color: var(--bg-light);
+
border-radius: var(--standard-border-radius);
+
background-color: var(--bg-light);
}
progress::-webkit-progress-value {
-
border-radius: var(--standard-border-radius);
-
background-color: var(--accent);
+
border-radius: var(--standard-border-radius);
+
background-color: var(--accent);
}
progress::-moz-progress-bar {
-
border-radius: var(--standard-border-radius);
-
background-color: var(--accent);
-
transition-property: width;
-
transition-duration: 0.3s;
+
border-radius: var(--standard-border-radius);
+
background-color: var(--accent);
+
transition-property: width;
+
transition-duration: 0.3s;
}
progress:indeterminate::-moz-progress-bar {
-
background-color: var(--bg-light);
+
background-color: var(--bg-light);
}
dialog {
-
max-width: 40rem;
-
margin: auto;
+
max-width: 40rem;
+
margin: auto;
}
dialog::backdrop {
-
background-color: var(--bg);
-
opacity: 0.8;
+
background-color: var(--bg);
+
opacity: 0.8;
}
@media only screen and (max-width: 720px) {
-
dialog {
-
max-width: 100%;
-
margin: auto 1em;
-
}
+
dialog {
+
max-width: 100%;
+
margin: auto 1em;
+
}
}
/* superscript & subscript */
/* prevent scripts from affecting line-height. */
sup,
sub {
-
vertical-align: baseline;
-
position: relative;
+
vertical-align: baseline;
+
position: relative;
}
sup {
-
top: -0.4em;
+
top: -0.4em;
}
sub {
-
top: 0.3em;
-
}
+
top: 0.3em;
+
}
+87 -366
sass/css/syntax-theme.css
···
* theme "Catppuccin" generated by syntect
*/
-
html[data-theme="light"] .z-code {
-
color: #4c4f69;
-
background-color: var(--sunset);
-
}
-
-
html[data-theme="light"] .z-comment {
-
color: #9ca0b0;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-string {
-
color: #40a02b;
-
}
-
html[data-theme="light"] .z-string.z-regexp {
-
color: #fe640b;
-
}
-
html[data-theme="light"] .z-constant.z-numeric {
-
color: #fe640b;
-
}
-
html[data-theme="light"] .z-constant.z-language.z-boolean {
-
color: #fe640b;
-
font-weight: bold;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-constant.z-language {
-
color: #7287fd;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-support.z-function.z-builtin {
-
color: #fe640b;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-variable.z-other.z-constant {
-
color: #fe640b;
-
}
-
html[data-theme="light"] .z-keyword {
-
color: #d20f39;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-keyword.z-control.z-loop,
-
html[data-theme="light"] .z-keyword.z-control.z-conditional,
-
html[data-theme="light"] .z-keyword.z-control.z-c\+\+ {
-
color: #8839ef;
-
font-weight: bold;
-
}
-
html[data-theme="light"] .z-keyword.z-control.z-return,
-
html[data-theme="light"] .z-keyword.z-control.z-flow.z-return {
-
color: #ea76cb;
-
font-weight: bold;
-
}
-
html[data-theme="light"] .z-support.z-type.z-exception {
-
color: #fe640b;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-keyword.z-operator,
-
html[data-theme="light"] .z-punctuation.z-accessor {
-
color: #04a5e5;
-
font-weight: bold;
-
}
-
html[data-theme="light"] .z-punctuation.z-separator {
-
color: #179299;
-
}
-
html[data-theme="light"] .z-punctuation.z-terminator {
-
color: #179299;
-
}
-
html[data-theme="light"] .z-punctuation.z-section {
-
color: #7c7f93;
-
}
-
html[data-theme="light"] .z-keyword.z-control.z-import.z-include {
-
color: #179299;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-storage {
-
color: #d20f39;
-
}
-
html[data-theme="light"] .z-storage.z-type {
-
color: #df8e1d;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-storage.z-modifier {
-
color: #d20f39;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-namespace,
-
html[data-theme="light"] .z-meta.z-path {
-
color: #dc8a78;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-storage.z-type.z-class {
-
color: #dc8a78;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-label {
-
color: #1e66f5;
-
}
-
html[data-theme="light"] .z-keyword.z-declaration.z-class {
-
color: #d20f39;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-class,
-
html[data-theme="light"] .z-meta.z-toc-list.z-full-identifier {
-
color: #04a5e5;
-
}
-
html[data-theme="light"] .z-entity.z-other.z-inherited-class {
-
color: #04a5e5;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-function,
-
html[data-theme="light"] .z-variable.z-function {
-
color: #1e66f5;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-function.z-preprocessor {
-
color: #d20f39;
-
}
-
html[data-theme="light"] .z-keyword.z-control.z-import {
-
color: #d20f39;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-function.z-constructor,
-
html[data-theme="light"] .z-entity.z-name.z-function.z-destructor {
-
color: #7287fd;
-
}
-
html[data-theme="light"] .z-variable.z-parameter.z-function {
-
color: #dc8a78;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-keyword.z-declaration.z-function {
-
color: #e64553;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-support.z-function {
-
color: #04a5e5;
-
}
-
html[data-theme="light"] .z-support.z-constant {
-
color: #1e66f5;
-
}
-
html[data-theme="light"] .z-support.z-type,
-
html[data-theme="light"] .z-support.z-class {
-
color: #1e66f5;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-variable.z-function {
-
color: #1e66f5;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-variable.z-parameter {
-
color: #dc8a78;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-variable.z-other {
-
color: #4c4f69;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-variable.z-other.z-member {
-
color: #dc8a78;
-
}
-
html[data-theme="light"] .z-variable.z-language {
-
color: #179299;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-tag {
-
color: #fe640b;
-
}
-
html[data-theme="light"] .z-entity.z-other.z-attribute-name {
-
color: #8839ef;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-punctuation.z-definition.z-tag {
-
color: #e64553;
-
}
-
html[data-theme="light"] .z-markup.z-underline.z-link.z-markdown {
-
color: #dc8a78;
-
text-decoration: underline;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-meta.z-link.z-inline.z-description {
-
color: #7287fd;
-
font-weight: bold;
-
}
-
html[data-theme="light"] .z-comment.z-block.z-markdown,
-
html[data-theme="light"] .z-meta.z-code-fence,
-
html[data-theme="light"] .z-markup.z-raw.z-code-fence,
-
html[data-theme="light"] .z-markup.z-raw.z-inline {
-
color: #179299;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-punctuation.z-definition.z-heading,
-
html[data-theme="light"] .z-entity.z-name.z-section {
-
color: #1e66f5;
-
font-weight: bold;
-
}
-
html[data-theme="light"] .z-markup.z-italic {
-
color: #e64553;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-markup.z-bold {
-
color: #e64553;
-
font-weight: bold;
-
}
-
html[data-theme="light"] .z-constant.z-character.z-escape {
-
color: #ea76cb;
-
}
-
html[data-theme="light"]
-
.z-source.z-shell.z-bash
-
.z-meta.z-function.z-shell
-
.z-meta.z-compound.z-shell
-
.z-meta.z-function-call.z-identifier.z-shell {
-
color: #ea76cb;
-
}
-
html[data-theme="light"] .z-variable.z-language.z-shell {
-
color: #d20f39;
-
font-style: italic;
-
}
-
html[data-theme="light"]
-
.z-source.z-lua
-
.z-meta.z-function.z-lua
-
.z-meta.z-block.z-lua
-
.z-meta.z-mapping.z-value.z-lua
-
.z-meta.z-mapping.z-key.z-lua
-
.z-string.z-unquoted.z-key.z-lua {
-
color: #7287fd;
-
font-style: italic;
-
}
-
html[data-theme="light"]
-
.z-source.z-lua
-
.z-meta.z-function.z-lua
-
.z-meta.z-block.z-lua
-
.z-meta.z-mapping.z-key.z-lua
-
.z-string.z-unquoted.z-key.z-lua {
-
color: #dd7878;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-constant.z-java {
-
color: #179299;
-
}
-
html[data-theme="light"] .z-support.z-type.z-property-name.z-css {
-
color: #dd7878;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-support.z-constant.z-property-value.z-css {
-
color: #4c4f69;
-
}
-
html[data-theme="light"] .z-constant.z-numeric.z-suffix.z-css,
-
html[data-theme="light"] .z-keyword.z-other.z-unit.z-css {
-
color: #179299;
-
font-style: italic;
-
}
-
html[data-theme="light"] .z-variable.z-other.z-custom-property.z-name.z-css,
-
html[data-theme="light"] .z-support.z-type.z-custom-property.z-name.z-css,
-
html[data-theme="light"] .z-punctuation.z-definition.z-custom-property.z-css {
-
color: #179299;
-
}
-
html[data-theme="light"] .z-entity.z-name.z-tag.z-css {
-
color: #7287fd;
-
}
-
html[data-theme="light"] .z-variable.z-other.z-sass {
-
color: #179299;
-
}
-
html[data-theme="light"] .z-invalid {
-
color: #4c4f69;
-
background-color: #d20f39;
-
}
-
html[data-theme="light"] .z-invalid.z-deprecated {
-
color: #4c4f69;
-
background-color: #8839ef;
-
}
-
html[data-theme="light"] .z-meta.z-diff,
-
html[data-theme="light"] .z-meta.z-diff.z-header {
-
color: #9ca0b0;
-
}
-
html[data-theme="light"] .z-markup.z-deleted {
-
color: #d20f39;
-
}
-
html[data-theme="light"] .z-markup.z-inserted {
-
color: #40a02b;
-
}
-
html[data-theme="light"] .z-markup.z-changed {
-
color: #df8e1d;
-
}
-
html[data-theme="light"] .z-message.z-error {
-
color: #d20f39;
-
}
-
/* dark */
-
html[data-theme="dark"] .z-code {
+
.z-code {
color: #cad3f5;
background-color: var(--nightshade-violet);
}
-
html[data-theme="dark"] .z-comment {
+
.z-comment {
color: #6e738d;
font-style: italic;
}
-
html[data-theme="dark"] .z-string {
+
.z-string {
color: #a6da95;
}
-
html[data-theme="dark"] .z-string.z-regexp {
+
.z-string.z-regexp {
color: #f5a97f;
}
-
html[data-theme="dark"] .z-constant.z-numeric {
+
.z-constant.z-numeric {
color: #f5a97f;
}
-
html[data-theme="dark"] .z-constant.z-language.z-boolean {
+
.z-constant.z-language.z-boolean {
color: #f5a97f;
font-weight: bold;
font-style: italic;
}
-
html[data-theme="dark"] .z-constant.z-language {
+
.z-constant.z-language {
color: #b7bdf8;
font-style: italic;
}
-
html[data-theme="dark"] .z-support.z-function.z-builtin {
+
.z-support.z-function.z-builtin {
color: #f5a97f;
font-style: italic;
}
-
html[data-theme="dark"] .z-variable.z-other.z-constant {
+
.z-variable.z-other.z-constant {
color: #f5a97f;
}
-
html[data-theme="dark"] .z-keyword {
+
.z-keyword {
color: #ed8796;
font-style: italic;
}
-
html[data-theme="dark"] .z-keyword.z-control.z-loop,
-
html[data-theme="dark"] .z-keyword.z-control.z-conditional,
-
html[data-theme="dark"] .z-keyword.z-control.z-c\+\+ {
+
.z-keyword.z-control.z-loop,
+
.z-keyword.z-control.z-conditional,
+
.z-keyword.z-control.z-c\+\+ {
color: #c6a0f6;
font-weight: bold;
}
-
html[data-theme="dark"] .z-keyword.z-control.z-return,
-
html[data-theme="dark"] .z-keyword.z-control.z-flow.z-return {
+
.z-keyword.z-control.z-return,
+
.z-keyword.z-control.z-flow.z-return {
color: #f5bde6;
font-weight: bold;
}
-
html[data-theme="dark"] .z-support.z-type.z-exception {
+
.z-support.z-type.z-exception {
color: #f5a97f;
font-style: italic;
}
-
html[data-theme="dark"] .z-keyword.z-operator,
-
html[data-theme="dark"] .z-punctuation.z-accessor {
+
.z-keyword.z-operator,
+
.z-punctuation.z-accessor {
color: #91d7e3;
font-weight: bold;
}
-
html[data-theme="dark"] .z-punctuation.z-separator {
+
.z-punctuation.z-separator {
color: #8bd5ca;
}
-
html[data-theme="dark"] .z-punctuation.z-terminator {
+
.z-punctuation.z-terminator {
color: #8bd5ca;
}
-
html[data-theme="dark"] .z-punctuation.z-section {
+
.z-punctuation.z-section {
color: #939ab7;
}
-
html[data-theme="dark"] .z-keyword.z-control.z-import.z-include {
+
.z-keyword.z-control.z-import.z-include {
color: #8bd5ca;
font-style: italic;
}
-
html[data-theme="dark"] .z-storage {
+
.z-storage {
color: #ed8796;
}
-
html[data-theme="dark"] .z-storage.z-type {
+
.z-storage.z-type {
color: #eed49f;
font-style: italic;
}
-
html[data-theme="dark"] .z-storage.z-modifier {
+
.z-storage.z-modifier {
color: #ed8796;
}
-
html[data-theme="dark"] .z-entity.z-name.z-namespace,
-
html[data-theme="dark"] .z-meta.z-path {
+
.z-entity.z-name.z-namespace,
+
.z-meta.z-path {
color: #f4dbd6;
font-style: italic;
}
-
html[data-theme="dark"] .z-storage.z-type.z-class {
+
.z-storage.z-type.z-class {
color: #f4dbd6;
font-style: italic;
}
-
html[data-theme="dark"] .z-entity.z-name.z-label {
+
.z-entity.z-name.z-label {
color: #8aadf4;
}
-
html[data-theme="dark"] .z-keyword.z-declaration.z-class {
+
.z-keyword.z-declaration.z-class {
color: #ed8796;
font-style: italic;
}
-
html[data-theme="dark"] .z-entity.z-name.z-class,
-
html[data-theme="dark"] .z-meta.z-toc-list.z-full-identifier {
+
.z-entity.z-name.z-class,
+
.z-meta.z-toc-list.z-full-identifier {
color: #91d7e3;
}
-
html[data-theme="dark"] .z-entity.z-other.z-inherited-class {
+
.z-entity.z-other.z-inherited-class {
color: #91d7e3;
font-style: italic;
}
-
html[data-theme="dark"] .z-entity.z-name.z-function,
-
html[data-theme="dark"] .z-variable.z-function {
+
.z-entity.z-name.z-function,
+
.z-variable.z-function {
color: #8aadf4;
font-style: italic;
}
-
html[data-theme="dark"] .z-entity.z-name.z-function.z-preprocessor {
+
.z-entity.z-name.z-function.z-preprocessor {
color: #ed8796;
}
-
html[data-theme="dark"] .z-keyword.z-control.z-import {
+
.z-keyword.z-control.z-import {
color: #ed8796;
}
-
html[data-theme="dark"] .z-entity.z-name.z-function.z-constructor,
-
html[data-theme="dark"] .z-entity.z-name.z-function.z-destructor {
+
.z-entity.z-name.z-function.z-constructor,
+
.z-entity.z-name.z-function.z-destructor {
color: #b7bdf8;
}
-
html[data-theme="dark"] .z-variable.z-parameter.z-function {
+
.z-variable.z-parameter.z-function {
color: #f4dbd6;
font-style: italic;
}
-
html[data-theme="dark"] .z-keyword.z-declaration.z-function {
+
.z-keyword.z-declaration.z-function {
color: #ee99a0;
font-style: italic;
}
-
html[data-theme="dark"] .z-support.z-function {
+
.z-support.z-function {
color: #91d7e3;
}
-
html[data-theme="dark"] .z-support.z-constant {
+
.z-support.z-constant {
color: #8aadf4;
}
-
html[data-theme="dark"] .z-support.z-type,
-
html[data-theme="dark"] .z-support.z-class {
+
.z-support.z-type,
+
.z-support.z-class {
color: #8aadf4;
font-style: italic;
}
-
html[data-theme="dark"] .z-variable.z-function {
+
.z-variable.z-function {
color: #8aadf4;
font-style: italic;
}
-
html[data-theme="dark"] .z-variable.z-parameter {
+
.z-variable.z-parameter {
color: #f4dbd6;
font-style: italic;
}
-
html[data-theme="dark"] .z-variable.z-other {
+
.z-variable.z-other {
color: #cad3f5;
font-style: italic;
}
-
html[data-theme="dark"] .z-variable.z-other.z-member {
+
.z-variable.z-other.z-member {
color: #f4dbd6;
}
-
html[data-theme="dark"] .z-variable.z-language {
+
.z-variable.z-language {
color: #8bd5ca;
}
-
html[data-theme="dark"] .z-entity.z-name.z-tag {
+
.z-entity.z-name.z-tag {
color: #f5a97f;
}
-
html[data-theme="dark"] .z-entity.z-other.z-attribute-name {
+
.z-entity.z-other.z-attribute-name {
color: #c6a0f6;
font-style: italic;
}
-
html[data-theme="dark"] .z-punctuation.z-definition.z-tag {
+
.z-punctuation.z-definition.z-tag {
color: #ee99a0;
}
-
html[data-theme="dark"] .z-markup.z-underline.z-link.z-markdown {
+
.z-markup.z-underline.z-link.z-markdown {
color: #f4dbd6;
text-decoration: underline;
font-style: italic;
}
-
html[data-theme="dark"] .z-meta.z-link.z-inline.z-description {
+
.z-meta.z-link.z-inline.z-description {
color: #b7bdf8;
font-weight: bold;
}
-
html[data-theme="dark"] .z-comment.z-block.z-markdown,
-
html[data-theme="dark"] .z-meta.z-code-fence,
-
html[data-theme="dark"] .z-markup.z-raw.z-code-fence,
-
html[data-theme="dark"] .z-markup.z-raw.z-inline {
+
.z-comment.z-block.z-markdown,
+
.z-meta.z-code-fence,
+
.z-markup.z-raw.z-code-fence,
+
.z-markup.z-raw.z-inline {
color: #8bd5ca;
font-style: italic;
}
-
html[data-theme="dark"] .z-punctuation.z-definition.z-heading,
-
html[data-theme="dark"] .z-entity.z-name.z-section {
+
.z-punctuation.z-definition.z-heading,
+
.z-entity.z-name.z-section {
color: #8aadf4;
font-weight: bold;
}
-
html[data-theme="dark"] .z-markup.z-italic {
+
.z-markup.z-italic {
color: #ee99a0;
font-style: italic;
}
-
html[data-theme="dark"] .z-markup.z-bold {
+
.z-markup.z-bold {
color: #ee99a0;
font-weight: bold;
}
-
html[data-theme="dark"] .z-constant.z-character.z-escape {
+
.z-constant.z-character.z-escape {
color: #f5bde6;
}
html[data-theme="dark"]
···
.z-meta.z-function-call.z-identifier.z-shell {
color: #f5bde6;
}
-
html[data-theme="dark"] .z-variable.z-language.z-shell {
+
.z-variable.z-language.z-shell {
color: #ed8796;
font-style: italic;
}
-
html[data-theme="dark"]
-
.z-source.z-lua
+
+
.z-source.z-lua
.z-meta.z-function.z-lua
.z-meta.z-block.z-lua
.z-meta.z-mapping.z-value.z-lua
···
color: #b7bdf8;
font-style: italic;
}
-
html[data-theme="dark"]
-
.z-source.z-lua
+
+
.z-source.z-lua
.z-meta.z-function.z-lua
.z-meta.z-block.z-lua
.z-meta.z-mapping.z-key.z-lua
.z-string.z-unquoted.z-key.z-lua {
color: #f0c6c6;
}
-
html[data-theme="dark"] .z-entity.z-name.z-constant.z-java {
+
.z-entity.z-name.z-constant.z-java {
color: #8bd5ca;
}
-
html[data-theme="dark"] .z-support.z-type.z-property-name.z-css {
+
.z-support.z-type.z-property-name.z-css {
color: #f0c6c6;
font-style: italic;
}
-
html[data-theme="dark"] .z-support.z-constant.z-property-value.z-css {
+
.z-support.z-constant.z-property-value.z-css {
color: #cad3f5;
}
-
html[data-theme="dark"] .z-constant.z-numeric.z-suffix.z-css,
-
html[data-theme="dark"] .z-keyword.z-other.z-unit.z-css {
+
.z-constant.z-numeric.z-suffix.z-css,
+
.z-keyword.z-other.z-unit.z-css {
color: #8bd5ca;
font-style: italic;
}
-
html[data-theme="dark"] .z-variable.z-other.z-custom-property.z-name.z-css,
-
html[data-theme="dark"] .z-support.z-type.z-custom-property.z-name.z-css,
-
html[data-theme="dark"] .z-punctuation.z-definition.z-custom-property.z-css {
+
.z-variable.z-other.z-custom-property.z-name.z-css,
+
.z-support.z-type.z-custom-property.z-name.z-css,
+
.z-punctuation.z-definition.z-custom-property.z-css {
color: #8bd5ca;
}
-
html[data-theme="dark"] .z-entity.z-name.z-tag.z-css {
+
.z-entity.z-name.z-tag.z-css {
color: #b7bdf8;
}
-
html[data-theme="dark"] .z-variable.z-other.z-sass {
+
.z-variable.z-other.z-sass {
color: #8bd5ca;
}
-
html[data-theme="dark"] .z-invalid {
+
.z-invalid {
color: #cad3f5;
background-color: #ed8796;
}
-
html[data-theme="dark"] .z-invalid.z-deprecated {
+
.z-invalid.z-deprecated {
color: #cad3f5;
background-color: #c6a0f6;
}
-
html[data-theme="dark"] .z-meta.z-diff,
-
html[data-theme="dark"] .z-meta.z-diff.z-header {
+
.z-meta.z-diff,
+
.z-meta.z-diff.z-header {
color: #6e738d;
}
-
html[data-theme="dark"] .z-markup.z-deleted {
+
.z-markup.z-deleted {
color: #ed8796;
}
-
html[data-theme="dark"] .z-markup.z-inserted {
+
.z-markup.z-inserted {
color: #a6da95;
}
-
html[data-theme="dark"] .z-markup.z-changed {
+
.z-markup.z-changed {
color: #eed49f;
}
-
html[data-theme="dark"] .z-message.z-error {
+
.z-message.z-error {
color: #ed8796;
}
+20
scripts/build.ts
···
+
#!/usr/bin/env bun
+
+
import { existsSync } from 'fs';
+
+
await Bun.$`rm -rf .zola-build`.quiet();
+
await Bun.$`mkdir -p .zola-build`.quiet();
+
await Bun.$`cp -r content .zola-build/`.quiet();
+
+
const optionalDirs = ['static', 'templates', 'sass', 'syntaxes'];
+
for (const dir of optionalDirs) {
+
if (existsSync(dir)) {
+
await Bun.$`cp -r ${dir} .zola-build/`.quiet();
+
}
+
}
+
+
await Bun.$`cp config.toml .zola-build/`.quiet();
+
await Bun.$`bun run scripts/preprocess.ts .zola-build/content`.quiet();
+
await Bun.$`cd .zola-build && zola build --force --output-dir ../public`;
+
await Bun.$`rm -rf .zola-build`.quiet();
+
+72
scripts/dev.ts
···
+
#!/usr/bin/env bun
+
+
import { watch } from 'fs';
+
import { existsSync } from 'fs';
+
import { spawn } from 'child_process';
+
+
let zolaProcess: any = null;
+
let isRebuilding = false;
+
+
function cleanup() {
+
if (zolaProcess) {
+
zolaProcess.kill();
+
}
+
process.exit(0);
+
}
+
+
process.on('SIGINT', cleanup);
+
process.on('SIGTERM', cleanup);
+
+
async function buildShadow() {
+
if (isRebuilding) return;
+
isRebuilding = true;
+
+
if (zolaProcess) {
+
zolaProcess.kill();
+
zolaProcess = null;
+
}
+
+
await Bun.$`rm -rf .zola-build`.quiet();
+
await Bun.$`mkdir -p .zola-build`.quiet();
+
await Bun.$`cp -r content .zola-build/`.quiet();
+
+
const optionalDirs = ['static', 'templates', 'sass', 'syntaxes'];
+
for (const dir of optionalDirs) {
+
if (existsSync(dir)) {
+
await Bun.$`cp -r ${dir} .zola-build/`.quiet();
+
}
+
}
+
+
await Bun.$`cp config.toml .zola-build/`.quiet();
+
await Bun.$`bun run scripts/preprocess.ts .zola-build/content`.quiet();
+
+
zolaProcess = spawn('zola', ['serve', '--force', '--interface', '0.0.0.0', '--output-dir', '../public'], {
+
cwd: '.zola-build',
+
stdio: 'inherit'
+
});
+
+
zolaProcess.on('error', (err: Error) => {
+
console.error('Failed to start Zola:', err);
+
});
+
+
isRebuilding = false;
+
}
+
+
await buildShadow();
+
+
const watchDirs = ['content', 'templates', 'sass', 'static', 'syntaxes'];
+
+
for (const dir of watchDirs) {
+
if (existsSync(dir)) {
+
watch(dir, { recursive: true }, async (event, filename) => {
+
if (filename && !filename.includes('.zola-build')) {
+
await buildShadow();
+
}
+
});
+
}
+
}
+
+
watch('config.toml', async () => {
+
await buildShadow();
+
});
+
+122
scripts/genOG.ts
···
+
import puppeteer from "puppeteer";
+
import { readdir, mkdir } from "node:fs/promises";
+
+
const template = await Bun.file("tools/og.html").text();
+
+
const browser = await puppeteer.launch({
+
args: ["--no-sandbox"],
+
executablePath: process.env.PUPPETEER_EXEC_PATH, // set by docker container
+
});
+
+
async function og(
+
postname: string,
+
type: string,
+
by: string | undefined,
+
outputPath: string,
+
width = 1200,
+
height = 630,
+
) {
+
const page = await browser.newPage();
+
+
await page.setViewport({ width, height });
+
+
await page.setContent(
+
template
+
.toString()
+
.replace("{{postname}}", postname)
+
.replace("{{type}}", type)
+
.replace("{{by}}", by || ""),
+
);
+
+
await page.screenshot({ path: outputPath });
+
}
+
+
async function fileExists(path: string): Promise<boolean> {
+
try {
+
await Bun.file(path);
+
return true;
+
} catch (e) {
+
return false;
+
}
+
}
+
+
try {
+
// check if the public/blog folder exists
+
// if not exit
+
// if it does, get all the folders and then get the title tag from the index.html
+
+
if (!(await fileExists("public/"))) {
+
console.error("public/ does not exist");
+
process.exit(1);
+
}
+
+
// read all the files in the current directory filtering for index.htmls
+
const files = (await readdir("public/", { recursive: true })).filter((file) =>
+
file.endsWith("index.html"),
+
);
+
+
const directories = new Set(
+
files.map((file) => file.replace("index.html", "")),
+
);
+
+
const existing = (await readdir("static/")).filter((file) =>
+
directories.has(file),
+
);
+
+
// create not existing
+
for (const dir of directories) {
+
if (!existing.includes(dir)) {
+
await mkdir(`static/${dir.split("/").slice(0, -1).join("/")}`, {
+
recursive: true,
+
});
+
}
+
}
+
+
console.log("Generating OG images for", files.length, "files");
+
+
// for each file, get the title tag from the index.html
+
for (const file of files) {
+
const index = await Bun.file(`public/${file}`).text();
+
const title = index.match(/<title>(.*?)<\/title>/)[1];
+
let type = "Page";
+
let by: string | undefined;
+
switch (file.split("/")[0]) {
+
case "blog":
+
type = "Blog";
+
if (file.split("/")[1] !== "index.html") {
+
by = "<p>A post ... yeah thats about it</p>";
+
} else {
+
by = "<p>All authored by me ... or are they???</p>";
+
}
+
break;
+
case "verify":
+
type = "Slash Page";
+
by = "<p>So you can stalk me ๐Ÿ’€</p>";
+
break;
+
case "pfp":
+
type = "Slash Page";
+
by = "<p>Want to stare at my pretty face?</p>";
+
break;
+
case "tags":
+
if (file.split("/")[1] === "index.html") {
+
type = "Tags";
+
by = "<p>A total archive!</p>";
+
} else {
+
type = "Tag";
+
by = "<p>Find more posts like this!</p>";
+
}
+
break;
+
case "index.html":
+
type = "Root";
+
by = "<p>Where it all begins</p>";
+
break;
+
}
+
+
console.log("Generating OG for", file, "title:", title, "with type:", type);
+
await og(title, type, by, `static/${file.replace("index.html", "og.png")}`);
+
}
+
} catch (e) {
+
console.error(e);
+
} finally {
+
await browser.close();
+
}
+111
scripts/og.html
···
+
<!DOCTYPE html>
+
<html>
+
<head>
+
<style>
+
:root,
+
::backdrop {
+
color-scheme: dark;
+
--bg: #222529;
+
--bg-light: #464949;
+
--text: #d6d6d6;
+
--text-light: #c5c0b7;
+
--accent: #78b6ad;
+
--accent-light: #87c9e5;
+
--accent-text: var(--bg);
+
--border: #dbd5bc;
+
--link: #e2c8a2;
+
--gradient-average-light: oklch(86.49% 0.018 73.05);
+
--gradient-average-dark: oklch(27.58% 0.0203 289.13);
+
--nightshade-violet: oklch(22.96% 0.0242 287.67);
+
--purple-night: oklch(18.96% 0.0242 287.67);
+
--dark-crushed-grape: oklch(74.02% 0.0756 311.96);
+
--light-crushed-grape: oklch(73.48% 0.1008 284.99);
+
--reseda-green: oklch(62.33% 0.0475 126.94);
+
--earth-yellow: oklch(87.45% 0.0203 74.93);
+
--sunset: oklch(87.45% 0.0334 74.93);
+
--ultra-violet: oklch(42.21% 0.0676 297.45);
+
--rose-quartz: oklch(65.32% 0.0585 311.96);
+
--pink-puree: oklch(75.65% 0.0555 290.76);
+
--lavendar-breeze: oklch(91.06% 0.0223 290.76);
+
--purple-gray: oklch(25.63% 0.0002 290.76);
+
--alice-blue: oklch(95.38% 0.0118 239.91);
+
}
+
+
body {
+
font-weight: 600;
+
color: var(--lavendar-breeze);
+
background-color: var(--purple-night);
+
font-family: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono",
+
monospace;
+
display: flex;
+
flex-direction: column;
+
text-align: center;
+
}
+
+
div {
+
margin: 0;
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
justify-content: center;
+
height: 90vh; /* 90% of viewport height */
+
width: 90vw; /* 90% of viewport width */
+
padding: 5vh 5vw; /* 5% border on all sides */
+
box-sizing: border-box;
+
align-self: center;
+
}
+
+
h1 {
+
font-size: calc(2 * 2vw);
+
}
+
h2 {
+
font-size: calc(1.75 * 2vw);
+
}
+
h3 {
+
font-size: calc(1.5 * 2vw);
+
}
+
h4 {
+
font-size: calc(1.25 * 2vw);
+
}
+
h5 {
+
font-size: calc(1 * 2vw);
+
}
+
h6 {
+
font-size: calc(0.75 * 2vw);
+
}
+
+
h1,
+
h2,
+
h3,
+
h4,
+
h5,
+
h6 {
+
margin: 0.5em 0 0.5em 0;
+
padding: 0.22em 0.4em 0.22em 0.4em;
+
border-radius: 0.1em;
+
width: fit-content;
+
color: var(--lavendar-breeze);
+
}
+
+
h1 {
+
background-color: var(--rose-quartz);
+
color: var(--purple-gray);
+
}
+
+
p {
+
margin: 1rem 0;
+
color: var(--pink-puree);
+
font-size: calc(
+
1rem + 1vw
+
); /* Adjust font size based on viewport width */
+
}
+
</style>
+
</head>
+
<body>
+
<div>
+
<h1>{{type}}</h1>
+
<h2>{{postname}}</h2>
+
{{by}}
+
</div>
+
</body>
+
</html>
+142
scripts/preprocess.ts
···
+
#!/usr/bin/env bun
+
+
import fs from 'fs';
+
import path from 'path';
+
import { glob } from 'glob';
+
+
const contentDir = process.argv[2] || 'content';
+
+
function splitByCodeBlocks(content: string): { text: string; isCode: boolean }[] {
+
const parts: { text: string; isCode: boolean }[] = [];
+
const codeBlockRegex = /^(```|~~~)/gm;
+
+
let lastIndex = 0;
+
let inCodeBlock = false;
+
let match;
+
+
codeBlockRegex.lastIndex = 0;
+
+
while ((match = codeBlockRegex.exec(content)) !== null) {
+
const segment = content.slice(lastIndex, match.index);
+
if (segment) {
+
parts.push({ text: segment, isCode: inCodeBlock });
+
}
+
inCodeBlock = !inCodeBlock;
+
lastIndex = match.index;
+
}
+
+
// Add remaining content
+
if (lastIndex < content.length) {
+
parts.push({ text: content.slice(lastIndex), isCode: inCodeBlock });
+
}
+
+
return parts;
+
}
+
+
function transformCallouts(content: string): string {
+
return content.replace(
+
/^> \[!(INFO|WARNING|WARN|DANGER|ERROR|TIP|HINT|NOTE)\]\n((?:> .*\n?)*)/gm,
+
(match, type, body) => {
+
const cleanBody = body.replace(/^> /gm, '').trim();
+
const normalizedType = type.toLowerCase() === 'warn' ? 'warning' :
+
type.toLowerCase() === 'error' ? 'danger' :
+
type.toLowerCase() === 'hint' ? 'tip' :
+
type.toLowerCase();
+
return `{% callout(type="${normalizedType}") %}\n${cleanBody}\n{% end %}\n`;
+
}
+
);
+
}
+
+
function transformImages(content: string): string {
+
// Transform multiple images: !![alt1](url1)[alt2](url2){attrs}
+
content = content.replace(
+
/!!(\[([^\]]*)\]\(([^)]+)\))+(?:\{([^}]+)\})?/g,
+
(match) => {
+
// Extract all [alt](url) pairs
+
const pairs = [...match.matchAll(/\[([^\]]*)\]\(([^)]+)\)/g)];
+
const urls = pairs.map(p => p[2]).join(', ');
+
const alts = pairs.map(p => p[1]).join(', ');
+
+
// Extract attrs if present
+
const attrsMatch = match.match(/\{([^}]+)\}$/);
+
const attrs = attrsMatch ? attrsMatch[1] : '';
+
+
const params: string[] = [`id="${urls}"`];
+
+
if (alts.trim()) {
+
params.push(`alt="${alts}"`);
+
}
+
+
if (attrs) {
+
const classes = attrs.match(/\.([a-zA-Z0-9_-]+)/g)?.map(c => c.slice(1)) || [];
+
if (classes.length) {
+
params.push(`class="${classes.join(' ')}"`);
+
}
+
+
const keyValueMatches = attrs.matchAll(/([a-zA-Z]+)=["']?([^"'\s}]+)["']?/g);
+
for (const [, key, value] of keyValueMatches) {
+
if (key !== 'class') {
+
params.push(`${key}="${value.replace(/["']/g, '')}"`);
+
}
+
}
+
}
+
+
return `{{ imgs(${params.join(', ')}) }}`;
+
}
+
);
+
+
// Transform single images: ![alt](url){attrs}
+
content = content.replace(
+
/!\[([^\]]*)\]\(([^)]+)\)(?:\{([^}]+)\})?/g,
+
(match, alt, url, attrs) => {
+
const params: string[] = [`id="${url}"`];
+
+
if (alt) {
+
params.push(`alt="${alt}"`);
+
}
+
+
if (attrs) {
+
const classes = attrs.match(/\.([a-zA-Z0-9_-]+)/g)?.map(c => c.slice(1)) || [];
+
if (classes.length) {
+
params.push(`class="${classes.join(' ')}"`);
+
}
+
+
const keyValueMatches = attrs.matchAll(/([a-zA-Z]+)=["']?([^"'\s}]+)["']?/g);
+
for (const [, key, value] of keyValueMatches) {
+
if (key !== 'class') {
+
params.push(`${key}="${value.replace(/["']/g, '')}"`);
+
}
+
}
+
}
+
+
return `{{ img(${params.join(', ')}) }}`;
+
}
+
);
+
+
return content;
+
}
+
+
function processFile(filePath: string): void {
+
let content = fs.readFileSync(filePath, 'utf8');
+
const originalContent = content;
+
+
// Split by code blocks and only transform non-code parts
+
const parts = splitByCodeBlocks(content);
+
content = parts.map(part => {
+
if (part.isCode) {
+
return part.text; // Don't transform code blocks
+
}
+
let text = part.text;
+
text = transformCallouts(text);
+
text = transformImages(text);
+
return text;
+
}).join('');
+
+
if (content !== originalContent) {
+
fs.writeFileSync(filePath, content);
+
}
+
}
+
+
const files = glob.sync(`${contentDir}/**/*.md`);
+
files.forEach(processFile);
+
+180
scripts/rehost-cdn.sh
···
+
#!/usr/bin/env bash
+
set -euo pipefail
+
+
API_URL="https://cdn.hackclub.com/api/v3/new"
+
TOKEN="${HACKCLUB_CDN_TOKEN:-}"
+
if [[ -z "${TOKEN}" ]]; then
+
TOKEN="${1:-}"
+
fi
+
if [[ -z "${TOKEN}" ]]; then
+
echo "Usage: HACKCLUB_CDN_TOKEN=... $0 [token] [--dry-run] [paths...]" >&2
+
exit 1
+
fi
+
+
DRY_RUN=false
+
SKIP_CHECK=false
+
CACHED_URLS=()
+
ARGS=()
+
for a in "$@"; do
+
case "$a" in
+
--dry-run) DRY_RUN=true ;;
+
--skip-check) SKIP_CHECK=true ;;
+
*) ARGS+=("$a") ;;
+
esac
+
done
+
# remove token if passed as first arg
+
if [[ ${#ARGS[@]} -gt 0 && "${ARGS[0]}" != "--dry-run" ]]; then
+
ARGS=("${ARGS[@]:1}")
+
fi
+
+
PATHS=("content")
+
if [[ ${#ARGS[@]} -gt 0 ]]; then PATHS=("${ARGS[@]}"); fi
+
+
TMP_DIR=".crush/rehost-cdn"
+
MAP_FILE="$TMP_DIR/map.tsv"
+
mkdir -p "$TMP_DIR"
+
touch "$MAP_FILE"
+
+
collect_urls() {
+
# Markdown images: ![alt](URL)
+
rg -n --no-heading -e '!\[[^\]]*\]\((https?://[^)\s]+)\)' -g '!**/*.map' -g '!**/*.lock' "${PATHS[@]}" 2>/dev/null |
+
awk -F: '{file=$1; sub(/^[^:]*:/, "", $0); match($0, /!\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/, m); if(m[1]!="") print file"\t"m[1]}' |
+
# Zola shortcode variants:
+
# - {% img(id="URL", ...) %}
+
# - {{ img(id="URL", ...) }}
+
cat <( rg -n --no-heading -e '\{[%{]\s*img[^}%]*[}%]\}' "${PATHS[@]}" 2>/dev/null | \
+
awk -F: '{file=$1; sub(/^[^:]*:/, "", $0); if (match($0, /(id|src)[[:space:]]*=[[:space:]]*"(https?:\/\/[^"[:space:]]+)"/, m)) print file"\t"m[2]}' ) |
+
awk -F'\t' '{print $1"\t"$2}' |
+
grep -E '\.(png|jpe?g|gif|webp|svg|bmp|tiff?|avif)(\?.*)?$' -i |
+
grep -vE 'hc-cdn\.|cdn\.hackclub\.com'
+
}
+
+
batch_upload() {
+
payload=$(jq -sR 'split("\n")|map(select(length>0))' <(printf "%s\n" "$@"))
+
for attempt in 1 2 3; do
+
resp=$(curl -sS -w "\n%{http_code}" -X POST "$API_URL" \
+
-H "Authorization: Bearer $TOKEN" \
+
-H 'Content-Type: application/json' \
+
--data-raw "$payload" 2>&1) || true
+
body=$(printf "%s" "$resp" | sed '$d')
+
code=$(printf "%s" "$resp" | tail -n1)
+
if [[ "$code" == "200" ]]; then
+
printf "%s" "$body" | jq -r '.files[] | .sourceUrl? as $s | .deployedUrl + "\t" + ( $s // "" )'
+
return 0
+
fi
+
echo "Upload attempt $attempt failed with $code" >&2
+
echo "Response body:" >&2
+
printf "%s\n" "$body" >&2
+
echo "Payload:" >&2
+
printf "%s\n" "$payload" >&2
+
sleep $((attempt*2))
+
done
+
echo "Upload failed after retries" >&2
+
return 1
+
}
+
+
mapfile -t LINES < <(collect_urls | sort -u)
+
+
URLS_TO_SEND=()
+
FILES=()
+
total=${#LINES[@]}
+
idx=0
+
for line in "${LINES[@]}"; do
+
idx=$((idx+1))
+
file="${line%%$'\t'*}"
+
url="${line#*$'\t'}"
+
if grep -Fq "${url}" "$MAP_FILE" 2>/dev/null; then
+
echo "[$idx/$total] cached: $url -> will rewrite only"
+
CACHED_URLS+=("$url")
+
continue
+
fi
+
if $DRY_RUN; then
+
echo "[$idx/$total] queued: $url"
+
URLS_TO_SEND+=("$url")
+
FILES+=("$file")
+
else
+
if $SKIP_CHECK; then
+
echo "[$idx/$total] no-check: $url"
+
URLS_TO_SEND+=("$url")
+
FILES+=("$file")
+
else
+
echo -n "[$idx/$total] checking: $url ... "
+
code=$(curl -sS -o /dev/null -w '%{http_code}' -L "$url" || echo 000)
+
if [[ "$code" =~ ^2|3 ]]; then
+
echo "ok ($code)"
+
URLS_TO_SEND+=("$url")
+
FILES+=("$file")
+
else
+
echo "fail ($code)"; echo "Skipping: $url" >&2
+
fi
+
fi
+
fi
+
done
+
+
if [[ ${#URLS_TO_SEND[@]} -eq 0 ]]; then
+
echo "No new image URLs to process"; exit 0
+
fi
+
+
BATCH=50
+
start=0
+
# Rewrites for cached URLs from map without uploading
+
if [ "${#CACHED_URLS[@]}" -gt 0 ] 2>/dev/null; then
+
echo "Rewriting cached URLs from map without upload..."
+
for src in "${CACHED_URLS[@]}"; do
+
dst=$(awk -F'\t' -v s="$src" '$1==s{print $2}' "$MAP_FILE" | head -n1)
+
[[ -z "$dst" ]] && continue
+
rg -l --fixed-strings -- "$src" "${PATHS[@]}" 2>/dev/null | while read -r f; do
+
mkdir -p "$TMP_DIR/backup"
+
if [[ ! -e "$TMP_DIR/backup/$f" ]]; then
+
mkdir -p "$TMP_DIR/backup/$(dirname "$f")"
+
cp "$f" "$TMP_DIR/backup/$f"
+
fi
+
sed -i "s#$(printf '%s' "$src" | sed -e 's/[.[\*^$]/\\&/g' -e 's#/#\\/#g')#$(printf '%s' "$dst" | sed -e 's/[&]/\\&/g' -e 's#/#\\/#g')#g" "$f"
+
echo "Rewrote (cached): $f"
+
done
+
done
+
fi
+
+
while [[ $start -lt ${#URLS_TO_SEND[@]} ]]; do
+
end=$(( start + BATCH ))
+
if [[ $end -gt ${#URLS_TO_SEND[@]} ]]; then end=${#URLS_TO_SEND[@]}; fi
+
chunk=("${URLS_TO_SEND[@]:start:end-start}")
+
if $DRY_RUN; then
+
for u in "${chunk[@]}"; do echo "DRY: would upload $u"; done
+
else
+
echo "Uploading ${#chunk[@]} URLs..."
+
resp=$(batch_upload "${chunk[@]}") || { echo "Upload failed" >&2; exit 1; }
+
echo "Upload response:"; printf "%s\n" "$resp"
+
mapfile -t deployed_arr < <(printf "%s\n" "$resp" | awk '{print $0}')
+
for i in "${!chunk[@]}"; do
+
src="${chunk[$i]}"
+
dst="${deployed_arr[$i]:-}"
+
if [[ -n "$dst" ]]; then
+
printf "%s\t%s\n" "$src" "$dst" | tee -a "$MAP_FILE"
+
fi
+
done
+
fi
+
start=$end
+
done
+
+
if $DRY_RUN; then echo "DRY: skipping replacements"; exit 0; fi
+
+
# Replace in-place using map
+
if [[ -s "$MAP_FILE" ]]; then
+
cp "$MAP_FILE" "$TMP_DIR/map-$(date +%s).tsv"
+
while IFS=$'\t' read -r src dst; do
+
[[ -z "$src" || -z "$dst" ]] && continue
+
rg -l --fixed-strings -- "$src" "${PATHS[@]}" 2>/dev/null | while read -r f; do
+
mkdir -p "$TMP_DIR/backup"
+
if [[ ! -e "$TMP_DIR/backup/$f" ]]; then
+
mkdir -p "$TMP_DIR/backup/$(dirname "$f")"
+
cp "$f" "$TMP_DIR/backup/$f"
+
fi
+
sed -i "s#$(printf '%s' "$src" | sed -e 's/[.[\*^$]/\\&/g' -e 's#/#\\/#g')#$(printf '%s' "$dst" | sed -e 's/[&]/\\&/g' -e 's#/#\\/#g')#g" "$f"
+
echo "Rewrote: $f"
+
done
+
done < "$MAP_FILE"
+
echo "Backups in $TMP_DIR/backup"
+
fi
+
+
echo "Done"
+1
static/badges/MadeByAHuman_04.svg
···
+
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="88" height="31" viewBox="0 0 88 31"><g id="Background"><rect width="88" height="31" fill="#b39ddb"/></g><g id="_3D"><polygon points="88 0 88 31 0 31 2 29 86 29 86 2 88 0" fill="#263238" opacity=".5"/><polygon points="88 0 86 2 2 2 2 29 0 31 0 0 88 0" fill="#fff" opacity=".5"/></g><g id="Text"><path d="m40.86,9.65c0-1.08-.59-1.64-1.48-1.64s-1.49.56-1.49,1.64v3.31h-1.21v-3.31c0-1.08-.59-1.64-1.48-1.64s-1.49.56-1.49,1.64v3.31h-1.22v-5.92h1.22v.68c.4-.48,1.04-.77,1.75-.77.93,0,1.72.4,2.13,1.17.37-.72,1.2-1.17,2.06-1.17,1.39,0,2.44.87,2.44,2.52v3.49h-1.21v-3.31Z" fill="#263238"/><path d="m46.01,6.94c1,0,1.69.47,2.05.96v-.86h1.24v5.92h-1.24v-.88c-.38.5-1.08.98-2.07.98-1.54,0-2.77-1.26-2.77-3.08s1.24-3.03,2.79-3.03Zm.26,1.06c-.91,0-1.79.69-1.79,1.97s.88,2.02,1.79,2.02,1.79-.72,1.79-2-.87-1.99-1.79-1.99Z" fill="#263238"/><path d="m53.31,6.94c.77,0,1.59.37,2.04.92v-2.86h1.24v7.95h-1.24v-.89c-.38.54-1.08.99-2.05.99-1.56,0-2.79-1.26-2.79-3.08s1.24-3.03,2.8-3.03Zm.25,1.06c-.91,0-1.79.69-1.79,1.97s.88,2.02,1.79,2.02,1.79-.72,1.79-2-.87-1.99-1.79-1.99Z" fill="#263238"/><path d="m60.73,13.05c-1.7,0-2.95-1.2-2.95-3.06s1.2-3.05,2.95-3.05,2.88,1.17,2.88,2.91c0,.2-.01.4-.04.6h-4.52c.09.98.78,1.57,1.69,1.57.75,0,1.17-.37,1.4-.83h1.32c-.33,1.03-1.27,1.86-2.72,1.86Zm-1.68-3.59h3.28c-.02-.91-.74-1.49-1.65-1.49-.83,0-1.49.56-1.62,1.49Z" fill="#263238"/><path d="m70.89,6.94c1.57,0,2.78,1.2,2.78,3.03s-1.22,3.08-2.78,3.08c-.98,0-1.68-.44-2.06-.96v.86h-1.22v-7.95h1.22v2.91c.39-.54,1.13-.98,2.06-.98Zm-.27,1.06c-.91,0-1.79.72-1.79,1.99s.88,2,1.79,2,1.8-.74,1.8-2.02-.88-1.97-1.8-1.97Z" fill="#263238"/><path d="m78.95,7.04h1.27l-3.63,8.7h-1.27l1.2-2.88-2.33-5.82h1.36l1.67,4.51,1.73-4.51Z" fill="#263238"/><path d="m34.48,17.94c1,0,1.69.47,2.05.96v-.86h1.24v5.92h-1.24v-.88c-.38.5-1.08.98-2.07.98-1.54,0-2.77-1.26-2.77-3.08s1.24-3.03,2.79-3.03Zm.26,1.06c-.91,0-1.79.69-1.79,1.97s.88,2.02,1.79,2.02,1.79-.72,1.79-2-.87-1.99-1.79-1.99Z" fill="#263238"/><path d="m42.17,16.01h1.22v2.72c.41-.49,1.07-.78,1.84-.78,1.32,0,2.35.87,2.35,2.52v3.49h-1.21v-3.31c0-1.08-.59-1.64-1.48-1.64s-1.49.56-1.49,1.64v3.31h-1.22v-7.95Z" fill="#263238"/><path d="m54.5,23.96h-1.22v-.71c-.39.5-1.05.79-1.75.79-1.39,0-2.44-.87-2.44-2.52v-3.48h1.21v3.3c0,1.08.59,1.64,1.48,1.64s1.49-.56,1.49-1.64v-3.3h1.22v5.92Z" fill="#263238"/><path d="m64.5,20.65c0-1.08-.59-1.64-1.48-1.64s-1.49.56-1.49,1.64v3.31h-1.21v-3.31c0-1.08-.59-1.64-1.48-1.64s-1.49.56-1.49,1.64v3.31h-1.22v-5.92h1.22v.68c.4-.48,1.04-.77,1.75-.77.93,0,1.72.4,2.13,1.17.37-.72,1.2-1.17,2.06-1.17,1.39,0,2.44.87,2.44,2.52v3.49h-1.21v-3.31Z" fill="#263238"/><path d="m69.65,17.94c1,0,1.69.47,2.05.96v-.86h1.24v5.92h-1.24v-.88c-.38.5-1.08.98-2.07.98-1.54,0-2.77-1.26-2.77-3.08s1.24-3.03,2.79-3.03Zm.26,1.06c-.91,0-1.79.69-1.79,1.97s.88,2.02,1.79,2.02,1.79-.72,1.79-2-.87-1.99-1.79-1.99Z" fill="#263238"/><path d="m78.74,20.65c0-1.08-.59-1.64-1.48-1.64s-1.49.56-1.49,1.64v3.31h-1.22v-5.92h1.22v.68c.4-.48,1.05-.77,1.76-.77,1.39,0,2.43.87,2.43,2.52v3.49h-1.21v-3.31Z" fill="#263238"/></g><g id="Image"><circle cx="15.37" cy="15.5" r="10.3" fill="#ffcc80" stroke="#263238" stroke-miterlimit="10" stroke-width="1.5"/><rect x="7.55" y="13.8" width="2.54" height="3.4" rx="1.27" ry="1.27" fill="#263238"/><rect x="20.65" y="13.8" width="2.54" height="3.4" rx="1.27" ry="1.27" fill="#263238"/><path d="m18.44,15.5c0,1.7-1.37,3.07-3.07,3.07s-3.07-1.37-3.07-3.07" fill="none" stroke="#263238" stroke-miterlimit="10" stroke-width="1.5"/></g></svg>
static/badges/get-netscape.gif

This is a binary file and will not be displayed.

static/badges/green-team.gif

This is a binary file and will not be displayed.

static/badges/hackclub.png

This is a binary file and will not be displayed.

static/badges/made-with-neovim.png

This is a binary file and will not be displayed.

static/badges/no-web3.gif

This is a binary file and will not be displayed.

static/badges/powered-by-nix.gif

This is a binary file and will not be displayed.

static/badges/tangled.png

This is a binary file and will not be displayed.

static/blog/adding-a-copy-button/og.png

This is a binary file and will not be displayed.

static/blog/analyzing-implications-of-online-safety-legislation/og.png

This is a binary file and will not be displayed.

static/blog/atuin/og.png

This is a binary file and will not be displayed.

static/blog/degraded-zpool-proxmox/og.png

This is a binary file and will not be displayed.

static/blog/garmin-vivoactive-homeassistant/og.png

This is a binary file and will not be displayed.

static/blog/hilton-tomfoolery/og.png

This is a binary file and will not be displayed.

static/blog/install-truenas-core-proxmox/og.png

This is a binary file and will not be displayed.

static/blog/mega/og.png

This is a binary file and will not be displayed.

static/blog/monaspace-vs-code-install/og.png

This is a binary file and will not be displayed.

static/blog/my-animations/og.png

This is a binary file and will not be displayed.

static/blog/my-life-story-with-tech/og.png

This is a binary file and will not be displayed.

static/blog/og.png

This is a binary file and will not be displayed.

static/blog/remove-exif-git-hook/og.png

This is a binary file and will not be displayed.

static/blog/spherical-ray-diagrams/og.png

This is a binary file and will not be displayed.

static/blog/ssd-removal-mbp-2017/og.png

This is a binary file and will not be displayed.

static/blog/tangled-sync/og.png

This is a binary file and will not be displayed.

+65 -53
static/js/copy-button.js
···
// Based on https://www.roboleary.net/2022/01/13/copy-code-to-clipboard-blog.html
-
document.addEventListener("DOMContentLoaded", () => {
-
const blocks = document.querySelectorAll("pre[class^='language-']");
+
function initCopyButtons() {
+
const blocks = document.querySelectorAll("pre[class^='language-']");
+
for (const block of blocks) {
-
if (navigator.clipboard) {
-
// Code block header title
-
const title = document.createElement("span");
-
title.style.color = "var(--accent-text)";
-
const lang = block.getAttribute("data-lang");
-
const comment =
-
block.previousElementSibling &&
-
(block.previousElementSibling.tagName === "blockquote" ||
-
block.previousElementSibling.nodeName === "BLOCKQUOTE")
-
? block.previousElementSibling
-
: null;
-
if (comment) block.previousElementSibling.remove();
-
title.innerHTML =
-
lang + (comment ? ` (${comment.textContent.trim()})` : "");
+
// Code block header title
+
const title = document.createElement("span");
+
title.style.color = "var(--accent-text)";
+
const lang = block.getAttribute("data-lang");
+
const comment =
+
block.previousElementSibling &&
+
(block.previousElementSibling.tagName === "blockquote" ||
+
block.previousElementSibling.nodeName === "BLOCKQUOTE")
+
? block.previousElementSibling
+
: null;
+
if (comment) block.previousElementSibling.remove();
+
title.innerHTML =
+
lang + (comment ? ` (${comment.textContent.trim()})` : "");
-
// Copy button icon
-
const icon = document.createElement("i");
-
icon.classList.add("icon");
+
// Copy button icon
+
const icon = document.createElement("i");
+
icon.classList.add("icon");
-
// Copy button
-
const button = document.createElement("button");
-
const copyCodeText = "Copy code"; // Use hardcoded text instead of getElementById
-
button.setAttribute("title", copyCodeText);
-
button.appendChild(icon);
+
// Copy button
+
const button = document.createElement("button");
+
const copyCodeText = "Copy code";
+
button.setAttribute("title", copyCodeText);
+
button.appendChild(icon);
-
// Code block header
-
const header = document.createElement("div");
-
header.classList.add("header");
-
header.appendChild(title);
-
header.appendChild(button);
+
// Code block header
+
const header = document.createElement("div");
+
header.classList.add("header");
+
header.appendChild(title);
+
header.appendChild(button);
-
// Container that holds header and the code block itself
-
const container = document.createElement("div");
-
container.classList.add("pre-container");
-
container.appendChild(header);
+
// Container that holds header and the code block itself
+
const container = document.createElement("div");
+
container.classList.add("pre-container");
+
container.appendChild(header);
-
// Move code block into the container
-
block.parentNode.insertBefore(container, block);
-
container.appendChild(block);
+
// Move code block into the container
+
block.parentNode.insertBefore(container, block);
+
container.appendChild(block);
-
button.addEventListener("click", async () => {
-
await copyCode(block, header, button); // Pass the button here
-
});
-
}
+
button.addEventListener("click", async () => {
+
await copyCode(block, header, button);
+
});
}
async function copyCode(block, header, button) {
const code = block.querySelector("code");
const text = code.innerText;
-
await navigator.clipboard.writeText(text);
+
// Only try to copy if clipboard API is available
+
if (navigator.clipboard) {
+
try {
+
await navigator.clipboard.writeText(text);
+
header.classList.add("active");
+
button.setAttribute("disabled", true);
-
header.classList.add("active");
-
button.setAttribute("disabled", true);
+
header.addEventListener(
+
"animationend",
+
() => {
+
header.classList.remove("active");
+
button.removeAttribute("disabled");
+
},
+
{ once: true },
+
);
+
} catch (err) {
+
console.error("Failed to copy:", err);
+
}
+
}
+
}
+
}
-
header.addEventListener(
-
"animationend",
-
() => {
-
header.classList.remove("active");
-
button.removeAttribute("disabled");
-
},
-
{ once: true },
-
);
-
}
-
});
+
// Since the script has defer attribute, the DOM is already loaded when this runs
+
if (document.readyState === 'loading') {
+
document.addEventListener('DOMContentLoaded', initCopyButtons);
+
} else {
+
initCopyButtons();
+
}
+65
static/js/emoji-replace.js
···
+
document.addEventListener("DOMContentLoaded", () => {
+
const content = document.querySelector("main");
+
if (!content) return;
+
+
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, {
+
acceptNode: (node) => {
+
// Skip code blocks, pre tags, and script/style tags
+
let parent = node.parentElement;
+
while (parent) {
+
const tag = parent.tagName.toLowerCase();
+
if (
+
tag === "code" ||
+
tag === "pre" ||
+
tag === "script" ||
+
tag === "style"
+
) {
+
return NodeFilter.FILTER_REJECT;
+
}
+
parent = parent.parentElement;
+
}
+
return NodeFilter.FILTER_ACCEPT;
+
},
+
});
+
+
const nodesToReplace = [];
+
while (walker.nextNode()) {
+
const node = walker.currentNode;
+
if (/:[\w-]+:/.test(node.textContent)) {
+
nodesToReplace.push(node);
+
}
+
}
+
+
nodesToReplace.forEach((node) => {
+
const frag = document.createDocumentFragment();
+
const parts = node.textContent.split(/(:[\w-]+:)/);
+
+
parts.forEach((part) => {
+
if (/^:[\w-]+:$/.test(part)) {
+
const name = part.slice(1, -1);
+
+
const span = document.createElement("span");
+
span.className = "emoji-inline--wrapper";
+
+
const img = document.createElement("img");
+
img.src = `https://cachet.dunkirk.sh/emojis/${name}/r`;
+
img.alt = part;
+
img.className = "emoji-inline";
+
img.loading = "lazy";
+
img.setAttribute("aria-label", `${name} emoji`);
+
+
// Fallback: if image fails to load, show original text
+
img.onerror = () => {
+
span.replaceWith(document.createTextNode(part));
+
};
+
+
span.appendChild(img);
+
frag.appendChild(span);
+
} else if (part) {
+
frag.appendChild(document.createTextNode(part));
+
}
+
});
+
+
node.replaceWith(frag);
+
});
+
});
-49
static/js/theme-toggle.js
···
-
const toggleButton = document.getElementById("theme-toggle");
-
const themeIcon = document
-
.getElementById("theme-toggle-label")
-
.querySelector("i");
-
const themeSound = document.getElementById("theme-sound");
-
-
// Function to update the theme icon based on the current theme
-
const updateThemeIcon = (isDarkMode) => {
-
themeIcon.style.setProperty(
-
"--icon-toggle",
-
isDarkMode ? "var(--icon-dark)" : "var(--icon-light)",
-
);
-
};
-
-
// Function to update the theme based on the current mode
-
const updateTheme = (isDarkMode) => {
-
const theme = isDarkMode ? "dark" : "light";
-
document.documentElement.setAttribute("data-theme", theme);
-
updateThemeIcon(isDarkMode);
-
};
-
-
// Function to toggle the theme
-
const toggleTheme = () => {
-
const isDarkMode = toggleButton.checked;
-
updateTheme(isDarkMode);
-
themeSound.currentTime = 0;
-
themeSound.play();
-
localStorage.setItem("theme", isDarkMode ? "dark" : "light");
-
};
-
-
// Event listener for theme toggle
-
toggleButton.addEventListener("change", toggleTheme);
-
-
// Function to initialize the theme based on the stored preference
-
const initializeTheme = () => {
-
const storedTheme = localStorage.getItem("theme");
-
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
-
const isDarkMode = storedTheme === "dark" || (!storedTheme && prefersDark);
-
toggleButton.checked = isDarkMode;
-
updateTheme(isDarkMode);
-
};
-
-
// Initialize the theme
-
initializeTheme();
-
-
// Listen for changes in system preference
-
window
-
.matchMedia("(prefers-color-scheme: dark)")
-
.addEventListener("change", initializeTheme);
+92
static/lightbox.js
···
+
let currentLightboxImages = [];
+
let currentLightboxIndex = 0;
+
+
function openLightbox(src) {
+
currentLightboxImages = [src];
+
currentLightboxIndex = 0;
+
showLightbox();
+
}
+
+
function openLightboxGroup(element) {
+
const group = element.closest('.img-group');
+
const images = Array.from(group.querySelectorAll('img')).map(img => img.src);
+
const clickedImg = element.querySelector('img');
+
+
currentLightboxImages = images;
+
currentLightboxIndex = images.indexOf(clickedImg.src);
+
showLightbox();
+
}
+
+
function showLightbox() {
+
let lightbox = document.getElementById('lightbox');
+
+
if (!lightbox) {
+
lightbox = document.createElement('div');
+
lightbox.id = 'lightbox';
+
lightbox.innerHTML = `
+
<div class="lightbox-content">
+
<button class="lightbox-close" onclick="closeLightbox()">&times;</button>
+
<img id="lightbox-img" src="" alt="">
+
<div class="lightbox-controls">
+
<button class="lightbox-prev" onclick="prevImage()">โ†</button>
+
<button class="lightbox-next" onclick="nextImage()">โ†’</button>
+
</div>
+
</div>
+
`;
+
document.body.appendChild(lightbox);
+
+
lightbox.addEventListener('click', (e) => {
+
if (e.target === lightbox) closeLightbox();
+
});
+
+
document.addEventListener('keydown', handleKeyPress);
+
}
+
+
updateLightboxImage();
+
lightbox.style.display = 'flex';
+
document.body.style.overflow = 'hidden';
+
}
+
+
function closeLightbox() {
+
const lightbox = document.getElementById('lightbox');
+
if (lightbox) {
+
lightbox.style.display = 'none';
+
document.body.style.overflow = '';
+
}
+
}
+
+
function updateLightboxImage() {
+
const img = document.getElementById('lightbox-img');
+
const controls = document.querySelector('.lightbox-controls');
+
+
img.src = currentLightboxImages[currentLightboxIndex];
+
+
if (currentLightboxImages.length === 1) {
+
controls.style.display = 'none';
+
} else {
+
controls.style.display = 'flex';
+
}
+
}
+
+
function prevImage() {
+
currentLightboxIndex = (currentLightboxIndex - 1 + currentLightboxImages.length) % currentLightboxImages.length;
+
updateLightboxImage();
+
}
+
+
function nextImage() {
+
currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxImages.length;
+
updateLightboxImage();
+
}
+
+
function handleKeyPress(e) {
+
const lightbox = document.getElementById('lightbox');
+
if (!lightbox || lightbox.style.display !== 'flex') return;
+
+
if (e.key === 'Escape') {
+
closeLightbox();
+
} else if (e.key === 'ArrowLeft') {
+
prevImage();
+
} else if (e.key === 'ArrowRight') {
+
nextImage();
+
}
+
}
static/now/og.png

This is a binary file and will not be displayed.

static/og.png

This is a binary file and will not be displayed.

static/pfp/og.png

This is a binary file and will not be displayed.

static/pfps/fall.jpg

This is a binary file and will not be displayed.

static/pfps/hands.jpg

This is a binary file and will not be displayed.

static/tags/accessibility/og.png

This is a binary file and will not be displayed.

static/tags/archival/og.png

This is a binary file and will not be displayed.

static/tags/atproto/og.png

This is a binary file and will not be displayed.

static/tags/biography/og.png

This is a binary file and will not be displayed.

static/tags/cool-stuff/og.png

This is a binary file and will not be displayed.

static/tags/essays/og.png

This is a binary file and will not be displayed.

static/tags/fancy/og.png

This is a binary file and will not be displayed.

static/tags/hilton/og.png

This is a binary file and will not be displayed.

static/tags/homelab/og.png

This is a binary file and will not be displayed.

static/tags/meta/og.png

This is a binary file and will not be displayed.

static/tags/mildrant/og.png

This is a binary file and will not be displayed.

static/tags/nix/og.png

This is a binary file and will not be displayed.

static/tags/og.png

This is a binary file and will not be displayed.

static/tags/physics/og.png

This is a binary file and will not be displayed.

static/tags/project/og.png

This is a binary file and will not be displayed.

static/tags/reverse-engineering/og.png

This is a binary file and will not be displayed.

static/tags/shell/og.png

This is a binary file and will not be displayed.

static/tags/teardown/og.png

This is a binary file and will not be displayed.

static/tags/tool/og.png

This is a binary file and will not be displayed.

static/tags/tutorial/og.png

This is a binary file and will not be displayed.

static/tags/yap-fest/og.png

This is a binary file and will not be displayed.

static/verify/og.png

This is a binary file and will not be displayed.

+433
syntaxes/authorized-keys.sublime-syntax
···
+
%YAML 1.2
+
---
+
# https://www.sublimetext.com/docs/syntax.html
+
# https://man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
+
# https://man.openbsd.org/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT
+
name: Authorized Keys
+
scope: text.authorized_keys
+
version: 2
+
extends: SSH Crypto.sublime-syntax
+
+
file_extensions:
+
- authorized_keys
+
- pub
+
+
hidden_file_extensions:
+
- authorized_keys2
+
+
contexts:
+
main:
+
- include: comments-number-sign
+
- match: ^
+
push:
+
- meta_scope: meta.line.authorized-key.authorized_keys
+
- include: pop-before-nl
+
- include: pop-nl
+
- include: ssh-key-types
+
- include: ssh-fingerprint-with-label
+
- include: flag-options
+
- include: value-options
+
- include: strings
+
- match: =
+
scope: keyword.operator.assignment.authorized_keys
+
- include: punctuation-comma-sequence
+
+
flag-options:
+
- match: (?:no-)?(?:pty|user-rc|(?:agent|port|X11)-forwarding)
+
scope: keyword.other.authorized_keys
+
- match: (?:no-touch-required|verify-required|cert-authority|restrict)
+
scope: keyword.other.authorized_keys
+
+
value-options:
+
- match: (principals)(=)
+
captures:
+
1: keyword.other.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
with_prototype:
+
- include: punctuation-comma-sequence
+
push: value-option-body
+
+
- match: (tunnel)(=)
+
captures:
+
1: keyword.other.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
with_prototype:
+
- match: \d{1,3}
+
scope: meta.number.integer.decimal.authorized_keys
+
constant.numeric.value.authorized_keys
+
push: value-option-body
+
+
- match: (?:(expiry-time)|(valid-before))(=)
+
captures:
+
1: keyword.other.authorized_keys
+
2: invalid.deprecated.authorized_keys
+
3: keyword.operator.assignment.authorized_keys
+
with_prototype:
+
- match: |-
+
(?x:
+
\d{4} # Year
+
(?:0\d|1[12]) # Month
+
(?:[0-2]\d|3[01]) # Day
+
(?: # Optionally:
+
(?:[01]\d|2[0-3]) # HH
+
(?:[0-5]\d){1,2} # MM and maybe SS
+
)?
+
Z? # Optional UTC
+
)
+
scope: meta.constant.date.authorized_keys
+
constant.numeric.integer.date.authorized_keys
+
push: value-option-body
+
+
# Technically, permitopen requires a host, but let's be lenient
+
- match: (permitlisten|permitopen)(=)
+
captures:
+
1: keyword.other.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
with_prototype:
+
- include: ipv4
+
- include: ipv6-square-bracket
+
- match: (?:([^"]*)(:))?(?:({{zero_to_65535}})|(\*))
+
captures:
+
1: meta.string.host.authorized_keys
+
2: punctuation.separator.sequence.authorized_keys
+
3: meta.number.integer.decimal.authorized_keys
+
constant.numeric.port-number.authorized_keys
+
4: constant.other.wildcard.asterisk.authorized_keys
+
push: value-option-body
+
+
- match: (from)(=)
+
captures:
+
1: keyword.other.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
with_prototype:
+
- include: operator-exclamation
+
- include: punctuation-comma-sequence
+
- include: punctuation-dot-sequence
+
- include: wildcards
+
push: value-option-body
+
+
- match: (environment)(=)
+
captures:
+
1: keyword.other.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
with_prototype:
+
- match: (\w+)(=)
+
captures:
+
1: variable.other.readwrite.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
push: value-option-body
+
+
- match: (command)(=)(")
+
captures:
+
1: keyword.other.authorized_keys
+
2: keyword.operator.assignment.authorized_keys
+
3: string.quoted.double.authorized_keys
+
punctuation.definition.string.begin.authorized_keys
+
# TODO: Allow escaped double-quote
+
embed: scope:source.shell.bash
+
embed_scope: source.shell.embedded
+
escape: '"|(?=$)'
+
escape_captures:
+
0: string.quoted.double.authorized_keys
+
punctuation.definition.string.end.authorized_keys
+
+
value-option-body:
+
- include: strings
+
- match: (?=,|\s)
+
pop: 1
+
- match: .
+
scope: invalid.illegal.authorized_keys
+
pop: 1
+
+
strings:
+
- match: '"'
+
scope: punctuation.definition.string.begin.authorized_keys
+
push:
+
- meta_scope: string.quoted.double.authorized_keys
+
- match: \\"
+
scope: constant.character.escape.authorized_keys
+
- match: '"'
+
scope: punctuation.definition.string.end.authorized_keys
+
pop: 1
+
+
+
+
comments:
+
- include: comments-number-sign
+
- include: comments-semicolon
+
+
comments-number-sign:
+
- match: ^\s*(#+)
+
captures:
+
1: comment.line.number-sign.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.number-sign.ssh.common
+
- match: \n
+
scope: comment.line.number-sign.ssh.common
+
pop: true
+
+
comments-semicolon:
+
- match: ^\s*(;+)
+
captures:
+
1: comment.line.semi-colon.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.semi-colon.ssh.common
+
- include: pop-nl
+
+
###[ COMPONENTS ]##############################################################
+
+
operator-exclamation:
+
- match: '!'
+
scope: keyword.operator.logical.ssh.common
+
+
wildcards:
+
- match: \*
+
scope: constant.other.wildcard.asterisk.ssh.common
+
- match: \?
+
scope: constant.other.wildcard.questionmark.ssh.common
+
+
punctuation-comma-sequence:
+
- match: ','
+
scope: punctuation.separator.sequence.ssh.common
+
+
punctuation-dot-sequence:
+
- match: \.
+
scope: punctuation.separator.sequence.ssh.common
+
+
punctuation-at:
+
- match: '@'
+
scope: punctuation.separator.sequence.ssh.common
+
+
ssh-fingerprint:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
+
ssh-fingerprint-with-label:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
push: expect-fingerprint-label
+
+
expect-fingerprint-label:
+
- include: pop-before-nl
+
- match: (?=\S)
+
push:
+
- meta_scope: meta.annotation.identifier.ssh.common
+
string.unquoted.ssh.common
+
- match: '(?=[ \t]*$)'
+
pop: 1
+
- include: punctuation-at
+
+
time-values:
+
# https://man.openbsd.org/sshd_config.5#TIME_FORMATS
+
# seconds, minutes, hours, days, weeks
+
- match: \b(?=[\dsmhdw]*\d[smhdw][\s,"])
+
push:
+
- meta_scope: meta.constant.time.ssh.common
+
meta.number.integer.decimal.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: (\d+)([smhdw])
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
+
bytes-values:
+
- match: \b(\d+)([KMG])(?=[\s,"])
+
scope: meta.constant.bytes.ssh.common
+
meta.number.integer.other.ssh.common
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
+
mac-addresses:
+
- match: (?:[0-9a-fA-F]{2}:){5}(?:[0-9a-fA-F]{2})
+
scope: entity.name.constant.mac-address.ssh.common
+
+
ipv4:
+
- match: '\b{{ipv4}}\b'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
+
ipv6:
+
- match: '{{ipv6}}'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
+
ipv6-square-bracket:
+
- match: (\[){{ipv6}}(\])
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
captures:
+
1: punctuation.definition.constant.begin.ssh.common
+
2: punctuation.definition.constant.end.ssh.common
+
+
ip-addresses:
+
- include: ipv6
+
- include: ipv4
+
+
ipv4-with-cidr:
+
- match: \b({{ipv4}})(?:(/)({{zero_to_32}}))?\b
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
+
ipv6-with-cidr:
+
- match: ({{ipv6}})(?:(/)({{zero_to_128}})\b)?
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
+
ip-addresses-with-cidr:
+
- include: ipv6-with-cidr
+
- include: ipv4-with-cidr
+
+
port-numbers:
+
- match: \b{{zero_to_65535}}(?![\w:])
+
scope: meta.number.integer.decimal.ssh.common
+
constant.numeric.port-number.ssh.common
+
+
match-all:
+
- match: '\b(?xi: all )\b'
+
scope: constant.language.boolean.true.ssh.common
+
+
none:
+
- match: \bnone\b
+
scope: constant.language.null.ssh.common
+
+
any:
+
- match: \bany\b
+
scope: constant.language.set.ssh.common
+
+
boolean:
+
- match: \byes\b
+
scope: constant.language.boolean.true.ssh.common
+
- match: \bno\b
+
scope: constant.language.boolean.false.ssh.common
+
+
boolean-with-typing:
+
- include: boolean
+
# Consume while typing as well, but unscoped
+
- match: \b(?:ye?|n)\b
+
+
log-level:
+
- match: '\b(?x: QUIET | FATAL | ERROR | INFO | DEBUG[1-3]? )\b'
+
scope: constant.language.log-level.ssh.common
+
+
possibly-quoted-value:
+
- meta_content_scope: meta.mapping.value.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_scope: string.quoted.double.ssh.common
+
- match: (")(?:\s*(\S.*))?
+
captures:
+
1: punctuation.definition.string.end.ssh.common
+
2: invalid.illegal.ssh.common
+
pop: 1
+
- match: \n|$
+
scope: invalid.illegal.unclosed-string.ssh.common
+
pop: 2
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- include: pop-before-nl
+
- include: pop-nl
+
+
string-patterns:
+
# https://man7.org/linux/man-pages/man5/ssh_config.5.html#PATTERNS
+
# https://man.openbsd.org/ssh_config.5#PATTERNS
+
# https://man7.org/linux/man-pages/man5/sshd_config.5.html#PATTERNS
+
# https://man.openbsd.org/sshd_config.5#PATTERNS
+
- include: punctuation-comma-sequence
+
- include: operator-exclamation
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_content_scope: string.quoted.double.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.end.ssh.common
+
pop: 1
+
- include: wildcards
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- match: (?=[,!\s])
+
pop: 1
+
- include: wildcards
+
+
paths:
+
# This is just heuristic. Expect failures.
+
- match: (?=~?[\w.\-?*${}%]*/[\w.\-?*${}%]?)
+
push:
+
- meta_scope: meta.path.ssh.common
+
entity.name.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: ~[\w\-.]*
+
scope: variable.language.home.ssh.common
+
- match: (/)(?:(\.{1,2})(?=/)|\.(?!/))?
+
captures:
+
1: punctuation.separator.path.ssh.common
+
2: constant.other.placeholder.ssh.common
+
- match: \.(?=[\w*?%])
+
scope: punctuation.separator.sequence.ssh.common
+
- include: wildcards
+
- include: tokens
+
- include: environment-variables
+
+
none-command-values:
+
- match: \s*(none)\b[ \t]*$
+
captures:
+
1: constant.language.null.ssh.common
+
- match: \s*((")(none)("))[ \t]*$
+
captures:
+
1: string.quoted.double.ssh.common
+
2: punctuation.definition.string.begin.ssh.common
+
3: constant.language.null.ssh.common
+
4: punctuation.definition.string.end.ssh.common
+
+
tokens: []
+
environment-variables: []
+
+
###[ PROTOTYPE ]###############################################################
+
+
pop-nl:
+
- match: \n
+
pop: 1
+
+
pop-before-nl:
+
- match: (?=\n)
+
pop: 1
+
+
###############################################################################
+
+
+
+
ssh-ciphers:
+
- match: \b(?:twofish256\-gcm@libassh\.org|twofish256\-ctr|twofish192\-ctr|twofish128\-gcm@libassh\.org|twofish128\-ctr|twofish\-ctr|crypticore128@ssh\.com|chacha20\-poly1305@openssh\.com|chacha20\-poly1305|camellia256\-ctr@openssh\.org|camellia256\-ctr|camellia192\-ctr@openssh\.org|camellia192\-ctr|camellia128\-ctr@openssh\.org|camellia128\-ctr|aes256\-gcm@openssh\.com|aes256\-gcm|aes256\-ctr|aes192\-gcm@openssh\.com|aes192\-ctr|aes128\-gcm@openssh\.com|aes128\-gcm|aes128\-ctr|AEAD_CAMELLIA_256_GCM|AEAD_CAMELLIA_128_GCM|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.cipher.ssh.crypto
+
- match: \b(?:twofish256\-cbc|twofish192\-cbc|twofish128\-cbc|twofish\-ofb|twofish\-ecb|twofish\-cfb|twofish\-cbc|serpent256\-gcm@libassh\.org|serpent256\-ctr|serpent256\-cbc|serpent192\-ctr|serpent192\-cbc|serpent128\-gcm@libassh\.org|serpent128\-ctr|serpent128\-cbc|seed\-ctr@ssh\.com|seed\-cbc@ssh\.com|rijndael256\-cbc|rijndael192\-cbc|rijndael128\-cbc|rijndael\-cbc@ssh\.com|rijndael\-cbc@lysator\.liu\.se|none|idea\-ofb|idea\-ecb|idea\-ctr|idea\-cfb|idea\-cbc|grasshopper\-ctr128|des\-ofb|des\-ecb|des\-cfb|des\-cbc@ssh\.com|des\-cbc\-ssh1|des\-cbc|des|cast128\-ofb|cast128\-ecb|cast128\-ctr|cast128\-cfb|cast128\-cbc|cast128\-12\-ofb|cast128\-12\-ecb|cast128\-12\-ctr|cast128\-12\-cfb|cast128\-12\-cbc|camellia256\-cbc@openssh\.org|camellia256\-cbc|camellia192\-cbc@openssh\.org|camellia192\-cbc|camellia128\-cbc@openssh\.org|camellia128\-cbc|blowfish\-ecb|blowfish\-ctr|blowfish\-cfb|blowfish\-cbc|blowfish|arcfour256|arcfour128|arcfour|aes256\-cbc|aes192\-cbc|aes128\-ocb@libassh\.org|aes128\-cbc|3des\-ofb|3des\-ecb|3des\-ctr|3des\-cfb|3des\-cbc|3des)(?=[,\s\"])
+
scope: invalid.deprecated.cipher.ssh.crypto
+
ssh-kex-algorithms:
+
- match: \b(?:x25519\-kyber512\-sha512@aws\.amazon\.com|x25519\-kyber\-512r3\-sha256\-d00@amazon\.com|sntrup761x25519\-sha512@openssh\.com|sntrup4591761x25519\-sha512@tinyssh\.org|sm2kep\-sha2\-nistp256|rsa2048\-sha256|mlkem768x25519\-sha256|mlkem768nistp256\-sha256|mlkem1024nistp384\-sha384|m511\-sha512@libassh\.org|m383\-sha384@libassh\.org|kexguess2@matt\.ucc\.asn\.au|kexAlgoECDH521|kexAlgoECDH384|kexAlgoECDH256|kexAlgoCurve25519SHA256|kex\-strict\-s\-v00@openssh\.com|kex\-strict\-c\-v00@openssh\.com|gss\-nistp521\-sha512\-|gss\-nistp384\-sha384\-|gss\-nistp384\-sha256\-|gss\-nistp256\-sha256\-|gss\-group18\-sha512\-|gss\-group17\-sha512\-|gss\-group16\-sha512\-|gss\-group15\-sha512\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group15\-sha512\-|gss\-group14\-sha256\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha256\-|gss\-gex\-sha256\-|gss\-curve448\-sha512\-|gss\-curve25519\-sha256\-|gss\-13\.3\.132\.0\.10\-sha256\-|ext\-info\-s|ext\-info\-c|ecmqv\-sha2|ecdh\-sha2\-wiRIU8TKjMZ418sMqlqtvQ==|ecdh\-sha2\-qcFQaMAMGhTziMT0z\+Tuzw==|ecdh\-sha2\-nistt571|ecdh\-sha2\-nistp521|ecdh\-sha2\-nistp384|ecdh\-sha2\-nistp256|ecdh\-sha2\-nistp224|ecdh\-sha2\-nistp192|ecdh\-sha2\-nistk409|ecdh\-sha2\-nistk283|ecdh\-sha2\-nistb409|ecdh\-sha2\-mNVwCXAoS1HGmHpLvBC94w==|ecdh\-sha2\-m/FtSAmrV4j/Wy6RVUaK7A==|ecdh\-sha2\-h/SsxnLCtRBh7I9ATyeB3A==|ecdh\-sha2\-curve25519|ecdh\-sha2\-brainpoolp521r1@genua\.de|ecdh\-sha2\-brainpoolp384r1@genua\.de|ecdh\-sha2\-brainpoolp256r1@genua\.de|ecdh\-sha2\-D3FefCjYoJ/kfXgAyLddYA==|ecdh\-sha2\-9UzNcgwTlEnSCECZa7V1mw==|ecdh\-sha2\-1\.3\.132\.0\.38|ecdh\-sha2\-1\.3\.132\.0\.37|ecdh\-sha2\-1\.3\.132\.0\.36|ecdh\-sha2\-1\.3\.132\.0\.35|ecdh\-sha2\-1\.3\.132\.0\.34|ecdh\-sha2\-1\.3\.132\.0\.16|ecdh\-sha2\-1\.3\.132\.0\.10|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.7|ecdh\-nistp521\-kyber\-1024r3\-sha512\-d00@openquantumsafe\.org|ecdh\-nistp384\-kyber\-768r3\-sha384\-d00@openquantumsafe\.org|ecdh\-nistp256\-kyber\-512r3\-sha256\-d00@openquantumsafe\.org|diffie\-hellman_group17\-sha512|diffie\-hellman\-group18\-sha512@ssh\.com|diffie\-hellman\-group18\-sha512|diffie\-hellman\-group17\-sha512|diffie\-hellman\-group16\-sha512@ssh\.com|diffie\-hellman\-group16\-sha512|diffie\-hellman\-group16\-sha384@ssh\.com|diffie\-hellman\-group16\-sha256|diffie\-hellman\-group15\-sha512|diffie\-hellman\-group15\-sha384@ssh\.com|diffie\-hellman\-group15\-sha256@ssh\.com|diffie\-hellman\-group15\-sha256|diffie\-hellman\-group14\-sha256@ssh\.com|diffie\-hellman\-group14\-sha256|diffie\-hellman\-group14\-sha224@ssh\.com|diffie\-hellman\-group1\-sha256|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha384@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha224@ssh\.com|curve448\-sha512@libssh\.org|curve448\-sha512|curve25519\-sha256@libssh\.org|curve25519\-sha256|Curve25519SHA256)(?=[,\s\"])
+
scope: support.function.kex-algorithm.ssh.crypto
+
- match: \b(?:rsa1024\-sha1|kexAlgoDH1SHA1|kexAlgoDH14SHA1|gss\-group14\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha1\-|gss\-group1\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group1\-sha1\-|gss\-gex\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-gex\-sha1\-|ecdh\-sha2\-zD/b3hu/71952ArpUG4OjQ==|ecdh\-sha2\-qCbG5Cn/jjsZ7nBeR7EnOA==|ecdh\-sha2\-nistk233|ecdh\-sha2\-nistk163|ecdh\-sha2\-nistb233|ecdh\-sha2\-VqBg4QRPjxx1EXZdV0GdWQ==|ecdh\-sha2\-5pPrSUQtIaTjUSt5VZNBjg==|ecdh\-sha2\-4MHB\+NBt3AlaSRQ7MnB4cg==|ecdh\-sha2\-1\.3\.132\.0\.33|ecdh\-sha2\-1\.3\.132\.0\.27|ecdh\-sha2\-1\.3\.132\.0\.26|ecdh\-sha2\-1\.3\.132\.0\.1|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.1|diffie\-hellman\-group14\-sha1|diffie\-hellman\-group1\-sha1|diffie\-hellman\-group\-exchange\-sha1)(?=[,\s\"])
+
scope: invalid.deprecated.kex-algorithm.ssh.crypto
+
ssh-key-types:
+
- match: \b(?:x509v3\-sign\-rsa\-sha512@ssh\.com|x509v3\-sign\-rsa\-sha384@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256|x509v3\-sign\-rsa\-sha224@ssh\.com|x509v3\-sign\-dss\-sha512@ssh\.com|x509v3\-sign\-dss\-sha384@ssh\.com|x509v3\-sign\-dss\-sha256@ssh\.com|x509v3\-sign\-dss\-sha224@ssh\.com|x509v3\-rsa2048\-sha256|x509v3\-ecdsa\-sha2\-nistp521|x509v3\-ecdsa\-sha2\-nistp384|x509v3\-ecdsa\-sha2\-nistp256|x509v3\-ecdsa\-sha2\-1\.3\.132\.0\.10|webauthn\-sk\-ecdsa\-sha2\-nistp256@openssh\.com|ssh\-rsa\-sha512@ssh\.com|ssh\-rsa\-sha384@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha2\-512|ssh\-rsa\-sha2\-256|ssh\-rsa|ssh\-gost\-2012\-512|ssh\-gost\-2012\-256|ssh\-gost\-2001|ssh\-ed448|ssh\-ed25519\-cert\-v01@openssh\.com|ssh\-ed25519|spi\-sign\-rsa|sk\-ecdsa\-sha2\-nistp256@openssh\.com|sk\-ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|rsa\-sha2\-512\-cert\-v01@openssh\.com|rsa\-sha2\-512|rsa\-sha2\-256\-cert\-v01@openssh\.com|rsa\-sha2\-256|eddsa\-e521\-shake256@libassh\.org|eddsa\-e382\-shake256@libassh\.org|ecdsa\-sha2\-nistt571|ecdsa\-sha2\-nistp521\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp521|ecdsa\-sha2\-nistp384\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp384|ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp256|ecdsa\-sha2\-nistk409|ecdsa\-sha2\-nistk283|ecdsa\-sha2\-nistk233|ecdsa\-sha2\-nistk163|ecdsa\-sha2\-nistb409|ecdsa\-sha2\-curve25519|ecdsa\-sha2\-1\.3\.132\.0\.10\-cert\-v01@openssh\.com|ecdsa\-sha2\-1\.3\.132\.0\.10|dsa3072\-sha256@libassh\.org|dsa2048\-sha256@libassh\.org|dsa2048\-sha224@libassh\.org)(?=[,\s\"])
+
scope: support.type.key-type.ssh.crypto
+
- match: \b(?:x509v3\-ssh\-rsa|x509v3\-ssh\-dss|x509v3\-sign\-rsa\-sha1|x509v3\-sign\-rsa|x509v3\-sign\-dss\-sha1|x509v3\-sign\-dss|ssh\-xmss@openssh\.com|ssh\-xmss\-cert\-v01@openssh\.com|ssh\-rsa1|ssh\-rsa\-cert\-v01@openssh\.com|ssh\-rsa\-cert\-v00@openssh\.com|ssh\-dss\-sha512@ssh\.com|ssh\-dss\-sha384@ssh\.com|ssh\-dss\-sha256@ssh\.com|ssh\-dss\-sha224@ssh\.com|ssh\-dss\-cert\-v01@openssh\.com|ssh\-dss\-cert\-v00@openssh\.com|ssh\-dss|ssh\-dsa|spki\-sign\-rsa|spki\-sign\-dss|pgp\-sign\-rsa|pgp\-sign\-dss|null|ecdsa\-sha2\-nistp224|ecdsa\-sha2\-nistp192|ecdsa\-sha2\-nistb233)(?=[,\s\"])
+
scope: invalid.deprecated.key-type.ssh.crypto
+
ssh-mac-algorithms:
+
- match: \b(?:umac\-96@openssh\.com|umac\-64@openssh\.com|umac\-64\-etm@openssh\.com|umac\-32@openssh\.com|umac\-128@openssh\.com|umac\-128\-etm@openssh\.com|umac\-128|hmac\-sha512@ssh\.com|hmac\-sha512|hmac\-sha3\-512|hmac\-sha3\-384|hmac\-sha3\-256|hmac\-sha3\-224|hmac\-sha256@ssh\.com|hmac\-sha256\-96@ssh\.com|hmac\-sha256|hmac\-sha2\-56|hmac\-sha2\-512\-etm@openssh\.com|hmac\-sha2\-512\-96\-etm@openssh\.com|hmac\-sha2\-512|hmac\-sha2\-384|hmac\-sha2\-256\-etm@openssh\.com|hmac\-sha2\-256\-96\-etm@openssh\.com|hmac\-sha2\-256|hmac\-sha2\-224|crypticore\-mac@ssh\.com|chacha20\-poly1305@openssh\.com|cbcmac\-twofish|cbcmac\-aes|aes256\-gcm|aes128\-gcm|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.mac-algorithm.ssh.crypto
+
- match: \b(?:sha1\-8|sha1|ripemd160\-8|ripemd160|none|md5\-8|md5|hmac\-sha2\-512\-96|hmac\-sha2\-256\-96|hmac\-sha1\-etm@openssh\.com|hmac\-sha1\-96\-etm@openssh\.com|hmac\-sha1\-96|hmac\-sha1|hmac\-ripemd160@openssh\.com|hmac\-ripemd160\-etm@openssh\.com|hmac\-ripemd160\-96|hmac\-ripemd160|hmac\-ripemd|hmac\-md5\-etm@openssh\.com|hmac\-md5\-96\-etm@openssh\.com|hmac\-md5\-96|hmac\-md5|cbcmac\-rijndael|cbcmac\-des|cbcmac\-blowfish|cbcmac\-3des)(?=[,\s\"])
+
scope: invalid.deprecated.mac-algorithm.ssh.crypto
+
extends: SSH Common.sublime-syntax
+
hidden: true
+
hidden_file_extensions:
+
- syntax_test_crypto
+
name: SSH Crypto
+
scope: text.ssh.crypto
+
version: 2
+
variables:
+
zero_to_65535: (?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])
+243
syntaxes/gleam.sublime-syntax
···
+
%YAML 1.2
+
---
+
version: 2
+
+
file_extensions:
+
- gleam
+
+
scope: source.gleam
+
+
variables:
+
lower_ident: '[[:lower:]][[:word:]]*'
+
upper_ident: '[[:upper:]][[:word:]]*'
+
+
contexts:
+
main:
+
- include: base
+
base:
+
- include: attribute
+
- include: bitstring
+
- include: block
+
- include: comment
+
- include: constant_def
+
- include: function_def
+
- include: keyword
+
- include: function_call
+
- include: record
+
- include: import
+
- include: number
+
- include: operator
+
- include: punctuation
+
- include: string
+
- include: unused_name
+
- include: type_name
+
+
# Attributes (annotations)
+
attribute:
+
- match: ^\s*(@{{lower_ident}})\(
+
captures:
+
1: variable.other.constant.gleam
+
push:
+
- include: arguments
+
- meta_scope: meta.annotation.gleam
+
- match: ^\s*(@{{lower_ident}})
+
scope: meta.annotation.gleam
+
captures:
+
1: variable.other.constant.gleam
+
+
# Arguments (to a function call, record constructor, or attribute)
+
arguments:
+
- include: bitstring
+
- include: block
+
- include: comment
+
- include: function_def
+
- include: function_call
+
- include: record
+
- include: number
+
- include: operator
+
- include: punctuation
+
- include: string
+
- include: unused_name
+
- include: type_name
+
- match: '\b{{lower_ident}}:'
+
scope: constant.other.gleam
+
- match: \)
+
pop: true
+
+
# Bitstrings
+
bitstring:
+
- match: '<<'
+
scope: punctuation.definition.generic.begin.gleam
+
push:
+
- include: number
+
- include: string
+
- match: \b(bytes|int|float|bits|utf8|utf16|utf32|utf8_codepoint|utf16_codepoint|utf32_codepoint|signed|unsigned|big|little|native|unit|size)\b
+
scope: keyword.other.gleam
+
- match: '>>'
+
scope: punctuation.definition.generic.end.gleam
+
pop: true
+
+
# Blocks
+
block:
+
- match: '{'
+
scope: punctuation.section.block.begin.gleam
+
push: base
+
- match: '}'
+
scope: punctuation.section.block.end.gleam
+
pop: true
+
+
# Comments
+
comment:
+
- match: ///?/?
+
scope: punctuation.definition.comment.line.gleam
+
push:
+
- meta_scope: comment.line.gleam
+
- match: $
+
pop: true
+
+
# Constant definitions
+
constant_def:
+
- match: \b(const)\s+({{lower_ident}})\b
+
captures:
+
1: keyword.other.gleam
+
2: entity.name.constant.gleam
+
+
# Function calls
+
function_call:
+
- match: \b(?:{{lower_ident}}\.)*({{lower_ident}})\(
+
captures:
+
1: variable.function.gleam
+
push: arguments
+
+
# Function definitions
+
function_def:
+
- match: \b(fn)(?:[[:space:]]+({{lower_ident}}))?[[:space:]]*\(
+
captures:
+
1: storage.type.function.gleam
+
2: entity.name.function.gleam
+
push: function_def_args
+
+
# Function arguments
+
function_def_args:
+
- include: function_def
+
- include: punctuation
+
- include: type_name
+
- include: unused_name
+
- match: ->
+
scope: keyword.operator.gleam
+
- match: \b(?:({{lower_ident}})[[:space:]]+)?({{lower_ident}}:)
+
captures:
+
1: constant.other.gleam
+
2: variable.parameter.gleam
+
- match: \(
+
push: function_def_args
+
- match: \)
+
pop: true
+
+
# Imports
+
import:
+
- match: ^import\b
+
scope: keyword.control.import.gleam
+
push:
+
- match: \bas\b
+
scope: keyword.control.import.gleam
+
- match: \b(?:{{lower_ident}}/)*{{lower_ident}}\b
+
scope: entity.name.namespace.gleam
+
- match: (\.)({)
+
captures:
+
1: punctuation.accessor.gleam
+
2: punctuation.definition.generic.begin.gleam
+
push:
+
- include: punctuation
+
- include: type_name
+
- match: \bas\b
+
scope: keyword.control.import.gleam
+
- match: \btype\b
+
scope: storage.type.gleam
+
- match: '}'
+
scope: punctuation.definition.generic.end.gleam
+
pop: true
+
- match: $
+
pop: true
+
+
# Keywords
+
keyword:
+
- match: \b(as|assert|case|const|echo|if|let|panic|todo|use)\b
+
scope: keyword.other.gleam
+
- match: \b(opaque|pub)\b
+
scope: storage.modifier.gleam
+
- match: \btype\b
+
scope: storage.type.gleam
+
- match: \bfn\b
+
scope: storage.type.function.gleam
+
# Reserved for future use
+
- match: \b(auto|delegate|derive|else|implement|macro|test)\b
+
scope: invalid.illegal.gleam
+
+
# Numbers
+
number:
+
- match: \b0b[01][01_]*\b
+
scope: constant.numeric.binary.gleam
+
- match: \b0o[0-7][0-7_]*\b
+
scope: constant.numeric.octal.gleam
+
- match: \b[0-9][0-9_]*(\.[0-9_]*(e-?[0-9][0-9_]*)?)?\b
+
scope: constant.numeric.decimal.gleam
+
- match: \b0x[[:xdigit:]][[:xdigit:]_]*\b
+
scope: constant.numeric.hexadecimal.gleam
+
+
# Operators
+
operator:
+
- match: <-
+
scope: keyword.operator.assignment.gleam
+
- match: (\|>|\.\.|<=\.|>=\.|==\.|!=\.|<\.|>\.|<=|>=|==|!=|<|>|<>)
+
scope: keyword.operator.gleam
+
- match: '='
+
scope: keyword.operator.assignment.gleam
+
- match: ->
+
scope: keyword.operator.gleam
+
- match: (\+\.|\-\.|/\.|\*\.|%\.|\+|\-|/|\*|%)
+
scope: keyword.operator.arithmetic.gleam
+
- match: (&&|\|\|)
+
scope: keyword.operator.logical.gleam
+
- match: \|
+
scope: keyword.operator.gleam
+
+
# Punctuation (separators, accessors)
+
punctuation:
+
- match: \.
+
scope: punctuation.accessor.gleam
+
- match: ','
+
scope: punctuation.separator.gleam
+
+
# Records (constructors with arguments)
+
record:
+
- match: \b((?:{{lower_ident}}\.)*{{upper_ident}})\(
+
captures:
+
1: entity.name.type.gleam
+
push: arguments
+
+
# Strings
+
string:
+
- match: '"'
+
scope: punctuation.definition.string.begin.gleam
+
push:
+
- meta_scope: string.quoted.double.gleam
+
- match: \\[fnrt"\\]
+
scope: constant.character.escape.gleam
+
- match: \\u\{[[:xdigit:]]{1,6}\}
+
scope: constant.character.escape.gleam
+
- match: \\
+
scope: invalid.illegal.gleam
+
- match: '"'
+
scope: punctuation.definition.string.end.gleam
+
pop: true
+
+
# Types and constructors
+
type_name:
+
- match: \b(?:{{lower_ident}}\.)*{{upper_ident}}\b
+
scope: entity.name.type.gleam
+
+
# Unused bindings
+
unused_name:
+
- match: \b_{{lower_ident}}\b
+
scope: comment.line.gleam
+305
syntaxes/known-hosts.sublime-syntax
···
+
%YAML 1.2
+
---
+
# Standalone version of known-hosts.sublime-syntax
+
# Merged with: ssh-common.sublime-syntax, ssh-crypto.sublime-syntax
+
+
name: Known Hosts
+
scope: text.known_hosts
+
version: 2
+
file_extensions:
+
- known_hosts
+
hidden_file_extensions:
+
- known_hosts.old
+
variables:
+
base64_char: '[a-zA-Z0-9+/]'
+
ssh_fingerprint: (?:AAAA(?:E2V|[BC]3N){{base64_char}}+={0,3})
+
zero_to_32: (?:3[0-2]|[12][0-9]|[0-9])
+
zero_to_128: (?:12[0-8]|1[01][0-9]|[1-9][0-9]|[0-9])
+
zero_to_255: (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9][0-9])|(?:[1-9][0-9])|[0-9])
+
zero_to_65535: (?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])
+
ipv4: (?:(?:{{zero_to_255}}\.){3}{{zero_to_255}})
+
ipv6: "(?xi:\n (?:::(?:ffff(?::0{1,4}){0,1}:){0,1}{{ipv4}}) # ::255.255.255.255\
+
\ ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses\
+
\ and IPv4-translated addresses)\n |(?:(?:[0-9a-f]{1,4}:){1,4}:{{ipv4}}) \
+
\ # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 \
+
\ (IPv4-Embedded IPv6 Address)\n |(?:fe80:(?::[0-9a-f]{1,4}){0,4}%[0-9a-z]{1,})\
+
\ # fe80::7:8%eth0 fe80::7:8%1 \
+
\ (link-local IPv6 addresses with zone index)\n |(?:(?:[0-9a-f]{1,4}:){7,7}\
+
\ [0-9a-f]{1,4}) # 1:2:3:4:5:6:7:8\n | (?:[0-9a-f]{1,4}: (?::[0-9a-f]{1,4}){1,6})\
+
\ # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8\n |(?:(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5})\
+
\ # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8\n |(?:(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4})\
+
\ # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8\n |(?:(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3})\
+
\ # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8\n |(?:(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2})\
+
\ # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8\n |(?:(?:[0-9a-f]{1,4}:){1,6}\
+
\ :[0-9a-f]{1,4}) # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8\n\
+
\ |(?:(?:[0-9a-f]{1,4}:){1,7} :) # 1:: \
+
\ 1:2:3:4:5:6:7::\n |(?::(?:(?::[0-9a-f]{1,4}){1,7}|:)) \
+
\ # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::\n)"
+
contexts:
+
main:
+
- include: comments-number-sign
+
- match: ^((@)(?:revoked|cert-authority))?
+
captures:
+
1: meta.annotation.known_hosts variable.annotation.known_hosts
+
2: punctuation.definition.annotation.known_hosts
+
push:
+
- meta_scope: meta.line.known-host.known_hosts
+
- include: pop-before-nl
+
- include: pop-nl
+
- include: punctuation-comma-sequence
+
- include: ssh-fingerprint-with-label
+
- include: ssh-key-types
+
- include: hostname-or-ip-value
+
comments:
+
- include: comments-number-sign
+
- include: comments-semicolon
+
comments-number-sign:
+
- match: ^\s*(#+)
+
captures:
+
1: comment.line.number-sign.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.number-sign.ssh.common
+
- match: \n
+
scope: comment.line.number-sign.ssh.common
+
pop: true
+
comments-semicolon:
+
- match: ^\s*(;+)
+
captures:
+
1: comment.line.semi-colon.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.semi-colon.ssh.common
+
- include: pop-nl
+
operator-exclamation:
+
- match: '!'
+
scope: keyword.operator.logical.ssh.common
+
wildcards:
+
- match: \*
+
scope: constant.other.wildcard.asterisk.ssh.common
+
- match: \?
+
scope: constant.other.wildcard.questionmark.ssh.common
+
punctuation-comma-sequence:
+
- match: ','
+
scope: punctuation.separator.sequence.ssh.common
+
punctuation-dot-sequence:
+
- match: \.
+
scope: punctuation.separator.sequence.ssh.common
+
punctuation-at:
+
- match: '@'
+
scope: punctuation.separator.sequence.ssh.common
+
ssh-fingerprint:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
ssh-fingerprint-with-label:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
push: expect-fingerprint-label
+
expect-fingerprint-label:
+
- include: pop-before-nl
+
- match: (?=\S)
+
push:
+
- meta_scope: meta.annotation.identifier.ssh.common string.unquoted.ssh.common
+
- match: (?=[ \t]*$)
+
pop: 1
+
- include: punctuation-at
+
time-values:
+
- match: \b(?=[\dsmhdw]*\d[smhdw][\s,"])
+
push:
+
- meta_scope: meta.constant.time.ssh.common meta.number.integer.decimal.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: (\d+)([smhdw])
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
bytes-values:
+
- match: \b(\d+)([KMG])(?=[\s,"])
+
scope: meta.constant.bytes.ssh.common meta.number.integer.other.ssh.common
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
mac-addresses:
+
- match: (?:[0-9a-fA-F]{2}:){5}(?:[0-9a-fA-F]{2})
+
scope: entity.name.constant.mac-address.ssh.common
+
ipv4:
+
- match: \b{{ipv4}}\b
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
ipv6:
+
- match: '{{ipv6}}'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
ipv6-square-bracket:
+
- match: (\[){{ipv6}}(\])
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
captures:
+
1: punctuation.definition.constant.begin.ssh.common
+
2: punctuation.definition.constant.end.ssh.common
+
ip-addresses:
+
- include: ipv6
+
- include: ipv4
+
ipv4-with-cidr:
+
- match: \b({{ipv4}})(?:(/)({{zero_to_32}}))?\b
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
ipv6-with-cidr:
+
- match: ({{ipv6}})(?:(/)({{zero_to_128}})\b)?
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
ip-addresses-with-cidr:
+
- include: ipv6-with-cidr
+
- include: ipv4-with-cidr
+
port-numbers:
+
- match: \b{{zero_to_65535}}(?![\w:])
+
scope: meta.number.integer.decimal.ssh.common constant.numeric.port-number.ssh.common
+
match-all:
+
- match: '\b(?xi: all )\b'
+
scope: constant.language.boolean.true.ssh.common
+
none:
+
- match: \bnone\b
+
scope: constant.language.null.ssh.common
+
any:
+
- match: \bany\b
+
scope: constant.language.set.ssh.common
+
boolean:
+
- match: \byes\b
+
scope: constant.language.boolean.true.ssh.common
+
- match: \bno\b
+
scope: constant.language.boolean.false.ssh.common
+
boolean-with-typing:
+
- include: boolean
+
- match: \b(?:ye?|n)\b
+
log-level:
+
- match: '\b(?x: QUIET | FATAL | ERROR | INFO | DEBUG[1-3]? )\b'
+
scope: constant.language.log-level.ssh.common
+
possibly-quoted-value:
+
- meta_content_scope: meta.mapping.value.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_scope: string.quoted.double.ssh.common
+
- match: (")(?:\s*(\S.*))?
+
captures:
+
1: punctuation.definition.string.end.ssh.common
+
2: invalid.illegal.ssh.common
+
pop: 1
+
- match: \n|$
+
scope: invalid.illegal.unclosed-string.ssh.common
+
pop: 2
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- include: pop-before-nl
+
- include: pop-nl
+
string-patterns:
+
- include: punctuation-comma-sequence
+
- include: operator-exclamation
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_content_scope: string.quoted.double.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.end.ssh.common
+
pop: 1
+
- include: wildcards
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- match: (?=[,!\s])
+
pop: 1
+
- include: wildcards
+
paths:
+
- match: (?=~?[\w.\-?*${}%]*/[\w.\-?*${}%]?)
+
push:
+
- meta_scope: meta.path.ssh.common entity.name.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: ~[\w\-.]*
+
scope: variable.language.home.ssh.common
+
- match: (/)(?:(\.{1,2})(?=/)|\.(?!/))?
+
captures:
+
1: punctuation.separator.path.ssh.common
+
2: constant.other.placeholder.ssh.common
+
- match: \.(?=[\w*?%])
+
scope: punctuation.separator.sequence.ssh.common
+
- include: wildcards
+
- include: tokens
+
- include: environment-variables
+
none-command-values:
+
- match: \s*(none)\b[ \t]*$
+
captures:
+
1: constant.language.null.ssh.common
+
- match: \s*((")(none)("))[ \t]*$
+
captures:
+
1: string.quoted.double.ssh.common
+
2: punctuation.definition.string.begin.ssh.common
+
3: constant.language.null.ssh.common
+
4: punctuation.definition.string.end.ssh.common
+
tokens: []
+
environment-variables: []
+
pop-nl:
+
- match: \n
+
pop: 1
+
pop-before-nl:
+
- match: (?=\n)
+
pop: 1
+
ssh-ciphers:
+
- match: \b(?:twofish256\-gcm@libassh\.org|twofish256\-ctr|twofish192\-ctr|twofish128\-gcm@libassh\.org|twofish128\-ctr|twofish\-ctr|crypticore128@ssh\.com|chacha20\-poly1305@openssh\.com|chacha20\-poly1305|camellia256\-ctr@openssh\.org|camellia256\-ctr|camellia192\-ctr@openssh\.org|camellia192\-ctr|camellia128\-ctr@openssh\.org|camellia128\-ctr|aes256\-gcm@openssh\.com|aes256\-gcm|aes256\-ctr|aes192\-gcm@openssh\.com|aes192\-ctr|aes128\-gcm@openssh\.com|aes128\-gcm|aes128\-ctr|AEAD_CAMELLIA_256_GCM|AEAD_CAMELLIA_128_GCM|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.cipher.ssh.crypto
+
- match: \b(?:twofish256\-cbc|twofish192\-cbc|twofish128\-cbc|twofish\-ofb|twofish\-ecb|twofish\-cfb|twofish\-cbc|serpent256\-gcm@libassh\.org|serpent256\-ctr|serpent256\-cbc|serpent192\-ctr|serpent192\-cbc|serpent128\-gcm@libassh\.org|serpent128\-ctr|serpent128\-cbc|seed\-ctr@ssh\.com|seed\-cbc@ssh\.com|rijndael256\-cbc|rijndael192\-cbc|rijndael128\-cbc|rijndael\-cbc@ssh\.com|rijndael\-cbc@lysator\.liu\.se|none|idea\-ofb|idea\-ecb|idea\-ctr|idea\-cfb|idea\-cbc|grasshopper\-ctr128|des\-ofb|des\-ecb|des\-cfb|des\-cbc@ssh\.com|des\-cbc\-ssh1|des\-cbc|des|cast128\-ofb|cast128\-ecb|cast128\-ctr|cast128\-cfb|cast128\-cbc|cast128\-12\-ofb|cast128\-12\-ecb|cast128\-12\-ctr|cast128\-12\-cfb|cast128\-12\-cbc|camellia256\-cbc@openssh\.org|camellia256\-cbc|camellia192\-cbc@openssh\.org|camellia192\-cbc|camellia128\-cbc@openssh\.org|camellia128\-cbc|blowfish\-ecb|blowfish\-ctr|blowfish\-cfb|blowfish\-cbc|blowfish|arcfour256|arcfour128|arcfour|aes256\-cbc|aes192\-cbc|aes128\-ocb@libassh\.org|aes128\-cbc|3des\-ofb|3des\-ecb|3des\-ctr|3des\-cfb|3des\-cbc|3des)(?=[,\s\"])
+
scope: invalid.deprecated.cipher.ssh.crypto
+
ssh-kex-algorithms:
+
- match: \b(?:x25519\-kyber512\-sha512@aws\.amazon\.com|x25519\-kyber\-512r3\-sha256\-d00@amazon\.com|sntrup761x25519\-sha512@openssh\.com|sntrup4591761x25519\-sha512@tinyssh\.org|sm2kep\-sha2\-nistp256|rsa2048\-sha256|mlkem768x25519\-sha256|mlkem768nistp256\-sha256|mlkem1024nistp384\-sha384|m511\-sha512@libassh\.org|m383\-sha384@libassh\.org|kexguess2@matt\.ucc\.asn\.au|kexAlgoECDH521|kexAlgoECDH384|kexAlgoECDH256|kexAlgoCurve25519SHA256|kex\-strict\-s\-v00@openssh\.com|kex\-strict\-c\-v00@openssh\.com|gss\-nistp521\-sha512\-|gss\-nistp384\-sha384\-|gss\-nistp384\-sha256\-|gss\-nistp256\-sha256\-|gss\-group18\-sha512\-|gss\-group17\-sha512\-|gss\-group16\-sha512\-|gss\-group15\-sha512\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group15\-sha512\-|gss\-group14\-sha256\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha256\-|gss\-gex\-sha256\-|gss\-curve448\-sha512\-|gss\-curve25519\-sha256\-|gss\-13\.3\.132\.0\.10\-sha256\-|ext\-info\-s|ext\-info\-c|ecmqv\-sha2|ecdh\-sha2\-wiRIU8TKjMZ418sMqlqtvQ==|ecdh\-sha2\-qcFQaMAMGhTziMT0z\+Tuzw==|ecdh\-sha2\-nistt571|ecdh\-sha2\-nistp521|ecdh\-sha2\-nistp384|ecdh\-sha2\-nistp256|ecdh\-sha2\-nistp224|ecdh\-sha2\-nistp192|ecdh\-sha2\-nistk409|ecdh\-sha2\-nistk283|ecdh\-sha2\-nistb409|ecdh\-sha2\-mNVwCXAoS1HGmHpLvBC94w==|ecdh\-sha2\-m/FtSAmrV4j/Wy6RVUaK7A==|ecdh\-sha2\-h/SsxnLCtRBh7I9ATyeB3A==|ecdh\-sha2\-curve25519|ecdh\-sha2\-brainpoolp521r1@genua\.de|ecdh\-sha2\-brainpoolp384r1@genua\.de|ecdh\-sha2\-brainpoolp256r1@genua\.de|ecdh\-sha2\-D3FefCjYoJ/kfXgAyLddYA==|ecdh\-sha2\-9UzNcgwTlEnSCECZa7V1mw==|ecdh\-sha2\-1\.3\.132\.0\.38|ecdh\-sha2\-1\.3\.132\.0\.37|ecdh\-sha2\-1\.3\.132\.0\.36|ecdh\-sha2\-1\.3\.132\.0\.35|ecdh\-sha2\-1\.3\.132\.0\.34|ecdh\-sha2\-1\.3\.132\.0\.16|ecdh\-sha2\-1\.3\.132\.0\.10|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.7|ecdh\-nistp521\-kyber\-1024r3\-sha512\-d00@openquantumsafe\.org|ecdh\-nistp384\-kyber\-768r3\-sha384\-d00@openquantumsafe\.org|ecdh\-nistp256\-kyber\-512r3\-sha256\-d00@openquantumsafe\.org|diffie\-hellman_group17\-sha512|diffie\-hellman\-group18\-sha512@ssh\.com|diffie\-hellman\-group18\-sha512|diffie\-hellman\-group17\-sha512|diffie\-hellman\-group16\-sha512@ssh\.com|diffie\-hellman\-group16\-sha512|diffie\-hellman\-group16\-sha384@ssh\.com|diffie\-hellman\-group16\-sha256|diffie\-hellman\-group15\-sha512|diffie\-hellman\-group15\-sha384@ssh\.com|diffie\-hellman\-group15\-sha256@ssh\.com|diffie\-hellman\-group15\-sha256|diffie\-hellman\-group14\-sha256@ssh\.com|diffie\-hellman\-group14\-sha256|diffie\-hellman\-group14\-sha224@ssh\.com|diffie\-hellman\-group1\-sha256|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha384@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha224@ssh\.com|curve448\-sha512@libssh\.org|curve448\-sha512|curve25519\-sha256@libssh\.org|curve25519\-sha256|Curve25519SHA256)(?=[,\s\"])
+
scope: support.function.kex-algorithm.ssh.crypto
+
- match: \b(?:rsa1024\-sha1|kexAlgoDH1SHA1|kexAlgoDH14SHA1|gss\-group14\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha1\-|gss\-group1\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group1\-sha1\-|gss\-gex\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-gex\-sha1\-|ecdh\-sha2\-zD/b3hu/71952ArpUG4OjQ==|ecdh\-sha2\-qCbG5Cn/jjsZ7nBeR7EnOA==|ecdh\-sha2\-nistk233|ecdh\-sha2\-nistk163|ecdh\-sha2\-nistb233|ecdh\-sha2\-VqBg4QRPjxx1EXZdV0GdWQ==|ecdh\-sha2\-5pPrSUQtIaTjUSt5VZNBjg==|ecdh\-sha2\-4MHB\+NBt3AlaSRQ7MnB4cg==|ecdh\-sha2\-1\.3\.132\.0\.33|ecdh\-sha2\-1\.3\.132\.0\.27|ecdh\-sha2\-1\.3\.132\.0\.26|ecdh\-sha2\-1\.3\.132\.0\.1|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.1|diffie\-hellman\-group14\-sha1|diffie\-hellman\-group1\-sha1|diffie\-hellman\-group\-exchange\-sha1)(?=[,\s\"])
+
scope: invalid.deprecated.kex-algorithm.ssh.crypto
+
ssh-key-types:
+
- match: \b(?:x509v3\-sign\-rsa\-sha512@ssh\.com|x509v3\-sign\-rsa\-sha384@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256|x509v3\-sign\-rsa\-sha224@ssh\.com|x509v3\-sign\-dss\-sha512@ssh\.com|x509v3\-sign\-dss\-sha384@ssh\.com|x509v3\-sign\-dss\-sha256@ssh\.com|x509v3\-sign\-dss\-sha224@ssh\.com|x509v3\-rsa2048\-sha256|x509v3\-ecdsa\-sha2\-nistp521|x509v3\-ecdsa\-sha2\-nistp384|x509v3\-ecdsa\-sha2\-nistp256|x509v3\-ecdsa\-sha2\-1\.3\.132\.0\.10|webauthn\-sk\-ecdsa\-sha2\-nistp256@openssh\.com|ssh\-rsa\-sha512@ssh\.com|ssh\-rsa\-sha384@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha2\-512|ssh\-rsa\-sha2\-256|ssh\-rsa|ssh\-gost\-2012\-512|ssh\-gost\-2012\-256|ssh\-gost\-2001|ssh\-ed448|ssh\-ed25519\-cert\-v01@openssh\.com|ssh\-ed25519|spi\-sign\-rsa|sk\-ecdsa\-sha2\-nistp256@openssh\.com|sk\-ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|rsa\-sha2\-512\-cert\-v01@openssh\.com|rsa\-sha2\-512|rsa\-sha2\-256\-cert\-v01@openssh\.com|rsa\-sha2\-256|eddsa\-e521\-shake256@libassh\.org|eddsa\-e382\-shake256@libassh\.org|ecdsa\-sha2\-nistt571|ecdsa\-sha2\-nistp521\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp521|ecdsa\-sha2\-nistp384\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp384|ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp256|ecdsa\-sha2\-nistk409|ecdsa\-sha2\-nistk283|ecdsa\-sha2\-nistk233|ecdsa\-sha2\-nistk163|ecdsa\-sha2\-nistb409|ecdsa\-sha2\-curve25519|ecdsa\-sha2\-1\.3\.132\.0\.10\-cert\-v01@openssh\.com|ecdsa\-sha2\-1\.3\.132\.0\.10|dsa3072\-sha256@libassh\.org|dsa2048\-sha256@libassh\.org|dsa2048\-sha224@libassh\.org)(?=[,\s\"])
+
scope: support.type.key-type.ssh.crypto
+
- match: \b(?:x509v3\-ssh\-rsa|x509v3\-ssh\-dss|x509v3\-sign\-rsa\-sha1|x509v3\-sign\-rsa|x509v3\-sign\-dss\-sha1|x509v3\-sign\-dss|ssh\-xmss@openssh\.com|ssh\-xmss\-cert\-v01@openssh\.com|ssh\-rsa1|ssh\-rsa\-cert\-v01@openssh\.com|ssh\-rsa\-cert\-v00@openssh\.com|ssh\-dss\-sha512@ssh\.com|ssh\-dss\-sha384@ssh\.com|ssh\-dss\-sha256@ssh\.com|ssh\-dss\-sha224@ssh\.com|ssh\-dss\-cert\-v01@openssh\.com|ssh\-dss\-cert\-v00@openssh\.com|ssh\-dss|ssh\-dsa|spki\-sign\-rsa|spki\-sign\-dss|pgp\-sign\-rsa|pgp\-sign\-dss|null|ecdsa\-sha2\-nistp224|ecdsa\-sha2\-nistp192|ecdsa\-sha2\-nistb233)(?=[,\s\"])
+
scope: invalid.deprecated.key-type.ssh.crypto
+
ssh-mac-algorithms:
+
- match: \b(?:umac\-96@openssh\.com|umac\-64@openssh\.com|umac\-64\-etm@openssh\.com|umac\-32@openssh\.com|umac\-128@openssh\.com|umac\-128\-etm@openssh\.com|umac\-128|hmac\-sha512@ssh\.com|hmac\-sha512|hmac\-sha3\-512|hmac\-sha3\-384|hmac\-sha3\-256|hmac\-sha3\-224|hmac\-sha256@ssh\.com|hmac\-sha256\-96@ssh\.com|hmac\-sha256|hmac\-sha2\-56|hmac\-sha2\-512\-etm@openssh\.com|hmac\-sha2\-512\-96\-etm@openssh\.com|hmac\-sha2\-512|hmac\-sha2\-384|hmac\-sha2\-256\-etm@openssh\.com|hmac\-sha2\-256\-96\-etm@openssh\.com|hmac\-sha2\-256|hmac\-sha2\-224|crypticore\-mac@ssh\.com|chacha20\-poly1305@openssh\.com|cbcmac\-twofish|cbcmac\-aes|aes256\-gcm|aes128\-gcm|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.mac-algorithm.ssh.crypto
+
- match: \b(?:sha1\-8|sha1|ripemd160\-8|ripemd160|none|md5\-8|md5|hmac\-sha2\-512\-96|hmac\-sha2\-256\-96|hmac\-sha1\-etm@openssh\.com|hmac\-sha1\-96\-etm@openssh\.com|hmac\-sha1\-96|hmac\-sha1|hmac\-ripemd160@openssh\.com|hmac\-ripemd160\-etm@openssh\.com|hmac\-ripemd160\-96|hmac\-ripemd160|hmac\-ripemd|hmac\-md5\-etm@openssh\.com|hmac\-md5\-96\-etm@openssh\.com|hmac\-md5\-96|hmac\-md5|cbcmac\-rijndael|cbcmac\-des|cbcmac\-blowfish|cbcmac\-3des)(?=[,\s\"])
+
scope: invalid.deprecated.mac-algorithm.ssh.crypto
+
hostname-or-ip-value:
+
- include: operator-exclamation
+
- match: \[
+
scope: punctuation.definition.string.begin.known_hosts
+
push:
+
- meta_scope: meta.brackets.host.known_hosts
+
- match: (\])(?:(:)({{zero_to_65535}}))?
+
captures:
+
1: punctuation.definition.string.end.known_hosts
+
2: punctuation.separator.sequence.known_hosts
+
3: meta.number.integer.decimal.known_hosts constant.numeric.port-number.known_hosts
+
pop: 1
+
- include: operator-exclamation
+
- include: ip-addresses
+
- match: ''
+
push:
+
- meta_scope: meta.string.host.known_hosts string.quoted.other.known_hosts
+
- match: (?=,|\])
+
pop: 1
+
- include: wildcards
+
- include: punctuation-dot-sequence
+
- include: ip-addresses
+
- match: (\|)(\d+)(\|)({{base64_char}}{27}=)(\|)({{base64_char}}{27}=)
+
scope: meta.string.host.obfuscated.known_hosts
+
captures:
+
1: punctuation.definition.known_hosts
+
2: constant.numeric.integer.algorithm.known_hosts
+
3: punctuation.definition.known_hosts
+
4: string.unquoted.salt.known_hosts
+
5: punctuation.definition.known_hosts
+
6: string.unquoted.hash.known_hosts
+
- match: (?=\S)
+
push: hostname
+
hostname:
+
- meta_content_scope: meta.string.host.known_hosts string.unquoted.known_hosts
+
- match: (?=[,\[\s])
+
pop: 1
+
- include: wildcards
+
- include: punctuation-dot-sequence
+151
syntaxes/pem.sublime-syntax
···
+
%YAML 1.2
+
---
+
# Not strictly just PEM. Includes some other stuff, just to be helpful.
+
+
# https://www.sublimetext.com/docs/syntax.html
+
# https://datatracker.ietf.org/doc/html/rfc7468 (PEM)
+
# https://datatracker.ietf.org/doc/html/rfc4716 (OpenSSH)
+
# https://datatracker.ietf.org/doc/html/rfc4880 (OpenPGP)
+
+
name: Private Encrypted Mail (PEM) Key
+
scope: source.pem
+
version: 2
+
extends: SSH Common.sublime-syntax
+
+
file_extensions:
+
- pem
+
+
hidden_file_extensions:
+
- cer
+
- cert
+
- crt
+
- id_dsa
+
- id_ed25519
+
- id_ed448
+
- id_eddsa
+
- id_rsa
+
+
first_line_match: |-
+
^(?x:
+
(-{4}[ -])
+
BEGIN [ ]
+
( (?:[0-9A-Z -]+[ ])? (?: PUBLIC | PRIVATE ) [ ] KEY
+
| (?:[0-9A-Z -]+[ ])? CERTIFICATE (?:[ ] REQUEST )?
+
| (?:[0-9A-Z -]+[ ])? PARAMETERS
+
| X509 [ ] CRL
+
| PKCS7
+
| PKCS [ ] \#7 [ ] SIGNED [ ] DATA
+
| CMS
+
| PGP [ ] MESSAGE (?:,[ ] PART [ ] \d+(?:/\d+)?)?
+
| PGP [ ] (?: PUBLIC | PRIVATE ) [ ] KEY [ ] BLOCK
+
| PGP [ ] SIGNATURE
+
)
+
([ -]-{4})
+
)
+
+
contexts:
+
main:
+
- include: comments-number-sign
+
- match: |-
+
^(?x:
+
(-{4}[ -])
+
BEGIN [ ]
+
( (?:[0-9A-Z -]+[ ])? (?: PUBLIC | PRIVATE ) [ ] KEY
+
| (?:[0-9A-Z -]+[ ])? CERTIFICATE (?:[ ] REQUEST )?
+
| (?:[0-9A-Z -]+[ ])? PARAMETERS
+
| X509 [ ] CRL
+
| PKCS7
+
| PKCS [ ] \#7 [ ] SIGNED [ ] DATA
+
| CMS
+
| PGP [ ] MESSAGE (?:,[ ] PART [ ] \d+(?:/\d+)?)?
+
| PGP [ ] (?: PUBLIC | PRIVATE ) [ ] KEY [ ] BLOCK
+
| PGP [ ] SIGNATURE
+
)
+
([ -]-{4})
+
)
+
scope: punctuation.section.block.begin.pem
+
push: pem-key
+
- include: setext-headings
+
+
pem-key:
+
- meta_scope: meta.block.pem
+
- match: ^\1END \2\3
+
scope: punctuation.section.block.end.pem
+
pop: 1
+
- include: comments-number-sign
+
- match: ^{{base64_char}}{1,100}(={0,3})?$
+
scope: string.unquoted.pem
+
captures:
+
1: punctuation.definition.string.end.pem
+
- include: headers
+
+
headers:
+
- match: ^(?i:(Comment))(:)
+
captures:
+
1: keyword.other.comment.pem
+
2: punctuation.separator.key-value.pem
+
push:
+
- meta_content_scope: comment.line.pem
+
- include: header-end
+
- match: ^((x-)?[\w-]+)(:)
+
captures:
+
1: meta.mapping.key.pem keyword.other.pem
+
2: variable.annotation.pem
+
3: punctuation.separator.key-value.pem
+
push: header-value
+
+
header-value:
+
- meta_scope: meta.mapping.pem
+
- meta_content_scope: meta.mapping.value.pem
+
- include: header-end
+
- include: punctuation-comma-sequence
+
- match: =
+
scope: punctuation.separator.key-value.pem
+
- match: '\b(?x: ENCRYPTED | MIC-ONLY | MIC-CLEAR )\b'
+
scope: storage.modifier.pem
+
- match: |-
+
\b(?x:
+
( AES-(?:128|256)-CBC
+
| DES-(?:EDE3-)?CBC
+
)\b
+
( (,) .+ )?
+
)
+
captures:
+
1: meta.function-call.identifier.pem
+
support.function.cipher.ssh.crypto
+
2: meta.function-call.arguments.pem
+
3: punctuation.section.arguments.begin.pem
+
+
header-end:
+
- match: \\\r?\n
+
scope: punctuation.separator.continuation.line.pem
+
push:
+
- match: ^
+
pop: 1
+
- match: (?=$)
+
pop: 1
+
+
setext-headings:
+
- match: ^(?:=+|(?=\S))
+
branch_point: maybe-heading
+
branch:
+
- setext-heading
+
- not-heading
+
+
setext-heading:
+
- meta_scope: markup.heading.pem
+
- meta_content_scope: entity.name.section.pem
+
- match: ^(={5,})[ \t]*$(\n?)
+
captures:
+
1: punctuation.definition.heading.setext.pem
+
2: meta.whitespace.newline.pem
+
pop: 1
+
- match: ^(?!=+)$
+
fail: maybe-heading
+
+
not-heading:
+
- match: ''
+
pop: 1
+
+
variables:
+
base64_char: '[a-zA-Z0-9+/]'
+288
syntaxes/ssh-common.sublime-syntax
···
+
%YAML 1.2
+
---
+
# This file is some kind of internal library which is used to store
+
# common rules which are used by the visible syntax files.
+
name: SSH Common
+
scope: text.ssh.common
+
version: 2
+
hidden: true
+
+
contexts:
+
main:
+
- include: comments-number-sign
+
+
###[ COMMENTS ]################################################################
+
+
comments:
+
- include: comments-number-sign
+
- include: comments-semicolon
+
+
comments-number-sign:
+
- match: ^\s*(#+)
+
captures:
+
1: comment.line.number-sign.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.number-sign.ssh.common
+
- match: \n
+
scope: comment.line.number-sign.ssh.common
+
pop: true
+
+
comments-semicolon:
+
- match: ^\s*(;+)
+
captures:
+
1: comment.line.semi-colon.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.semi-colon.ssh.common
+
- include: pop-nl
+
+
###[ COMPONENTS ]##############################################################
+
+
operator-exclamation:
+
- match: '!'
+
scope: keyword.operator.logical.ssh.common
+
+
wildcards:
+
- match: \*
+
scope: constant.other.wildcard.asterisk.ssh.common
+
- match: \?
+
scope: constant.other.wildcard.questionmark.ssh.common
+
+
punctuation-comma-sequence:
+
- match: ','
+
scope: punctuation.separator.sequence.ssh.common
+
+
punctuation-dot-sequence:
+
- match: \.
+
scope: punctuation.separator.sequence.ssh.common
+
+
punctuation-at:
+
- match: '@'
+
scope: punctuation.separator.sequence.ssh.common
+
+
ssh-fingerprint:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
+
ssh-fingerprint-with-label:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
push: expect-fingerprint-label
+
+
expect-fingerprint-label:
+
- include: pop-before-nl
+
- match: (?=\S)
+
push:
+
- meta_scope: meta.annotation.identifier.ssh.common
+
string.unquoted.ssh.common
+
- match: '(?=[ \t]*$)'
+
pop: 1
+
- include: punctuation-at
+
+
time-values:
+
# https://man.openbsd.org/sshd_config.5#TIME_FORMATS
+
# seconds, minutes, hours, days, weeks
+
- match: \b(?=[\dsmhdw]*\d[smhdw][\s,"])
+
push:
+
- meta_scope: meta.constant.time.ssh.common
+
meta.number.integer.decimal.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: (\d+)([smhdw])
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
+
bytes-values:
+
- match: \b(\d+)([KMG])(?=[\s,"])
+
scope: meta.constant.bytes.ssh.common
+
meta.number.integer.other.ssh.common
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
+
mac-addresses:
+
- match: (?:[0-9a-fA-F]{2}:){5}(?:[0-9a-fA-F]{2})
+
scope: entity.name.constant.mac-address.ssh.common
+
+
ipv4:
+
- match: '\b{{ipv4}}\b'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
+
ipv6:
+
- match: '{{ipv6}}'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
+
ipv6-square-bracket:
+
- match: (\[){{ipv6}}(\])
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
captures:
+
1: punctuation.definition.constant.begin.ssh.common
+
2: punctuation.definition.constant.end.ssh.common
+
+
ip-addresses:
+
- include: ipv6
+
- include: ipv4
+
+
ipv4-with-cidr:
+
- match: \b({{ipv4}})(?:(/)({{zero_to_32}}))?\b
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
+
ipv6-with-cidr:
+
- match: ({{ipv6}})(?:(/)({{zero_to_128}})\b)?
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
+
ip-addresses-with-cidr:
+
- include: ipv6-with-cidr
+
- include: ipv4-with-cidr
+
+
port-numbers:
+
- match: \b{{zero_to_65535}}(?![\w:])
+
scope: meta.number.integer.decimal.ssh.common
+
constant.numeric.port-number.ssh.common
+
+
match-all:
+
- match: '\b(?xi: all )\b'
+
scope: constant.language.boolean.true.ssh.common
+
+
none:
+
- match: \bnone\b
+
scope: constant.language.null.ssh.common
+
+
any:
+
- match: \bany\b
+
scope: constant.language.set.ssh.common
+
+
boolean:
+
- match: \byes\b
+
scope: constant.language.boolean.true.ssh.common
+
- match: \bno\b
+
scope: constant.language.boolean.false.ssh.common
+
+
boolean-with-typing:
+
- include: boolean
+
# Consume while typing as well, but unscoped
+
- match: \b(?:ye?|n)\b
+
+
log-level:
+
- match: '\b(?x: QUIET | FATAL | ERROR | INFO | DEBUG[1-3]? )\b'
+
scope: constant.language.log-level.ssh.common
+
+
possibly-quoted-value:
+
- meta_content_scope: meta.mapping.value.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_scope: string.quoted.double.ssh.common
+
- match: (")(?:\s*(\S.*))?
+
captures:
+
1: punctuation.definition.string.end.ssh.common
+
2: invalid.illegal.ssh.common
+
pop: 1
+
- match: \n|$
+
scope: invalid.illegal.unclosed-string.ssh.common
+
pop: 2
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- include: pop-before-nl
+
- include: pop-nl
+
+
string-patterns:
+
# https://man7.org/linux/man-pages/man5/ssh_config.5.html#PATTERNS
+
# https://man.openbsd.org/ssh_config.5#PATTERNS
+
# https://man7.org/linux/man-pages/man5/sshd_config.5.html#PATTERNS
+
# https://man.openbsd.org/sshd_config.5#PATTERNS
+
- include: punctuation-comma-sequence
+
- include: operator-exclamation
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_content_scope: string.quoted.double.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.end.ssh.common
+
pop: 1
+
- include: wildcards
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- match: (?=[,!\s])
+
pop: 1
+
- include: wildcards
+
+
paths:
+
# This is just heuristic. Expect failures.
+
- match: (?=~?[\w.\-?*${}%]*/[\w.\-?*${}%]?)
+
push:
+
- meta_scope: meta.path.ssh.common
+
entity.name.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: ~[\w\-.]*
+
scope: variable.language.home.ssh.common
+
- match: (/)(?:(\.{1,2})(?=/)|\.(?!/))?
+
captures:
+
1: punctuation.separator.path.ssh.common
+
2: constant.other.placeholder.ssh.common
+
- match: \.(?=[\w*?%])
+
scope: punctuation.separator.sequence.ssh.common
+
- include: wildcards
+
- include: tokens
+
- include: environment-variables
+
+
none-command-values:
+
- match: \s*(none)\b[ \t]*$
+
captures:
+
1: constant.language.null.ssh.common
+
- match: \s*((")(none)("))[ \t]*$
+
captures:
+
1: string.quoted.double.ssh.common
+
2: punctuation.definition.string.begin.ssh.common
+
3: constant.language.null.ssh.common
+
4: punctuation.definition.string.end.ssh.common
+
+
tokens: []
+
environment-variables: []
+
+
###[ PROTOTYPE ]###############################################################
+
+
pop-nl:
+
- match: \n
+
pop: 1
+
+
pop-before-nl:
+
- match: (?=\n)
+
pop: 1
+
+
###############################################################################
+
+
variables:
+
base64_char: '[a-zA-Z0-9+/]'
+
ssh_fingerprint: (?:AAAA(?:E2V|[BC]3N){{base64_char}}+={0,3})
+
# ipv4_basic: (?:(?:\d{1,3}\.){3}\d{1,3})
+
# ipv6_basic: (?i:(?:[a-f0-9:]+:+)+[a-f0-9]+)
+
zero_to_32: (?:3[0-2]|[12][0-9]|[0-9])
+
zero_to_128: (?:12[0-8]|1[01][0-9]|[1-9][0-9]|[0-9])
+
zero_to_255: (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9][0-9])|(?:[1-9][0-9])|[0-9])
+
zero_to_65535: (?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])
+
ipv4: (?:(?:{{zero_to_255}}\.){3}{{zero_to_255}})
+
ipv6: |-
+
(?xi:
+
(?:::(?:ffff(?::0{1,4}){0,1}:){0,1}{{ipv4}}) # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
+
|(?:(?:[0-9a-f]{1,4}:){1,4}:{{ipv4}}) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address)
+
|(?:fe80:(?::[0-9a-f]{1,4}){0,4}%[0-9a-z]{1,}) # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index)
+
|(?:(?:[0-9a-f]{1,4}:){7,7} [0-9a-f]{1,4}) # 1:2:3:4:5:6:7:8
+
| (?:[0-9a-f]{1,4}: (?::[0-9a-f]{1,4}){1,6}) # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
+
|(?:(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}) # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
+
|(?:(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}) # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
+
|(?:(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}) # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
+
|(?:(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}) # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
+
|(?:(?:[0-9a-f]{1,4}:){1,6} :[0-9a-f]{1,4}) # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
+
|(?:(?:[0-9a-f]{1,4}:){1,7} :) # 1:: 1:2:3:4:5:6:7::
+
|(?::(?:(?::[0-9a-f]{1,4}){1,7}|:)) # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
+
)
+556
syntaxes/ssh-config.sublime-syntax
···
+
%YAML 1.2
+
---
+
# Standalone version of ssh-config.sublime-syntax
+
# Merged with: ssh-common.sublime-syntax, ssh-crypto.sublime-syntax
+
+
name: SSH Config
+
scope: source.ssh_config
+
version: 2
+
file_extensions:
+
- ssh_config
+
variables:
+
base64_char: '[a-zA-Z0-9+/]'
+
ssh_fingerprint: (?:AAAA(?:E2V|[BC]3N){{base64_char}}+={0,3})
+
zero_to_32: (?:3[0-2]|[12][0-9]|[0-9])
+
zero_to_128: (?:12[0-8]|1[01][0-9]|[1-9][0-9]|[0-9])
+
zero_to_255: (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9][0-9])|(?:[1-9][0-9])|[0-9])
+
zero_to_65535: (?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])
+
ipv4: (?:(?:{{zero_to_255}}\.){3}{{zero_to_255}})
+
ipv6: "(?xi:\n (?:::(?:ffff(?::0{1,4}){0,1}:){0,1}{{ipv4}}) # ::255.255.255.255\
+
\ ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses\
+
\ and IPv4-translated addresses)\n |(?:(?:[0-9a-f]{1,4}:){1,4}:{{ipv4}}) \
+
\ # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 \
+
\ (IPv4-Embedded IPv6 Address)\n |(?:fe80:(?::[0-9a-f]{1,4}){0,4}%[0-9a-z]{1,})\
+
\ # fe80::7:8%eth0 fe80::7:8%1 \
+
\ (link-local IPv6 addresses with zone index)\n |(?:(?:[0-9a-f]{1,4}:){7,7}\
+
\ [0-9a-f]{1,4}) # 1:2:3:4:5:6:7:8\n | (?:[0-9a-f]{1,4}: (?::[0-9a-f]{1,4}){1,6})\
+
\ # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8\n |(?:(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5})\
+
\ # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8\n |(?:(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4})\
+
\ # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8\n |(?:(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3})\
+
\ # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8\n |(?:(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2})\
+
\ # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8\n |(?:(?:[0-9a-f]{1,4}:){1,6}\
+
\ :[0-9a-f]{1,4}) # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8\n\
+
\ |(?:(?:[0-9a-f]{1,4}:){1,7} :) # 1:: \
+
\ 1:2:3:4:5:6:7::\n |(?::(?:(?::[0-9a-f]{1,4}){1,7}|:)) \
+
\ # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::\n)"
+
tokens_standard: (?:%[%CdhikLlnpru])
+
tokens_knownhosts: (?:{{tokens_standard}}|%[%fHIKt])
+
tokens_hostname: (?:%[%h])
+
tokens_proxycommand: (?:%[%hnpr])
+
tokens_all: (?:{{tokens_knownhosts}}|%T)
+
tokens_localcommand: '{{tokens_all}}'
+
match_parameters: "\\b(?xi:\n all | canonical | final | exec | localnetwork | host\
+
\ | originalhost\n| tagged | command | user | localuser | version | sessiontype\n\
+
)\\b"
+
contexts:
+
main:
+
- include: comments
+
- include: host-block
+
- include: match
+
- include: naked-parameters
+
comments:
+
- include: comments-number-sign
+
- include: comments-semicolon
+
comments-number-sign:
+
- match: ^\s*(#+)
+
captures:
+
1: comment.line.number-sign.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.number-sign.ssh.common
+
- match: \n
+
scope: comment.line.number-sign.ssh.common
+
pop: true
+
comments-semicolon:
+
- match: ^\s*(;+)
+
captures:
+
1: comment.line.semi-colon.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.semi-colon.ssh.common
+
- include: pop-nl
+
operator-exclamation:
+
- match: '!'
+
scope: keyword.operator.logical.ssh.common
+
wildcards:
+
- match: \*
+
scope: constant.other.wildcard.asterisk.ssh.common
+
- match: \?
+
scope: constant.other.wildcard.questionmark.ssh.common
+
punctuation-comma-sequence:
+
- match: ','
+
scope: punctuation.separator.sequence.ssh.common
+
punctuation-dot-sequence:
+
- match: \.
+
scope: punctuation.separator.sequence.ssh.common
+
punctuation-at:
+
- match: '@'
+
scope: punctuation.separator.sequence.ssh.common
+
ssh-fingerprint:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
ssh-fingerprint-with-label:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
push: expect-fingerprint-label
+
expect-fingerprint-label:
+
- include: pop-before-nl
+
- match: (?=\S)
+
push:
+
- meta_scope: meta.annotation.identifier.ssh.common string.unquoted.ssh.common
+
- match: (?=[ \t]*$)
+
pop: 1
+
- include: punctuation-at
+
time-values:
+
- match: \b(?=[\dsmhdw]*\d[smhdw][\s,"])
+
push:
+
- meta_scope: meta.constant.time.ssh.common meta.number.integer.decimal.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: (\d+)([smhdw])
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
bytes-values:
+
- match: \b(\d+)([KMG])(?=[\s,"])
+
scope: meta.constant.bytes.ssh.common meta.number.integer.other.ssh.common
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
mac-addresses:
+
- match: (?:[0-9a-fA-F]{2}:){5}(?:[0-9a-fA-F]{2})
+
scope: entity.name.constant.mac-address.ssh.common
+
ipv4:
+
- match: \b{{ipv4}}\b
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
ipv6:
+
- match: '{{ipv6}}'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
ipv6-square-bracket:
+
- match: (\[){{ipv6}}(\])
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
captures:
+
1: punctuation.definition.constant.begin.ssh.common
+
2: punctuation.definition.constant.end.ssh.common
+
ip-addresses:
+
- include: ipv6
+
- include: ipv4
+
ipv4-with-cidr:
+
- match: \b({{ipv4}})(?:(/)({{zero_to_32}}))?\b
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
ipv6-with-cidr:
+
- match: ({{ipv6}})(?:(/)({{zero_to_128}})\b)?
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
ip-addresses-with-cidr:
+
- include: ipv6-with-cidr
+
- include: ipv4-with-cidr
+
port-numbers:
+
- match: \b{{zero_to_65535}}(?![\w:])
+
scope: meta.number.integer.decimal.ssh.common constant.numeric.port-number.ssh.common
+
match-all:
+
- match: '\b(?xi: all )\b'
+
scope: constant.language.boolean.true.ssh.common
+
none:
+
- match: \bnone\b
+
scope: constant.language.null.ssh.common
+
any:
+
- match: \bany\b
+
scope: constant.language.set.ssh.common
+
boolean:
+
- match: \byes\b
+
scope: constant.language.boolean.true.ssh.common
+
- match: \bno\b
+
scope: constant.language.boolean.false.ssh.common
+
boolean-with-typing:
+
- include: boolean
+
- match: \b(?:ye?|n)\b
+
log-level:
+
- match: '\b(?x: QUIET | FATAL | ERROR | INFO | DEBUG[1-3]? )\b'
+
scope: constant.language.log-level.ssh.common
+
possibly-quoted-value:
+
- meta_content_scope: meta.mapping.value.ssh_config
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh_config
+
push:
+
- meta_scope: string.quoted.double.ssh_config
+
- match: (")(?:\s*(\S.*))?
+
captures:
+
1: punctuation.definition.string.end.ssh_config
+
2: invalid.illegal.ssh_config
+
pop: 1
+
- match: \n|$
+
scope: invalid.illegal.unclosed-string.ssh_config
+
pop: 2
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh_config
+
- include: pop-before-nl
+
- include: pop-nl
+
string-patterns:
+
- include: punctuation-comma-sequence
+
- include: operator-exclamation
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_content_scope: string.quoted.double.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.end.ssh.common
+
pop: 1
+
- include: wildcards
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- match: (?=[,!\s])
+
pop: 1
+
- include: wildcards
+
paths:
+
- match: (?=~?[\w.\-?*${}%]*/[\w.\-?*${}%]?)
+
push:
+
- meta_scope: meta.path.ssh.common entity.name.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: ~[\w\-.]*
+
scope: variable.language.home.ssh.common
+
- match: (/)(?:(\.{1,2})(?=/)|\.(?!/))?
+
captures:
+
1: punctuation.separator.path.ssh.common
+
2: constant.other.placeholder.ssh.common
+
- match: \.(?=[\w*?%])
+
scope: punctuation.separator.sequence.ssh.common
+
- include: wildcards
+
- include: tokens
+
- include: environment-variables
+
none-command-values:
+
- match: \s*(none)\b[ \t]*$
+
captures:
+
1: constant.language.null.ssh.common
+
- match: \s*((")(none)("))[ \t]*$
+
captures:
+
1: string.quoted.double.ssh.common
+
2: punctuation.definition.string.begin.ssh.common
+
3: constant.language.null.ssh.common
+
4: punctuation.definition.string.end.ssh.common
+
tokens:
+
- match: '%%'
+
scope: constant.character.escape.ssh_config
+
- match: '{{tokens_standard}}'
+
scope: constant.other.placeholder.ssh_config
+
environment-variables:
+
- include: scope:source.shell#parameter-expansions
+
pop-nl:
+
- match: \n
+
pop: 1
+
pop-before-nl:
+
- match: (?=\n)
+
pop: 1
+
ssh-ciphers:
+
- match: \b(?:twofish256\-gcm@libassh\.org|twofish256\-ctr|twofish192\-ctr|twofish128\-gcm@libassh\.org|twofish128\-ctr|twofish\-ctr|crypticore128@ssh\.com|chacha20\-poly1305@openssh\.com|chacha20\-poly1305|camellia256\-ctr@openssh\.org|camellia256\-ctr|camellia192\-ctr@openssh\.org|camellia192\-ctr|camellia128\-ctr@openssh\.org|camellia128\-ctr|aes256\-gcm@openssh\.com|aes256\-gcm|aes256\-ctr|aes192\-gcm@openssh\.com|aes192\-ctr|aes128\-gcm@openssh\.com|aes128\-gcm|aes128\-ctr|AEAD_CAMELLIA_256_GCM|AEAD_CAMELLIA_128_GCM|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.cipher.ssh.crypto
+
- match: \b(?:twofish256\-cbc|twofish192\-cbc|twofish128\-cbc|twofish\-ofb|twofish\-ecb|twofish\-cfb|twofish\-cbc|serpent256\-gcm@libassh\.org|serpent256\-ctr|serpent256\-cbc|serpent192\-ctr|serpent192\-cbc|serpent128\-gcm@libassh\.org|serpent128\-ctr|serpent128\-cbc|seed\-ctr@ssh\.com|seed\-cbc@ssh\.com|rijndael256\-cbc|rijndael192\-cbc|rijndael128\-cbc|rijndael\-cbc@ssh\.com|rijndael\-cbc@lysator\.liu\.se|none|idea\-ofb|idea\-ecb|idea\-ctr|idea\-cfb|idea\-cbc|grasshopper\-ctr128|des\-ofb|des\-ecb|des\-cfb|des\-cbc@ssh\.com|des\-cbc\-ssh1|des\-cbc|des|cast128\-ofb|cast128\-ecb|cast128\-ctr|cast128\-cfb|cast128\-cbc|cast128\-12\-ofb|cast128\-12\-ecb|cast128\-12\-ctr|cast128\-12\-cfb|cast128\-12\-cbc|camellia256\-cbc@openssh\.org|camellia256\-cbc|camellia192\-cbc@openssh\.org|camellia192\-cbc|camellia128\-cbc@openssh\.org|camellia128\-cbc|blowfish\-ecb|blowfish\-ctr|blowfish\-cfb|blowfish\-cbc|blowfish|arcfour256|arcfour128|arcfour|aes256\-cbc|aes192\-cbc|aes128\-ocb@libassh\.org|aes128\-cbc|3des\-ofb|3des\-ecb|3des\-ctr|3des\-cfb|3des\-cbc|3des)(?=[,\s\"])
+
scope: invalid.deprecated.cipher.ssh.crypto
+
ssh-kex-algorithms:
+
- match: \b(?:x25519\-kyber512\-sha512@aws\.amazon\.com|x25519\-kyber\-512r3\-sha256\-d00@amazon\.com|sntrup761x25519\-sha512@openssh\.com|sntrup4591761x25519\-sha512@tinyssh\.org|sm2kep\-sha2\-nistp256|rsa2048\-sha256|mlkem768x25519\-sha256|mlkem768nistp256\-sha256|mlkem1024nistp384\-sha384|m511\-sha512@libassh\.org|m383\-sha384@libassh\.org|kexguess2@matt\.ucc\.asn\.au|kexAlgoECDH521|kexAlgoECDH384|kexAlgoECDH256|kexAlgoCurve25519SHA256|kex\-strict\-s\-v00@openssh\.com|kex\-strict\-c\-v00@openssh\.com|gss\-nistp521\-sha512\-|gss\-nistp384\-sha384\-|gss\-nistp384\-sha256\-|gss\-nistp256\-sha256\-|gss\-group18\-sha512\-|gss\-group17\-sha512\-|gss\-group16\-sha512\-|gss\-group15\-sha512\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group15\-sha512\-|gss\-group14\-sha256\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha256\-|gss\-gex\-sha256\-|gss\-curve448\-sha512\-|gss\-curve25519\-sha256\-|gss\-13\.3\.132\.0\.10\-sha256\-|ext\-info\-s|ext\-info\-c|ecmqv\-sha2|ecdh\-sha2\-wiRIU8TKjMZ418sMqlqtvQ==|ecdh\-sha2\-qcFQaMAMGhTziMT0z\+Tuzw==|ecdh\-sha2\-nistt571|ecdh\-sha2\-nistp521|ecdh\-sha2\-nistp384|ecdh\-sha2\-nistp256|ecdh\-sha2\-nistp224|ecdh\-sha2\-nistp192|ecdh\-sha2\-nistk409|ecdh\-sha2\-nistk283|ecdh\-sha2\-nistb409|ecdh\-sha2\-mNVwCXAoS1HGmHpLvBC94w==|ecdh\-sha2\-m/FtSAmrV4j/Wy6RVUaK7A==|ecdh\-sha2\-h/SsxnLCtRBh7I9ATyeB3A==|ecdh\-sha2\-curve25519|ecdh\-sha2\-brainpoolp521r1@genua\.de|ecdh\-sha2\-brainpoolp384r1@genua\.de|ecdh\-sha2\-brainpoolp256r1@genua\.de|ecdh\-sha2\-D3FefCjYoJ/kfXgAyLddYA==|ecdh\-sha2\-9UzNcgwTlEnSCECZa7V1mw==|ecdh\-sha2\-1\.3\.132\.0\.38|ecdh\-sha2\-1\.3\.132\.0\.37|ecdh\-sha2\-1\.3\.132\.0\.36|ecdh\-sha2\-1\.3\.132\.0\.35|ecdh\-sha2\-1\.3\.132\.0\.34|ecdh\-sha2\-1\.3\.132\.0\.16|ecdh\-sha2\-1\.3\.132\.0\.10|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.7|ecdh\-nistp521\-kyber\-1024r3\-sha512\-d00@openquantumsafe\.org|ecdh\-nistp384\-kyber\-768r3\-sha384\-d00@openquantumsafe\.org|ecdh\-nistp256\-kyber\-512r3\-sha256\-d00@openquantumsafe\.org|diffie\-hellman_group17\-sha512|diffie\-hellman\-group18\-sha512@ssh\.com|diffie\-hellman\-group18\-sha512|diffie\-hellman\-group17\-sha512|diffie\-hellman\-group16\-sha512@ssh\.com|diffie\-hellman\-group16\-sha512|diffie\-hellman\-group16\-sha384@ssh\.com|diffie\-hellman\-group16\-sha256|diffie\-hellman\-group15\-sha512|diffie\-hellman\-group15\-sha384@ssh\.com|diffie\-hellman\-group15\-sha256@ssh\.com|diffie\-hellman\-group15\-sha256|diffie\-hellman\-group14\-sha256@ssh\.com|diffie\-hellman\-group14\-sha256|diffie\-hellman\-group14\-sha224@ssh\.com|diffie\-hellman\-group1\-sha256|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha384@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha224@ssh\.com|curve448\-sha512@libssh\.org|curve448\-sha512|curve25519\-sha256@libssh\.org|curve25519\-sha256|Curve25519SHA256)(?=[,\s\"])
+
scope: support.function.kex-algorithm.ssh.crypto
+
- match: \b(?:rsa1024\-sha1|kexAlgoDH1SHA1|kexAlgoDH14SHA1|gss\-group14\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha1\-|gss\-group1\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group1\-sha1\-|gss\-gex\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-gex\-sha1\-|ecdh\-sha2\-zD/b3hu/71952ArpUG4OjQ==|ecdh\-sha2\-qCbG5Cn/jjsZ7nBeR7EnOA==|ecdh\-sha2\-nistk233|ecdh\-sha2\-nistk163|ecdh\-sha2\-nistb233|ecdh\-sha2\-VqBg4QRPjxx1EXZdV0GdWQ==|ecdh\-sha2\-5pPrSUQtIaTjUSt5VZNBjg==|ecdh\-sha2\-4MHB\+NBt3AlaSRQ7MnB4cg==|ecdh\-sha2\-1\.3\.132\.0\.33|ecdh\-sha2\-1\.3\.132\.0\.27|ecdh\-sha2\-1\.3\.132\.0\.26|ecdh\-sha2\-1\.3\.132\.0\.1|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.1|diffie\-hellman\-group14\-sha1|diffie\-hellman\-group1\-sha1|diffie\-hellman\-group\-exchange\-sha1)(?=[,\s\"])
+
scope: invalid.deprecated.kex-algorithm.ssh.crypto
+
ssh-key-types:
+
- match: \b(?:x509v3\-sign\-rsa\-sha512@ssh\.com|x509v3\-sign\-rsa\-sha384@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256|x509v3\-sign\-rsa\-sha224@ssh\.com|x509v3\-sign\-dss\-sha512@ssh\.com|x509v3\-sign\-dss\-sha384@ssh\.com|x509v3\-sign\-dss\-sha256@ssh\.com|x509v3\-sign\-dss\-sha224@ssh\.com|x509v3\-rsa2048\-sha256|x509v3\-ecdsa\-sha2\-nistp521|x509v3\-ecdsa\-sha2\-nistp384|x509v3\-ecdsa\-sha2\-nistp256|x509v3\-ecdsa\-sha2\-1\.3\.132\.0\.10|webauthn\-sk\-ecdsa\-sha2\-nistp256@openssh\.com|ssh\-rsa\-sha512@ssh\.com|ssh\-rsa\-sha384@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha2\-512|ssh\-rsa\-sha2\-256|ssh\-rsa|ssh\-gost\-2012\-512|ssh\-gost\-2012\-256|ssh\-gost\-2001|ssh\-ed448|ssh\-ed25519\-cert\-v01@openssh\.com|ssh\-ed25519|spi\-sign\-rsa|sk\-ecdsa\-sha2\-nistp256@openssh\.com|sk\-ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|rsa\-sha2\-512\-cert\-v01@openssh\.com|rsa\-sha2\-512|rsa\-sha2\-256\-cert\-v01@openssh\.com|rsa\-sha2\-256|eddsa\-e521\-shake256@libassh\.org|eddsa\-e382\-shake256@libassh\.org|ecdsa\-sha2\-nistt571|ecdsa\-sha2\-nistp521\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp521|ecdsa\-sha2\-nistp384\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp384|ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp256|ecdsa\-sha2\-nistk409|ecdsa\-sha2\-nistk283|ecdsa\-sha2\-nistk233|ecdsa\-sha2\-nistk163|ecdsa\-sha2\-nistb409|ecdsa\-sha2\-curve25519|ecdsa\-sha2\-1\.3\.132\.0\.10\-cert\-v01@openssh\.com|ecdsa\-sha2\-1\.3\.132\.0\.10|dsa3072\-sha256@libassh\.org|dsa2048\-sha256@libassh\.org|dsa2048\-sha224@libassh\.org)(?=[,\s\"])
+
scope: support.type.key-type.ssh.crypto
+
- match: \b(?:x509v3\-ssh\-rsa|x509v3\-ssh\-dss|x509v3\-sign\-rsa\-sha1|x509v3\-sign\-rsa|x509v3\-sign\-dss\-sha1|x509v3\-sign\-dss|ssh\-xmss@openssh\.com|ssh\-xmss\-cert\-v01@openssh\.com|ssh\-rsa1|ssh\-rsa\-cert\-v01@openssh\.com|ssh\-rsa\-cert\-v00@openssh\.com|ssh\-dss\-sha512@ssh\.com|ssh\-dss\-sha384@ssh\.com|ssh\-dss\-sha256@ssh\.com|ssh\-dss\-sha224@ssh\.com|ssh\-dss\-cert\-v01@openssh\.com|ssh\-dss\-cert\-v00@openssh\.com|ssh\-dss|ssh\-dsa|spki\-sign\-rsa|spki\-sign\-dss|pgp\-sign\-rsa|pgp\-sign\-dss|null|ecdsa\-sha2\-nistp224|ecdsa\-sha2\-nistp192|ecdsa\-sha2\-nistb233)(?=[,\s\"])
+
scope: invalid.deprecated.key-type.ssh.crypto
+
ssh-mac-algorithms:
+
- match: \b(?:umac\-96@openssh\.com|umac\-64@openssh\.com|umac\-64\-etm@openssh\.com|umac\-32@openssh\.com|umac\-128@openssh\.com|umac\-128\-etm@openssh\.com|umac\-128|hmac\-sha512@ssh\.com|hmac\-sha512|hmac\-sha3\-512|hmac\-sha3\-384|hmac\-sha3\-256|hmac\-sha3\-224|hmac\-sha256@ssh\.com|hmac\-sha256\-96@ssh\.com|hmac\-sha256|hmac\-sha2\-56|hmac\-sha2\-512\-etm@openssh\.com|hmac\-sha2\-512\-96\-etm@openssh\.com|hmac\-sha2\-512|hmac\-sha2\-384|hmac\-sha2\-256\-etm@openssh\.com|hmac\-sha2\-256\-96\-etm@openssh\.com|hmac\-sha2\-256|hmac\-sha2\-224|crypticore\-mac@ssh\.com|chacha20\-poly1305@openssh\.com|cbcmac\-twofish|cbcmac\-aes|aes256\-gcm|aes128\-gcm|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.mac-algorithm.ssh.crypto
+
- match: \b(?:sha1\-8|sha1|ripemd160\-8|ripemd160|none|md5\-8|md5|hmac\-sha2\-512\-96|hmac\-sha2\-256\-96|hmac\-sha1\-etm@openssh\.com|hmac\-sha1\-96\-etm@openssh\.com|hmac\-sha1\-96|hmac\-sha1|hmac\-ripemd160@openssh\.com|hmac\-ripemd160\-etm@openssh\.com|hmac\-ripemd160\-96|hmac\-ripemd160|hmac\-ripemd|hmac\-md5\-etm@openssh\.com|hmac\-md5\-96\-etm@openssh\.com|hmac\-md5\-96|hmac\-md5|cbcmac\-rijndael|cbcmac\-des|cbcmac\-blowfish|cbcmac\-3des)(?=[,\s\"])
+
scope: invalid.deprecated.mac-algorithm.ssh.crypto
+
parameters:
+
- include: comments
+
- include: parameter-hostname
+
- include: parameter-localcommand
+
- include: parameter-proxycommand
+
- include: parameter-proxyjump
+
- include: parameter-knownhostscommand
+
- include: parameter-with-boolean-values
+
- include: parameter-with-boolean-values-plus-ask
+
- include: parameter-generic
+
pop-before-match-parameter:
+
- include: pop-before-nl
+
- match: (?=\s*{{match_parameters}})
+
pop: 1
+
pop-before-next-host:
+
- match: '(?=^\s*(?xi: Host | Match )\b)'
+
pop: 1
+
naked-parameters:
+
- match: (?=\S)
+
push:
+
- meta_scope: meta.block.naked.ssh_config
+
- include: pop-before-next-host
+
- include: parameters
+
host-pattern:
+
- meta_content_scope: entity.name.label.host-alias.ssh_config
+
- include: punctuation-dot-sequence
+
- include: wildcards
+
- match: (?=\s|,)
+
pop: 1
+
host-patterns:
+
- include: operator-exclamation
+
- include: punctuation-comma-sequence
+
- match: (?=\S)
+
push: host-pattern
+
host-block:
+
- match: ^\s*((?i:Host))\b
+
captures:
+
1: keyword.declaration.host-alias.ssh_config
+
set: host-aliases
+
host-aliases:
+
- meta_scope: meta.block.host.ssh_config
+
- match: (?=\n)
+
set: host-body
+
- include: host-patterns
+
host-body:
+
- meta_scope: meta.block.host.ssh_config
+
- include: pop-before-next-host
+
- include: parameters
+
match:
+
- match: ^\s*((?i:Match))\b
+
captures:
+
1: keyword.control.conditional.ssh_config
+
set: match-conditions
+
match-conditions:
+
- meta_scope: meta.block.match.ssh_config
+
- meta_content_scope: meta.statement.conditional.ssh_config
+
- match: \n
+
set: match-body
+
- include: operator-exclamation
+
- include: match-all
+
- match: '\b(?xi: canonical | final )\b'
+
scope: keyword.other.ssh_config
+
- match: '\b((?xi: exec ))\b\s*(\")'
+
captures:
+
1: keyword.other.ssh_config
+
2: string.quoted.double.ssh_config punctuation.definition.string.begin.ssh_config
+
escape: (?<!\\)\"(?=\s*(?:#.*)?)
+
escape_captures:
+
0: meta.block.match.ssh_config meta.statement.conditional.ssh_config string.quoted.double.ssh_config
+
punctuation.definition.string.end.ssh_config
+
embed_scope: string.quoted.double.ssh_config
+
embed: scope:source.shell.embedded.ssh
+
- match: '\b((?xi: exec ))\b[ \t]+'
+
captures:
+
1: keyword.other.ssh_config
+
embed: scope:source.shell.embedded.ssh
+
escape: (?=\s*(?:{{match_parameters}}|$))
+
- match: '\b(?xi: (?:original)? host )\b'
+
scope: keyword.other.ssh_config
+
push:
+
- include: pop-before-match-parameter
+
- include: punctuation-comma-sequence
+
- include: host-patterns
+
- match: '\b(?xi: (?:local)? user | tagged | version | command | sessiontype )\b'
+
scope: keyword.other.ssh_config
+
push:
+
- include: pop-before-match-parameter
+
- include: string-patterns
+
- match: '\b(?xi: localnetwork )\b'
+
scope: keyword.other.ssh_config
+
push:
+
- include: pop-before-match-parameter
+
- include: punctuation-comma-sequence
+
- include: ip-addresses-with-cidr
+
match-body:
+
- meta_content_scope: meta.block.match.ssh_config
+
- include: pop-before-next-host
+
- include: parameters
+
parameter-hostname:
+
- match: ^\s*((?i:HostName))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.ssh_config keyword.declaration.host.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
push:
+
- meta_content_scope: meta.string.host.ssh_config
+
- include: pop-nl
+
- include: ip-addresses
+
- match: (?=\S+)
+
push:
+
- meta_content_scope: string.unquoted.hostname.ssh_config
+
- include: pop-before-nl
+
- include: punctuation-dot-sequence
+
- match: '{{tokens_hostname}}'
+
scope: constant.character.escape.ssh_config
+
- match: \s+(\S+)
+
captures:
+
1: invalid.illegal.ssh_config
+
parameter-proxyjump:
+
- match: ^\s*((?i:ProxyJump))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
push:
+
- meta_content_scope: meta.mapping.value.ssh_config
+
- include: pop-nl
+
- include: none-command-values
+
- match: '"'
+
scope: string.quoted.double.ssh_config punctuation.definition.string.begin.ssh_config
+
escape: (")|(?=\n|$)
+
escape_captures:
+
1: meta.mapping.value.ssh_config string.quoted.double.ssh_config punctuation.definition.string.end.ssh_config
+
embed_scope: string.quoted.double.ssh_config
+
embed: proxyjump-values
+
- match: (?=\S)
+
escape: (?=\n|$)
+
embed: proxyjump-values
+
proxyjump-values:
+
- include: ip-addresses
+
- include: punctuation-comma-sequence
+
- match: (?=[\w%]+@)
+
push:
+
- meta_content_scope: meta.string.user.ssh_config string.unquoted.ssh_config
+
- match: '%%'
+
scope: constant.character.escape.ssh_config
+
- match: '{{tokens_proxycommand}}'
+
scope: constant.other.placeholder.ssh_config
+
- match: '@'
+
scope: punctuation.separator.ssh_config
+
pop: 1
+
- match: :(?={{zero_to_65535}}(?![\w:]))
+
scope: punctuation.separator.ssh_config
+
push:
+
- match: (?=\D)
+
pop: 1
+
- include: port-numbers
+
- match: (?=\S+)
+
push:
+
- meta_content_scope: string.unquoted.hostname.ssh_config
+
- match: (?=[\s,:"])
+
pop: 1
+
- include: punctuation-dot-sequence
+
- match: '%%'
+
scope: constant.character.escape.ssh_config
+
- match: '{{tokens_proxycommand}}'
+
scope: constant.other.placeholder.ssh_config
+
parameter-proxycommand:
+
- match: ^\s*((?i:ProxyCommand))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
push:
+
- meta_content_scope: meta.mapping.value.ssh_config
+
- include: pop-nl
+
- include: none-command-values
+
- match: '"'
+
scope: string.quoted.double.ssh_config punctuation.definition.string.begin.ssh_config
+
escape: (")|(?=\n|$)
+
escape_captures:
+
1: meta.mapping.value.ssh_config string.quoted.double.ssh_config punctuation.definition.string.end.ssh_config
+
embed_scope: string.quoted.double.ssh_config source.shell.embedded.ssh.proxycommand
+
embed: scope:source.shell.embedded.ssh.proxycommand
+
- match: (?=\S)
+
escape: (?=\n|$)
+
embed: scope:source.shell.embedded.ssh.proxycommand
+
parameter-localcommand:
+
- match: ^\s*((?i:LocalCommand))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
push:
+
- meta_content_scope: meta.mapping.value.ssh_config
+
- include: pop-nl
+
- include: none-command-values
+
- match: '"'
+
scope: string.quoted.double.ssh_config punctuation.definition.string.begin.ssh_config
+
escape: (")|(?=$)
+
escape_captures:
+
1: meta.mapping.value.ssh_config string.quoted.double.ssh_config punctuation.definition.string.end.ssh_config
+
embed_scope: string.quoted.double.ssh_config source.shell.embedded.ssh.localcommand
+
embed: scope:source.shell.embedded.ssh.localcommand
+
- match: (?=\S)
+
escape: (?=$)
+
embed: scope:source.shell.embedded.ssh.localcommand
+
parameter-knownhostscommand:
+
- match: ^\s*((?i:KnownHostsCommand))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
push:
+
- meta_content_scope: meta.mapping.value.ssh_config
+
- include: pop-nl
+
- include: none-command-values
+
- match: '"'
+
scope: string.quoted.double.ssh_config punctuation.definition.string.begin.ssh_config
+
escape: (")|(?=$)
+
escape_captures:
+
1: meta.mapping.value.ssh_config string.quoted.double.ssh_config punctuation.definition.string.end.ssh_config
+
embed_scope: string.quoted.double.ssh_config source.shell.embedded.ssh.knownhostscommand
+
embed: scope:source.shell.embedded.ssh.knownhostscommand
+
- match: (?=\S)
+
escape: (?=$)
+
embed: scope:source.shell.embedded.ssh.knownhostscommand
+
parameter-with-boolean-values:
+
- match: "(?xi:\n ^\\s*\n (\n (?: Pubkey | HostBased | Password | ChallengeResponse\n\
+
\ | KbdInteractive | (?:Rhosts)? RSA\n ) Authentication # Auth\n | ForwardAgent\
+
\ | ForwardX11(?:Trusted)? | ClearAllForwardings\n | ExitOnForwardFailure #\
+
\ Fwds\n | BatchMode | CanonicalizeFallbackLocal | CheckHostIP | Compression\n\
+
\ | EnableEscapeCommandLine | EnableSSHKeySign\n | ForkAfterAuthentication\
+
\ | GatewayPorts | HashKnownHosts\n | IdentitiesOnly | NoHostAuthenticationForLocalhost\n\
+
\ | PermitLocalCommand | ProxyUseFdpass | RefuseConnection | StdinNull\n |\
+
\ StreamLocalBindUnlink | TCPKeepAlive\n | UseKeychain | UsePrivilegedPort\
+
\ | VisualHostKey\n | GSSAPI (?:\n Authentication | KeyExchange | DelegateCredentials\n\
+
\ | RenewalForcesRekey | TrustDNS ) # GSSAPI\n )\n \\b[ \\t]*(=)?\n)"
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
with_prototype:
+
- include: boolean-with-typing
+
- match: '[^"\s]+'
+
scope: invalid.illegal.sshd_config
+
push: possibly-quoted-value
+
parameter-with-boolean-values-plus-ask:
+
- match: "(?xi:\n ^\\s*\n ( ControlMaster | StrictHostKeyChecking | UpdateHostKeys\n\
+
\ | VerifyHostKeyDNS\n )\n \\b[ \\t]*(=)?\n)"
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
with_prototype:
+
- include: boolean-with-typing
+
- include: ask
+
- match: \bas?\b
+
- match: '[^"\s]+'
+
scope: invalid.illegal.sshd_config
+
push: possibly-quoted-value
+
parameter-generic:
+
- match: ^\s*([a-zA-Z1]+)\b[ \t]*(=)?
+
captures:
+
1: meta.mapping.key.ssh_config keyword.other.ssh_config
+
2: keyword.operator.assignment.ssh_config
+
with_prototype:
+
- include: generic-parameter-values
+
push: possibly-quoted-value
+
ask:
+
- match: \bask\b
+
scope: constant.language.ssh_config
+
generic-parameter-values:
+
- include: environment-variables
+
- include: none
+
- include: boolean
+
- include: any
+
- include: ask
+
- include: tokens
+
- include: wildcards
+
- include: operator-exclamation
+
- include: punctuation-comma-sequence
+
- include: ssh-key-types
+
- include: ssh-ciphers
+
- include: ssh-kex-algorithms
+
- include: ssh-mac-algorithms
+
- include: ipv6-square-bracket
+
- include: ip-addresses-with-cidr
+
- include: time-values
+
- include: bytes-values
+
- include: log-level
+
- include: paths
+
- match: \b\d+(?=[\s,"])
+
scope: meta.number.integer.ssh_config constant.numeric.value.ssh_config
+48
syntaxes/ssh-crypto.sublime-syntax
···
+
%YAML 1.2
+
---
+
contexts:
+
main:
+
- include: comments
+
- match: '^key type:'
+
push:
+
- include: pop-before-nl
+
- include: ssh-key-types
+
- match: '^cipher:'
+
push:
+
- include: pop-before-nl
+
- include: ssh-ciphers
+
- match: '^kex:'
+
push:
+
- include: pop-before-nl
+
- include: ssh-kex-algorithms
+
- match: '^mac:'
+
push:
+
- include: pop-before-nl
+
- include: ssh-mac-algorithms
+
ssh-ciphers:
+
- match: \b(?:twofish256\-gcm@libassh\.org|twofish256\-ctr|twofish192\-ctr|twofish128\-gcm@libassh\.org|twofish128\-ctr|twofish\-ctr|crypticore128@ssh\.com|chacha20\-poly1305@openssh\.com|chacha20\-poly1305|camellia256\-ctr@openssh\.org|camellia256\-ctr|camellia192\-ctr@openssh\.org|camellia192\-ctr|camellia128\-ctr@openssh\.org|camellia128\-ctr|aes256\-gcm@openssh\.com|aes256\-gcm|aes256\-ctr|aes192\-gcm@openssh\.com|aes192\-ctr|aes128\-gcm@openssh\.com|aes128\-gcm|aes128\-ctr|AEAD_CAMELLIA_256_GCM|AEAD_CAMELLIA_128_GCM|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.cipher.ssh.crypto
+
- match: \b(?:twofish256\-cbc|twofish192\-cbc|twofish128\-cbc|twofish\-ofb|twofish\-ecb|twofish\-cfb|twofish\-cbc|serpent256\-gcm@libassh\.org|serpent256\-ctr|serpent256\-cbc|serpent192\-ctr|serpent192\-cbc|serpent128\-gcm@libassh\.org|serpent128\-ctr|serpent128\-cbc|seed\-ctr@ssh\.com|seed\-cbc@ssh\.com|rijndael256\-cbc|rijndael192\-cbc|rijndael128\-cbc|rijndael\-cbc@ssh\.com|rijndael\-cbc@lysator\.liu\.se|none|idea\-ofb|idea\-ecb|idea\-ctr|idea\-cfb|idea\-cbc|grasshopper\-ctr128|des\-ofb|des\-ecb|des\-cfb|des\-cbc@ssh\.com|des\-cbc\-ssh1|des\-cbc|des|cast128\-ofb|cast128\-ecb|cast128\-ctr|cast128\-cfb|cast128\-cbc|cast128\-12\-ofb|cast128\-12\-ecb|cast128\-12\-ctr|cast128\-12\-cfb|cast128\-12\-cbc|camellia256\-cbc@openssh\.org|camellia256\-cbc|camellia192\-cbc@openssh\.org|camellia192\-cbc|camellia128\-cbc@openssh\.org|camellia128\-cbc|blowfish\-ecb|blowfish\-ctr|blowfish\-cfb|blowfish\-cbc|blowfish|arcfour256|arcfour128|arcfour|aes256\-cbc|aes192\-cbc|aes128\-ocb@libassh\.org|aes128\-cbc|3des\-ofb|3des\-ecb|3des\-ctr|3des\-cfb|3des\-cbc|3des)(?=[,\s\"])
+
scope: invalid.deprecated.cipher.ssh.crypto
+
ssh-kex-algorithms:
+
- match: \b(?:x25519\-kyber512\-sha512@aws\.amazon\.com|x25519\-kyber\-512r3\-sha256\-d00@amazon\.com|sntrup761x25519\-sha512@openssh\.com|sntrup4591761x25519\-sha512@tinyssh\.org|sm2kep\-sha2\-nistp256|rsa2048\-sha256|mlkem768x25519\-sha256|mlkem768nistp256\-sha256|mlkem1024nistp384\-sha384|m511\-sha512@libassh\.org|m383\-sha384@libassh\.org|kexguess2@matt\.ucc\.asn\.au|kexAlgoECDH521|kexAlgoECDH384|kexAlgoECDH256|kexAlgoCurve25519SHA256|kex\-strict\-s\-v00@openssh\.com|kex\-strict\-c\-v00@openssh\.com|gss\-nistp521\-sha512\-|gss\-nistp384\-sha384\-|gss\-nistp384\-sha256\-|gss\-nistp256\-sha256\-|gss\-group18\-sha512\-|gss\-group17\-sha512\-|gss\-group16\-sha512\-|gss\-group15\-sha512\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group15\-sha512\-|gss\-group14\-sha256\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha256\-|gss\-gex\-sha256\-|gss\-curve448\-sha512\-|gss\-curve25519\-sha256\-|gss\-13\.3\.132\.0\.10\-sha256\-|ext\-info\-s|ext\-info\-c|ecmqv\-sha2|ecdh\-sha2\-wiRIU8TKjMZ418sMqlqtvQ==|ecdh\-sha2\-qcFQaMAMGhTziMT0z\+Tuzw==|ecdh\-sha2\-nistt571|ecdh\-sha2\-nistp521|ecdh\-sha2\-nistp384|ecdh\-sha2\-nistp256|ecdh\-sha2\-nistp224|ecdh\-sha2\-nistp192|ecdh\-sha2\-nistk409|ecdh\-sha2\-nistk283|ecdh\-sha2\-nistb409|ecdh\-sha2\-mNVwCXAoS1HGmHpLvBC94w==|ecdh\-sha2\-m/FtSAmrV4j/Wy6RVUaK7A==|ecdh\-sha2\-h/SsxnLCtRBh7I9ATyeB3A==|ecdh\-sha2\-curve25519|ecdh\-sha2\-brainpoolp521r1@genua\.de|ecdh\-sha2\-brainpoolp384r1@genua\.de|ecdh\-sha2\-brainpoolp256r1@genua\.de|ecdh\-sha2\-D3FefCjYoJ/kfXgAyLddYA==|ecdh\-sha2\-9UzNcgwTlEnSCECZa7V1mw==|ecdh\-sha2\-1\.3\.132\.0\.38|ecdh\-sha2\-1\.3\.132\.0\.37|ecdh\-sha2\-1\.3\.132\.0\.36|ecdh\-sha2\-1\.3\.132\.0\.35|ecdh\-sha2\-1\.3\.132\.0\.34|ecdh\-sha2\-1\.3\.132\.0\.16|ecdh\-sha2\-1\.3\.132\.0\.10|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.7|ecdh\-nistp521\-kyber\-1024r3\-sha512\-d00@openquantumsafe\.org|ecdh\-nistp384\-kyber\-768r3\-sha384\-d00@openquantumsafe\.org|ecdh\-nistp256\-kyber\-512r3\-sha256\-d00@openquantumsafe\.org|diffie\-hellman_group17\-sha512|diffie\-hellman\-group18\-sha512@ssh\.com|diffie\-hellman\-group18\-sha512|diffie\-hellman\-group17\-sha512|diffie\-hellman\-group16\-sha512@ssh\.com|diffie\-hellman\-group16\-sha512|diffie\-hellman\-group16\-sha384@ssh\.com|diffie\-hellman\-group16\-sha256|diffie\-hellman\-group15\-sha512|diffie\-hellman\-group15\-sha384@ssh\.com|diffie\-hellman\-group15\-sha256@ssh\.com|diffie\-hellman\-group15\-sha256|diffie\-hellman\-group14\-sha256@ssh\.com|diffie\-hellman\-group14\-sha256|diffie\-hellman\-group14\-sha224@ssh\.com|diffie\-hellman\-group1\-sha256|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha384@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha224@ssh\.com|curve448\-sha512@libssh\.org|curve448\-sha512|curve25519\-sha256@libssh\.org|curve25519\-sha256|Curve25519SHA256)(?=[,\s\"])
+
scope: support.function.kex-algorithm.ssh.crypto
+
- match: \b(?:rsa1024\-sha1|kexAlgoDH1SHA1|kexAlgoDH14SHA1|gss\-group14\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha1\-|gss\-group1\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group1\-sha1\-|gss\-gex\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-gex\-sha1\-|ecdh\-sha2\-zD/b3hu/71952ArpUG4OjQ==|ecdh\-sha2\-qCbG5Cn/jjsZ7nBeR7EnOA==|ecdh\-sha2\-nistk233|ecdh\-sha2\-nistk163|ecdh\-sha2\-nistb233|ecdh\-sha2\-VqBg4QRPjxx1EXZdV0GdWQ==|ecdh\-sha2\-5pPrSUQtIaTjUSt5VZNBjg==|ecdh\-sha2\-4MHB\+NBt3AlaSRQ7MnB4cg==|ecdh\-sha2\-1\.3\.132\.0\.33|ecdh\-sha2\-1\.3\.132\.0\.27|ecdh\-sha2\-1\.3\.132\.0\.26|ecdh\-sha2\-1\.3\.132\.0\.1|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.1|diffie\-hellman\-group14\-sha1|diffie\-hellman\-group1\-sha1|diffie\-hellman\-group\-exchange\-sha1)(?=[,\s\"])
+
scope: invalid.deprecated.kex-algorithm.ssh.crypto
+
ssh-key-types:
+
- match: \b(?:x509v3\-sign\-rsa\-sha512@ssh\.com|x509v3\-sign\-rsa\-sha384@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256|x509v3\-sign\-rsa\-sha224@ssh\.com|x509v3\-sign\-dss\-sha512@ssh\.com|x509v3\-sign\-dss\-sha384@ssh\.com|x509v3\-sign\-dss\-sha256@ssh\.com|x509v3\-sign\-dss\-sha224@ssh\.com|x509v3\-rsa2048\-sha256|x509v3\-ecdsa\-sha2\-nistp521|x509v3\-ecdsa\-sha2\-nistp384|x509v3\-ecdsa\-sha2\-nistp256|x509v3\-ecdsa\-sha2\-1\.3\.132\.0\.10|webauthn\-sk\-ecdsa\-sha2\-nistp256@openssh\.com|ssh\-rsa\-sha512@ssh\.com|ssh\-rsa\-sha384@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha2\-512|ssh\-rsa\-sha2\-256|ssh\-rsa|ssh\-gost\-2012\-512|ssh\-gost\-2012\-256|ssh\-gost\-2001|ssh\-ed448|ssh\-ed25519\-cert\-v01@openssh\.com|ssh\-ed25519|spi\-sign\-rsa|sk\-ecdsa\-sha2\-nistp256@openssh\.com|sk\-ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|rsa\-sha2\-512\-cert\-v01@openssh\.com|rsa\-sha2\-512|rsa\-sha2\-256\-cert\-v01@openssh\.com|rsa\-sha2\-256|eddsa\-e521\-shake256@libassh\.org|eddsa\-e382\-shake256@libassh\.org|ecdsa\-sha2\-nistt571|ecdsa\-sha2\-nistp521\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp521|ecdsa\-sha2\-nistp384\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp384|ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp256|ecdsa\-sha2\-nistk409|ecdsa\-sha2\-nistk283|ecdsa\-sha2\-nistk233|ecdsa\-sha2\-nistk163|ecdsa\-sha2\-nistb409|ecdsa\-sha2\-curve25519|ecdsa\-sha2\-1\.3\.132\.0\.10\-cert\-v01@openssh\.com|ecdsa\-sha2\-1\.3\.132\.0\.10|dsa3072\-sha256@libassh\.org|dsa2048\-sha256@libassh\.org|dsa2048\-sha224@libassh\.org)(?=[,\s\"])
+
scope: support.type.key-type.ssh.crypto
+
- match: \b(?:x509v3\-ssh\-rsa|x509v3\-ssh\-dss|x509v3\-sign\-rsa\-sha1|x509v3\-sign\-rsa|x509v3\-sign\-dss\-sha1|x509v3\-sign\-dss|ssh\-xmss@openssh\.com|ssh\-xmss\-cert\-v01@openssh\.com|ssh\-rsa1|ssh\-rsa\-cert\-v01@openssh\.com|ssh\-rsa\-cert\-v00@openssh\.com|ssh\-dss\-sha512@ssh\.com|ssh\-dss\-sha384@ssh\.com|ssh\-dss\-sha256@ssh\.com|ssh\-dss\-sha224@ssh\.com|ssh\-dss\-cert\-v01@openssh\.com|ssh\-dss\-cert\-v00@openssh\.com|ssh\-dss|ssh\-dsa|spki\-sign\-rsa|spki\-sign\-dss|pgp\-sign\-rsa|pgp\-sign\-dss|null|ecdsa\-sha2\-nistp224|ecdsa\-sha2\-nistp192|ecdsa\-sha2\-nistb233)(?=[,\s\"])
+
scope: invalid.deprecated.key-type.ssh.crypto
+
ssh-mac-algorithms:
+
- match: \b(?:umac\-96@openssh\.com|umac\-64@openssh\.com|umac\-64\-etm@openssh\.com|umac\-32@openssh\.com|umac\-128@openssh\.com|umac\-128\-etm@openssh\.com|umac\-128|hmac\-sha512@ssh\.com|hmac\-sha512|hmac\-sha3\-512|hmac\-sha3\-384|hmac\-sha3\-256|hmac\-sha3\-224|hmac\-sha256@ssh\.com|hmac\-sha256\-96@ssh\.com|hmac\-sha256|hmac\-sha2\-56|hmac\-sha2\-512\-etm@openssh\.com|hmac\-sha2\-512\-96\-etm@openssh\.com|hmac\-sha2\-512|hmac\-sha2\-384|hmac\-sha2\-256\-etm@openssh\.com|hmac\-sha2\-256\-96\-etm@openssh\.com|hmac\-sha2\-256|hmac\-sha2\-224|crypticore\-mac@ssh\.com|chacha20\-poly1305@openssh\.com|cbcmac\-twofish|cbcmac\-aes|aes256\-gcm|aes128\-gcm|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.mac-algorithm.ssh.crypto
+
- match: \b(?:sha1\-8|sha1|ripemd160\-8|ripemd160|none|md5\-8|md5|hmac\-sha2\-512\-96|hmac\-sha2\-256\-96|hmac\-sha1\-etm@openssh\.com|hmac\-sha1\-96\-etm@openssh\.com|hmac\-sha1\-96|hmac\-sha1|hmac\-ripemd160@openssh\.com|hmac\-ripemd160\-etm@openssh\.com|hmac\-ripemd160\-96|hmac\-ripemd160|hmac\-ripemd|hmac\-md5\-etm@openssh\.com|hmac\-md5\-96\-etm@openssh\.com|hmac\-md5\-96|hmac\-md5|cbcmac\-rijndael|cbcmac\-des|cbcmac\-blowfish|cbcmac\-3des)(?=[,\s\"])
+
scope: invalid.deprecated.mac-algorithm.ssh.crypto
+
extends: SSH Common.sublime-syntax
+
hidden: true
+
hidden_file_extensions:
+
- syntax_test_crypto
+
name: SSH Crypto
+
scope: text.ssh.crypto
+
version: 2
+496
syntaxes/sshd-config.sublime-syntax
···
+
%YAML 1.2
+
---
+
# Standalone version of sshd-config.sublime-syntax
+
# Merged with: ssh-common.sublime-syntax, ssh-crypto.sublime-syntax
+
+
name: SSHD Config
+
scope: source.sshd_config
+
version: 2
+
file_extensions:
+
- sshd_config
+
variables:
+
base64_char: '[a-zA-Z0-9+/]'
+
ssh_fingerprint: (?:AAAA(?:E2V|[BC]3N){{base64_char}}+={0,3})
+
zero_to_32: (?:3[0-2]|[12][0-9]|[0-9])
+
zero_to_128: (?:12[0-8]|1[01][0-9]|[1-9][0-9]|[0-9])
+
zero_to_255: (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9][0-9])|(?:[1-9][0-9])|[0-9])
+
zero_to_65535: (?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])
+
ipv4: (?:(?:{{zero_to_255}}\.){3}{{zero_to_255}})
+
ipv6: "(?xi:\n (?:::(?:ffff(?::0{1,4}){0,1}:){0,1}{{ipv4}}) # ::255.255.255.255\
+
\ ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses\
+
\ and IPv4-translated addresses)\n |(?:(?:[0-9a-f]{1,4}:){1,4}:{{ipv4}}) \
+
\ # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 \
+
\ (IPv4-Embedded IPv6 Address)\n |(?:fe80:(?::[0-9a-f]{1,4}){0,4}%[0-9a-z]{1,})\
+
\ # fe80::7:8%eth0 fe80::7:8%1 \
+
\ (link-local IPv6 addresses with zone index)\n |(?:(?:[0-9a-f]{1,4}:){7,7}\
+
\ [0-9a-f]{1,4}) # 1:2:3:4:5:6:7:8\n | (?:[0-9a-f]{1,4}: (?::[0-9a-f]{1,4}){1,6})\
+
\ # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8\n |(?:(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5})\
+
\ # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8\n |(?:(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4})\
+
\ # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8\n |(?:(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3})\
+
\ # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8\n |(?:(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2})\
+
\ # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8\n |(?:(?:[0-9a-f]{1,4}:){1,6}\
+
\ :[0-9a-f]{1,4}) # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8\n\
+
\ |(?:(?:[0-9a-f]{1,4}:){1,7} :) # 1:: \
+
\ 1:2:3:4:5:6:7::\n |(?::(?:(?::[0-9a-f]{1,4}){1,7}|:)) \
+
\ # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::\n)"
+
all_parameters: "\\b(?xi:\n AcceptEnv | AddressFamily\n | Allow (?: AgentForwarding\
+
\ | Groups | StreamLocalForwarding\n | TcpForwarding | Users)\n | AuthenticationMethods\n\
+
\ | Authorized (?: Keys | Principals )(?: Command | CommandUser | File )\n |\
+
\ Banner\n | CASignatureAlgorithms | ChallengeResponseAuthentication\n | ChannelTimeout\
+
\ | ChrootDirectory | Ciphers | ClientAliveCountMax\n | ClientAliveInterval |\
+
\ Compression\n | DenyGroups | DenyUsers | DisableForwarding\n | ExposeAuthInfo\n\
+
\ | FingerprintHash | ForceCommand\n | GatewayPorts | GSSAPIAuthentication |\
+
\ GSSAPICleanupCredentials\n | GSSAPIStrictAcceptorCheck\n | Hostbased (?: AcceptedAlgorithms\
+
\ | AcceptedKeyTypes | Authentication\n | UsesNameFromPacketOnly\
+
\ )\n | HostCertificate | HostKey | HostKeyAgent | HostKeyAlgorithms\n | IgnoreRhosts\
+
\ | IgnoreUserKnownHosts | Include | IPQoS\n | KbdInteractiveAuthentication\n\
+
\ | Kerberos (?: Authentication | GetAFSToken | OrLocalPasswd\n |\
+
\ TicketCleanup )\n | KexAlgorithms | KeyRegenerationInterval\n | ListenAddress\
+
\ | LoginGraceTime | LogLevel | LogVerbose\n | MACs | Match | MaxAuthTries |\
+
\ MaxSessions | MaxStartups | ModuliFile\n | PasswordAuthentication | PAMServiceName\n\
+
\ | Permit (?: EmptyPasswords | Listen | Open | RootLogin | TTY | Tunnel\n \
+
\ | UserEnvironment | UserRC )\n | PerSource (?: MaxStartups | NetBlockSize\
+
\ | Penalties\n | PenaltyExemptList )\n | PidFile | Port | PrintLastLog\
+
\ | PrintMotd | Protocol\n | Pubkey (?: AcceptedAlgorithms | AcceptedKeyTypes\
+
\ | AuthOptions\n | Authentication )\n | RefuseConnection | RekeyLimit\
+
\ | RequiredRSASize | RevokedKeys | RDomain\n | RhostsRSAAuthentication | RSAAuthentication\n\
+
\ | SecurityKeyProvider | ServerKeyBits | SetEnv | ShowPatchLevel\n # SshdAuthPath\
+
\ and SshSessionPath are just for tests\n | StreamLocalBindMask | StreamLocalBindUnlink\n\
+
\ | StrictModes | Subsystem | SyslogFacility\n | TCPKeepAlive | TrustedUserCAKeys\n\
+
\ | UnusedConnectionTimeout | UseDNS | UseLogin | UsePAM\n | UsePrivilegeSeparation\n\
+
\ | VersionAddendum\n | X11DisplayOffset | X11Forwarding | X11UseLocalhost |\
+
\ XAuthLocation\n)\\b"
+
parameters_boolean: "\\b(?xi:\n AllowAgentForwarding\n | ChallengeResponseAuthentication\
+
\ | Compression\n | ExposeAuthInfo\n | GSSAPIAuthentication | GSSAPICleanupCredentials\n\
+
\ | GSSAPIStrictAcceptorCheck\n | HostbasedAuthentication | HostbasedUsesNameFromPacketOnly\n\
+
\ | IgnoreRhosts | IgnoreUserKnownHosts\n | KbdInteractiveAuthentication | KerberosAuthentication\n\
+
\ | KerberosGetAFSToken | KerberosOrLocalPasswd\n | KerberosTicketCleanup\n\
+
\ | PasswordAuthentication | PermitEmptyPasswords | PermitTTY\n | PermitUserEnvironment\
+
\ | PermitUserRC | PrintLastLog | PrintMotd\n | PubkeyAuthentication\n | RefuseConnection\n\
+
\ | StreamLocalBindUnlink | StrictModes\n | TCPKeepAlive\n | UseDNS | UsePAM\n\
+
\ | X11Forwarding | X11UseLocalhost\n)\\b"
+
contexts:
+
main:
+
- include: comments
+
- include: match
+
- include: parameters
+
comments:
+
- match: (#+)(?:\s*({{all_parameters}}))?
+
captures:
+
1: punctuation.definition.comment.sshd_config
+
2: meta.keyword.comment.sshd_config
+
push:
+
- meta_scope: comment.line.number-sign.sshd_config
+
- include: pop-nl
+
- match: (;+)(?:\s*({{all_parameters}}))?
+
captures:
+
1: punctuation.definition.comment.sshd_config
+
2: meta.keyword.comment.sshd_config
+
push:
+
- meta_scope: comment.line.semi-colon.sshd_config
+
- include: pop-nl
+
comments-number-sign:
+
- match: ^\s*(#+)
+
captures:
+
1: comment.line.number-sign.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.number-sign.ssh.common
+
- match: \n
+
scope: comment.line.number-sign.ssh.common
+
pop: true
+
comments-semicolon:
+
- match: ^\s*(;+)
+
captures:
+
1: comment.line.semi-colon.ssh.common punctuation.definition.comment.ssh.common
+
push:
+
- meta_content_scope: comment.line.semi-colon.ssh.common
+
- include: pop-nl
+
operator-exclamation:
+
- match: '!'
+
scope: keyword.operator.logical.ssh.common
+
wildcards:
+
- match: \*
+
scope: constant.other.wildcard.asterisk.ssh.common
+
- match: \?
+
scope: constant.other.wildcard.questionmark.ssh.common
+
punctuation-comma-sequence:
+
- match: ','
+
scope: punctuation.separator.sequence.ssh.common
+
punctuation-dot-sequence:
+
- match: \.
+
scope: punctuation.separator.sequence.ssh.common
+
punctuation-at:
+
- match: '@'
+
scope: punctuation.separator.sequence.ssh.common
+
ssh-fingerprint:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
ssh-fingerprint-with-label:
+
- match: '{{ssh_fingerprint}}'
+
scope: variable.other.fingerprint.ssh.common
+
push: expect-fingerprint-label
+
expect-fingerprint-label:
+
- include: pop-before-nl
+
- match: (?=\S)
+
push:
+
- meta_scope: meta.annotation.identifier.ssh.common string.unquoted.ssh.common
+
- match: (?=[ \t]*$)
+
pop: 1
+
- include: punctuation-at
+
time-values:
+
- match: \b(?=[\dsmhdw]*\d[smhdw][\s,"])
+
push:
+
- meta_scope: meta.constant.time.ssh.common meta.number.integer.decimal.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: (\d+)([smhdw])
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
bytes-values:
+
- match: \b(\d+)([KMG])(?=[\s,"])
+
scope: meta.constant.bytes.ssh.common meta.number.integer.other.ssh.common
+
captures:
+
1: constant.numeric.value.ssh.common
+
2: constant.numeric.suffix.ssh.common
+
mac-addresses:
+
- match: (?:[0-9a-fA-F]{2}:){5}(?:[0-9a-fA-F]{2})
+
scope: entity.name.constant.mac-address.ssh.common
+
ipv4:
+
- match: \b{{ipv4}}\b
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
ipv6:
+
- match: '{{ipv6}}'
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
ipv6-square-bracket:
+
- match: (\[){{ipv6}}(\])
+
scope: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
captures:
+
1: punctuation.definition.constant.begin.ssh.common
+
2: punctuation.definition.constant.end.ssh.common
+
ip-addresses:
+
- include: ipv6
+
- include: ipv4
+
ipv4-with-cidr:
+
- match: \b({{ipv4}})(?:(/)({{zero_to_32}}))?\b
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v4.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
ipv6-with-cidr:
+
- match: ({{ipv6}})(?:(/)({{zero_to_128}})\b)?
+
captures:
+
1: meta.number.integer.other.ssh.common constant.numeric.ip-address.v6.ssh.common
+
2: punctuation.separator.sequence.ssh.common
+
3: constant.other.range.ssh.common
+
ip-addresses-with-cidr:
+
- include: ipv6-with-cidr
+
- include: ipv4-with-cidr
+
port-numbers:
+
- match: \b{{zero_to_65535}}(?![\w:])
+
scope: meta.number.integer.decimal.ssh.common constant.numeric.port-number.ssh.common
+
match-all:
+
- match: '\b(?xi: all )\b'
+
scope: constant.language.boolean.true.ssh.common
+
none:
+
- match: \bnone\b
+
scope: constant.language.null.ssh.common
+
any:
+
- match: \bany\b
+
scope: constant.language.set.ssh.common
+
boolean:
+
- match: \byes\b
+
scope: constant.language.boolean.true.ssh.common
+
- match: \bno\b
+
scope: constant.language.boolean.false.ssh.common
+
boolean-with-typing:
+
- include: boolean
+
- match: \b(?:ye?|n)\b
+
log-level:
+
- match: '\b(?x: QUIET | FATAL | ERROR | INFO | DEBUG[1-3]? )\b'
+
scope: constant.language.log-level.ssh.common
+
possibly-quoted-value:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- match: '"'
+
scope: punctuation.definition.string.begin.sshd_config
+
push:
+
- meta_scope: string.quoted.double.sshd_config
+
- match: (")(?:\s*(\S.*))?
+
captures:
+
1: punctuation.definition.string.end.sshd_config
+
2: invalid.illegal.sshd_config
+
pop: 1
+
- match: \n|$
+
scope: invalid.illegal.unclosed-string.sshd_config
+
pop: 2
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.sshd_config
+
- include: pop-before-nl
+
- include: pop-nl
+
string-patterns:
+
- include: punctuation-comma-sequence
+
- include: operator-exclamation
+
- match: '"'
+
scope: punctuation.definition.string.begin.ssh.common
+
push:
+
- meta_content_scope: string.quoted.double.ssh.common
+
- match: '"'
+
scope: punctuation.definition.string.end.ssh.common
+
pop: 1
+
- include: wildcards
+
- match: (?=\S)
+
push:
+
- meta_content_scope: string.unquoted.ssh.common
+
- match: (?=[,!\s])
+
pop: 1
+
- include: wildcards
+
paths:
+
- match: (?=~?[\w.\-?*${}%]*/[\w.\-?*${}%]?)
+
push:
+
- meta_scope: meta.path.ssh.common entity.name.ssh.common
+
- match: (?=[\s,"])
+
pop: 1
+
- match: ~[\w\-.]*
+
scope: variable.language.home.ssh.common
+
- match: (/)(?:(\.{1,2})(?=/)|\.(?!/))?
+
captures:
+
1: punctuation.separator.path.ssh.common
+
2: constant.other.placeholder.ssh.common
+
- match: \.(?=[\w*?%])
+
scope: punctuation.separator.sequence.ssh.common
+
- include: wildcards
+
- include: tokens
+
- include: environment-variables
+
none-command-values:
+
- match: \s*(none)\b[ \t]*$
+
captures:
+
1: constant.language.null.ssh.common
+
- match: \s*((")(none)("))[ \t]*$
+
captures:
+
1: string.quoted.double.ssh.common
+
2: punctuation.definition.string.begin.ssh.common
+
3: constant.language.null.ssh.common
+
4: punctuation.definition.string.end.ssh.common
+
tokens:
+
- match: '%%'
+
scope: constant.character.escape.sshd_config
+
- match: '%[hUu]'
+
scope: constant.other.placeholder.sshd_config
+
environment-variables: []
+
pop-nl:
+
- match: \n
+
pop: 1
+
pop-before-nl:
+
- match: (?=\n)
+
pop: 1
+
ssh-ciphers:
+
- match: \b(?:twofish256\-gcm@libassh\.org|twofish256\-ctr|twofish192\-ctr|twofish128\-gcm@libassh\.org|twofish128\-ctr|twofish\-ctr|crypticore128@ssh\.com|chacha20\-poly1305@openssh\.com|chacha20\-poly1305|camellia256\-ctr@openssh\.org|camellia256\-ctr|camellia192\-ctr@openssh\.org|camellia192\-ctr|camellia128\-ctr@openssh\.org|camellia128\-ctr|aes256\-gcm@openssh\.com|aes256\-gcm|aes256\-ctr|aes192\-gcm@openssh\.com|aes192\-ctr|aes128\-gcm@openssh\.com|aes128\-gcm|aes128\-ctr|AEAD_CAMELLIA_256_GCM|AEAD_CAMELLIA_128_GCM|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.cipher.ssh.crypto
+
- match: \b(?:twofish256\-cbc|twofish192\-cbc|twofish128\-cbc|twofish\-ofb|twofish\-ecb|twofish\-cfb|twofish\-cbc|serpent256\-gcm@libassh\.org|serpent256\-ctr|serpent256\-cbc|serpent192\-ctr|serpent192\-cbc|serpent128\-gcm@libassh\.org|serpent128\-ctr|serpent128\-cbc|seed\-ctr@ssh\.com|seed\-cbc@ssh\.com|rijndael256\-cbc|rijndael192\-cbc|rijndael128\-cbc|rijndael\-cbc@ssh\.com|rijndael\-cbc@lysator\.liu\.se|none|idea\-ofb|idea\-ecb|idea\-ctr|idea\-cfb|idea\-cbc|grasshopper\-ctr128|des\-ofb|des\-ecb|des\-cfb|des\-cbc@ssh\.com|des\-cbc\-ssh1|des\-cbc|des|cast128\-ofb|cast128\-ecb|cast128\-ctr|cast128\-cfb|cast128\-cbc|cast128\-12\-ofb|cast128\-12\-ecb|cast128\-12\-ctr|cast128\-12\-cfb|cast128\-12\-cbc|camellia256\-cbc@openssh\.org|camellia256\-cbc|camellia192\-cbc@openssh\.org|camellia192\-cbc|camellia128\-cbc@openssh\.org|camellia128\-cbc|blowfish\-ecb|blowfish\-ctr|blowfish\-cfb|blowfish\-cbc|blowfish|arcfour256|arcfour128|arcfour|aes256\-cbc|aes192\-cbc|aes128\-ocb@libassh\.org|aes128\-cbc|3des\-ofb|3des\-ecb|3des\-ctr|3des\-cfb|3des\-cbc|3des)(?=[,\s\"])
+
scope: invalid.deprecated.cipher.ssh.crypto
+
ssh-kex-algorithms:
+
- match: \b(?:x25519\-kyber512\-sha512@aws\.amazon\.com|x25519\-kyber\-512r3\-sha256\-d00@amazon\.com|sntrup761x25519\-sha512@openssh\.com|sntrup4591761x25519\-sha512@tinyssh\.org|sm2kep\-sha2\-nistp256|rsa2048\-sha256|mlkem768x25519\-sha256|mlkem768nistp256\-sha256|mlkem1024nistp384\-sha384|m511\-sha512@libassh\.org|m383\-sha384@libassh\.org|kexguess2@matt\.ucc\.asn\.au|kexAlgoECDH521|kexAlgoECDH384|kexAlgoECDH256|kexAlgoCurve25519SHA256|kex\-strict\-s\-v00@openssh\.com|kex\-strict\-c\-v00@openssh\.com|gss\-nistp521\-sha512\-|gss\-nistp384\-sha384\-|gss\-nistp384\-sha256\-|gss\-nistp256\-sha256\-|gss\-group18\-sha512\-|gss\-group17\-sha512\-|gss\-group16\-sha512\-|gss\-group15\-sha512\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group15\-sha512\-|gss\-group14\-sha256\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha256\-|gss\-gex\-sha256\-|gss\-curve448\-sha512\-|gss\-curve25519\-sha256\-|gss\-13\.3\.132\.0\.10\-sha256\-|ext\-info\-s|ext\-info\-c|ecmqv\-sha2|ecdh\-sha2\-wiRIU8TKjMZ418sMqlqtvQ==|ecdh\-sha2\-qcFQaMAMGhTziMT0z\+Tuzw==|ecdh\-sha2\-nistt571|ecdh\-sha2\-nistp521|ecdh\-sha2\-nistp384|ecdh\-sha2\-nistp256|ecdh\-sha2\-nistp224|ecdh\-sha2\-nistp192|ecdh\-sha2\-nistk409|ecdh\-sha2\-nistk283|ecdh\-sha2\-nistb409|ecdh\-sha2\-mNVwCXAoS1HGmHpLvBC94w==|ecdh\-sha2\-m/FtSAmrV4j/Wy6RVUaK7A==|ecdh\-sha2\-h/SsxnLCtRBh7I9ATyeB3A==|ecdh\-sha2\-curve25519|ecdh\-sha2\-brainpoolp521r1@genua\.de|ecdh\-sha2\-brainpoolp384r1@genua\.de|ecdh\-sha2\-brainpoolp256r1@genua\.de|ecdh\-sha2\-D3FefCjYoJ/kfXgAyLddYA==|ecdh\-sha2\-9UzNcgwTlEnSCECZa7V1mw==|ecdh\-sha2\-1\.3\.132\.0\.38|ecdh\-sha2\-1\.3\.132\.0\.37|ecdh\-sha2\-1\.3\.132\.0\.36|ecdh\-sha2\-1\.3\.132\.0\.35|ecdh\-sha2\-1\.3\.132\.0\.34|ecdh\-sha2\-1\.3\.132\.0\.16|ecdh\-sha2\-1\.3\.132\.0\.10|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.7|ecdh\-nistp521\-kyber\-1024r3\-sha512\-d00@openquantumsafe\.org|ecdh\-nistp384\-kyber\-768r3\-sha384\-d00@openquantumsafe\.org|ecdh\-nistp256\-kyber\-512r3\-sha256\-d00@openquantumsafe\.org|diffie\-hellman_group17\-sha512|diffie\-hellman\-group18\-sha512@ssh\.com|diffie\-hellman\-group18\-sha512|diffie\-hellman\-group17\-sha512|diffie\-hellman\-group16\-sha512@ssh\.com|diffie\-hellman\-group16\-sha512|diffie\-hellman\-group16\-sha384@ssh\.com|diffie\-hellman\-group16\-sha256|diffie\-hellman\-group15\-sha512|diffie\-hellman\-group15\-sha384@ssh\.com|diffie\-hellman\-group15\-sha256@ssh\.com|diffie\-hellman\-group15\-sha256|diffie\-hellman\-group14\-sha256@ssh\.com|diffie\-hellman\-group14\-sha256|diffie\-hellman\-group14\-sha224@ssh\.com|diffie\-hellman\-group1\-sha256|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha512@ssh\.com|diffie\-hellman\-group\-exchange\-sha384@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256@ssh\.com|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha256|diffie\-hellman\-group\-exchange\-sha224@ssh\.com|curve448\-sha512@libssh\.org|curve448\-sha512|curve25519\-sha256@libssh\.org|curve25519\-sha256|Curve25519SHA256)(?=[,\s\"])
+
scope: support.function.kex-algorithm.ssh.crypto
+
- match: \b(?:rsa1024\-sha1|kexAlgoDH1SHA1|kexAlgoDH14SHA1|gss\-group14\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group14\-sha1\-|gss\-group1\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-group1\-sha1\-|gss\-gex\-sha1\-toWM5Slw5Ew8Mqkay\+al2g==|gss\-gex\-sha1\-|ecdh\-sha2\-zD/b3hu/71952ArpUG4OjQ==|ecdh\-sha2\-qCbG5Cn/jjsZ7nBeR7EnOA==|ecdh\-sha2\-nistk233|ecdh\-sha2\-nistk163|ecdh\-sha2\-nistb233|ecdh\-sha2\-VqBg4QRPjxx1EXZdV0GdWQ==|ecdh\-sha2\-5pPrSUQtIaTjUSt5VZNBjg==|ecdh\-sha2\-4MHB\+NBt3AlaSRQ7MnB4cg==|ecdh\-sha2\-1\.3\.132\.0\.33|ecdh\-sha2\-1\.3\.132\.0\.27|ecdh\-sha2\-1\.3\.132\.0\.26|ecdh\-sha2\-1\.3\.132\.0\.1|ecdh\-sha2\-1\.2\.840\.10045\.3\.1\.1|diffie\-hellman\-group14\-sha1|diffie\-hellman\-group1\-sha1|diffie\-hellman\-group\-exchange\-sha1)(?=[,\s\"])
+
scope: invalid.deprecated.kex-algorithm.ssh.crypto
+
ssh-key-types:
+
- match: \b(?:x509v3\-sign\-rsa\-sha512@ssh\.com|x509v3\-sign\-rsa\-sha384@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256@ssh\.com|x509v3\-sign\-rsa\-sha256|x509v3\-sign\-rsa\-sha224@ssh\.com|x509v3\-sign\-dss\-sha512@ssh\.com|x509v3\-sign\-dss\-sha384@ssh\.com|x509v3\-sign\-dss\-sha256@ssh\.com|x509v3\-sign\-dss\-sha224@ssh\.com|x509v3\-rsa2048\-sha256|x509v3\-ecdsa\-sha2\-nistp521|x509v3\-ecdsa\-sha2\-nistp384|x509v3\-ecdsa\-sha2\-nistp256|x509v3\-ecdsa\-sha2\-1\.3\.132\.0\.10|webauthn\-sk\-ecdsa\-sha2\-nistp256@openssh\.com|ssh\-rsa\-sha512@ssh\.com|ssh\-rsa\-sha384@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha256@ssh\.com|ssh\-rsa\-sha2\-512|ssh\-rsa\-sha2\-256|ssh\-rsa|ssh\-gost\-2012\-512|ssh\-gost\-2012\-256|ssh\-gost\-2001|ssh\-ed448|ssh\-ed25519\-cert\-v01@openssh\.com|ssh\-ed25519|spi\-sign\-rsa|sk\-ecdsa\-sha2\-nistp256@openssh\.com|sk\-ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|rsa\-sha2\-512\-cert\-v01@openssh\.com|rsa\-sha2\-512|rsa\-sha2\-256\-cert\-v01@openssh\.com|rsa\-sha2\-256|eddsa\-e521\-shake256@libassh\.org|eddsa\-e382\-shake256@libassh\.org|ecdsa\-sha2\-nistt571|ecdsa\-sha2\-nistp521\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp521|ecdsa\-sha2\-nistp384\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp384|ecdsa\-sha2\-nistp256\-cert\-v01@openssh\.com|ecdsa\-sha2\-nistp256|ecdsa\-sha2\-nistk409|ecdsa\-sha2\-nistk283|ecdsa\-sha2\-nistk233|ecdsa\-sha2\-nistk163|ecdsa\-sha2\-nistb409|ecdsa\-sha2\-curve25519|ecdsa\-sha2\-1\.3\.132\.0\.10\-cert\-v01@openssh\.com|ecdsa\-sha2\-1\.3\.132\.0\.10|dsa3072\-sha256@libassh\.org|dsa2048\-sha256@libassh\.org|dsa2048\-sha224@libassh\.org)(?=[,\s\"])
+
scope: support.type.key-type.ssh.crypto
+
- match: \b(?:x509v3\-ssh\-rsa|x509v3\-ssh\-dss|x509v3\-sign\-rsa\-sha1|x509v3\-sign\-rsa|x509v3\-sign\-dss\-sha1|x509v3\-sign\-dss|ssh\-xmss@openssh\.com|ssh\-xmss\-cert\-v01@openssh\.com|ssh\-rsa1|ssh\-rsa\-cert\-v01@openssh\.com|ssh\-rsa\-cert\-v00@openssh\.com|ssh\-dss\-sha512@ssh\.com|ssh\-dss\-sha384@ssh\.com|ssh\-dss\-sha256@ssh\.com|ssh\-dss\-sha224@ssh\.com|ssh\-dss\-cert\-v01@openssh\.com|ssh\-dss\-cert\-v00@openssh\.com|ssh\-dss|ssh\-dsa|spki\-sign\-rsa|spki\-sign\-dss|pgp\-sign\-rsa|pgp\-sign\-dss|null|ecdsa\-sha2\-nistp224|ecdsa\-sha2\-nistp192|ecdsa\-sha2\-nistb233)(?=[,\s\"])
+
scope: invalid.deprecated.key-type.ssh.crypto
+
ssh-mac-algorithms:
+
- match: \b(?:umac\-96@openssh\.com|umac\-64@openssh\.com|umac\-64\-etm@openssh\.com|umac\-32@openssh\.com|umac\-128@openssh\.com|umac\-128\-etm@openssh\.com|umac\-128|hmac\-sha512@ssh\.com|hmac\-sha512|hmac\-sha3\-512|hmac\-sha3\-384|hmac\-sha3\-256|hmac\-sha3\-224|hmac\-sha256@ssh\.com|hmac\-sha256\-96@ssh\.com|hmac\-sha256|hmac\-sha2\-56|hmac\-sha2\-512\-etm@openssh\.com|hmac\-sha2\-512\-96\-etm@openssh\.com|hmac\-sha2\-512|hmac\-sha2\-384|hmac\-sha2\-256\-etm@openssh\.com|hmac\-sha2\-256\-96\-etm@openssh\.com|hmac\-sha2\-256|hmac\-sha2\-224|crypticore\-mac@ssh\.com|chacha20\-poly1305@openssh\.com|cbcmac\-twofish|cbcmac\-aes|aes256\-gcm|aes128\-gcm|AEAD_AES_256_GCM|AEAD_AES_128_GCM)(?=[,\s\"])
+
scope: support.function.mac-algorithm.ssh.crypto
+
- match: \b(?:sha1\-8|sha1|ripemd160\-8|ripemd160|none|md5\-8|md5|hmac\-sha2\-512\-96|hmac\-sha2\-256\-96|hmac\-sha1\-etm@openssh\.com|hmac\-sha1\-96\-etm@openssh\.com|hmac\-sha1\-96|hmac\-sha1|hmac\-ripemd160@openssh\.com|hmac\-ripemd160\-etm@openssh\.com|hmac\-ripemd160\-96|hmac\-ripemd160|hmac\-ripemd|hmac\-md5\-etm@openssh\.com|hmac\-md5\-96\-etm@openssh\.com|hmac\-md5\-96|hmac\-md5|cbcmac\-rijndael|cbcmac\-des|cbcmac\-blowfish|cbcmac\-3des)(?=[,\s\"])
+
scope: invalid.deprecated.mac-algorithm.ssh.crypto
+
parameters:
+
- include: comments
+
- include: parameter-forcecommand
+
- include: parameter-authorizedkeyscommand
+
- include: parameter-authorizedprincipalscommand
+
- include: parameter-path-with-tokens
+
- include: parameter-routingdomain
+
- include: parameter-with-boolean-values
+
- include: parameter-generic
+
pop-before-match-option:
+
- include: pop-before-nl
+
- match: '(?=\s*(?xi: all | user | group | host | (?:local)? address | localport
+
)\b)'
+
pop: 1
+
pop-before-next-match:
+
- match: (?=^\s*(?i:Match)\b)
+
pop: 1
+
match:
+
- match: ^\s*((?i:Match))\b
+
captures:
+
1: keyword.control.conditional.sshd_config
+
set: match-conditions
+
match-conditions:
+
- meta_scope: meta.block.match.sshd_config
+
- meta_content_scope: meta.statement.conditional.sshd_config
+
- match: \n
+
set: match-body
+
- include: operator-exclamation
+
- include: match-all
+
- match: '\b(?xi: invalid-user )\b'
+
scope: constant.language.null.sshd_config
+
- match: '\b(?xi: host )\b'
+
scope: meta.mapping.key.sshd_config keyword.other.sshd_config
+
with_prototype:
+
- include: punctuation-dot-sequence
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-before-match-option
+
- include: string-patterns
+
- match: '\b(?xi: user | group )\b'
+
scope: meta.mapping.key.sshd_config keyword.other.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-before-match-option
+
- include: string-patterns
+
- match: '\b(?xi: (?:local)? address )\b'
+
scope: meta.mapping.key.sshd_config keyword.other.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-before-match-option
+
- include: operator-exclamation
+
- include: wildcards
+
- include: punctuation-comma-sequence
+
- include: ip-addresses-with-cidr
+
- match: '\b(?xi: localport )\b'
+
scope: meta.mapping.key.sshd_config keyword.other.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-before-match-option
+
- include: port-numbers
+
- match: '\b(?xi: rdomain )\b'
+
scope: meta.mapping.key.sshd_config keyword.other.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-before-match-option
+
- match: \b{{zero_to_255}}\b
+
scope: meta.number.integer.decimal.sshd_config constant.numeric.value.sshd_config
+
match-body:
+
- meta_content_scope: meta.block.match.sshd_config
+
- include: pop-before-next-match
+
- include: parameters
+
parameter-forcecommand:
+
- match: ^\s*((?i:ForceCommand))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-nl
+
- include: none-command-values
+
- match: '"'
+
scope: string.quoted.double.sshd_config punctuation.definition.string.begin.sshd_config
+
escape: (")|(?=$)
+
escape_captures:
+
1: meta.mapping.value.sshd_config string.quoted.double.sshd_config punctuation.definition.string.end.sshd_config
+
embed_scope: string.quoted.double.sshd_config
+
embed: scope:source.shell
+
- match: (?=\S)
+
escape: (?=$)
+
embed: scope:source.shell
+
parameter-authorizedkeyscommand:
+
- match: ^\s*((?i:AuthorizedKeysCommand))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-nl
+
- match: '"'
+
scope: string.quoted.double.sshd_config punctuation.definition.string.begin.sshd_config
+
escape: (")|(?=$)
+
escape_captures:
+
1: meta.mapping.value.sshd_config string.quoted.double.sshd_config punctuation.definition.string.end.sshd_config
+
embed_scope: string.quoted.double.ssh_config source.shell.embedded.ssh.authorizedkeyscommand
+
embed: scope:source.shell.embedded.ssh.authorizedkeyscommand
+
- match: (?=\S)
+
escape: (?=$)
+
embed: scope:source.shell.embedded.ssh.authorizedkeyscommand
+
parameter-authorizedprincipalscommand:
+
- match: ^\s*((?i:AuthorizedPrincipalsCommand))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
push:
+
- meta_content_scope: meta.mapping.value.sshd_config
+
- include: pop-nl
+
- match: '"'
+
scope: string.quoted.double.sshd_config punctuation.definition.string.begin.sshd_config
+
escape: (")|(?=$)
+
escape_captures:
+
1: meta.mapping.value.sshd_config string.quoted.double.sshd_config punctuation.definition.string.end.sshd_config
+
embed_scope: string.quoted.double.ssh_config source.shell.embedded.ssh.authorizedprincipalscommand
+
embed: scope:source.shell.embedded.ssh.authorizedprincipalscommand
+
- match: (?=\S)
+
escape: (?=$)
+
embed: scope:source.shell.embedded.ssh.authorizedprincipalscommand
+
parameter-path-with-tokens:
+
- match: '^\s*((?ix: AuthorizedKeysFile | AuthorizedPrincipalsFile | ChrootDirectory
+
))\b\s*(=)?'
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
with_prototype:
+
- include: tokens
+
- include: none
+
- include: paths
+
push: possibly-quoted-value
+
parameter-routingdomain:
+
- match: ^\s*((?i:RoutingDomain))\b\s*(=)?
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
with_prototype:
+
- match: '%D'
+
scope: constant.other.placeholder.sshd_config
+
- include: numeric-values
+
push: possibly-quoted-value
+
parameter-with-boolean-values:
+
- match: ^\s*({{parameters_boolean}})\s*(=)?
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
with_prototype:
+
- include: boolean-with-typing
+
- match: '[^"\s]+'
+
scope: invalid.illegal.sshd_config
+
push: possibly-quoted-value
+
parameter-generic:
+
- match: ^\s*([a-zA-Z1]+)\b\s*(=)?
+
captures:
+
1: meta.mapping.key.sshd_config keyword.other.sshd_config
+
2: keyword.operator.assignment.sshd_config
+
with_prototype:
+
- include: generic-parameter-values
+
push: possibly-quoted-value
+
generic-parameter-values:
+
- include: boolean
+
- include: none
+
- include: any
+
- match: '\b(?xi: default )\b'
+
scope: constant.language.default.sshd_config
+
- include: ssh-key-types
+
- include: ssh-ciphers
+
- include: ssh-kex-algorithms
+
- include: ssh-mac-algorithms
+
- include: ipv6-square-bracket
+
- include: ip-addresses-with-cidr
+
- include: time-values
+
- include: bytes-values
+
- include: operator-exclamation
+
- include: wildcards
+
- include: punctuation-comma-sequence
+
- include: log-level
+
- include: paths
+
- include: numeric-values
+
- match: ':'
+
scope: punctuation.separator.sequence.sshd_config
+
numeric-values:
+
- match: \b\d+(?=[\s,:"])
+
scope: constant.numeric.sshd_config
+51 -55
templates/footer.html
···
<hr />
<div id="footer-container">
-
<p style="margin-bottom: 0.5rem">
-
&copy; {{ now() | date(format="%Y") }} Kieran Klukas ||
-
<code id="visits">0</code> page visits || {% set hash =
-
get_env(name="CF_PAGES_COMMIT_SHA", default=load_data(path=".git/refs/heads/main", required=false))%}{% if hash is not string %}{% set hash = "unknown" %}{% endif %}<a href=https://tangled.sh/@dunkirk.sh/zera/commit/{{ hash }}>zera@{{ hash |
-
truncate(length=7, end="")}}</a>
-
</p>
-
<p style="margin-bottom: 0.5rem">
-
Webrings:
-
<a href="https://w.elr.sh">elr</a>
-
[<a href='javascript:void(0)' onclick='randomSite()'>random</a> |
-
<a href='#' id='prev-link'>prev</a> |
-
<a href='#' id='next-link'>next</a>] โ€ข
-
<a href="https://ctp-webr.ing">ctp</a>
-
[<a href="https://ctp-webr.ing/dunkirk/previous">prev</a> |
-
<a href="https://ctp-webr.ing/dunkirk/next">next</a>]
-
</p>
-
<p>
-
Content licensed under
-
<a
-
target="_blank"
-
rel="noopener noreferrer"
-
href="https://creativecommons.org/licenses/by-nc-sa/4.0/"
-
>CC BY-NC-SA 4.0</a
-
>
-
</p>
-
<p>
-
Code licensed under
-
<a
-
target="_blank"
-
rel="noopener noreferrer"
-
href="https://tangled.sh/@dunkirk.sh/zera/blob/main/LICENSE.md"
-
>AGPL 3.0</a
-
>
-
</p>
-
<script type="text/javascript" src="https://w.elr.sh/onionring-variables.js"></script>
-
<script>
-
thisSite = "https://dunkirk.sh"
-
thisIndex = null;
+
<p class="badge-row">
+
<a href="https://512kb.club"><img src="/badges/green-team.gif"
+
alt="a proud member of the green team of 512KB club" /></a>
+
<a href="https://hackclub.com"><img src="/badges/hackclub.png" alt="linux powered" /></a>
+
<a href="https://dunkirk.sh/ai"><img src="/badges/MadeByAHuman_04.svg" alt="made by a human" /></a>
+
<a href="https://tangled.org"><img src="/badges/tangled.png" alt="tangled beta" /></a>
+
<a href="https://www.netscape-communications.com/download/"><img src="/badges/get-netscape.gif"
+
alt="get netscape" /></a>
+
<a href="https://tangled.org/@dunkirk.sh/dots"><img src="/badges/powered-by-nix.gif" alt="powered by nix" /></a>
+
<a href="https://tangled.org/@dunkirk.sh/nixvim"><img src="/badges/made-with-neovim.png" /></a>
+
</p>
+
<p style="margin-bottom: 0.5rem">
+
&copy; {{ now() | date(format="%Y") }} Kieran Klukas ||
+
<code id="visits">0</code> page visits || {% set hash =
+
get_env(name="CF_PAGES_COMMIT_SHA", default=load_data(path=".git/refs/heads/main", required=false))%}{% if hash is
+
not string %}{% set hash = "unknown" %}{% endif %}<a href=https://tangled.sh/@dunkirk.sh/zera/commit/{{ hash
+
}}>zera@{{ hash |
+
truncate(length=7, end="")}}</a>
+
</p>
+
<p style="margin-bottom: 0.5rem">
+
Webrings:
+
<a href="https://w.elr.sh">elr</a>
+
[<a href='javascript:void(0)' onclick='randomSite()'>random</a> |
+
<a href='#' id='prev-link'>prev</a> |
+
<a href='#' id='next-link'>next</a>] โ€ข
+
<a href="https://ctp-webr.ing">ctp</a>
+
[<a href="https://ctp-webr.ing/dunkirk/previous">prev</a> |
+
<a href="https://ctp-webr.ing/dunkirk/next">next</a>]
+
</p>
-
for (i = 0; i < sites.length; i++) {
-
if (thisSite.startsWith(sites[i])) {
-
thisIndex = i;
-
break;
-
}
+
<script type="text/javascript" src="https://w.elr.sh/onionring-variables.js"></script>
+
<script>
+
thisSite = "https://dunkirk.sh"
+
thisIndex = null;
+
+
for (i = 0; i < sites.length; i++) {
+
if (thisSite.startsWith(sites[i])) {
+
thisIndex = i;
+
break;
}
+
}
-
function randomSite() {
-
otherSites = sites.slice();
-
otherSites.splice(thisIndex, 1);
-
randomIndex = Math.floor(Math.random() * otherSites.length);
-
location.href = otherSites[randomIndex];
-
}
+
function randomSite() {
+
otherSites = sites.slice();
+
otherSites.splice(thisIndex, 1);
+
randomIndex = Math.floor(Math.random() * otherSites.length);
+
location.href = otherSites[randomIndex];
+
}
-
previousIndex = (thisIndex-1 < 0) ? sites.length-1 : thisIndex-1;
-
nextIndex = (thisIndex+1 >= sites.length) ? 0 : thisIndex+1;
+
previousIndex = (thisIndex - 1 < 0) ? sites.length - 1 : thisIndex - 1;
+
nextIndex = (thisIndex + 1 >= sites.length) ? 0 : thisIndex + 1;
-
document.getElementById('prev-link').href = sites[previousIndex];
-
document.getElementById('next-link').href = sites[nextIndex];
-
</script>
-
</div>
+
document.getElementById('prev-link').href = sites[previousIndex];
+
document.getElementById('next-link').href = sites[nextIndex];
+
</script>
+
</div>
+14 -5
templates/head.html
···
type="text/css"
href="{{ get_url(path='css/main.css?' ~ cssHash, trailing_slash=false) | safe }}"
/>
-
{% endblock css %} {% set jsHash = get_hash(path="js/theme-toggle.js", sha_type=256,
+
{% endblock css %}
+
+
{% set jsHash = get_hash(path="js/copy-button.js", sha_type=256,
+
base64=true) %}
+
<script
+
src="{{ get_url(path='js/copy-button.js?' ~ jsHash, trailing_slash=false) | safe }}"
+
defer
+
></script>
+
+
{% set emojiJsHash = get_hash(path="js/emoji-replace.js", sha_type=256,
base64=true) %}
<script
-
src="{{ get_url(path='js/theme-toggle.js?' ~ jsHash, trailing_slash=false) | safe }}"
+
src="{{ get_url(path='js/emoji-replace.js?' ~ emojiJsHash, trailing_slash=false) | safe }}"
defer
></script>
-
{% set jsHash = get_hash(path="js/copy-button.js", sha_type=256,
+
{% set lightboxJsHash = get_hash(path="lightbox.js", sha_type=256,
base64=true) %}
<script
-
src="{{ get_url(path='js/copy-button.js?' ~ jsHash, trailing_slash=false) | safe }}"
+
src="{{ get_url(path='lightbox.js?' ~ lightboxJsHash, trailing_slash=false) | safe }}"
defer
></script>
···
<div class="p-adr h-adr">
<span class="p-country-name">United States of America</span>
</div>
-
<img class="u-photo" src="https://cachet.dunkirk.sh/users/U062UG485EE/r" alt="kieran with a white and gray spotted kitten with a grainy background and star dust" />
+
<img class="u-photo" src="/pfps/fall.jpg" alt="kieran wearing a robotics sweatshirt and standing in front of a tree with fall leaves" />
</div>
+17 -23
templates/header.html
···
-
{% if config.extra.header_nav %} {% if not current_url %} {% set current_url =
-
"" %} {% endif %}
+
{% if config.extra.header_nav %}
+
{% if page %}
+
{% set active_path = page.path | trim_end_matches(pat="/") %}
+
{% elif section %}
+
{% set active_path = section.path | trim_end_matches(pat="/") %}
+
{% elif current_path %}
+
{% set active_path = current_path | trim_end_matches(pat="/") %}
+
{% else %}
+
{% set active_path = "" %}
+
{% endif %}
<nav id="nav-bar">
-
{% for nav_item in config.extra.header_nav %}
-
<a
-
href="{{ nav_item.url }}"
-
class="{% if nav_item.url == current_url %}active{% endif %} text-glow"
-
>
-
{{ nav_item.name }}
-
</a>
-
{% endfor %}
-
<div>
-
<input type="checkbox" id="theme-toggle" style="display: none" />
-
<label for="theme-toggle" id="theme-toggle-label"
-
><i class="icon"></i
-
></label>
-
<audio id="theme-sound">
-
<source
-
src="{{ get_url(path='click.ogg', trailing_slash=false) | safe }}"
-
type="audio/ogg"
-
/>
-
</audio>
-
</div>
+
{% for nav_item in config.extra.header_nav %}
+
<a href="{{ nav_item.url }}"
+
class="{% if nav_item.url == active_path or (nav_item.url == '/' and active_path == '') %}active{% endif %}">
+
{{ nav_item.name }}
+
</a>
+
{% endfor %}
</nav>
-
{% endif %}
+
{% endif %}
+42
templates/shortcodes/callout.html
···
+
{%- set type = type | default(value="info") | lower -%}
+
{%- set title = title | default(value="") -%}
+
+
{%- if type == "info" -%}
+
{%- set icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>' -%}
+
{%- set color = "blue" -%}
+
{%- set default_title = "Info" -%}
+
{%- elif type == "warning" or type == "warn" -%}
+
{%- set icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>' -%}
+
{%- set color = "yellow" -%}
+
{%- set default_title = "Warning" -%}
+
{%- elif type == "danger" or type == "error" -%}
+
{%- set icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>' -%}
+
{%- set color = "red" -%}
+
{%- set default_title = "Danger" -%}
+
{%- elif type == "tip" or type == "hint" -%}
+
{%- set icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path></svg>' -%}
+
{%- set color = "green" -%}
+
{%- set default_title = "Tip" -%}
+
{%- elif type == "note" -%}
+
{%- set icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg>' -%}
+
{%- set color = "gray" -%}
+
{%- set default_title = "Note" -%}
+
{%- else -%}
+
{%- set icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>' -%}
+
{%- set color = "blue" -%}
+
{%- set default_title = "Info" -%}
+
{%- endif -%}
+
+
{%- if title == "" -%}
+
{%- set title = default_title -%}
+
{%- endif -%}
+
+
<div class="callout callout-{{ color }}">
+
<div class="callout-title">
+
<span class="callout-icon">{{ icon | safe }}</span>
+
<strong>{{ title }}</strong>
+
</div>
+
<div class="callout-content">
+
{{ body | markdown | safe }}
+
</div>
+
</div>
+3 -1
templates/shortcodes/img.html
···
<figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}>
-
<img src="{{id}}" {% if alt %}alt="{{alt}}" {% endif %} />
+
<div class="img-container" onclick="openLightbox('{{id}}')">
+
<img src="{{id}}" {% if alt %}alt="{{alt}}" {% endif %} />
+
</div>
{% if caption %}
<figcaption>{{caption | markdown | safe}}</figcaption>
{% endif %}
+14
templates/shortcodes/imgs.html
···
+
<figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}>
+
<div class="img-group" data-images="{{id}}" data-alts="{{alt | default(value='')}}">
+
{% set images = id | split(pat=",") %}
+
{% set alts = alt | default(value="") | split(pat=",") %}
+
{% for image in images %}
+
<div class="img-container" onclick="openLightboxGroup(this)">
+
<img src="{{image | trim}}" {% if alts[loop.index0] %}alt="{{alts[loop.index0] | trim}}" {% endif %} />
+
</div>
+
{% endfor %}
+
</div>
+
{% if caption %}
+
<figcaption>{{caption | markdown | safe}}</figcaption>
+
{% endif %}
+
</figure>
-72
test-code-blocks.html
···
-
<!DOCTYPE html>
-
<html lang="en">
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>Code Block Test</title>
-
<link rel="stylesheet" href="sass/css/reset.css">
-
<link rel="stylesheet" href="sass/css/suCSS.css">
-
<link rel="stylesheet" href="sass/css/mods.css">
-
<link rel="stylesheet" href="sass/css/syntax-theme.css">
-
<link rel="stylesheet" href="sass/css/code-blocks.css">
-
<style>
-
body {
-
padding: 20px;
-
}
-
h1 {
-
margin-bottom: 30px;
-
}
-
.test-section {
-
margin-bottom: 40px;
-
}
-
</style>
-
</head>
-
<body>
-
<h1>Code Block Styling Test</h1>
-
-
<div class="test-section">
-
<h2>With JavaScript (Copy Button)</h2>
-
<blockquote>A comment about this code</blockquote>
-
<pre class="language-javascript" data-lang="javascript">
-
<code>// This is some JavaScript code
-
function sayHello() {
-
console.log("Hello, world!");
-
}
-
-
// Call the function
-
sayHello();</code></pre>
-
</div>
-
-
<div class="test-section">
-
<h2>Without JavaScript (No Copy Button)</h2>
-
<blockquote>A comment about this CSS</blockquote>
-
<pre class="language-css" data-lang="css">
-
<code>/* This is some CSS code */
-
.container {
-
display: flex;
-
flex-direction: column;
-
justify-content: center;
-
}
-
-
.item {
-
color: var(--accent);
-
margin: 10px;
-
}</code></pre>
-
</div>
-
-
<div class="test-section">
-
<h2>Regular Pre Tag (No Language)</h2>
-
<pre>
-
<code>This is a regular code block
-
with no language specified
-
and no copy button.</code></pre>
-
</div>
-
-
<div class="test-section">
-
<h2>Inline Code Example</h2>
-
<p>Here's some <code>inline code</code> that should be styled differently.</p>
-
</div>
-
-
<script src="static/js/copy-button.js"></script>
-
</body>
-
</html>
-233
tools/bun.lock
···
-
{
-
"lockfileVersion": 1,
-
"workspaces": {
-
"": {
-
"name": "zera",
-
"dependencies": {
-
"dotenv": "^16.4.7",
-
},
-
"devDependencies": {
-
"@types/bun": "latest",
-
"puppeteer": "^23.6.0",
-
},
-
"peerDependencies": {
-
"typescript": "^5.0.0",
-
},
-
},
-
},
-
"packages": {
-
"@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
-
-
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
-
-
"@puppeteer/browsers": ["@puppeteer/browsers@2.6.1", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg=="],
-
-
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
-
-
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
-
-
"@types/node": ["@types/node@22.13.4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="],
-
-
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
-
-
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
-
-
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
-
-
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
-
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
-
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
-
-
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
-
-
"b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="],
-
-
"bare-events": ["bare-events@2.5.4", "", {}, "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA=="],
-
-
"bare-fs": ["bare-fs@4.0.1", "", { "dependencies": { "bare-events": "^2.0.0", "bare-path": "^3.0.0", "bare-stream": "^2.0.0" } }, "sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg=="],
-
-
"bare-os": ["bare-os@3.4.0", "", {}, "sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA=="],
-
-
"bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="],
-
-
"bare-stream": ["bare-stream@2.6.5", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA=="],
-
-
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
-
-
"basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="],
-
-
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
-
-
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
-
-
"bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
-
-
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
-
-
"chromium-bidi": ["chromium-bidi@0.11.0", "", { "dependencies": { "mitt": "3.0.1", "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA=="],
-
-
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
-
-
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
-
-
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
-
-
"cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="],
-
-
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
-
-
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
-
-
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
-
-
"devtools-protocol": ["devtools-protocol@0.0.1367902", "", {}, "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg=="],
-
-
"dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
-
-
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
-
"end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
-
-
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
-
-
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
-
-
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
-
-
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
-
-
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
-
-
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
-
-
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
-
-
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
-
-
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
-
-
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
-
-
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
-
-
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
-
-
"get-uri": ["get-uri@6.0.4", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ=="],
-
-
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
-
-
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
-
-
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
-
-
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
-
-
"ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="],
-
-
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
-
-
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
-
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
-
-
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
-
-
"jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="],
-
-
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
-
-
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
-
-
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
-
-
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
-
-
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
-
-
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
-
-
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
-
-
"pac-proxy-agent": ["pac-proxy-agent@7.1.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw=="],
-
-
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
-
-
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
-
-
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
-
-
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
-
-
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
-
-
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
-
-
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
-
-
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
-
-
"pump": ["pump@3.0.2", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="],
-
-
"puppeteer": ["puppeteer@23.11.1", "", { "dependencies": { "@puppeteer/browsers": "2.6.1", "chromium-bidi": "0.11.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1367902", "puppeteer-core": "23.11.1", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw=="],
-
-
"puppeteer-core": ["puppeteer-core@23.11.1", "", { "dependencies": { "@puppeteer/browsers": "2.6.1", "chromium-bidi": "0.11.0", "debug": "^4.4.0", "devtools-protocol": "0.0.1367902", "typed-query-selector": "^2.12.0", "ws": "^8.18.0" } }, "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg=="],
-
-
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
-
-
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
-
-
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
-
-
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
-
-
"socks": ["socks@2.8.4", "", { "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" } }, "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ=="],
-
-
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
-
-
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
-
-
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
-
-
"streamx": ["streamx@2.22.0", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw=="],
-
-
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
-
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
-
"tar-fs": ["tar-fs@3.0.8", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg=="],
-
-
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
-
-
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
-
-
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
-
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
-
-
"typed-query-selector": ["typed-query-selector@2.12.0", "", {}, "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg=="],
-
-
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
-
-
"unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="],
-
-
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
-
-
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
-
-
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
-
-
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
-
-
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
-
-
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
-
-
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
-
-
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
-
-
"zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
-
}
-
}
-122
tools/genOG.ts
···
-
import puppeteer from "puppeteer";
-
import { readdir, mkdir } from "node:fs/promises";
-
-
const template = await Bun.file("tools/og.html").text();
-
-
const browser = await puppeteer.launch({
-
args: ["--no-sandbox"],
-
executablePath: process.env.PUPPETEER_EXEC_PATH, // set by docker container
-
});
-
-
async function og(
-
postname: string,
-
type: string,
-
by: string | undefined,
-
outputPath: string,
-
width = 1200,
-
height = 630,
-
) {
-
const page = await browser.newPage();
-
-
await page.setViewport({ width, height });
-
-
await page.setContent(
-
template
-
.toString()
-
.replace("{{postname}}", postname)
-
.replace("{{type}}", type)
-
.replace("{{by}}", by || ""),
-
);
-
-
await page.screenshot({ path: outputPath });
-
}
-
-
async function fileExists(path: string): Promise<boolean> {
-
try {
-
await Bun.file(path);
-
return true;
-
} catch (e) {
-
return false;
-
}
-
}
-
-
try {
-
// check if the public/blog folder exists
-
// if not exit
-
// if it does, get all the folders and then get the title tag from the index.html
-
-
if (!(await fileExists("public/"))) {
-
console.error("public/ does not exist");
-
process.exit(1);
-
}
-
-
// read all the files in the current directory filtering for index.htmls
-
const files = (await readdir("public/", { recursive: true })).filter((file) =>
-
file.endsWith("index.html"),
-
);
-
-
const directories = new Set(
-
files.map((file) => file.replace("index.html", "")),
-
);
-
-
const existing = (await readdir("static/")).filter((file) =>
-
directories.has(file),
-
);
-
-
// create not existing
-
for (const dir of directories) {
-
if (!existing.includes(dir)) {
-
await mkdir(`static/${dir.split("/").slice(0, -1).join("/")}`, {
-
recursive: true,
-
});
-
}
-
}
-
-
console.log("Generating OG images for", files.length, "files");
-
-
// for each file, get the title tag from the index.html
-
for (const file of files) {
-
const index = await Bun.file(`public/${file}`).text();
-
const title = index.match(/<title>(.*?)<\/title>/)[1];
-
let type = "Page";
-
let by: string | undefined;
-
switch (file.split("/")[0]) {
-
case "blog":
-
type = "Blog";
-
if (file.split("/")[1] !== "index.html") {
-
by = "<p>A post ... yeah thats about it</p>";
-
} else {
-
by = "<p>All authored by me ... or are they???</p>";
-
}
-
break;
-
case "verify":
-
type = "Slash Page";
-
by = "<p>So you can stalk me ๐Ÿ’€</p>";
-
break;
-
case "pfp":
-
type = "Slash Page";
-
by = "<p>Want to stare at my pretty face?</p>";
-
break;
-
case "tags":
-
if (file.split("/")[1] === "index.html") {
-
type = "Tags";
-
by = "<p>A total archive!</p>";
-
} else {
-
type = "Tag";
-
by = "<p>Find more posts like this!</p>";
-
}
-
break;
-
case "index.html":
-
type = "Root";
-
by = "<p>Where it all begins</p>";
-
break;
-
}
-
-
console.log("Generating OG for", file, "title:", title, "with type:", type);
-
await og(title, type, by, `static/${file.replace("index.html", "og.png")}`);
-
}
-
} catch (e) {
-
console.error(e);
-
} finally {
-
await browser.close();
-
}
-111
tools/og.html
···
-
<!DOCTYPE html>
-
<html>
-
<head>
-
<style>
-
:root,
-
::backdrop {
-
color-scheme: dark;
-
--bg: #222529;
-
--bg-light: #464949;
-
--text: #d6d6d6;
-
--text-light: #c5c0b7;
-
--accent: #78b6ad;
-
--accent-light: #87c9e5;
-
--accent-text: var(--bg);
-
--border: #dbd5bc;
-
--link: #e2c8a2;
-
--gradient-average-light: oklch(86.49% 0.018 73.05);
-
--gradient-average-dark: oklch(27.58% 0.0203 289.13);
-
--nightshade-violet: oklch(22.96% 0.0242 287.67);
-
--purple-night: oklch(18.96% 0.0242 287.67);
-
--dark-crushed-grape: oklch(74.02% 0.0756 311.96);
-
--light-crushed-grape: oklch(73.48% 0.1008 284.99);
-
--reseda-green: oklch(62.33% 0.0475 126.94);
-
--earth-yellow: oklch(87.45% 0.0203 74.93);
-
--sunset: oklch(87.45% 0.0334 74.93);
-
--ultra-violet: oklch(42.21% 0.0676 297.45);
-
--rose-quartz: oklch(65.32% 0.0585 311.96);
-
--pink-puree: oklch(75.65% 0.0555 290.76);
-
--lavendar-breeze: oklch(91.06% 0.0223 290.76);
-
--purple-gray: oklch(25.63% 0.0002 290.76);
-
--alice-blue: oklch(95.38% 0.0118 239.91);
-
}
-
-
body {
-
font-weight: 600;
-
color: var(--lavendar-breeze);
-
background-color: var(--purple-night);
-
font-family: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono",
-
monospace;
-
display: flex;
-
flex-direction: column;
-
text-align: center;
-
}
-
-
div {
-
margin: 0;
-
display: flex;
-
flex-direction: column;
-
align-items: center;
-
justify-content: center;
-
height: 90vh; /* 90% of viewport height */
-
width: 90vw; /* 90% of viewport width */
-
padding: 5vh 5vw; /* 5% border on all sides */
-
box-sizing: border-box;
-
align-self: center;
-
}
-
-
h1 {
-
font-size: calc(2 * 2vw);
-
}
-
h2 {
-
font-size: calc(1.75 * 2vw);
-
}
-
h3 {
-
font-size: calc(1.5 * 2vw);
-
}
-
h4 {
-
font-size: calc(1.25 * 2vw);
-
}
-
h5 {
-
font-size: calc(1 * 2vw);
-
}
-
h6 {
-
font-size: calc(0.75 * 2vw);
-
}
-
-
h1,
-
h2,
-
h3,
-
h4,
-
h5,
-
h6 {
-
margin: 0.5em 0 0.5em 0;
-
padding: 0.22em 0.4em 0.22em 0.4em;
-
border-radius: 0.1em;
-
width: fit-content;
-
color: var(--lavendar-breeze);
-
}
-
-
h1 {
-
background-color: var(--rose-quartz);
-
color: var(--purple-gray);
-
}
-
-
p {
-
margin: 1rem 0;
-
color: var(--pink-puree);
-
font-size: calc(
-
1rem + 1vw
-
); /* Adjust font size based on viewport width */
-
}
-
</style>
-
</head>
-
<body>
-
<div>
-
<h1>{{type}}</h1>
-
<h2>{{postname}}</h2>
-
{{by}}
-
</div>
-
</body>
-
</html>
-18
tools/package.json
···
-
{
-
"name": "zera",
-
"module": "index.ts",
-
"type": "module",
-
"scripts": {
-
"gen": "bun genOG.ts"
-
},
-
"devDependencies": {
-
"@types/bun": "latest",
-
"puppeteer": "^23.6.0"
-
},
-
"peerDependencies": {
-
"typescript": "^5.0.0"
-
},
-
"dependencies": {
-
"dotenv": "^16.4.7"
-
}
-
}