diff --git a/.sops.yaml b/.sops.yaml
index ee31af7..7314a89 100644
--- a/.sops.yaml
+++ b/.sops.yaml
@@ -21,27 +21,6 @@ keys:
- &toto age16vzhcvz8tyxj8e0f47fy0z4p3dsg0ak4vl52ut3l07a0tz465cxslmhevl
- &twothreetunnel age1g7atkxdlt4ymeh7v7aa2yzr2hq2qkvzrc4r49ugttm3n582ymv9qrmpk8d
- &winters age1s0vssf9fey2l456hucppzx2x58xep279nsdcglvkqm30sr9ht37s8rvpza
- - &summers-ankisync age1kyue7mfvzuxprjz2g6ulz2mxlr57rgzg6lfpnrqedkelehley5ls3enwsd
- - &summers-atuin age1qpgj3ell93rzkpjq0ezs6t669ds3nyxx67pj50smx597pspz6fqs4jc6pt
- - &summers-audio age1f63r2klnpfxmntswz5xydpa75ckgjqcs2yzkm0msqwqgz9aqgu0qwzr659
- - &summers-firefly age17328xwk0z3znalpmma5rvp0lt5ghn5p8xfvnrtdxwsw80dqysacqj9j37q
- - &summers-forgejo age1qdzkn6v3xhrfjwe8jxz3945dhyyhevwal0narjtr8whf9y7nh3wsn524u5
- - &summers-freshrss age1etgfym5m8hn3hxs6cgg757zcv5zg5n22wq38fuq59n7qk7nef5uqyg6vvs
- - &summers-homebox age17mugmkdw0y768a3huuf37r45eff9apyknxvwk3agg6xzsjmqp96q57tcty
- - &summers-immich age16gf76uustmyyksm3t56zcq9g6j8avy0wrngh8laknfq733s5welqedeg4x
- - &summers-jellyfin age1fnvlmhzju0yq908xtgags0sy85q3tacl2sc3w3vdd3yfp27xv5aq06v948
- - &summers-kanidm age1s5gcxtatd9frwctzwg54fqycsx2sa73ll36k7qrpm9wwyknkldtst90gn4
- - &summers-kavita age1d89878cvt7wsa07ydwtexspku5gppwstrpnpph4ufx5pcd4fadyqgf6lvl
- - &summers-koillection age1ayupuxlrkepyvjk7xwgrd0pvcj3tfcha688mcuc8ees2hg3g2ersd0q3nc
- - &summers-matrix age1cq7wxnugpfvjk6dgqpfmc8vemzhkg75drkgeaqjd9fuylz5qh40slazr4u
- - &summers-monitoring age1vn6ya0japzpgc256jg57fldsqe4udmq50sj5hmkywn7rxfnskevsx2q96u
- - &summers-nextcloud age1t7zagjfddns4yltupk7nx8xps4gh7mupyz85uuys0wd22cxj5qsq2hw0p7
- - &summers-paperless age1rn0pxluh7m8dyeshek06d7scejqlrcewlk8xmyrwt5e5nev2dc2s3s78vq
- - &summers-postgresql age12jh5836w3cmazec8ql652p9h3a3xn6quztztzqxg4n0kz7r96dnqqlhxxw
- - &summers-radicale age1gxg2peektn8x36kk3nsgmeawl73e54kaadqd649ygwrv43kkvejq2cw64z
- - &summers-storage age1kn34ny229gm0rg7wlcvxmcyjtz4gka6f2vd958fde6vmuzrxcvcsufra90
- - &summers-transmission age1y69f2elvmq39lc3t3ucq9y7wt675520n7rvug88qg368qsmmk47qvwrtny
-
creation_rules:
- path_regex: secrets/repo/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -63,26 +42,6 @@ creation_rules:
- *dgx
- *hintbooth-adguardhome
- *hintbooth-nginx
- - *summers-ankisync
- - *summers-atuin
- - *summers-audio
- - *summers-firefly
- - *summers-forgejo
- - *summers-freshrss
- - *summers-homebox
- - *summers-immich
- - *summers-jellyfin
- - *summers-kanidm
- - *summers-kavita
- - *summers-koillection
- - *summers-matrix
- - *summers-monitoring
- - *summers-nextcloud
- - *summers-paperless
- - *summers-postgresql
- - *summers-radicale
- - *summers-storage
- - *summers-transmission
- path_regex: secrets/work/[^/]+\.(yaml|json|env|ini)$
key_groups:
@@ -200,7 +159,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-ankisync
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/atuin/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -208,7 +166,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-atuin
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/audio/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -216,7 +173,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-audio
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/firefly/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -224,7 +180,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-firefly
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/forgejo/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -232,7 +187,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-forgejo
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/freshrss/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -240,7 +194,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-freshrss
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/homebox/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -248,7 +201,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-homebox
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/immich/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -256,7 +208,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-immich
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/jellyfin/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -264,7 +215,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-jellyfin
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/kanidm/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -272,7 +222,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-kanidm
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/kavita/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -280,7 +229,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-kavita
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/koillection/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -288,7 +236,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-koillection
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/matrix/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -296,7 +243,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-matrix
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/monitoring/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -304,7 +250,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-monitoring
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/nextcloud/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -312,7 +257,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-nextcloud
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/paperless/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -320,7 +264,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-paperless
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/postgresql/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -328,7 +271,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-postgresql
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/radicale/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -336,7 +278,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-radicale
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/storage/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -344,7 +285,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-storage
- path_regex: hosts/nixos/x86_64-linux/summers/secrets/transmission/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
@@ -352,7 +292,6 @@ creation_rules:
- *swarsel
age:
- *summers
- - *summers-transmission
- path_regex: hosts/darwin/x86_64-darwin/nbm-imba-166/secrets/[^/]+\.(yaml|json|env|ini|enc)$
key_groups:
diff --git a/SwarselSystems.org b/SwarselSystems.org
index 23924af..c7be594 100644
--- a/SwarselSystems.org
+++ b/SwarselSystems.org
@@ -61,12 +61,7 @@ That is the reason why I keep this configuration as a literate one: so that I am
For a beginner, I recommend to read this file like a book, from start to finish. I will try to explain concepts whenever they first come up, and will regularly link to [[#h:8ea35dcc-ef94-4c10-9112-8be8efd6f424][Appendix C: Explanations to nix functions and operators]] when more context is needed. For the first few times that I am using a new function, I will place such a link again. However, to keep the writing of this file manageable, I will generally only do this no more than three times.
-This page offers some utilities to you:
-- you can pin specific headings to the right "pinned" bar by hovering over the heading and clicking =[pin]=
-- If a section seems uninteresting to you, you can press the =↓= button to skip to the next one.
-- If a sections upcoming subsections seem uninteresting to you, you can press the =⇣= button to skip to the next heading of at least same or higher level.
-- If you are currently in a level of subsections that all seem uninteresting to you, you can press the =⇑= button to skip to the next heading that is at least one level higher.
-- And if you want to send a section to somebody else, you can click the =#= in order to copy its link to the clipboard. Your pinned headings will be saved locally, so you can continue reading in case you take a break.
+This page offers some utilities to you: you can pin specific headings to the right "pinned" bar by hovering over the heading and clicking =[pin]=. If a section seems uninteresting to you, you can press the =↓= button to skip to the next one. And if you want to send a section to somebody else, you can click the =#= in order to copy its link to the clipboard. Your pinned headings will be saved locally, so you can continue reading in case you take a break.
** Structure of this file
:PROPERTIES:
@@ -188,7 +183,7 @@ I add a javascript bit to the file in order to have a darkmode toggle when expor
I also add this javascript to add header pinning functionality to the site, using the same trick as above (this is defined in [[#h:e5f900a0-9d68-4269-a663-53c52434c342][HTML Export: Docs QoL]]):
-#+begin_src elisp :tangle no :noweb yes :exports both :results html
+#+begin_src elisp :noweb yes :exports both :results html
"
#+end_src
-** HTML export javascript: Docs QoL
+** HTML Export: Docs QoL
:PROPERTIES:
:CUSTOM_ID: h:e5f900a0-9d68-4269-a663-53c52434c342
:END:
@@ -33748,562 +33447,502 @@ This adds the following functionalities to the [[#h:12880c64-229c-4063-9eea-387a
- Section pinning with persistent pins
- Copy section links to clipboard button
- Searching in table of contents
-- Skip to next section buttons (one for simply next section, one for same level, one for at least higher level)
+- Skip to next section button
#+begin_src javascript :noweb-ref js-docs-qol :exports code
(function() {
- function ready(fn) {
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', fn);
- } else {
- fn();
- }
+ function ready(fn) {
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', fn);
+ } else {
+ fn();
+ }
+ }
+
+ ready(function initPinned() {
+ const STORAGE_KEY = 'org-pinned-items-v2';
+
+ let pinnedPanel = document.getElementById('pinned-panel');
+ if (!pinnedPanel) {
+ pinnedPanel = document.createElement('aside');
+ pinnedPanel.id = 'pinned-panel';
+ pinnedPanel.innerHTML = `
+
+ Clear All
+
+ `;
+ document.body.appendChild(pinnedPanel);
}
- ready(function initPinned() {
- const STORAGE_KEY = 'org-pinned-items-v2';
+ let showBtn = document.getElementById('show-pinned-btn');
+ if (!showBtn) {
+ showBtn = document.createElement('button');
+ showBtn.id = 'show-pinned-btn';
+ showBtn.type = 'button';
+ showBtn.textContent = 'Pinned';
+ document.body.appendChild(showBtn);
+ }
- let pinnedPanel = document.getElementById('pinned-panel');
- if (!pinnedPanel) {
- pinnedPanel = document.createElement('aside');
- pinnedPanel.id = 'pinned-panel';
- pinnedPanel.innerHTML = `
-
- Clear All
-
- `;
- document.body.appendChild(pinnedPanel);
- }
+ const content = document.getElementById('content');
+ const pinnedList = document.getElementById('pinned-list');
+ const toggleBtn = document.getElementById('toggle-pinned-btn');
+ const clearAllBtn = document.getElementById('clear-all-pins-btn');
+ const toc = document.getElementById('table-of-contents');
+ const body = document.body;
- let showBtn = document.getElementById('show-pinned-btn');
- if (!showBtn) {
- showBtn = document.createElement('button');
- showBtn.id = 'show-pinned-btn';
- showBtn.type = 'button';
- showBtn.textContent = 'Pinned';
- document.body.appendChild(showBtn);
- }
+ if (!content || !pinnedList || !toggleBtn || !clearAllBtn || !toc) return;
- const content = document.getElementById('content');
- const pinnedList = document.getElementById('pinned-list');
- const toggleBtn = document.getElementById('toggle-pinned-btn');
- const clearAllBtn = document.getElementById('clear-all-pins-btn');
- const toc = document.getElementById('table-of-contents');
- const body = document.body;
+ function injectSearch() {
+ // Check if already injected
+ if (document.getElementById('toc-search-input')) return;
- if (!content || !pinnedList || !toggleBtn || !clearAllBtn || !toc) return;
+ const searchContainer = document.createElement('div');
+ searchContainer.id = 'toc-search-container';
- function injectSearch() {
- if (document.getElementById('toc-search-input')) return;
+ const searchInput = document.createElement('input');
+ searchInput.id = 'toc-search-input';
+ searchInput.type = 'text';
+ searchInput.placeholder = 'Search TOC...';
+ searchInput.autocomplete = 'off';
- const searchContainer = document.createElement('div');
- searchContainer.id = 'toc-search-container';
+ const clearBtn = document.createElement('button');
+ clearBtn.id = 'toc-search-clear';
+ clearBtn.type = 'button';
+ clearBtn.textContent = 'Clear';
- const searchInput = document.createElement('input');
- searchInput.id = 'toc-search-input';
- searchInput.type = 'text';
- searchInput.placeholder = 'Search TOC...';
- searchInput.autocomplete = 'off';
+ searchContainer.appendChild(searchInput);
+ searchContainer.appendChild(clearBtn);
- const clearBtn = document.createElement('button');
- clearBtn.id = 'toc-search-clear';
- clearBtn.type = 'button';
- clearBtn.textContent = 'Clear';
+ toc.insertBefore(searchContainer, toc.firstChild);
- searchContainer.appendChild(searchInput);
- searchContainer.appendChild(clearBtn);
+ function filterTOC(term) {
+ const allLinks = toc.querySelectorAll('a');
- toc.insertBefore(searchContainer, toc.firstChild);
+ allLinks.forEach(link => {
+ const li = link.closest('li');
+ if (!li) return;
- function filterTOC(term) {
- const allLinks = toc.querySelectorAll('a');
+ const text = link.textContent.toLowerCase();
+ const matches = text.includes(term);
- allLinks.forEach(link => {
- const li = link.closest('li');
- if (!li) return;
-
- const text = link.textContent.toLowerCase();
- const matches = text.includes(term);
-
- if (matches) {
- li.classList.remove('hidden-by-search');
- let parent = li.parentElement;
- while (parent && parent !== toc) {
- if (parent.tagName === 'UL') {
- parent.style.display = '';
- }
- if (parent.tagName === 'LI') {
- parent.classList.remove('hidden-by-search');
- }
- parent = parent.parentElement;
- }
- } else {
- li.classList.add('hidden-by-search');
- }
- });
-
- if (term === '') {
- const allLis = toc.querySelectorAll('li');
- allLis.forEach(li => li.classList.remove('hidden-by-search'));
- }
- }
-
- searchInput.addEventListener('input', function(e) {
- const term = e.target.value.toLowerCase();
- filterTOC(term);
- });
-
- clearBtn.addEventListener('click', function() {
- searchInput.value = '';
- filterTOC('');
- searchInput.focus();
- });
- }
- injectSearch();
-
- function addHeadingLinks() {
- const headers = content.querySelectorAll('h1, h2, h3, h4, h5, h6, h7, h8, h9');
- headers.forEach(header => {
- const id = header.getAttribute('id');
- if (!id) return;
-
- if (header.querySelector('.heading-link')) return;
-
- const link = document.createElement('a');
- link.className = 'heading-link';
- link.href = '#' + id;
- link.textContent = '#';
- link.title = 'Copy link to this heading';
-
- const pinBtn = header.querySelector('.toc-pin-btn');
- if (pinBtn) {
- header.insertBefore(link, pinBtn);
- } else {
- header.appendChild(link);
- }
-
- link.addEventListener('click', function(e) {
- e.preventDefault();
- const url = window.location.origin + window.location.pathname + '#' + id;
-
- if (navigator.clipboard && navigator.clipboard.writeText) {
- navigator.clipboard.writeText(url)
- .then(() => {
- const originalText = link.textContent;
- link.textContent = '✓';
- setTimeout(() => {
- link.textContent = originalText;
- }, 1000);
- })
- .catch(err => {
- console.warn('Failed to copy to clipboard', err);
- window.location.hash = id;
- });
- } else {
- window.location.hash = id;
- }
- });
- });
- }
- addHeadingLinks();
-
- function addNextHeadingButtons() {
- const headers = Array.from(content.querySelectorAll('h1, h2, h3, h4, h5, h6, h7, h8, h9'));
-
- function getLevel(header) {
- return parseInt(header.tagName.substring(1), 10);
- }
-
- headers.forEach((header, index) => {
- const currentLevel = getLevel(header);
-
- const headingLink = header.querySelector('.heading-link');
- const pinBtn = header.querySelector('.toc-pin-btn');
-
- function insertActionBtn(btn) {
- if (pinBtn) {
- header.insertBefore(btn, pinBtn);
- return;
- }
- if (headingLink && headingLink.parentNode === header) {
- headingLink.after(btn);
- return;
- }
- header.appendChild(btn);
- }
-
- const nextHeader = headers[index + 1];
- if (nextHeader && !header.querySelector('.heading-next')) {
- const nextId = nextHeader.getAttribute('id');
- if (nextId) {
- const nextBtn = document.createElement('button');
- nextBtn.className = 'heading-next';
- nextBtn.type = 'button';
- nextBtn.textContent = '↓';
- nextBtn.title = 'Jump to next heading';
-
- insertActionBtn(nextBtn);
-
- nextBtn.addEventListener('click', function(e) {
- e.preventDefault();
- nextHeader.scrollIntoView({ behavior: 'smooth', block: 'start' });
- history.pushState(null, null, '#' + nextId);
- });
- }
- }
-
- if (!header.querySelector('.heading-skip')) {
- let skipHeader = null;
- for (let i = index + 1; i < headers.length; i++) {
- if (getLevel(headers[i]) <= currentLevel) {
- skipHeader = headers[i];
- break;
- }
- }
-
- if (skipHeader) {
- const skipId = skipHeader.getAttribute('id');
- if (skipId) {
- const skipBtn = document.createElement('button');
- skipBtn.className = 'heading-skip';
- skipBtn.type = 'button';
- skipBtn.textContent = '⇣';
- skipBtn.title = 'Skip to next section (same level)';
-
- insertActionBtn(skipBtn);
-
- skipBtn.addEventListener('click', function(e) {
- e.preventDefault();
- skipHeader.scrollIntoView({ behavior: 'smooth', block: 'start' });
- history.pushState(null, null, '#' + skipId);
- });
- }
- }
- }
-
- if (!header.querySelector('.heading-skip-up')) {
- let upHeader = null;
- for (let i = index + 1; i < headers.length; i++) {
- if (getLevel(headers[i]) < currentLevel) {
- upHeader = headers[i];
- break;
- }
- }
-
- if (upHeader) {
- const upId = upHeader.getAttribute('id');
- if (upId) {
- const upBtn = document.createElement('button');
- upBtn.className = 'heading-skip-up';
- upBtn.type = 'button';
- upBtn.textContent = '⇑';
- upBtn.title = 'Jump to next higher-level section (escape subsections)';
-
- insertActionBtn(upBtn);
-
- upBtn.addEventListener('click', function(e) {
- e.preventDefault();
- upHeader.scrollIntoView({ behavior: 'smooth', block: 'start' });
- history.pushState(null, null, '#' + upId);
- });
- }
- }
- }
- });
- }
- addNextHeadingButtons();
-
- let mobileTocBtn = document.getElementById('mobile-toc-toggle');
- if (!mobileTocBtn) {
- mobileTocBtn = document.createElement('button');
- mobileTocBtn.id = 'mobile-toc-toggle';
- mobileTocBtn.type = 'button';
- mobileTocBtn.textContent = 'TOC';
- document.body.appendChild(mobileTocBtn);
- }
-
- let mobilePinnedBtn = document.getElementById('mobile-pinned-toggle');
- if (!mobilePinnedBtn) {
- mobilePinnedBtn = document.createElement('button');
- mobilePinnedBtn.id = 'mobile-pinned-toggle';
- mobilePinnedBtn.type = 'button';
- mobilePinnedBtn.textContent = 'Pinned';
- document.body.appendChild(mobilePinnedBtn);
- }
-
- function anyMobilePanelOpen() {
- return toc.classList.contains('mobile-visible') ||
- pinnedPanel.classList.contains('mobile-visible');
- }
-
- function updateBodyMobilePanelState() {
- if (anyMobilePanelOpen()) body.classList.add('mobile-panel-open');
- else body.classList.remove('mobile-panel-open');
- }
-
- document.addEventListener('click', function(e) {
- if (window.innerWidth > 1000) return;
-
- if (!anyMobilePanelOpen()) return;
-
- const clickedInsideToc = toc.contains(e.target);
- const clickedInsidePinned = pinnedPanel.contains(e.target);
- const clickedTocBtn = mobileTocBtn.contains(e.target);
- const clickedPinnedBtn = mobilePinnedBtn.contains(e.target);
-
- const clickedInteractive =
- e.target.tagName === 'A' ||
- e.target.tagName === 'BUTTON' ||
- e.target.tagName === 'INPUT' ||
- e.target.tagName === 'TEXTAREA' ||
- e.target.closest('a') ||
- e.target.closest('button');
-
- if (!clickedInsideToc && !clickedInsidePinned && !clickedTocBtn && !clickedPinnedBtn && !clickedInteractive) {
- toc.classList.remove('mobile-visible');
- pinnedPanel.classList.remove('mobile-visible');
- updateBodyMobilePanelState();
+ if (matches) {
+ li.classList.remove('hidden-by-search');
+ let parent = li.parentElement;
+ while (parent && parent !== toc) {
+ if (parent.tagName === 'UL') {
+ parent.style.display = '';
+ }
+ if (parent.tagName === 'LI') {
+ parent.classList.remove('hidden-by-search');
+ }
+ parent = parent.parentElement;
}
+ } else {
+ li.classList.add('hidden-by-search');
+ }
});
- mobileTocBtn.addEventListener('click', function() {
- const isOpen = toc.classList.toggle('mobile-visible');
- if (isOpen) {
- pinnedPanel.classList.remove('mobile-visible');
- }
- updateBodyMobilePanelState();
- });
+ if (term === '') {
+ const allLis = toc.querySelectorAll('li');
+ allLis.forEach(li => li.classList.remove('hidden-by-search'));
+ }
+ }
- mobilePinnedBtn.addEventListener('click', function() {
- const isOpen = pinnedPanel.classList.toggle('mobile-visible');
- if (isOpen) {
- toc.classList.remove('mobile-visible');
- }
- updateBodyMobilePanelState();
- });
+ searchInput.addEventListener('input', function(e) {
+ const term = e.target.value.toLowerCase();
+ filterTOC(term);
+ });
- const pinnedItems = new Map();
- let initiallyPinnedHrefs = new Set();
+ clearBtn.addEventListener('click', function() {
+ searchInput.value = '';
+ filterTOC('');
+ searchInput.focus();
+ });
+ }
+ injectSearch();
- function loadFromStorage() {
- try {
- const raw = window.localStorage && localStorage.getItem(STORAGE_KEY);
- if (!raw) return;
- const arr = JSON.parse(raw);
- if (!Array.isArray(arr)) return;
- initiallyPinnedHrefs = new Set(arr);
- } catch (e) {
- console.warn('Pinned: failed to load from localStorage', e);
- }
+ function addHeadingLinks() {
+ const headers = content.querySelectorAll('h1, h2, h3, h4, h5, h6, h7, h8, h9');
+ headers.forEach(header => {
+ const id = header.getAttribute('id');
+ if (!id) return;
+
+ if (header.querySelector('.heading-link')) return;
+
+ const link = document.createElement('a');
+ link.className = 'heading-link';
+ link.href = '#' + id;
+ link.textContent = '#';
+ link.title = 'Copy link to this heading';
+
+ const pinBtn = header.querySelector('.toc-pin-btn');
+ if (pinBtn) {
+ header.insertBefore(link, pinBtn);
+ } else {
+ header.appendChild(link);
}
- function saveToStorage() {
- try {
- if (!window.localStorage) return;
- const arr = [];
- pinnedItems.forEach((entry, href) => {
- if (entry.li) arr.push(href);
- });
- localStorage.setItem(STORAGE_KEY, JSON.stringify(arr));
- } catch (e) {
- console.warn('Pinned: failed to save to localStorage', e);
- }
- }
+ link.addEventListener('click', function(e) {
+ e.preventDefault();
+ const url = window.location.origin + window.location.pathname + '#' + id;
- function sortPinnedList() {
- const items = Array.from(pinnedList.children)
- .map(li => {
- const link = li.querySelector('a');
- return {
- li: li,
- text: link ? link.textContent.trim()
- .toLowerCase() : ''
- };
- });
- items.sort((a, b) => a.text.localeCompare(b.text));
- items.forEach(item => pinnedList.appendChild(item.li));
- }
-
- function hidePinnedPanel() {
- pinnedPanel.classList.add('hidden');
- content.classList.add('pinned-hidden');
- showBtn.classList.add('visible');
- }
-
- function showPinnedPanel() {
- pinnedPanel.classList.remove('hidden');
- content.classList.remove('pinned-hidden');
- showBtn.classList.remove('visible');
- }
-
- toggleBtn.addEventListener('click', hidePinnedPanel);
- showBtn.addEventListener('click', showPinnedPanel);
-
- clearAllBtn.addEventListener('click', function() {
- if (pinnedItems.size === 0) return;
-
- const confirmed = confirm('Are you sure you want to clear all pinned items?');
- if (!confirmed) return;
-
- pinnedItems.forEach((entry, href) => {
- if (entry.li && entry.li.parentElement) {
- entry.li.parentElement.removeChild(entry.li);
- }
- entry.li = null;
- entry.btns.forEach(b => b.textContent = '[pin]');
- });
-
- saveToStorage();
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(url)
+ .then(() => {
+ const originalText = link.textContent;
+ link.textContent = '✓';
+ setTimeout(() => {
+ link.textContent = originalText;
+ }, 1000);
+ })
+ .catch(err => {
+ console.warn('Failed to copy to clipboard', err);
+ window.location.hash = id;
+ });
+ } else {
+ window.location.hash = id;
+ }
});
+ });
+ }
+ addHeadingLinks();
- function attachPinBehavior(pinBtn, href, text) {
- if (!href) return;
+ function addNextHeadingButtons() {
+ const headers = Array.from(content.querySelectorAll('h1, h2, h3, h4, h5, h6, h7, h8, h9'));
- if (!pinnedItems.has(href)) {
- pinnedItems.set(href, {
- li: null,
- btns: new Set(),
- text: text
- });
- }
- const entry = pinnedItems.get(href);
- entry.btns.add(pinBtn);
+ headers.forEach((header, index) => {
+ // Skip if button already exists
+ if (header.querySelector('.heading-next')) return;
- pinBtn.textContent = entry.li ? '[unpin]' : '[pin]';
+ // Find next heading
+ const nextHeader = headers[index + 1];
+ if (!nextHeader) return; // No next heading
- pinBtn.addEventListener('click', function(e) {
- e.preventDefault();
- e.stopPropagation();
- const current = pinnedItems.get(href);
- if (!current) return;
+ const nextId = nextHeader.getAttribute('id');
+ if (!nextId) return;
- if (current.li) {
- if (current.li.parentElement) {
- current.li.parentElement.removeChild(current.li);
- }
- current.li = null;
- current.btns.forEach(b => b.textContent = '[pin]');
- saveToStorage();
- } else {
- const li = document.createElement('li');
+ const nextBtn = document.createElement('button');
+ nextBtn.className = 'heading-next';
+ nextBtn.type = 'button';
+ nextBtn.textContent = '↓';
+ nextBtn.title = 'Jump to next heading';
- const a = document.createElement('a');
- a.href = href;
- a.textContent = current.text;
+ // Insert after the heading link, before the pin button
+ const headingLink = header.querySelector('.heading-link');
+ const pinBtn = header.querySelector('.toc-pin-btn');
- const removeBtn = document.createElement('button');
- removeBtn.className = 'pin-remove';
- removeBtn.type = 'button';
- removeBtn.textContent = '✕';
- removeBtn.addEventListener('click', () => {
- const cur = pinnedItems.get(href);
- if (!cur) return;
- if (cur.li && cur.li.parentElement) {
- cur.li.parentElement.removeChild(cur.li);
- }
- cur.li = null;
- cur.btns.forEach(b => b.textContent = '[pin]');
- saveToStorage();
- });
-
- li.appendChild(a);
- li.appendChild(removeBtn);
- pinnedList.appendChild(li);
-
- current.li = li;
- current.btns.forEach(b => b.textContent = '[unpin]');
-
- sortPinnedList();
- saveToStorage();
- }
- });
+ if (pinBtn) {
+ header.insertBefore(nextBtn, pinBtn);
+ } else if (headingLink) {
+ headingLink.after(nextBtn);
+ } else {
+ header.appendChild(nextBtn);
}
- loadFromStorage();
-
- const tocLinks = document.querySelectorAll('#text-table-of-contents a');
- tocLinks.forEach(link => {
- if (link.parentElement && link.parentElement.classList.contains('toc-entry')) {
- return;
- }
- const li = link.closest('li');
- if (!li) return;
-
- const wrapper = document.createElement('span');
- wrapper.className = 'toc-entry';
- li.insertBefore(wrapper, link);
- wrapper.appendChild(link);
-
- const pinBtn = document.createElement('button');
- pinBtn.className = 'toc-pin-btn';
- pinBtn.type = 'button';
- pinBtn.textContent = '[pin]';
- wrapper.appendChild(pinBtn);
-
- const href = link.getAttribute('href');
- const text = link.textContent.trim();
- attachPinBehavior(pinBtn, href, text);
+ nextBtn.addEventListener('click', function(e) {
+ e.preventDefault();
+ nextHeader.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ // Update URL hash
+ history.pushState(null, null, '#' + nextId);
});
+ });
+ }
+ addNextHeadingButtons();
- const headers = content.querySelectorAll('h2, h3, h4, h5, h6, h7, h8, h9');
- headers.forEach(header => {
- const id = header.getAttribute('id');
- if (!id) return;
- if (header.querySelector('.toc-pin-btn')) return;
+ let mobileTocBtn = document.getElementById('mobile-toc-toggle');
+ if (!mobileTocBtn) {
+ mobileTocBtn = document.createElement('button');
+ mobileTocBtn.id = 'mobile-toc-toggle';
+ mobileTocBtn.type = 'button';
+ mobileTocBtn.textContent = 'TOC';
+ document.body.appendChild(mobileTocBtn);
+ }
- const href = '#' + id;
- const text = header.textContent.trim();
+ let mobilePinnedBtn = document.getElementById('mobile-pinned-toggle');
+ if (!mobilePinnedBtn) {
+ mobilePinnedBtn = document.createElement('button');
+ mobilePinnedBtn.id = 'mobile-pinned-toggle';
+ mobilePinnedBtn.type = 'button';
+ mobilePinnedBtn.textContent = 'Pinned';
+ document.body.appendChild(mobilePinnedBtn);
+ }
- const pinBtn = document.createElement('button');
- pinBtn.className = 'toc-pin-btn';
- pinBtn.type = 'button';
- pinBtn.textContent = '[pin]';
- pinBtn.style.marginLeft = '0.8rem';
- pinBtn.style.fontSize = '0.75em';
+ function anyMobilePanelOpen() {
+ return toc.classList.contains('mobile-visible') ||
+ pinnedPanel.classList.contains('mobile-visible');
+ }
- header.appendChild(pinBtn);
- attachPinBehavior(pinBtn, href, text);
- });
+ function updateBodyMobilePanelState() {
+ if (anyMobilePanelOpen()) body.classList.add('mobile-panel-open');
+ else body.classList.remove('mobile-panel-open');
+ }
- initiallyPinnedHrefs.forEach(href => {
- const entry = pinnedItems.get(href);
- if (!entry) return;
+ document.addEventListener('click', function(e) {
+ if (window.innerWidth > 1000) return;
- const li = document.createElement('li');
+ if (!anyMobilePanelOpen()) return;
- const a = document.createElement('a');
- a.href = href;
- a.textContent = entry.text;
+ const clickedInsideToc = toc.contains(e.target);
+ const clickedInsidePinned = pinnedPanel.contains(e.target);
+ const clickedTocBtn = mobileTocBtn.contains(e.target);
+ const clickedPinnedBtn = mobilePinnedBtn.contains(e.target);
- const removeBtn = document.createElement('button');
- removeBtn.className = 'pin-remove';
- removeBtn.type = 'button';
- removeBtn.textContent = '✕';
- removeBtn.addEventListener('click', () => {
- const cur = pinnedItems.get(href);
- if (!cur) return;
- if (cur.li && cur.li.parentElement) {
- cur.li.parentElement.removeChild(cur.li);
- }
- cur.li = null;
- cur.btns.forEach(b => b.textContent = '[pin]');
- saveToStorage();
- });
+ const clickedInteractive =
+ e.target.tagName === 'A' ||
+ e.target.tagName === 'BUTTON' ||
+ e.target.tagName === 'INPUT' ||
+ e.target.tagName === 'TEXTAREA' ||
+ e.target.closest('a') ||
+ e.target.closest('button');
- li.appendChild(a);
- li.appendChild(removeBtn);
- pinnedList.appendChild(li);
-
- entry.li = li;
- entry.btns.forEach(b => b.textContent = '[unpin]');
- });
-
- sortPinnedList();
- saveToStorage();
+ if (!clickedInsideToc && !clickedInsidePinned && !clickedTocBtn && !clickedPinnedBtn && !clickedInteractive) {
+ toc.classList.remove('mobile-visible');
+ pinnedPanel.classList.remove('mobile-visible');
+ updateBodyMobilePanelState();
+ }
});
+
+ mobileTocBtn.addEventListener('click', function() {
+ const isOpen = toc.classList.toggle('mobile-visible');
+ if (isOpen) {
+ pinnedPanel.classList.remove('mobile-visible');
+ }
+ updateBodyMobilePanelState();
+ });
+
+ mobilePinnedBtn.addEventListener('click', function() {
+ const isOpen = pinnedPanel.classList.toggle('mobile-visible');
+ if (isOpen) {
+ toc.classList.remove('mobile-visible');
+ }
+ updateBodyMobilePanelState();
+ });
+
+ const pinnedItems = new Map();
+ let initiallyPinnedHrefs = new Set();
+
+ function loadFromStorage() {
+ try {
+ const raw = window.localStorage && localStorage.getItem(STORAGE_KEY);
+ if (!raw) return;
+ const arr = JSON.parse(raw);
+ if (!Array.isArray(arr)) return;
+ initiallyPinnedHrefs = new Set(arr);
+ } catch (e) {
+ console.warn('Pinned: failed to load from localStorage', e);
+ }
+ }
+
+ function saveToStorage() {
+ try {
+ if (!window.localStorage) return;
+ const arr = [];
+ pinnedItems.forEach((entry, href) => {
+ if (entry.li) arr.push(href);
+ });
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(arr));
+ } catch (e) {
+ console.warn('Pinned: failed to save to localStorage', e);
+ }
+ }
+
+ function sortPinnedList() {
+ const items = Array.from(pinnedList.children)
+ .map(li => {
+ const link = li.querySelector('a');
+ return {
+ li: li,
+ text: link ? link.textContent.trim()
+ .toLowerCase() : ''
+ };
+ });
+ items.sort((a, b) => a.text.localeCompare(b.text));
+ items.forEach(item => pinnedList.appendChild(item.li));
+ }
+
+ function hidePinnedPanel() {
+ pinnedPanel.classList.add('hidden');
+ content.classList.add('pinned-hidden');
+ showBtn.classList.add('visible');
+ }
+
+ function showPinnedPanel() {
+ pinnedPanel.classList.remove('hidden');
+ content.classList.remove('pinned-hidden');
+ showBtn.classList.remove('visible');
+ }
+
+ toggleBtn.addEventListener('click', hidePinnedPanel);
+ showBtn.addEventListener('click', showPinnedPanel);
+
+ clearAllBtn.addEventListener('click', function() {
+ if (pinnedItems.size === 0) return;
+
+ const confirmed = confirm('Are you sure you want to clear all pinned items?');
+ if (!confirmed) return;
+
+ pinnedItems.forEach((entry, href) => {
+ if (entry.li && entry.li.parentElement) {
+ entry.li.parentElement.removeChild(entry.li);
+ }
+ entry.li = null;
+ entry.btns.forEach(b => b.textContent = '[pin]');
+ });
+
+ saveToStorage();
+ });
+
+ function attachPinBehavior(pinBtn, href, text) {
+ if (!href) return;
+
+ if (!pinnedItems.has(href)) {
+ pinnedItems.set(href, {
+ li: null,
+ btns: new Set(),
+ text: text
+ });
+ }
+ const entry = pinnedItems.get(href);
+ entry.btns.add(pinBtn);
+
+ pinBtn.textContent = entry.li ? '[unpin]' : '[pin]';
+
+ pinBtn.addEventListener('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ const current = pinnedItems.get(href);
+ if (!current) return;
+
+ if (current.li) {
+ if (current.li.parentElement) {
+ current.li.parentElement.removeChild(current.li);
+ }
+ current.li = null;
+ current.btns.forEach(b => b.textContent = '[pin]');
+ saveToStorage();
+ } else {
+ const li = document.createElement('li');
+
+ const a = document.createElement('a');
+ a.href = href;
+ a.textContent = current.text;
+
+ const removeBtn = document.createElement('button');
+ removeBtn.className = 'pin-remove';
+ removeBtn.type = 'button';
+ removeBtn.textContent = '✕';
+ removeBtn.addEventListener('click', () => {
+ const cur = pinnedItems.get(href);
+ if (!cur) return;
+ if (cur.li && cur.li.parentElement) {
+ cur.li.parentElement.removeChild(cur.li);
+ }
+ cur.li = null;
+ cur.btns.forEach(b => b.textContent = '[pin]');
+ saveToStorage();
+ });
+
+ li.appendChild(a);
+ li.appendChild(removeBtn);
+ pinnedList.appendChild(li);
+
+ current.li = li;
+ current.btns.forEach(b => b.textContent = '[unpin]');
+
+ sortPinnedList();
+ saveToStorage();
+ }
+ });
+ }
+
+ loadFromStorage();
+
+ const tocLinks = document.querySelectorAll('#text-table-of-contents a');
+ tocLinks.forEach(link => {
+ if (link.parentElement && link.parentElement.classList.contains('toc-entry')) {
+ return;
+ }
+ const li = link.closest('li');
+ if (!li) return;
+
+ const wrapper = document.createElement('span');
+ wrapper.className = 'toc-entry';
+ li.insertBefore(wrapper, link);
+ wrapper.appendChild(link);
+
+ const pinBtn = document.createElement('button');
+ pinBtn.className = 'toc-pin-btn';
+ pinBtn.type = 'button';
+ pinBtn.textContent = '[pin]';
+ wrapper.appendChild(pinBtn);
+
+ const href = link.getAttribute('href');
+ const text = link.textContent.trim();
+ attachPinBehavior(pinBtn, href, text);
+ });
+
+ const headers = content.querySelectorAll('h2, h3, h4, h5, h6, h7, h8, h9');
+ headers.forEach(header => {
+ const id = header.getAttribute('id');
+ if (!id) return;
+ if (header.querySelector('.toc-pin-btn')) return;
+
+ const href = '#' + id;
+ const text = header.textContent.trim();
+
+ const pinBtn = document.createElement('button');
+ pinBtn.className = 'toc-pin-btn';
+ pinBtn.type = 'button';
+ pinBtn.textContent = '[pin]';
+ pinBtn.style.marginLeft = '0.8rem';
+ pinBtn.style.fontSize = '0.75em';
+
+ header.appendChild(pinBtn);
+ attachPinBehavior(pinBtn, href, text);
+ });
+
+ initiallyPinnedHrefs.forEach(href => {
+ const entry = pinnedItems.get(href);
+ if (!entry) return;
+
+ const li = document.createElement('li');
+
+ const a = document.createElement('a');
+ a.href = href;
+ a.textContent = entry.text;
+
+ const removeBtn = document.createElement('button');
+ removeBtn.className = 'pin-remove';
+ removeBtn.type = 'button';
+ removeBtn.textContent = '✕';
+ removeBtn.addEventListener('click', () => {
+ const cur = pinnedItems.get(href);
+ if (!cur) return;
+ if (cur.li && cur.li.parentElement) {
+ cur.li.parentElement.removeChild(cur.li);
+ }
+ cur.li = null;
+ cur.btns.forEach(b => b.textContent = '[pin]');
+ saveToStorage();
+ });
+
+ li.appendChild(a);
+ li.appendChild(removeBtn);
+ pinnedList.appendChild(li);
+
+ entry.li = li;
+ entry.btns.forEach(b => b.textContent = '[unpin]');
+ });
+
+ sortPinnedList();
+ saveToStorage();
+ });
})();
#+end_src
@@ -35410,72 +35049,6 @@ This is the stylesheet used by the [[#h:12880c64-229c-4063-9eea-387a97490676][HT
visibility: visible;
}
- .heading-skip {
- opacity: 0;
- visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
- margin-left: 0.5rem;
- color: #718ca1;
- text-decoration: none;
- font-size: 0.8em;
- vertical-align: middle;
- cursor: pointer;
- background: none;
- border: none;
- padding: 0;
- }
-
- .heading-skip:hover {
- color: #5ec4ff;
- text-decoration: none;
- }
-
- h1:hover .heading-skip,
- h2:hover .heading-skip,
- h3:hover .heading-skip,
- h4:hover .heading-skip,
- h5:hover .heading-skip,
- h6:hover .heading-skip,
- h7:hover .heading-skip,
- h8:hover .heading-skip,
- h9:hover .heading-skip {
- opacity: 1;
- visibility: visible;
- }
-
- .heading-skip-up {
- opacity: 0;
- visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
- margin-left: 0.5rem;
- color: #718ca1;
- text-decoration: none;
- font-size: 0.8em;
- vertical-align: middle;
- cursor: pointer;
- background: none;
- border: none;
- padding: 0;
- }
-
- .heading-skip-up:hover {
- color: #5ec4ff;
- text-decoration: none;
- }
-
- h1:hover .heading-skip-up,
- h2:hover .heading-skip-up,
- h3:hover .heading-skip-up,
- h4:hover .heading-skip-up,
- h5:hover .heading-skip-up,
- h6:hover .heading-skip-up,
- h7:hover .heading-skip-up,
- h8:hover .heading-skip-up,
- h9:hover .heading-skip-up {
- opacity: 1;
- visibility: visible;
- }
-
@media (max-width: 1600px) {
#content {
max-width: 100%;
diff --git a/flake.lock b/flake.lock
index b264af1..f579e9a 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1460,11 +1460,11 @@
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
- "lastModified": 1767971910,
- "narHash": "sha256-j8vLAUaH8oAU5TSprSGa81wx+roo89iG98mUAutsjb8=",
+ "lastModified": 1757854196,
+ "narHash": "sha256-RDr3/JTpRyXSR1OOg+wzdOUmDL1Ke05OLV/xctbuQOw=",
"owner": "oddlama",
"repo": "nixos-extra-modules",
- "rev": "672abff4255796950924b284b1a2b3dd37113bd2",
+ "rev": "a584a970a05d0410dcb00e0ade684a0c0ce00c4b",
"type": "github"
},
"original": {
@@ -1598,22 +1598,6 @@
"type": "github"
}
},
- "nixpkgs-bisect": {
- "locked": {
- "lastModified": 1768161245,
- "narHash": "sha256-fSidazKIcZElti/a1SOmIwSXw6hXR2GLO/2XmkXgtX4=",
- "owner": "nixos",
- "repo": "nixpkgs",
- "rev": "d2a6c7729bbe95f5770ccd4d15a38e5037984b04",
- "type": "github"
- },
- "original": {
- "owner": "nixos",
- "ref": "master",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
"nixpkgs-dev": {
"locked": {
"lastModified": 1767131767,
@@ -1909,11 +1893,11 @@
},
"nixpkgs_14": {
"locked": {
- "lastModified": 1737885589,
- "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=",
+ "lastModified": 1763966396,
+ "narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8",
+ "rev": "5ae3b07d8d6527c42f17c876e404993199144b6a",
"type": "github"
},
"original": {
@@ -2675,7 +2659,6 @@
"nixos-images": "nixos-images",
"nixos-nftables-firewall": "nixos-nftables-firewall",
"nixpkgs": "nixpkgs_18",
- "nixpkgs-bisect": "nixpkgs-bisect",
"nixpkgs-dev": "nixpkgs-dev",
"nixpkgs-kernel": "nixpkgs-kernel",
"nixpkgs-stable": "nixpkgs-stable_3",
diff --git a/flake.nix b/flake.nix
index 29a5883..f879860 100644
--- a/flake.nix
+++ b/flake.nix
@@ -27,7 +27,6 @@
smallpkgs.url = "github:nixos/nixpkgs/08fcb0dcb59df0344652b38ea6326a2d8271baff?narHash=sha256-HXIQzULIG/MEUW2Q/Ss47oE3QrjxvpUX7gUl4Xp6lnc%3D&shallow=1";
nixpkgs-dev.url = "github:Swarsel/nixpkgs/main";
- nixpkgs-bisect.url = "github:nixos/nixpkgs/master";
nixpkgs-kernel.url = "github:NixOS/nixpkgs/063f43f2dbdef86376cc29ad646c45c46e93234c?narHash=sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o%3D"; #specifically pinned for kernel version
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-stable24_05.url = "github:NixOS/nixpkgs/nixos-24.05";
diff --git a/hosts/nixos/x86_64-linux/hintbooth/default.nix b/hosts/nixos/x86_64-linux/hintbooth/default.nix
index 937ec67..77fabd2 100644
--- a/hosts/nixos/x86_64-linux/hintbooth/default.nix
+++ b/hosts/nixos/x86_64-linux/hintbooth/default.nix
@@ -68,8 +68,8 @@
guests = lib.mkIf (!minimal && config.swarselsystems.withMicroVMs) (
{ }
- // confLib.mkMicrovm "adguardhome" { }
- // confLib.mkMicrovm "nginx" { }
+ // confLib.mkMicrovm "adguardhome"
+ // confLib.mkMicrovm "nginx"
);
}
diff --git a/hosts/nixos/x86_64-linux/hintbooth/guests/adguardhome/default.nix b/hosts/nixos/x86_64-linux/hintbooth/guests/adguardhome/default.nix
index eaf90f4..b10f730 100644
--- a/hosts/nixos/x86_64-linux/hintbooth/guests/adguardhome/default.nix
+++ b/hosts/nixos/x86_64-linux/hintbooth/guests/adguardhome/default.nix
@@ -3,7 +3,6 @@
imports = [
"${self}/profiles/nixos/microvm"
"${self}/modules/nixos"
- "${self}/modules/nixos/optional/microvm-guest-shares.nix"
];
swarselsystems = {
diff --git a/hosts/nixos/x86_64-linux/hintbooth/guests/nginx/default.nix b/hosts/nixos/x86_64-linux/hintbooth/guests/nginx/default.nix
index aef38bd..39656a1 100644
--- a/hosts/nixos/x86_64-linux/hintbooth/guests/nginx/default.nix
+++ b/hosts/nixos/x86_64-linux/hintbooth/guests/nginx/default.nix
@@ -6,7 +6,6 @@ in
imports = [
"${self}/profiles/nixos/microvm"
"${self}/modules/nixos"
- "${self}/modules/nixos/optional/microvm-guest-shares.nix"
];
swarselsystems = {
diff --git a/hosts/nixos/x86_64-linux/summers/default.nix b/hosts/nixos/x86_64-linux/summers/default.nix
index ad093b0..ddc929d 100644
--- a/hosts/nixos/x86_64-linux/summers/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/default.nix
@@ -1,4 +1,4 @@
-{ self, config, inputs, lib, minimal, confLib, ... }:
+{ self, inputs, lib, minimal, ... }:
{
imports = [
@@ -39,7 +39,7 @@
writeGlobalNetworks = false;
networkKernelModules = [ "igb" ];
rootDisk = "/dev/disk/by-id/ata-TS120GMTS420S_J024880123";
- withMicroVMs = true;
+ withMicroVMs = false;
localVLANs = [ "services" "home" ]; # devices is only provided on interface for bmc
initrdVLAN = "home";
server = {
@@ -83,7 +83,7 @@
acme = false; # cert handled by proxy
nfs = true;
- # kavita = true;
+ kavita = true;
restic = true;
jellyfin = true;
navidrome = true;
@@ -109,29 +109,29 @@
opkssh = true;
};
- guests = lib.mkIf (!minimal && config.swarselsystems.withMicroVMs) (
- { }
- // confLib.mkMicrovm "kavita" { withZfs = true; }
- // confLib.mkMicrovm "jellyfin" { withZfs = true; }
- // confLib.mkMicrovm "audio" { withZfs = true; }
- // confLib.mkMicrovm "postgresql" { withZfs = true; }
- // confLib.mkMicrovm "matrix" { withZfs = true; }
- // confLib.mkMicrovm "nextcloud" { withZfs = true; }
- // confLib.mkMicrovm "immich" { withZfs = true; }
- // confLib.mkMicrovm "paperless" { withZfs = true; }
- // confLib.mkMicrovm "transmission" { withZfs = true; }
- // confLib.mkMicrovm "storage" { withZfs = true; }
- // confLib.mkMicrovm "monitoring" { withZfs = true; }
- // confLib.mkMicrovm "freshrss" { withZfs = true; }
- // confLib.mkMicrovm "kanidm" { withZfs = true; }
- // confLib.mkMicrovm "firefly" { withZfs = true; }
- // confLib.mkMicrovm "koillection" { withZfs = true; }
- // confLib.mkMicrovm "radicale" { withZfs = true; }
- // confLib.mkMicrovm "atuin" { withZfs = true; }
- // confLib.mkMicrovm "forgejo" { withZfs = true; }
- // confLib.mkMicrovm "ankisync" { withZfs = true; }
- // confLib.mkMicrovm "homebox" { withZfs = true; }
- );
+ # guests = lib.mkIf (!minimal && config.swarselsystems.withMicroVMs) (
+ # { }
+ # // confLib.mkMicrovm "kavita"
+ # // confLib.mkMicrovm "jellyfin"
+ # // confLib.mkMicrovm "audio"
+ # // confLib.mkMicrovm "postgresql"
+ # // confLib.mkMicrovm "matrix"
+ # // confLib.mkMicrovm "nextcloud"
+ # // confLib.mkMicrovm "immich"
+ # // confLib.mkMicrovm "paperless"
+ # // confLib.mkMicrovm "transmission"
+ # // confLib.mkMicrovm "storage"
+ # // confLib.mkMicrovm "monitoring"
+ # // confLib.mkMicrovm "freshrss"
+ # // confLib.mkMicrovm "kanidm"
+ # // confLib.mkMicrovm "firefly"
+ # // confLib.mkMicrovm "koillection"
+ # // confLib.mkMicrovm "radicale"
+ # // confLib.mkMicrovm "atuin"
+ # // confLib.mkMicrovm "forgejo"
+ # // confLib.mkMicrovm "ankisync"
+ # // confLib.mkMicrovm "homebox"
+ # );
networking.nftables.firewall.zones.untrusted.interfaces = [ "lan" "bmc" ];
diff --git a/hosts/nixos/x86_64-linux/summers/guests/ankisync/default.nix b/hosts/nixos/x86_64-linux/summers/guests/ankisync/default.nix
index 84c16b1..e46d3ee 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/ankisync/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/ankisync/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 1;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # ankisync = true;
+ ankisync = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/atuin/default.nix b/hosts/nixos/x86_64-linux/summers/guests/atuin/default.nix
index 6a0e601..7d4eeea 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/atuin/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/atuin/default.nix
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # atuin = true;
+ atuin = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/audio/default.nix b/hosts/nixos/x86_64-linux/summers/guests/audio/default.nix
index 114e332..5f2ddd6 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/audio/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/audio/default.nix
@@ -36,9 +36,9 @@
};
swarselmodules.server = {
- # navidrome = true;
- # spotifyd = true;
- # mpd = true;
+ navidrome = true;
+ spotifyd = true;
+ mpd = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/firefly/default.nix b/hosts/nixos/x86_64-linux/summers/guests/firefly/default.nix
index 461ebff..592424a 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/firefly/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/firefly/default.nix
@@ -36,9 +36,8 @@
};
swarselmodules.server = {
- # firefly-iii = true;
- # nginx = true;
- # acme = true;
+ firefly-iii = true;
+ nginx = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/forgejo/default.nix b/hosts/nixos/x86_64-linux/summers/guests/forgejo/default.nix
index f19dc48..8efd8f2 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/forgejo/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/forgejo/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 1;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # forgejo = true;
+ forgejo = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/freshrss/default.nix b/hosts/nixos/x86_64-linux/summers/guests/freshrss/default.nix
index bde8bbb..0d43b72 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/freshrss/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/freshrss/default.nix
@@ -36,9 +36,8 @@
};
swarselmodules.server = {
- # freshrss = true;
- # nginx = true;
- # acme = true;
+ freshrss = true;
+ nginx = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/homebox/default.nix b/hosts/nixos/x86_64-linux/summers/guests/homebox/default.nix
index 062c0df..da4d446 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/homebox/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/homebox/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 1;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # homebox = true;
+ homebox = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/immich/default.nix b/hosts/nixos/x86_64-linux/summers/guests/immich/default.nix
index 86a4814..b2362e4 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/immich/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/immich/default.nix
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # immich = true;
+ immich = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/jellyfin/default.nix b/hosts/nixos/x86_64-linux/summers/guests/jellyfin/default.nix
index 3f80bef..7b7701b 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/jellyfin/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/jellyfin/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 3;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # jellyfin = true;
+ jellyfin = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/kanidm/default.nix b/hosts/nixos/x86_64-linux/summers/guests/kanidm/default.nix
index c70f9df..7b0f882 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/kanidm/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/kanidm/default.nix
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # kanidm = true;
+ kanidm = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/kavita/default.nix b/hosts/nixos/x86_64-linux/summers/guests/kavita/default.nix
index 4a3027b..4c06623 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/kavita/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/kavita/default.nix
@@ -29,7 +29,6 @@
microvm = {
mem = 1024 * 1;
vcpu = 1;
-
};
swarselprofiles = {
@@ -37,7 +36,7 @@
};
swarselmodules.server = {
- # kavita = true;
+ kavita = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/koillection/default.nix b/hosts/nixos/x86_64-linux/summers/guests/koillection/default.nix
index cbd5f2a..31a62fc 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/koillection/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/koillection/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 1;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # koillection = true;
+ koillection = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/matrix/default.nix b/hosts/nixos/x86_64-linux/summers/guests/matrix/default.nix
index 1575ad4..f406585 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/matrix/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/matrix/default.nix
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # matrix = true;
+ matrix = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/monitoring/default.nix b/hosts/nixos/x86_64-linux/summers/guests/monitoring/default.nix
index d015cb2..71dea57 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/monitoring/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/monitoring/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 3;
+ mem = 1024 * 2;
vcpu = 2;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # grafana = true;
+ grafana = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/nextcloud/default.nix b/hosts/nixos/x86_64-linux/summers/guests/nextcloud/default.nix
index d8580b0..09bc775 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/nextcloud/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/nextcloud/default.nix
@@ -36,9 +36,8 @@
};
swarselmodules.server = {
- # nextcloud = true;
- # nginx = true;
- # acme = true;
+ nextcloud = true;
+ nginx = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/paperless/default.nix b/hosts/nixos/x86_64-linux/summers/guests/paperless/default.nix
index 74fe3a6..fc66f5c 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/paperless/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/paperless/default.nix
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # paperless = true;
+ paperless = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/postgresql/default.nix b/hosts/nixos/x86_64-linux/summers/guests/postgresql/default.nix
index f94f5f1..663af81 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/postgresql/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/postgresql/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 1;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # postgresql = true;
+ postgresql = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/radicale/default.nix b/hosts/nixos/x86_64-linux/summers/guests/radicale/default.nix
index aa70516..3bb36f9 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/radicale/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/radicale/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 1;
+ mem = 1024 * 2;
vcpu = 1;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # radicale = true;
+ radicale = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/storage/default.nix b/hosts/nixos/x86_64-linux/summers/guests/storage/default.nix
index b6da313..cfd5fd5 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/storage/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/storage/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 4;
+ mem = 1024 * 2;
vcpu = 2;
};
@@ -36,8 +36,8 @@
};
swarselmodules.server = {
- # nfs = true;
- # syncthing = true;
+ nfs = true;
+ syncthing = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/summers/guests/transmission/default.nix b/hosts/nixos/x86_64-linux/summers/guests/transmission/default.nix
index e4e7bd3..e5629b8 100644
--- a/hosts/nixos/x86_64-linux/summers/guests/transmission/default.nix
+++ b/hosts/nixos/x86_64-linux/summers/guests/transmission/default.nix
@@ -27,7 +27,7 @@
} // lib.optionalAttrs (!minimal) {
microvm = {
- mem = 1024 * 4;
+ mem = 1024 * 2;
vcpu = 2;
};
@@ -36,7 +36,7 @@
};
swarselmodules.server = {
- # transmission = true;
+ transmission = true;
};
}
diff --git a/hosts/nixos/x86_64-linux/winters/hardware-configuration.nix b/hosts/nixos/x86_64-linux/winters/hardware-configuration.nix
index 3222d17..13a956d 100644
--- a/hosts/nixos/x86_64-linux/winters/hardware-configuration.nix
+++ b/hosts/nixos/x86_64-linux/winters/hardware-configuration.nix
@@ -1,4 +1,4 @@
-{ lib, config, modulesPath, ... }:
+{ lib, modulesPath, ... }:
{
imports =
diff --git a/modules/nixos/common/pii.nix b/modules/nixos/common/pii.nix
index d10ed18..17d8130 100644
--- a/modules/nixos/common/pii.nix
+++ b/modules/nixos/common/pii.nix
@@ -1,5 +1,5 @@
# largely based on https://github.com/oddlama/nix-config/blob/main/modules/secrets.nix
-{ config, inputs, lib, nodes, globals, ... }:
+{ config, inputs, lib, nodes, ... }:
let
# If the given expression is a bare set, it will be wrapped in a function,
# so that the imported file can always be applied to the inputs, similar to
@@ -53,7 +53,7 @@ in
secrets = lib.mkOption {
readOnly = true;
- default = lib.mapAttrs (_: x: importEncrypted x { inherit lib nodes globals inputs; inherit (inputs.topologyPrivate) topologyPrivate; }) config.repo.secretFiles;
+ default = lib.mapAttrs (_: x: importEncrypted x { inherit lib nodes inputs; inherit (inputs.topologyPrivate) topologyPrivate; }) config.repo.secretFiles;
type = lib.types.unspecified;
description = "Exposes the loaded repo secrets. This option is read-only.";
};
diff --git a/modules/nixos/common/users.nix b/modules/nixos/common/users.nix
index e6c11a4..52e389a 100644
--- a/modules/nixos/common/users.nix
+++ b/modules/nixos/common/users.nix
@@ -13,8 +13,6 @@
};
"${config.swarselsystems.mainUser}" = {
isNormalUser = true;
- uid = 1000;
- autoSubUidGidRange = false;
description = "Leon S";
password = lib.mkIf (minimal || config.swarselsystems.isPublic) "setup";
hashedPasswordFile = lib.mkIf (!minimal && !config.swarselsystems.isPublic) config.sops.secrets.main-user-hashed-pw.path;
diff --git a/modules/nixos/optional/microvm-guest-shares.nix b/modules/nixos/optional/microvm-guest-shares.nix
deleted file mode 100644
index f6ad083..0000000
--- a/modules/nixos/optional/microvm-guest-shares.nix
+++ /dev/null
@@ -1,15 +0,0 @@
-{ self, lib, config, inputs, microVMParent, nodes, ... }:
-{
- config = {
- microvm = {
- shares = [
- {
- tag = "persist";
- source = "${lib.optionalString nodes.${microVMParent}.config.swarselsystems.isImpermanence "/persist"}/microvms/${config.networking.hostName}";
- mountPoint = "/persist";
- proto = "virtiofs";
- }
- ];
- };
- };
-}
diff --git a/modules/nixos/optional/microvm-guest.nix b/modules/nixos/optional/microvm-guest.nix
index 28c8c91..21c29ce 100644
--- a/modules/nixos/optional/microvm-guest.nix
+++ b/modules/nixos/optional/microvm-guest.nix
@@ -1,4 +1,4 @@
-{ self, lib, config, inputs, microVMParent, nodes, globals, confLib, ... }:
+{ self, lib, config, inputs, microVMParent, nodes, ... }:
{
imports = [
inputs.disko.nixosModules.disko
@@ -49,16 +49,24 @@
};
};
- # microvm = {
- # mount the writeable overlay so that we can use nix shells inside the microvm
- # volumes = [
- # {
- # image = "/tmp/nix-store-overlay-${config.networking.hostName}.img";
- # autoCreate = true;
- # mountPoint = config.microvm.writableStoreOverlay;
- # size = 1024;
- # }
- # ];
- # };
+ microvm = {
+ shares = [
+ {
+ tag = "persist";
+ source = "${lib.optionalString nodes.${microVMParent}.config.swarselsystems.isImpermanence "/persist"}/microvms/${config.networking.hostName}";
+ mountPoint = "/persist";
+ proto = "virtiofs";
+ }
+ ];
+ # mount the writeable overlay so that we can use nix shells inside the microvm
+ volumes = [
+ {
+ image = "/tmp/nix-store-overlay-${config.networking.hostName}.img";
+ autoCreate = true;
+ mountPoint = config.microvm.writableStoreOverlay;
+ size = 1024;
+ }
+ ];
+ };
};
}
diff --git a/modules/nixos/optional/microvm-host.nix b/modules/nixos/optional/microvm-host.nix
index 5d5c503..073353c 100644
--- a/modules/nixos/optional/microvm-host.nix
+++ b/modules/nixos/optional/microvm-host.nix
@@ -1,4 +1,4 @@
-{ config, lib, confLib, ... }:
+{ config, lib, ... }:
{
config = lib.mkIf (config.guests != { }) {
@@ -17,7 +17,5 @@
(builtins.attrNames config.guests)
);
- users.persistentIds.microvm = confLib.mkIds 999;
-
};
}
diff --git a/modules/nixos/server/acme.nix b/modules/nixos/server/acme.nix
index e36bdf2..1bbeaec 100644
--- a/modules/nixos/server/acme.nix
+++ b/modules/nixos/server/acme.nix
@@ -1,4 +1,4 @@
-{ self, pkgs, lib, config, globals, confLib, ... }:
+{ self, pkgs, lib, config, globals, ... }:
let
inherit (config.repo.secrets.common) dnsProvider dnsBase dnsMail;
@@ -21,10 +21,7 @@ in
'';
};
- users = {
- persistentIds.acme = confLib.mkIds 967;
- groups.acme.members = lib.mkIf config.swarselmodules.server.nginx [ "nginx" ];
- };
+ users.groups.acme.members = lib.mkIf config.swarselmodules.server.nginx [ "nginx" ];
security.acme = {
acceptTerms = true;
diff --git a/modules/nixos/server/disk-encrypt.nix b/modules/nixos/server/disk-encrypt.nix
index d5dde08..8a8ea3e 100644
--- a/modules/nixos/server/disk-encrypt.nix
+++ b/modules/nixos/server/disk-encrypt.nix
@@ -45,9 +45,9 @@ in
};
boot = lib.mkIf (!config.swarselsystems.isClient) {
- # kernelParams = lib.mkIf (!config.swarselsystems.isCloud && ((config.swarselsystems.localVLANs == []) || isRouter)) [
- # "ip=${localIp}::${gatewayIp}:${subnetMask}:${config.networking.hostName}::none"
- # ];
+ kernelParams = lib.mkIf (!config.swarselsystems.isCloud && ((config.swarselsystems.localVLANs == [ ]) || isRouter)) [
+ "ip=${localIp}::${gatewayIp}:${subnetMask}:${config.networking.hostName}::none"
+ ];
initrd = {
secrets."/tmp${hostKeyPathBase}" = if minimal then (lib.mkForce generatedHostKey) else (lib.mkForce hostKeyPath); # need to mkForce this or it behaves stateful
availableKernelModules = config.swarselsystems.networkKernelModules;
diff --git a/modules/nixos/server/firefly-iii.nix b/modules/nixos/server/firefly-iii.nix
index cf77b73..f1ed188 100644
--- a/modules/nixos/server/firefly-iii.nix
+++ b/modules/nixos/server/firefly-iii.nix
@@ -13,9 +13,6 @@ in
config = lib.mkIf config.swarselmodules.server.${serviceName} {
users = {
- persistentIds = {
- firefly-iii = confLib.mkIds 983;
- };
groups.${serviceGroup} = { };
users.${serviceUser} = {
group = lib.mkForce serviceGroup;
diff --git a/modules/nixos/server/forgejo.nix b/modules/nixos/server/forgejo.nix
index 5ae8125..ea246b1 100644
--- a/modules/nixos/server/forgejo.nix
+++ b/modules/nixos/server/forgejo.nix
@@ -12,14 +12,9 @@ in
# networking.firewall.allowedTCPPorts = [ servicePort ];
- users = {
- persistentIds = {
- forgejo = confLib.mkIds 985;
- };
- users.${serviceUser} = {
- group = serviceGroup;
- isSystemUser = true;
- };
+ users.users.${serviceUser} = {
+ group = serviceGroup;
+ isSystemUser = true;
};
users.groups.${serviceGroup} = { };
diff --git a/modules/nixos/server/freshrss.nix b/modules/nixos/server/freshrss.nix
index df0a809..dd764b4 100644
--- a/modules/nixos/server/freshrss.nix
+++ b/modules/nixos/server/freshrss.nix
@@ -9,15 +9,10 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
- users = {
- persistentIds = {
- freshrss = confLib.mkIds 986;
- };
- users.${serviceUser} = {
- extraGroups = [ "users" ];
- group = serviceGroup;
- isSystemUser = true;
- };
+ users.users.${serviceUser} = {
+ extraGroups = [ "users" ];
+ group = serviceGroup;
+ isSystemUser = true;
};
users.groups.${serviceGroup} = { };
diff --git a/modules/nixos/server/homebox.nix b/modules/nixos/server/homebox.nix
index 5325cce..468ce7d 100644
--- a/modules/nixos/server/homebox.nix
+++ b/modules/nixos/server/homebox.nix
@@ -13,10 +13,6 @@ in
icon = "${self}/files/topology-images/${serviceName}.png";
};
- users.persistentIds = {
- homebox = confLib.mkIds 981;
- };
-
globals = {
networks = {
${webProxyIf}.hosts = lib.mkIf isProxied {
@@ -33,25 +29,14 @@ in
};
};
- systemd.services.homebox = {
- environment = {
- TMPDIR = "/var/lib/homebox/.tmp";
- };
- serviceConfig = {
- # ReadWritePaths = "/var/lib/homebox";
- RuntimeDirectory = "homebox";
- BindPaths = "/run/homebox:/var/lib/homebox/.tmp";
- };
- };
-
services.${serviceName} = {
enable = true;
- package = pkgs.bisect.homebox;
+ package = pkgs.dev.homebox;
database.createLocally = true;
settings = {
HBOX_WEB_PORT = builtins.toString servicePort;
HBOX_OPTIONS_ALLOW_REGISTRATION = "false";
- HBOX_STORAGE_CONN_STRING = "file:///var/lib/homebox";
+ HBOX_STORAGE_CONN_STRING = "file:///Vault/data/homebox";
HBOX_STORAGE_PREFIX_PATH = ".data";
};
};
diff --git a/modules/nixos/server/id.nix b/modules/nixos/server/id.nix
deleted file mode 100644
index f6db715..0000000
--- a/modules/nixos/server/id.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-{ lib, config, confLib, ... }:
-let
- inherit (lib)
- concatLists
- flip
- mapAttrsToList
- mkDefault
- mkIf
- mkOption
- types
- ;
-
- cfg = config.users.persistentIds;
-in
-{
- options = {
- swarselmodules.server.ids = lib.mkEnableOption "enable persistent ids on server";
- users.persistentIds = mkOption {
- default = { };
- description = ''
- Maps a user or group name to its expected uid/gid values. If a user/group is
- used on the system without specifying a uid/gid, this module will assign the
- corresponding ids defined here, or show an error if the definition is missing.
- '';
- type = types.attrsOf (
- types.submodule {
- options = {
- uid = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = "The uid to assign if it is missing in `users.users.`.";
- };
- gid = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = "The gid to assign if it is missing in `users.groups.`.";
- };
- };
- }
- );
- };
-
- users.users = mkOption {
- type = types.attrsOf (
- types.submodule (
- { name, ... }:
- {
- config.uid =
- let
- persistentUid = cfg.${name}.uid or null;
- in
- mkIf (persistentUid != null) (mkDefault persistentUid);
- }
- )
- );
- };
-
- users.groups = mkOption {
- type = types.attrsOf (
- types.submodule (
- { name, ... }:
- {
- config.gid =
- let
- persistentGid = cfg.${name}.gid or null;
- in
- mkIf (persistentGid != null) (mkDefault persistentGid);
- }
- )
- );
- };
- };
- config = lib.mkIf config.swarselmodules.server.ids {
- assertions =
- concatLists
- (
- flip mapAttrsToList config.users.users (
- name: user: [
- {
- assertion = user.uid != null;
- message = "non-persistent uid detected for '${name}', please assign one via `users.persistentIds`";
- }
- {
- assertion = !user.autoSubUidGidRange;
- message = "non-persistent subUids/subGids detected for: ${name}";
- }
- ]
- )
- )
- ++ flip mapAttrsToList config.users.groups (
- name: group: {
- assertion = group.gid != null;
- message = "non-persistent gid detected for '${name}', please assign one via `users.persistentIds`";
- }
- );
- users.persistentIds = {
- systemd-coredump = confLib.mkIds 998;
- systemd-oom = confLib.mkIds 997;
- polkituser = confLib.mkIds 973;
- nscd = confLib.mkIds 972;
- };
- };
-}
diff --git a/modules/nixos/server/immich.nix b/modules/nixos/server/immich.nix
index 568b516..bdbc739 100644
--- a/modules/nixos/server/immich.nix
+++ b/modules/nixos/server/immich.nix
@@ -7,14 +7,8 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
- users = {
- persistentIds = {
- immich = confLib.mkIds 989;
- redis-immich = confLib.mkIds 977;
- };
- users.${serviceUser} = {
- extraGroups = [ "video" "render" "users" ];
- };
+ users.users.${serviceUser} = {
+ extraGroups = [ "video" "render" "users" ];
};
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
diff --git a/modules/nixos/server/jellyfin.nix b/modules/nixos/server/jellyfin.nix
index fcd9910..4c2a972 100644
--- a/modules/nixos/server/jellyfin.nix
+++ b/modules/nixos/server/jellyfin.nix
@@ -7,12 +7,10 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
- users = {
- persistentIds.jellyfin = confLib.mkIds 994;
- users.${serviceUser} = {
- extraGroups = [ "video" "render" "users" ];
- };
+ users.users.${serviceUser} = {
+ extraGroups = [ "video" "render" "users" ];
};
+
# nixpkgs.config.packageOverrides = pkgs: {
# intel-vaapi-driver = pkgs.intel-vaapi-driver.override { enableHybridCodec = true; };
# };
diff --git a/modules/nixos/server/kanidm.nix b/modules/nixos/server/kanidm.nix
index 17dd259..bdf247b 100644
--- a/modules/nixos/server/kanidm.nix
+++ b/modules/nixos/server/kanidm.nix
@@ -34,9 +34,6 @@ in
users = {
- persistentIds = {
- kanidm = confLib.mkIds 984;
- };
users.${serviceUser} = {
group = serviceGroup;
isSystemUser = true;
diff --git a/modules/nixos/server/kavita.nix b/modules/nixos/server/kavita.nix
index ce6bf10..f952f49 100644
--- a/modules/nixos/server/kavita.nix
+++ b/modules/nixos/server/kavita.nix
@@ -12,13 +12,10 @@ in
calibre
];
- users = {
- persistentIds.kavita = confLib.mkIds 995;
- users.${serviceUser} = {
- extraGroups = [ "users" ];
- };
- };
+ users.users.${serviceUser} = {
+ extraGroups = [ "users" ];
+ };
sops.secrets.kavita-token = { inherit sopsFile; owner = serviceUser; };
diff --git a/modules/nixos/server/kea.nix b/modules/nixos/server/kea.nix
index 21c4228..93eee37 100644
--- a/modules/nixos/server/kea.nix
+++ b/modules/nixos/server/kea.nix
@@ -82,8 +82,6 @@ in
{ directory = serviceDir; mode = "0700"; }
];
- users.persistentIds.kea = confLib.mkIds 968;
-
topology = {
extractors.kea.enable = false;
self.services.${serviceName} = {
diff --git a/modules/nixos/server/matrix.nix b/modules/nixos/server/matrix.nix
index c77caa7..a2a7aba 100644
--- a/modules/nixos/server/matrix.nix
+++ b/modules/nixos/server/matrix.nix
@@ -314,11 +314,6 @@ in
# restart the bridges daily. this is done for the signal bridge mainly which stops carrying
# messages out after a while.
- users.persistentIds = {
- mautrix-signal = confLib.mkIds 993;
- mautrix-whatsapp = confLib.mkIds 992;
- mautrix-telegram = confLib.mkIds 991;
- };
nodes =
let
diff --git a/modules/nixos/server/monitoring.nix b/modules/nixos/server/monitoring.nix
index a74489e..3e4e3b3 100644
--- a/modules/nixos/server/monitoring.nix
+++ b/modules/nixos/server/monitoring.nix
@@ -42,11 +42,6 @@ in
};
users = {
- persistentIds = {
- nextcloud-exporter = confLib.mkIds 988;
- node-exporter = confLib.mkIds 987;
- grafana = confLib.mkIds 974;
- };
users = {
nextcloud-exporter = {
extraGroups = [ "nextcloud" ];
diff --git a/modules/nixos/server/nextcloud.nix b/modules/nixos/server/nextcloud.nix
index c91e79a..5b9bdda 100644
--- a/modules/nixos/server/nextcloud.nix
+++ b/modules/nixos/server/nextcloud.nix
@@ -16,11 +16,6 @@ in
kanidm-nextcloud-client = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; };
};
- users.persistentIds = {
- nextcloud = confLib.mkIds 990;
- redis-nextcloud = confLib.mkIds 976;
- };
-
globals.services.${serviceName} = {
domain = serviceDomain;
inherit proxyAddress4 proxyAddress6 isHome serviceAddress;
diff --git a/modules/nixos/server/nfs.nix b/modules/nixos/server/nfs.nix
index fb6a776..746a583 100644
--- a/modules/nixos/server/nfs.nix
+++ b/modules/nixos/server/nfs.nix
@@ -1,15 +1,10 @@
-{ lib, config, pkgs, globals, confLib, ... }:
+{ lib, config, pkgs, globals, ... }:
let
nfsUser = globals.user.name;
in
{
options.swarselmodules.server.nfs = lib.mkEnableOption "enable nfs on server";
config = lib.mkIf config.swarselmodules.server.nfs {
-
- users.persistentIds = {
- avahi = confLib.mkIds 978;
- };
-
services = {
# add a user with sudo smbpasswd -a
samba = {
diff --git a/modules/nixos/server/opkssh.nix b/modules/nixos/server/opkssh.nix
index 597240d..178e3d7 100644
--- a/modules/nixos/server/opkssh.nix
+++ b/modules/nixos/server/opkssh.nix
@@ -11,9 +11,6 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
- users.persistentIds = {
- opksshuser = confLib.mkIds 980;
- };
services.${serviceName} = {
enable = true;
diff --git a/modules/nixos/server/paperless.nix b/modules/nixos/server/paperless.nix
index 0d119a5..0bd12f6 100644
--- a/modules/nixos/server/paperless.nix
+++ b/modules/nixos/server/paperless.nix
@@ -12,13 +12,8 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
- users = {
- persistentIds = {
- redis-paperless = confLib.mkIds 975;
- };
- users.${serviceUser} = {
- extraGroups = [ "users" ];
- };
+ users.users.${serviceUser} = {
+ extraGroups = [ "users" ];
};
sops.secrets = {
diff --git a/modules/nixos/server/pipewire.nix b/modules/nixos/server/pipewire.nix
index 41a602d..b6b315a 100644
--- a/modules/nixos/server/pipewire.nix
+++ b/modules/nixos/server/pipewire.nix
@@ -1,11 +1,9 @@
-{ lib, config, confLib, ... }:
+{ lib, config, ... }:
{
- config = lib.mkIf (config.swarselmodules.server.mpd || config.swarselmodules.server.navidrome) {
+ config = lib.mkIf (config?swarselmodules.server.mpd || config?swarselmodules.server.navidrome) {
security.rtkit.enable = true; # this is required for pipewire real-time access
- users.persistentIds.rtkit = confLib.mkIds 996;
-
services.pipewire = {
enable = true;
pulse.enable = true;
diff --git a/modules/nixos/server/podman.nix b/modules/nixos/server/podman.nix
index 0a27be5..192e0c3 100644
--- a/modules/nixos/server/podman.nix
+++ b/modules/nixos/server/podman.nix
@@ -1,4 +1,4 @@
-{ config, lib, confLib, ... }:
+{ config, lib, ... }:
let
serviceName = "podman";
in
@@ -6,10 +6,6 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
- users.persistentIds = {
- podman = confLib.mkIds 969;
- };
-
virtualisation = {
podman.enable = true;
oci-containers.backend = "podman";
diff --git a/modules/nixos/server/radicale.nix b/modules/nixos/server/radicale.nix
index 5953bdc..ed09675 100644
--- a/modules/nixos/server/radicale.nix
+++ b/modules/nixos/server/radicale.nix
@@ -29,10 +29,6 @@ in
};
};
- users.persistentIds = {
- radicale = confLib.mkIds 982;
- };
-
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
globals = {
diff --git a/modules/nixos/server/restic.nix b/modules/nixos/server/restic.nix
index 2b0bd01..cb5c046 100644
--- a/modules/nixos/server/restic.nix
+++ b/modules/nixos/server/restic.nix
@@ -11,10 +11,6 @@ in
paths = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
- withPostgres = lib.mkOption {
- type = lib.types.bool;
- default = false;
- };
};
config = lib.mkIf config.swarselmodules.server.restic {
diff --git a/modules/nixos/server/ssh.nix b/modules/nixos/server/ssh.nix
index 22a2204..877552a 100644
--- a/modules/nixos/server/ssh.nix
+++ b/modules/nixos/server/ssh.nix
@@ -1,4 +1,4 @@
-{ self, lib, config, withHomeManager, confLib, ... }:
+{ self, lib, config, withHomeManager, ... }:
{
options.swarselmodules.server.ssh = lib.mkEnableOption "enable ssh on server";
config = lib.mkIf config.swarselmodules.server.ssh {
@@ -21,22 +21,17 @@
}
];
};
- users = {
- persistentIds = {
- sshd = lib.mkIf config.swarselmodules.server.ids (confLib.mkIds 979);
- };
- users = {
- "${config.swarselsystems.mainUser}".openssh.authorizedKeys.keyFiles = lib.mkIf withHomeManager [
- (self + /secrets/public/ssh/yubikey.pub)
- (self + /secrets/public/ssh/magicant.pub)
- # (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
- ];
- root.openssh.authorizedKeys.keyFiles = [
- (self + /secrets/public/ssh/yubikey.pub)
- (self + /secrets/public/ssh/magicant.pub)
- # (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
- ];
- };
+ users.users = {
+ "${config.swarselsystems.mainUser}".openssh.authorizedKeys.keyFiles = lib.mkIf withHomeManager [
+ (self + /secrets/public/ssh/yubikey.pub)
+ (self + /secrets/public/ssh/magicant.pub)
+ # (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
+ ];
+ root.openssh.authorizedKeys.keyFiles = [
+ (self + /secrets/public/ssh/yubikey.pub)
+ (self + /secrets/public/ssh/magicant.pub)
+ # (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
+ ];
};
security.sudo.extraConfig = ''
Defaults env_keep+=SSH_AUTH_SOCK
diff --git a/modules/nixos/server/transmission.nix b/modules/nixos/server/transmission.nix
index c3f8e4c..8d53c9a 100644
--- a/modules/nixos/server/transmission.nix
+++ b/modules/nixos/server/transmission.nix
@@ -25,10 +25,6 @@ in
# this user/group section is probably unneeded
users = {
- persistentIds = {
- prowlarr = confLib.mkIds 971;
- readarr = confLib.mkIds 970;
- };
groups = {
dockeruser = {
gid = 1155;
diff --git a/modules/shared/config-lib.nix b/modules/shared/config-lib.nix
index 4439036..9f91ae2 100644
--- a/modules/shared/config-lib.nix
+++ b/modules/shared/config-lib.nix
@@ -53,104 +53,44 @@ in
homeServiceAddress = lib.optionalString (config.swarselsystems.server.wireguard.interfaces ? wgHome) globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgHome.serverNetConfigPrefix}-wgHome".hosts.${config.node.name}.ipv4;
};
- mkIds = id: {
- uid = id;
- gid = id;
- };
-
- mkDeviceMac = id:
- let
- mod = n: d: n - (n / d) * d;
- toHexByte = n:
- let
- hex = "0123456789abcdef";
- hi = n / 16;
- lo = mod n 16;
- in
- builtins.substring hi 1 hex
- + builtins.substring lo 1 hex;
-
- max = 16777215; # 256^3 - 1
-
- b1 = id / (256 * 256);
- r1 = mod id (256 * 256);
- b2 = r1 / 256;
- b3 = mod r1 256;
- in
- if
- (id <= max)
- then
- (builtins.concatStringsSep ":"
- (map toHexByte [ b1 b2 b3 ]))
- else
- (throw "Device MAC ID too large (max is 16777215)");
-
mkMicrovm =
if config.swarselsystems.withMicroVMs then
- (guestName:
- { enableStorage ? false
- , withZfs ? false
- , ...
- }:
- {
- ${guestName} = {
- backend = "microvm";
- autostart = true;
- zfs = lib.mkIf withZfs {
- # stateful config that should be backed up
- "/state" = {
- pool = "Vault";
- dataset = "guests/${guestName}/state";
+ (guestName: {
+ ${guestName} = {
+ backend = "microvm";
+ autostart = true;
+ modules = [
+ (config.node.configDir + /guests/${guestName}/default.nix)
+ {
+ node.secretsDir = config.node.configDir + /secrets/${guestName};
+ node.configDir = config.node.configDir + /guests/${guestName};
+ networking.nftables.firewall = {
+ zones.untrusted.interfaces = lib.mkIf
+ (
+ lib.length config.guests.${guestName}.networking.links == 1
+ )
+ config.guests.${guestName}.networking.links;
};
- # data that should be backed up
- "/storage" = lib.mkIf enableStorage {
- pool = "Vault";
- dataset = "guests/${guestName}/storage";
- };
- # other stuff that should only reside on disk, not backed up
- "/persist" = {
- pool = "Vault";
- dataset = "guests/${guestName}/persist";
- };
- };
- modules = [
- (config.node.configDir + /guests/${guestName}/default.nix)
- {
- node.secretsDir = config.node.configDir + /secrets/${guestName};
- node.configDir = config.node.configDir + /guests/${guestName};
- networking.nftables.firewall = {
- zones.untrusted.interfaces = lib.mkIf
- (
- lib.length config.guests.${guestName}.networking.links == 1
- )
- config.guests.${guestName}.networking.links;
- };
- }
- "${self}/modules/nixos/optional/microvm-guest.nix"
- "${self}/modules/nixos/optional/systemd-networkd-base.nix"
- ];
- microvm = {
- system = config.node.arch;
- baseMac = config.repo.secrets.local.networking.networks.lan.mac;
- interfaces.vlan-services = {
- mac = lib.mkForce "02:${lib.substring 3 5 config.guests.${guestName}.microvm.baseMac}:${mkDeviceMac globals.networks.home-lan.vlans.services.hosts."${config.node.name}-${guestName}".id}";
-
- };
- };
- extraSpecialArgs = {
- inherit (inputs.self) nodes;
- inherit (inputs.self.pkgs.${config.node.arch}) lib;
- inherit inputs outputs minimal;
- inherit (inputs) self;
- withHomeManager = false;
- microVMParent = config.node.name;
- globals = inputs.self.globals.${config.node.arch};
- };
+ }
+ "${self}/modules/nixos/optional/microvm-guest.nix"
+ "${self}/modules/nixos/optional/systemd-networkd-base.nix"
+ ];
+ microvm = {
+ system = config.node.arch;
+ baseMac = config.repo.secrets.local.networking.networks.lan.mac;
+ interfaces.vlan-services = { };
};
- }) else
- (_: {
- _ = { };
- });
+ extraSpecialArgs = {
+ inherit (inputs.self) nodes;
+ inherit (inputs.self.pkgs.${config.node.arch}) lib;
+ inherit inputs outputs minimal;
+ inherit (inputs) self;
+ withHomeManager = false;
+ microVMParent = config.node.name;
+ globals = inputs.self.globals.${config.node.arch};
+ };
+ };
+ }) else (_: { _ = { }; });
genNginx =
{ serviceAddress
diff --git a/profiles/nixos/localserver/default.nix b/profiles/nixos/localserver/default.nix
index 8ff0411..7de318e 100644
--- a/profiles/nixos/localserver/default.nix
+++ b/profiles/nixos/localserver/default.nix
@@ -17,7 +17,6 @@
nftables = lib.mkDefault true;
server = {
general = lib.mkDefault true;
- ids = lib.mkDefault true;
network = lib.mkDefault true;
diskEncryption = lib.mkDefault true;
packages = lib.mkDefault true;
diff --git a/profiles/nixos/microvm/default.nix b/profiles/nixos/microvm/default.nix
index 226edc7..689bbbf 100644
--- a/profiles/nixos/microvm/default.nix
+++ b/profiles/nixos/microvm/default.nix
@@ -18,7 +18,6 @@
nftables = lib.mkDefault true;
server = {
general = lib.mkDefault true;
- ids = lib.mkDefault true;
packages = lib.mkDefault true;
ssh = lib.mkDefault true;
wireguard = lib.mkDefault true;
diff --git a/secrets/repo/pii.nix.enc b/secrets/repo/pii.nix.enc
index f2d6444..7dbf7b6 100644
--- a/secrets/repo/pii.nix.enc
+++ b/secrets/repo/pii.nix.enc
@@ -1,5 +1,5 @@
{
- "data": "ENC[AES256_GCM,data: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,iv:uO2n6uM8pG16CzQoRGpqqRp9CpSALkzFGnVdgGSeMS0=,tag:yPHLjZ+spz2djJuzg11U9A==,type:str]",
+ "data": "ENC[AES256_GCM,data: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,iv:6cj17MfBuVgr3MEEOGcU7IhGnxJA3jhkduRzgK2IHjU=,tag:bOsZVs+51ojqIZA9V2Dk3w==,type:str]",
"sops": {
"age": [
{
@@ -63,8 +63,8 @@
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1RGpibkIreUVYNlA2TXUw\nNmZzdnFtNHNOQ21QTE5ycTZHaFU0U0d3RzFnClFXRGFlZnB1WjRBZW5WN1BSR3dK\nY0Q4R3FGdEQycDEranUzWFJyWWVwOWMKLS0tIHp1Rm0wdi94Tk9kM3crWUtjb2Vo\nSHU4ay9WenpGTTg5T2Y1TzVicHBPZWMKEpeP/SVorT881MiLvR+6O9lcc9Fn0VV9\n5PqcowYMKFFXBQ27qjoFBDSwmD0UxpUgJeMtMAL+QLMILZ+G/0EcGw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
- "lastmodified": "2026-01-12T20:26:18Z",
- "mac": "ENC[AES256_GCM,data:jIB7rrK4yYjLlqUdl10JfYg3qaAYbuePPjZoGCBCCaohFdTWJPDoJMg5EFMFrZIDVOeWDjYVh6bB+PvXiIvU39NrEymS/Gu50ehCG7Lls7a90K+NSZQhuHS1y91+93t8vOq3kPisN0fh0J9tJwphB/NbmgDrE5e/NQsA4FjOsWU=,iv:VITEc80bKA5ZXuy9iUz7NjobYvVDKXDWhnXFyFwy84c=,tag:POCbrUFiKcc0418v9njPNw==,type:str]",
+ "lastmodified": "2026-01-09T13:03:32Z",
+ "mac": "ENC[AES256_GCM,data:1IPWZKNuDEObu7tuBZ5WiwWhx3srmudOiZFVFh27zfr7N5sGG2VAQGOCYgw8e8SuscUyyHRcw1uPuXGy0gB+ITX/kcClwJNqdT/ZvDvmP9zDAQMSAkwA+3xUBlobkabtj4+txtAc4xYleXIZN/22hpyeHJ1bOHaHNw+Zm2elk5Q=,iv:NL8trDkA+lMLnQqgrzsb31Tglvc6PtFky+vvSA6yIKY=,tag:S4BcowZWKscT2y1/OtZ86g==,type:str]",
"pgp": [
{
"created_at": "2026-01-10T00:52:57Z",