Compare commits
3
Commits
d0ef7f5b7e
...
2a8a72b7cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a8a72b7cb | ||
|
|
6f68f7d9e6 | ||
|
|
cffaec5fe8 |
Generated
+26
-4
@@ -877,11 +877,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784906229,
|
"lastModified": 1785677859,
|
||||||
"narHash": "sha256-9ZzpwieFwushXgbZZj1W4i8uhI0dmLZCcQSb6qVhn18=",
|
"narHash": "sha256-ZXWSL0F1ZDzeLSXFGvRom89nOEq80gXghc+ky/qA/l8=",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
"rev": "374e630d8fd0fa063753ffaa3537ada4fb4bc11e",
|
"rev": "f9894abfc9cd9c2a5dacb9fe3609b58f5d407392",
|
||||||
"revCount": 55,
|
"revCount": 69,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git"
|
"url": "ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git"
|
||||||
},
|
},
|
||||||
@@ -911,6 +911,7 @@
|
|||||||
"pppdotpm-site": "pppdotpm-site",
|
"pppdotpm-site": "pppdotpm-site",
|
||||||
"puppy-tracker": "puppy-tracker",
|
"puppy-tracker": "puppy-tracker",
|
||||||
"solo-referee": "solo-referee",
|
"solo-referee": "solo-referee",
|
||||||
|
"todo": "todo",
|
||||||
"whib-backend": "whib-backend",
|
"whib-backend": "whib-backend",
|
||||||
"whib-frontend": "whib-frontend"
|
"whib-frontend": "whib-frontend"
|
||||||
}
|
}
|
||||||
@@ -1042,6 +1043,27 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"todo": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1786622639,
|
||||||
|
"narHash": "sha256-q28DCpRkhuFcysdWNio4mIsR5VlonXbM8G+DyxitRPo=",
|
||||||
|
"ref": "main",
|
||||||
|
"rev": "a43e443720c3cc3fc38e9367bdac32e6f046c238",
|
||||||
|
"revCount": 17,
|
||||||
|
"type": "git",
|
||||||
|
"url": "file:///home/alex/code/todo"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "file:///home/alex/code/todo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"whib-backend": {
|
"whib-backend": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|||||||
@@ -98,6 +98,15 @@
|
|||||||
# url = "path:/home/alex/code/solo-referee";
|
# url = "path:/home/alex/code/solo-referee";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
todo = {
|
||||||
|
# Not yet pushed to gitea; using the local git checkout for now (git+file
|
||||||
|
# respects .gitignore, so web/node_modules isn't copied into the store).
|
||||||
|
# Flip these once the repo is published, matching the other apps above.
|
||||||
|
# url = "git+ssh://gitea@git.ppp.pm:1122/alex/todo.git?ref=main";
|
||||||
|
url = "git+file:///home/alex/code/todo?ref=main";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ in
|
|||||||
git.enable = true;
|
git.enable = true;
|
||||||
nginx.enable = true;
|
nginx.enable = true;
|
||||||
syncthing.enable = true;
|
syncthing.enable = true;
|
||||||
restic.enable = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
let
|
|
||||||
enabled = config.mod.restic.enable;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
mod.restic = {
|
|
||||||
enable = lib.mkEnableOption "Enable restic";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
|
||||||
fileSystems."/home/alex/backup" = {
|
|
||||||
device = "/dev/disk/by-uuid/34601701-65e6-4b2c-ac4d-8bef3dfd743f";
|
|
||||||
fsType = "ext4";
|
|
||||||
options = [ "nofail" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
services = {
|
|
||||||
restic.backups = {
|
|
||||||
"sync-to-external" = {
|
|
||||||
initialize = true;
|
|
||||||
|
|
||||||
passwordFile = config.age.secrets.restic-password.path;
|
|
||||||
|
|
||||||
paths = [ "/home/alex/sync" ];
|
|
||||||
repository = "/home/alex/backup/restic";
|
|
||||||
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "*-*-* 0/12:00:00"; # Every 12th hour, i.e. twice a day
|
|
||||||
Persistent = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
pruneOpts = [
|
|
||||||
"--keep-daily 1"
|
|
||||||
"--keep-weekly 7"
|
|
||||||
"--keep-yearly 12"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
"sync-to-cloud" = {
|
|
||||||
initialize = true;
|
|
||||||
|
|
||||||
passwordFile = config.age.secrets.restic-password.path;
|
|
||||||
environmentFile = config.age.secrets.restic-cloud-sync-key.path;
|
|
||||||
repositoryFile = config.age.secrets.restic-cloud-sync-repository.path;
|
|
||||||
|
|
||||||
paths = [ "/home/alex/sync" ];
|
|
||||||
exclude = [ "/home/alex/sync/reading-material" ];
|
|
||||||
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "*-*-* 0/12:00:00"; # Every 12th hour, i.e. twice a day
|
|
||||||
Persistent = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
pruneOpts = [
|
|
||||||
"--keep-daily 1"
|
|
||||||
"--keep-weekly 7"
|
|
||||||
"--keep-yearly 12"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
age = {
|
|
||||||
secrets = {
|
|
||||||
"restic-password".file = ../../../../secrets/backwards/restic-password.age;
|
|
||||||
"restic-cloud-sync-key".file = ../../../../secrets/backwards/restic-cloud-sync-key.age;
|
|
||||||
"restic-cloud-sync-repository".file =
|
|
||||||
../../../../secrets/backwards/restic-cloud-sync-repository.age;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -59,6 +59,19 @@
|
|||||||
"--http-timeout=60"
|
"--http-timeout=60"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"todo.ppp.pm" = {
|
||||||
|
dnsProvider = "hetzner";
|
||||||
|
environmentFile = config.age.secrets.hetzner-dns.path;
|
||||||
|
group = "nginx";
|
||||||
|
|
||||||
|
extraLegoFlags = [
|
||||||
|
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
|
||||||
|
"--dns.propagation-wait=60s"
|
||||||
|
"--dns-timeout=60"
|
||||||
|
"--http-timeout=60"
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ in
|
|||||||
|
|
||||||
nginx.enable = true;
|
nginx.enable = true;
|
||||||
syncthing.enable = true;
|
syncthing.enable = true;
|
||||||
|
restic.enable = true;
|
||||||
transmission.enable = true;
|
transmission.enable = true;
|
||||||
audiobookshelf.enable = true;
|
audiobookshelf.enable = true;
|
||||||
jellyfin.enable = true;
|
jellyfin.enable = true;
|
||||||
@@ -27,6 +28,7 @@ in
|
|||||||
disk-smart.enable = true;
|
disk-smart.enable = true;
|
||||||
puppy-tracker.enable = true;
|
puppy-tracker.enable = true;
|
||||||
solo-referee.enable = true;
|
solo-referee.enable = true;
|
||||||
|
todo.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
script = ''
|
script = ''
|
||||||
SUBDOMAINS="ha komga romm puppy"
|
SUBDOMAINS="ha komga romm puppy todo"
|
||||||
INTERFACE="enp3s0"
|
INTERFACE="enp3s0"
|
||||||
|
|
||||||
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
|
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
|
||||||
|
|||||||
@@ -834,6 +834,7 @@
|
|||||||
let fitMode = 'width';
|
let fitMode = 'width';
|
||||||
let naturalW = 0, naturalH = 0;
|
let naturalW = 0, naturalH = 0;
|
||||||
let pageMode = 'single'; // 'single' or 'double'
|
let pageMode = 'single'; // 'single' or 'double'
|
||||||
|
let isPdf = false; // current book is a PDF (rendered client-side via pdf.js)
|
||||||
|
|
||||||
const el = id => document.getElementById(id);
|
const el = id => document.getElementById(id);
|
||||||
const $loginScreen = el('login-screen');
|
const $loginScreen = el('login-screen');
|
||||||
@@ -883,6 +884,56 @@
|
|||||||
img.src = url;
|
img.src = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════
|
||||||
|
// PDF SUPPORT (pdf.js, lazy-loaded)
|
||||||
|
//
|
||||||
|
// Komga serves PDF pages as raw application/pdf (one single-page
|
||||||
|
// PDF per /pages/{n} request), which an <img> can't decode. For
|
||||||
|
// PDF books we fetch that mini-PDF and rasterize it to a canvas
|
||||||
|
// with pdf.js, then feed it through the same zoom/pan/double-page
|
||||||
|
// machinery as image pages.
|
||||||
|
// ══════════════════════════════════════════════
|
||||||
|
const PDFJS_VERSION = '3.11.174';
|
||||||
|
let pdfjsReady = null;
|
||||||
|
|
||||||
|
function ensurePdfjs() {
|
||||||
|
if (window.pdfjsLib) return Promise.resolve();
|
||||||
|
if (pdfjsReady) return pdfjsReady;
|
||||||
|
pdfjsReady = new Promise((resolve, reject) => {
|
||||||
|
const s = document.createElement('script');
|
||||||
|
s.src = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS_VERSION}/pdf.min.js`;
|
||||||
|
s.onload = () => {
|
||||||
|
window.pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||||
|
`https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS_VERSION}/pdf.worker.min.js`;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
s.onerror = () => { pdfjsReady = null; reject(new Error('pdf.js failed to load')); };
|
||||||
|
document.head.appendChild(s);
|
||||||
|
});
|
||||||
|
return pdfjsReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch page `pageNum` (a single-page PDF) and render it to a fresh
|
||||||
|
// offscreen canvas at a resolution that stays crisp when zoomed.
|
||||||
|
async function renderPdfPage(pageNum) {
|
||||||
|
const url = serverUrl.replace(/\/$/, '') + `/api/v1/books/${currentBook.id}/pages/${pageNum}`;
|
||||||
|
const buf = await fetch(url, { headers: { 'Authorization': authHeader } }).then(r => r.arrayBuffer());
|
||||||
|
const doc = await window.pdfjsLib.getDocument({ data: buf }).promise;
|
||||||
|
try {
|
||||||
|
const page = await doc.getPage(1);
|
||||||
|
const base = page.getViewport({ scale: 1 });
|
||||||
|
const scale = Math.min(4, Math.max(1, 2000 / base.width));
|
||||||
|
const viewport = page.getViewport({ scale });
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = Math.ceil(viewport.width);
|
||||||
|
canvas.height = Math.ceil(viewport.height);
|
||||||
|
await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
|
||||||
|
return canvas;
|
||||||
|
} finally {
|
||||||
|
doc.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ══════════════════════════════════════════════
|
// ══════════════════════════════════════════════
|
||||||
// LOGIN
|
// LOGIN
|
||||||
// ══════════════════════════════════════════════
|
// ══════════════════════════════════════════════
|
||||||
@@ -1139,6 +1190,7 @@
|
|||||||
async function openBook(book) {
|
async function openBook(book) {
|
||||||
currentBook = book;
|
currentBook = book;
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
|
isPdf = book.media?.mediaType === 'application/pdf';
|
||||||
document.querySelectorAll('.list-item').forEach(e => e.classList.remove('active'));
|
document.querySelectorAll('.list-item').forEach(e => e.classList.remove('active'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1150,6 +1202,16 @@
|
|||||||
if (bd.readProgress && !bd.readProgress.completed) currentPage = bd.readProgress.page || 1;
|
if (bd.readProgress && !bd.readProgress.completed) currentPage = bd.readProgress.page || 1;
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
|
if (isPdf) {
|
||||||
|
try {
|
||||||
|
await ensurePdfjs();
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
$readerPlaceholder.querySelector('p').textContent = 'Failed to load the PDF renderer (needs network for pdf.js).';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$readerPlaceholder.style.display = 'none';
|
$readerPlaceholder.style.display = 'none';
|
||||||
$imageContainer.style.display = 'block';
|
$imageContainer.style.display = 'block';
|
||||||
$readerControls.style.display = 'flex';
|
$readerControls.style.display = 'flex';
|
||||||
@@ -1190,6 +1252,8 @@
|
|||||||
currentPage = pageNum;
|
currentPage = pageNum;
|
||||||
$loadingOverlay.style.display = 'flex';
|
$loadingOverlay.style.display = 'flex';
|
||||||
|
|
||||||
|
if (isPdf) { loadPdfPage(pageNum); return; }
|
||||||
|
|
||||||
if (pageMode === 'double') {
|
if (pageMode === 'double') {
|
||||||
const leftPage = currentPage;
|
const leftPage = currentPage;
|
||||||
const rightPage = currentPage + 1 <= totalPages ? currentPage + 1 : null;
|
const rightPage = currentPage + 1 <= totalPages ? currentPage + 1 : null;
|
||||||
@@ -1256,6 +1320,69 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadPdfPage(pageNum) {
|
||||||
|
if (pageMode === 'double') {
|
||||||
|
const leftPage = currentPage;
|
||||||
|
const rightPage = currentPage + 1 <= totalPages ? currentPage + 1 : null;
|
||||||
|
|
||||||
|
$pageIndicator.textContent = rightPage
|
||||||
|
? `${leftPage}-${rightPage} / ${totalPages}`
|
||||||
|
: `${leftPage} / ${totalPages}`;
|
||||||
|
|
||||||
|
const jobs = [renderPdfPage(leftPage)];
|
||||||
|
if (rightPage) jobs.push(renderPdfPage(rightPage));
|
||||||
|
|
||||||
|
Promise.all(jobs)
|
||||||
|
.then(canvases => {
|
||||||
|
const left = canvases[0];
|
||||||
|
const right = canvases[1] || null;
|
||||||
|
|
||||||
|
const lw = left.width, lh = left.height;
|
||||||
|
const rw = right ? right.width : 0;
|
||||||
|
const rh = right ? right.height : 0;
|
||||||
|
const canvasW = lw + (right ? rw : 0);
|
||||||
|
const canvasH = Math.max(lh, rh || 0);
|
||||||
|
|
||||||
|
$comicCanvas.width = canvasW;
|
||||||
|
$comicCanvas.height = canvasH;
|
||||||
|
const ctx = $comicCanvas.getContext('2d');
|
||||||
|
ctx.fillStyle = '#0a0a0c';
|
||||||
|
ctx.fillRect(0, 0, canvasW, canvasH);
|
||||||
|
ctx.drawImage(left, 0, (canvasH - lh) / 2);
|
||||||
|
if (right) ctx.drawImage(right, lw, (canvasH - rh) / 2);
|
||||||
|
|
||||||
|
const prevW = naturalW;
|
||||||
|
naturalW = canvasW;
|
||||||
|
naturalH = canvasH;
|
||||||
|
showElement($comicCanvas);
|
||||||
|
|
||||||
|
if (!prevW) applyFitMode(); else centerAtCurrentScale();
|
||||||
|
$loadingOverlay.style.display = 'none';
|
||||||
|
updateReadProgress(rightPage || leftPage);
|
||||||
|
})
|
||||||
|
.catch(() => { $loadingOverlay.style.display = 'none'; });
|
||||||
|
} else {
|
||||||
|
$pageIndicator.textContent = `${currentPage} / ${totalPages}`;
|
||||||
|
|
||||||
|
renderPdfPage(currentPage)
|
||||||
|
.then(canvas => {
|
||||||
|
$comicCanvas.width = canvas.width;
|
||||||
|
$comicCanvas.height = canvas.height;
|
||||||
|
$comicCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
||||||
|
|
||||||
|
const prevW = naturalW;
|
||||||
|
naturalW = canvas.width;
|
||||||
|
naturalH = canvas.height;
|
||||||
|
showElement($comicCanvas);
|
||||||
|
|
||||||
|
if (!prevW) applyFitMode(); else centerAtCurrentScale();
|
||||||
|
$loadingOverlay.style.display = 'none';
|
||||||
|
updateReadProgress(currentPage);
|
||||||
|
})
|
||||||
|
.catch(() => { $loadingOverlay.style.display = 'none'; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function pageStep() {
|
function pageStep() {
|
||||||
return pageMode === 'double' ? 2 : 1;
|
return pageMode === 'double' ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
enabled = config.mod.restic.enable;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.mod.restic.enable = lib.mkEnableOption "Enable restic";
|
||||||
|
|
||||||
|
config = lib.mkIf enabled {
|
||||||
|
fileSystems."/mnt/backup" = {
|
||||||
|
device = "/dev/disk/by-uuid/34601701-65e6-4b2c-ac4d-8bef3dfd743f";
|
||||||
|
fsType = "ext4";
|
||||||
|
options = [ "nofail" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.restic.backups = {
|
||||||
|
"sync-to-external" = {
|
||||||
|
initialize = true;
|
||||||
|
passwordFile = config.age.secrets.restic-password.path;
|
||||||
|
paths = [ "/mnt/sync/public" ];
|
||||||
|
repository = "/mnt/backup/restic";
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "*-*-* 0/12:00:00";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-daily 1"
|
||||||
|
"--keep-weekly 7"
|
||||||
|
"--keep-yearly 12"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
"sync-to-cloud" = {
|
||||||
|
initialize = true;
|
||||||
|
passwordFile = config.age.secrets.restic-password.path;
|
||||||
|
environmentFile = config.age.secrets.restic-cloud-sync-key.path;
|
||||||
|
repositoryFile = config.age.secrets.restic-cloud-sync-repository.path;
|
||||||
|
paths = [ "/mnt/sync/public" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "*-*-* 0/12:00:00";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-daily 1"
|
||||||
|
"--keep-weekly 7"
|
||||||
|
"--keep-yearly 12"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
age.secrets = {
|
||||||
|
"restic-password".file = ../../../../secrets/manatee/restic-password.age;
|
||||||
|
"restic-cloud-sync-key".file = ../../../../secrets/manatee/restic-cloud-sync-key.age;
|
||||||
|
"restic-cloud-sync-repository".file = ../../../../secrets/manatee/restic-cloud-sync-repository.age;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
enabled = config.mod.todo.enable;
|
||||||
|
nginxEnabled = config.mod.nginx.enable;
|
||||||
|
port = 8091;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
mod.todo = {
|
||||||
|
enable = lib.mkEnableOption "Enable todo module";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
inputs.todo.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
|
config = lib.mkIf enabled {
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Todo";
|
||||||
|
port = port;
|
||||||
|
description = "Offline-first todo";
|
||||||
|
# Login needs HTTPS (Secure cookies), so link to the public vhost.
|
||||||
|
url = "https://todo.ppp.pm";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
services.todo = {
|
||||||
|
enable = true;
|
||||||
|
address = "127.0.0.1";
|
||||||
|
inherit port;
|
||||||
|
# Served publicly over HTTPS via the nginx vhost below.
|
||||||
|
secureCookies = true;
|
||||||
|
tokenFile = config.age.secrets."todo-token".path;
|
||||||
|
# sessionSecretFile is left unset: the app generates one in its state dir
|
||||||
|
# (/var/lib/todo) on first boot, mode 0600.
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = lib.mkIf nginxEnabled {
|
||||||
|
virtualHosts."todo.ppp.pm" = {
|
||||||
|
forceSSL = true;
|
||||||
|
useACMEHost = "todo.ppp.pm";
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:${toString port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
age.secrets."todo-token".file = ../../../../secrets/manatee/todo-token.age;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
age-encryption.org/v1
|
|
||||||
-> ssh-ed25519 Pu0HWg rTAkGJbth0WCE8KM50fHaCyXeO/NrmWXiDT/JH9ciAI
|
|
||||||
kTMVbJRwOSh9Da1O9YYx21/7IdfZrb2OhoOJxNEIKSg
|
|
||||||
-> ssh-ed25519 +oNaHQ DtMpPTuAjS1GyK2WalNJzErEE1mCos/R5aZyMnun+UQ
|
|
||||||
B81FnJ5z70HzI6yvqiy6Lhr2X9ZjEi5dhM6u47flujA
|
|
||||||
--- r9HCFWVU5LBiRBdRwOA1+IRBY1/I/1UeukGtFz7BxWE
|
|
||||||
TÔK%£žWÔ*&˜Ä°–-&àÛÕ8)|×áॣù Œ¯è07—�µ¾ªŒúÍòl»ÁQò0†ÒßÔ––ÆÂ]¬¬Û¸”mP”^~ç/þ#†šõæ"ëÀš+ž)ÕY¾n¼@ã჌R§“¨_pÓÂFÜÛ%᤼#
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
age-encryption.org/v1
|
|
||||||
-> ssh-ed25519 Pu0HWg tZ6zAXOBdiWyyUeOZZ66w1ij8xuHY98fvClPn8/jQVs
|
|
||||||
AVp3Y04vSbnkurqjAouiDojd5IMFVCYyldXv0v4n9W4
|
|
||||||
-> ssh-ed25519 +oNaHQ KK44MdrfQLZK44eYWpLiTFm3d/bx6WTsHm98MkvhQTw
|
|
||||||
CYJJWbpHbLQHvzTWLbujg1AZ3KvgCshVUrolPE1hUho
|
|
||||||
--- aBOxH3rbMriVBctdVGdQXFH/KYWLbweGzda5sN4HJOA
|
|
||||||
íNo´Ç¡O>š‡æãõ\IÉ#0¬‰õ+Y8ƒvàCS#é�O›žÖeGè;-T“d®�,V@3” 䛸þ]`Ç\b²Dõ+„ö6½öšð䘤nŠ%‡NÈ’
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
age-encryption.org/v1
|
|
||||||
-> ssh-ed25519 Pu0HWg qmcWFPndrhXlpjBtSsVNARAOHM9UNtfcEvtSGx/BLHY
|
|
||||||
EdnzUWju9g61idxWmDaaxSZ6ZiVvhFLOKH3hY1Kyk8s
|
|
||||||
-> ssh-ed25519 +oNaHQ CMBvVWHwVsY89rbdYvVoVeoZlIXLJoIN8xsoqqBnSiA
|
|
||||||
+xogVU/MBezQzq1rbIOqT5PGNYqM8o0Pmojk2npqT4U
|
|
||||||
--- mdhPf3weI9cOcaAh9j/CVB+KRfPSRuT678oueeJjdCk
|
|
||||||
ZñmݹÿÅÓº*ˆŒ9kÀw¶N¯Zh&ÈGô␍ßkÞÀºÆŸþŸA#ª“�lù u¤Qôïù—›ÛîTÔŸ×˜Ž²Ž,ßC�V3†Äµ”
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 wkRvNA T7nivShTgBPRAWTTOUYWfCWNAFlQ5e6rlB92XV/xRVM
|
||||||
|
JJrxtTSteza0sCOJVz4z8y/8npX3hWhfo8y85UFAU80
|
||||||
|
-> ssh-ed25519 +oNaHQ DuAtW/id53RC4G4EKWr2y1G8K7UbI5r1vGf1ICG5pC8
|
||||||
|
xkkGeHOjMmuJ0mKnziUALSEJpV/fVUX5oOBiTptuapo
|
||||||
|
--- aVjxHPHDuWLhSPMKG5wE67YIzR18LKihrcPqYC+8bnM
|
||||||
|
…Ób”áõˆ–3qÆó|À7܇È}žÀœŠMí ;† [␍LH% Úr‰ævdTíþY….¯t¾Ì©zFÒ*©3-±D¾ð:¾Š©Y>ûòÀþõò“Tn2Fž¡HJŽM_¶éŒ{$ÑOUšä˜º,ìÝÑ•”k*l¢D���¥^ÏTd+®†
|
||||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
|||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 wkRvNA eL6ZQuNPvdO0gHEW5IObuLQzcOFpOaxzw/wcYvuxfUs
|
||||||
|
M88b5Ex9I3XY29oYML73BM/0TLo+e4pGG008M2VloKM
|
||||||
|
-> ssh-ed25519 +oNaHQ hIzDhww0WePNFVdvTwGL9wsOWN9r0ZiUmYkjb82KeQg
|
||||||
|
DwwCiFXFbYQV64JuRc8pc997CY+2rZxC8cRdTvHPSqA
|
||||||
|
--- u37YqZsBBtrxZ0Wmw6QJWgazNbqW00GVsQgyRxE41Gc
|
||||||
|
›-bpP ÷W.²ˆ-»:ˆNÕûc¦~‡géH‚u\¸MaX·prACæU�)¾lÓá:—+Û\X\í<N¸ J`"Þðo»éDÆtOÂ8²å…
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 wkRvNA 11epAkGKr0IpXqtWGQ73rZUsiWQRlL++fc2B2TvQ90M
|
||||||
|
lSKGeNiL82UIyhg1wrY9ylltFcITDffJLgA6j02eS2M
|
||||||
|
-> ssh-ed25519 +oNaHQ pg1rC56bQRxO2Eb5onV8TkeB/Wsc7HhvufBfslkK+gA
|
||||||
|
znYqNOBhQX9bx5k07vVTHye/RLxOAkQ2dCagZWyWBkQ
|
||||||
|
--- Dxthx4NFJfe26jFnXH/3NYILME+tlO2FUsSVGCP4ucY
|
||||||
|
°]œê&Ì�¥ü…²�+/ªÖ×Dš‘�ô[6HIÂe>™“‚¾ÛIà Ná¡1Ak¬’ä4ZHG
|
||||||
|
T}o8×f<Û¦r<Â
|
||||||
+4
-3
@@ -44,6 +44,10 @@ in {
|
|||||||
"manatee/romm-metadata-api-keys.age".publicKeys = [ manatee alex ];
|
"manatee/romm-metadata-api-keys.age".publicKeys = [ manatee alex ];
|
||||||
"manatee/puppy-tracker-invite-code.age".publicKeys = [ manatee alex ];
|
"manatee/puppy-tracker-invite-code.age".publicKeys = [ manatee alex ];
|
||||||
"manatee/solo-referee-api-key.age".publicKeys = [ manatee alex ];
|
"manatee/solo-referee-api-key.age".publicKeys = [ manatee alex ];
|
||||||
|
"manatee/todo-token.age".publicKeys = [ manatee alex ];
|
||||||
|
"manatee/restic-password.age".publicKeys = [ manatee alex ];
|
||||||
|
"manatee/restic-cloud-sync-key.age".publicKeys = [ manatee alex ];
|
||||||
|
"manatee/restic-cloud-sync-repository.age".publicKeys = [ manatee alex ];
|
||||||
|
|
||||||
"backwards/root.backwards.age".publicKeys = [ backwards alex ];
|
"backwards/root.backwards.age".publicKeys = [ backwards alex ];
|
||||||
"backwards/root.backwards.pub.age".publicKeys = [ backwards alex ];
|
"backwards/root.backwards.pub.age".publicKeys = [ backwards alex ];
|
||||||
@@ -51,9 +55,6 @@ in {
|
|||||||
"backwards/alex.backwards-manatee.pub.age".publicKeys = [ backwards manatee alex ];
|
"backwards/alex.backwards-manatee.pub.age".publicKeys = [ backwards manatee alex ];
|
||||||
"backwards/syncthing-cert.age".publicKeys = [ backwards alex ];
|
"backwards/syncthing-cert.age".publicKeys = [ backwards alex ];
|
||||||
"backwards/syncthing-key.age".publicKeys = [ backwards alex ];
|
"backwards/syncthing-key.age".publicKeys = [ backwards alex ];
|
||||||
"backwards/restic-password.age".publicKeys = [ backwards alex ];
|
|
||||||
"backwards/restic-cloud-sync-key.age".publicKeys = [ backwards alex ];
|
|
||||||
"backwards/restic-cloud-sync-repository.age".publicKeys = [ backwards alex ];
|
|
||||||
"backwards/alex.backwards-git.ppp.pm.age".publicKeys = [ backwards alex ];
|
"backwards/alex.backwards-git.ppp.pm.age".publicKeys = [ backwards alex ];
|
||||||
"backwards/alex.backwards-git.ppp.pm.pub.age".publicKeys = [ backwards alex ];
|
"backwards/alex.backwards-git.ppp.pm.pub.age".publicKeys = [ backwards alex ];
|
||||||
"backwards/wireless-network-secrets.age".publicKeys = [ backwards alex ];
|
"backwards/wireless-network-secrets.age".publicKeys = [ backwards alex ];
|
||||||
|
|||||||
Reference in New Issue
Block a user