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 = ` +
+

Pinned

+ +
+ + + `; + 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 = ` -
-

Pinned

- -
- - - `; - 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",