mirror of
https://github.com/oqyude/nixos.git
synced 2026-10-10 05:55:24 +03:00
vtimeline added and formatted
This commit is contained in:
@@ -34,7 +34,8 @@ let
|
|||||||
# an empty omo.jsonc that drops every agent override — see the journal entry
|
# an empty omo.jsonc that drops every agent override — see the journal entry
|
||||||
# below).
|
# below).
|
||||||
ohMyOpenagentConfig = {
|
ohMyOpenagentConfig = {
|
||||||
"$schema" = "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/omo.schema.json";
|
"$schema" =
|
||||||
|
"https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/omo.schema.json";
|
||||||
_migrations = [
|
_migrations = [
|
||||||
"2026-07-opencode-config-unification"
|
"2026-07-opencode-config-unification"
|
||||||
"2026-08-reasoning-unification"
|
"2026-08-reasoning-unification"
|
||||||
@@ -404,7 +405,7 @@ in
|
|||||||
# The versioned symlink (`home-manager-NN-link`) is found by following
|
# The versioned symlink (`home-manager-NN-link`) is found by following
|
||||||
# `home-manager` one hop rather than hardcoding `home-manager-24-link`,
|
# `home-manager` one hop rather than hardcoding `home-manager-24-link`,
|
||||||
# so this keeps working across HM major-version bumps.
|
# so this keeps working across HM major-version bumps.
|
||||||
home.activation.relinkHomeManager = lib.hm.dag.entryAfter [] ''
|
home.activation.relinkHomeManager = lib.hm.dag.entryAfter [ ] ''
|
||||||
hmVersioned="$(readlink "$HOME/.local/state/nix/profiles/home-manager" 2>/dev/null || true)"
|
hmVersioned="$(readlink "$HOME/.local/state/nix/profiles/home-manager" 2>/dev/null || true)"
|
||||||
target="$HOME/.local/state/nix/profiles/$hmVersioned"
|
target="$HOME/.local/state/nix/profiles/$hmVersioned"
|
||||||
newGen="$(readlink -e "''${XDG_STATE_HOME:-$HOME/.local/state}/home-manager/gcroots/new-home" 2>/dev/null || true)"
|
newGen="$(readlink -e "''${XDG_STATE_HOME:-$HOME/.local/state}/home-manager/gcroots/new-home" 2>/dev/null || true)"
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ in
|
|||||||
# is the only source — and the container will refuse to start with
|
# is the only source — and the container will refuse to start with
|
||||||
# WEBUI_SECRET_KEY="" (env.py:762 — SystemExit). The error message is
|
# WEBUI_SECRET_KEY="" (env.py:762 — SystemExit). The error message is
|
||||||
# the clear signal that the secret needs to be created.
|
# the clear signal that the secret needs to be created.
|
||||||
environmentFiles = lib.optional (builtins.pathExists ./secrets/open-webui.env)
|
environmentFiles = lib.optional (builtins.pathExists ./secrets/open-webui.env) "/run/secrets/open-webui-env";
|
||||||
"/run/secrets/open-webui-env";
|
|
||||||
volumes = [
|
volumes = [
|
||||||
"${panel}/data:/app/backend/data:rw"
|
"${panel}/data:/app/backend/data:rw"
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ let
|
|||||||
# of relying on `@/etc/nix/machines`, which Nix 2.34 parses but does
|
# of relying on `@/etc/nix/machines`, which Nix 2.34 parses but does
|
||||||
# not act on (the daemon's `external-builders` list stays empty and the
|
# not act on (the daemon's `external-builders` list stays empty and the
|
||||||
# client reports "configure remote builders via 'builders'" forever).
|
# client reports "configure remote builders via 'builders'" forever).
|
||||||
formatBuilder = b:
|
formatBuilder =
|
||||||
|
b:
|
||||||
let
|
let
|
||||||
# Nix 2.34 refuses to dispatch derivations to a builder whose protocol
|
# Nix 2.34 refuses to dispatch derivations to a builder whose protocol
|
||||||
# is `ssh` (the NixOS default): the daemon leaves `external-builders`
|
# is `ssh` (the NixOS default): the daemon leaves `external-builders`
|
||||||
@@ -42,19 +43,19 @@ let
|
|||||||
proto = "ssh-ng://";
|
proto = "ssh-ng://";
|
||||||
user = if b.sshUser != null && b.sshUser != "" then "${b.sshUser}@" else "";
|
user = if b.sshUser != null && b.sshUser != "" then "${b.sshUser}@" else "";
|
||||||
systems =
|
systems =
|
||||||
if b.system != null then b.system
|
if b.system != null then
|
||||||
else if b.systems != [ ] then lib.concatStringsSep "," b.systems
|
b.system
|
||||||
else "-";
|
else if b.systems != [ ] then
|
||||||
|
lib.concatStringsSep "," b.systems
|
||||||
|
else
|
||||||
|
"-";
|
||||||
sshKey = if b.sshKey != null && b.sshKey != "" then b.sshKey else "-";
|
sshKey = if b.sshKey != null && b.sshKey != "" then b.sshKey else "-";
|
||||||
maxJobs = toString b.maxJobs;
|
maxJobs = toString b.maxJobs;
|
||||||
speedFactor = toString b.speedFactor;
|
speedFactor = toString b.speedFactor;
|
||||||
allFeats = b.supportedFeatures ++ b.mandatoryFeatures;
|
allFeats = b.supportedFeatures ++ b.mandatoryFeatures;
|
||||||
supported =
|
supported = if allFeats == [ ] then "-" else lib.concatStringsSep "," allFeats;
|
||||||
if allFeats == [ ] then "-"
|
|
||||||
else lib.concatStringsSep "," allFeats;
|
|
||||||
mandatory =
|
mandatory =
|
||||||
if b.mandatoryFeatures == [ ] then "-"
|
if b.mandatoryFeatures == [ ] then "-" else lib.concatStringsSep "," b.mandatoryFeatures;
|
||||||
else lib.concatStringsSep "," b.mandatoryFeatures;
|
|
||||||
publicKey = if b.publicHostKey != null then b.publicHostKey else "-";
|
publicKey = if b.publicHostKey != null then b.publicHostKey else "-";
|
||||||
in
|
in
|
||||||
lib.concatStringsSep " " [
|
lib.concatStringsSep " " [
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
./systemd.nix
|
./systemd.nix
|
||||||
./ttyd.nix
|
./ttyd.nix
|
||||||
|
./vtimeline.nix
|
||||||
./uptime-kuma.nix
|
./uptime-kuma.nix
|
||||||
# ../containers/remnawave.nix
|
# ../containers/remnawave.nix
|
||||||
# ./coturn.nix
|
# ./coturn.nix
|
||||||
|
|||||||
+14
-26
@@ -123,23 +123,24 @@ in
|
|||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
};
|
};
|
||||||
# vtimeline.zeroq.su — static site behind Authelia forward-auth.
|
# vtimeline.zeroq.su — Veeam Timeline View. Reverse-proxies the
|
||||||
# Files live under /home/oqyude/External/Git/VeeamTimelineView/public_html,
|
# entire vhost to a local Node.js/Express process (managed by
|
||||||
# which is bind-mounted to /var/lib/vtimeline (see systemd.mounts below)
|
# systemd as `vtimeline-api` — see modules/server/vtimeline.nix),
|
||||||
# because /home/oqyude is mode 700 and the nginx user (uid 60) cannot
|
# which serves both the static frontend (public_html/) and the
|
||||||
# traverse it. Authentication is delegated to Authelia via
|
# /api/uploads JSON-upload CRUD over a single listener on
|
||||||
# auth_request: nginx sub-requests /authelia on every hit, Authelia
|
# 127.0.0.1:8000. Authelia forward-auth is wired on `/` so every
|
||||||
# returns 2xx if the session cookie is valid or 401 (which nginx
|
# hit (static OR /api/*) requires a valid session cookie.
|
||||||
# converts into a 401 to the client; Authelia's response headers
|
#
|
||||||
# carry the redirect target). The login UI itself is served by the
|
# client_max_body_size 6m matches the server.js body limit
|
||||||
# authelia.zeroq.su vhost below — same Authelia container, different
|
# (5 MB hard cap). nginx's default 1m would 413 any upload near
|
||||||
# vhost.
|
# the cap before the request reached the node process.
|
||||||
"vtimeline.zeroq.su" = {
|
"vtimeline.zeroq.su" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
root = "/var/lib/vtimeline";
|
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8000";
|
||||||
|
proxyWebsockets = true;
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
auth_request /authelia;
|
auth_request /authelia;
|
||||||
auth_request_set $authelia_user $upstream_http_remote_user;
|
auth_request_set $authelia_user $upstream_http_remote_user;
|
||||||
@@ -160,6 +161,7 @@ in
|
|||||||
# Authelia constructs an absolute redirect back to the
|
# Authelia constructs an absolute redirect back to the
|
||||||
# original vhost.
|
# original vhost.
|
||||||
error_page 401 =302 https://authelia.zeroq.su/?rd=$scheme://$host$request_uri;
|
error_page 401 =302 https://authelia.zeroq.su/?rd=$scheme://$host$request_uri;
|
||||||
|
client_max_body_size 6m;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"= /authelia" = {
|
"= /authelia" = {
|
||||||
@@ -368,20 +370,6 @@ in
|
|||||||
443
|
443
|
||||||
];
|
];
|
||||||
|
|
||||||
# Bind-mount the vtimeline source tree into /var/lib so the nginx user
|
|
||||||
# (uid 60) doesn't have to traverse /home/oqyude (mode 700). The mount is
|
|
||||||
# lazy (x-systemd.automount) and nofail, so a missing /home/oqyude/External
|
|
||||||
# only shows up as a per-request 500/403, never as a hard boot failure.
|
|
||||||
systemd.mounts = [
|
|
||||||
(xlib.helpers.mkSystemdBind {
|
|
||||||
what = "/home/oqyude/External/Git/VeeamTimelineView/public_html";
|
|
||||||
where = "/var/lib/vtimeline";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
(xlib.helpers.mkTmpfile "d" "/var/lib/vtimeline" "0755" "nginx" "nginx")
|
|
||||||
];
|
|
||||||
|
|
||||||
# Note: the previous vtimeline-htpasswd sops declaration lived here. It
|
# Note: the previous vtimeline-htpasswd sops declaration lived here. It
|
||||||
# was removed when authelia replaced nginx's auth_basic (see the vtimeline
|
# was removed when authelia replaced nginx's auth_basic (see the vtimeline
|
||||||
# vhost above). The encrypted file modules/server/secrets/vtimeline-htpasswd.yaml
|
# vhost above). The encrypted file modules/server/secrets/vtimeline-htpasswd.yaml
|
||||||
|
|||||||
@@ -100,8 +100,10 @@ let
|
|||||||
# а в 24.x был путём. `lib.getExe` умеет оба: derivation → bin/<name>,
|
# а в 24.x был путём. `lib.getExe` умеет оба: derivation → bin/<name>,
|
||||||
# string → возвращает как есть.
|
# string → возвращает как есть.
|
||||||
userShellExe =
|
userShellExe =
|
||||||
let shell = config.users.users.${cfg.user}.shell or "/run/current-system/sw/bin/bash";
|
let
|
||||||
in if builtins.isString shell then shell else lib.getExe shell;
|
shell = config.users.users.${cfg.user}.shell or "/run/current-system/sw/bin/bash";
|
||||||
|
in
|
||||||
|
if builtins.isString shell then shell else lib.getExe shell;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
services.ttyd = {
|
services.ttyd = {
|
||||||
@@ -109,7 +111,11 @@ in
|
|||||||
port = 7681;
|
port = 7681;
|
||||||
interface = "127.0.0.1";
|
interface = "127.0.0.1";
|
||||||
user = "oqyude";
|
user = "oqyude";
|
||||||
entrypoint = [ userShellExe "-i" "-l" ];
|
entrypoint = [
|
||||||
|
userShellExe
|
||||||
|
"-i"
|
||||||
|
"-l"
|
||||||
|
];
|
||||||
writeable = true;
|
writeable = true;
|
||||||
checkOrigin = true;
|
checkOrigin = true;
|
||||||
maxClients = 0;
|
maxClients = 0;
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
xlib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
# Veeam Timeline View — static frontend + REST API, both served by a
|
||||||
|
# single Node.js/Express process. nginx reverse-proxies the entire
|
||||||
|
# vtimeline.zeroq.su vhost to the local listener (see the vhost in
|
||||||
|
# modules/server/nginx.nix — no `root`, no `try_files`).
|
||||||
|
#
|
||||||
|
# API surface (server.js):
|
||||||
|
# GET /api/uploads list uploaded JSONs
|
||||||
|
# POST /api/uploads upload (raw JSON or {name, content}, ≤ 5 MB)
|
||||||
|
# GET /api/uploads/:name fetch parsed
|
||||||
|
# DELETE /api/uploads/:name delete
|
||||||
|
#
|
||||||
|
# Auth: delegated to Authelia at the nginx layer (`auth_request
|
||||||
|
# /authelia` on the vhost). The API itself trusts whoever reaches it —
|
||||||
|
# the only ingress is the same-origin nginx proxy, so the wide-open
|
||||||
|
# CORS header in server.js is a no-op in practice.
|
||||||
|
#
|
||||||
|
# Storage: /home/oqyude/External/Git/VeeamTimelineView/
|
||||||
|
# ├── public_html/ static frontend (read-only at runtime)
|
||||||
|
# ├── server.js Express app
|
||||||
|
# ├── node_modules/ installed deps (express + transitive)
|
||||||
|
# └── uploads/ persistent JSON uploads (read+write)
|
||||||
|
#
|
||||||
|
# The repo lives on the External ext4 drive (fstab). The service gates
|
||||||
|
# on home-oqyude-External.mount so it never starts against an empty
|
||||||
|
# directory after a cold boot without the drive plugged in.
|
||||||
|
let
|
||||||
|
user = xlib.device.username;
|
||||||
|
group = "users";
|
||||||
|
repo = "/home/oqyude/External/Git/VeeamTimelineView";
|
||||||
|
port = 8000;
|
||||||
|
node = pkgs.nodejs_22;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# systemPackages so /run/current-system/sw/bin/node exists for any
|
||||||
|
# operator tooling (logs, ad-hoc npm scripts). The systemd unit
|
||||||
|
# below references the absolute Nix-store path, so this is purely
|
||||||
|
# for the CLI path.
|
||||||
|
environment.systemPackages = [ node ];
|
||||||
|
|
||||||
|
# Bind-mount public_html into /var/lib. The current vhost proxies
|
||||||
|
# all traffic to the node listener, so nginx itself does not need
|
||||||
|
# this — kept for parity with the pre-API setup (so any future
|
||||||
|
# static-only fallback or external inspection has a stable
|
||||||
|
# /home-independent path). x-systemd.automount + nofail: a missing
|
||||||
|
# /home/oqyude/External only surfaces as a per-request 404, never a
|
||||||
|
# boot failure.
|
||||||
|
systemd.mounts = [
|
||||||
|
(xlib.helpers.mkSystemdBind {
|
||||||
|
what = "${repo}/public_html";
|
||||||
|
where = "/var/lib/vtimeline";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
(xlib.helpers.mkTmpfile "d" "/var/lib/vtimeline" "0755" "nginx" "nginx")
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.vtimeline-api = {
|
||||||
|
description = "Veeam Timeline View API (Node.js + Express)";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
after = [
|
||||||
|
"network-online.target"
|
||||||
|
"home-oqyude-External.mount"
|
||||||
|
];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
requires = [ "home-oqyude-External.mount" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = user;
|
||||||
|
Group = group;
|
||||||
|
WorkingDirectory = repo;
|
||||||
|
ExecStart = "${node}/bin/node ${repo}/server.js";
|
||||||
|
Environment = "PORT=${toString port}";
|
||||||
|
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "5s";
|
||||||
|
|
||||||
|
# Sandbox. Server.js only needs to read public_html/ +
|
||||||
|
# node_modules/ and to read-write uploads/. ReadWritePaths
|
||||||
|
# lifts the ProtectHome/ProtectSystem write protection for
|
||||||
|
# uploads/ only; everywhere else stays read-only.
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = "read-only";
|
||||||
|
ReadWritePaths = [ "${repo}/uploads" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user