Merge branch 'main' of github.com:philip-peterson/petersweb-infra
This commit is contained in:
commit
c55f02a9e4
9 changed files with 198 additions and 19 deletions
32
bump-blog-quine.sh
Executable file
32
bump-blog-quine.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LINUX=/root/petersweb-infra/nixos/linux.nix
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 <sha256-digest>"
|
||||||
|
echo " e.g. $0 sha256:2e2d92abae0ba68be780fff581523480ac05444690dbf38bf4330f1dda099e2a"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $# -eq 1 ]] || usage
|
||||||
|
|
||||||
|
NEW_DIGEST="${1#sha256:}" # strip leading "sha256:" if provided
|
||||||
|
|
||||||
|
# Validate: hex string of the right length
|
||||||
|
if ! [[ "$NEW_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "Error: digest must be a 64-character lowercase hex string (got: $NEW_DIGEST)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
OLD_LINE=$(grep -n 'blog-quine@sha256:' "$LINUX")
|
||||||
|
echo "Current: $OLD_LINE"
|
||||||
|
|
||||||
|
sed -i -E "s|(blog-quine@sha256:)[0-9a-f]{64}|\1${NEW_DIGEST}|" "$LINUX"
|
||||||
|
|
||||||
|
NEW_LINE=$(grep -n 'blog-quine@sha256:' "$LINUX")
|
||||||
|
echo "Updated: $NEW_LINE"
|
||||||
|
|
||||||
|
echo "Applying NixOS configuration..."
|
||||||
|
nixos-rebuild switch --flake /root/petersweb-infra/nixos#mainframe
|
||||||
|
echo "Done. Tail logs with: docker logs -f blog-quine"
|
||||||
|
|
@ -31,6 +31,7 @@ nixos-rebuild switch --flake /root/petersweb-infra/nixos#mainframe
|
||||||
| `keys/` | SSH public keys used as age recipients |
|
| `keys/` | SSH public keys used as age recipients |
|
||||||
| `system/` | User definitions and home-manager config |
|
| `system/` | User definitions and home-manager config |
|
||||||
| `pdxdestiny/` | Static site files for pdxdestiny.com |
|
| `pdxdestiny/` | Static site files for pdxdestiny.com |
|
||||||
|
| `vnc-desktop/` | Dockerfile + build scripts for the KDE Plasma VNC desktop container |
|
||||||
|
|
||||||
## Secrets (agenix)
|
## Secrets (agenix)
|
||||||
|
|
||||||
|
|
@ -75,19 +76,32 @@ Note: `secrets/default.nix` is the agenix recipients file. Agenix looks for `sec
|
||||||
| `gitea-runner-ubuntu.service` | Forgejo (Gitea) Actions CI runner, uses docker images |
|
| `gitea-runner-ubuntu.service` | Forgejo (Gitea) Actions CI runner, uses docker images |
|
||||||
| `forgejo-arion.service` | Forgejo itself, run via Arion/Podman |
|
| `forgejo-arion.service` | Forgejo itself, run via Arion/Podman |
|
||||||
| `riverside-arion.service` | Riverside app, run via Arion/Docker |
|
| `riverside-arion.service` | Riverside app, run via Arion/Docker |
|
||||||
| `docker-navidrome.service` | Navidrome music server on port 4533 |
|
| `podman-navidrome.service` | Navidrome music server on port 4533 |
|
||||||
| `docker-nextcloud.service` | Nextcloud/SSH container on port 8087 |
|
| `podman-nextcloud.service` | Nextcloud/SSH container on port 8087 |
|
||||||
| `docker-sync.io.service` | sync.io app on port 9090 |
|
| `podman-sync.io.service` | sync.io app on port 9090 |
|
||||||
| `docker-blog-quine.service` | Blog on port 3010 |
|
| `podman-blog-quine.service` | Blog on port 3010 |
|
||||||
| `docker-coldairnetworks.service` | Cold Air Networks site on port 3012 |
|
| `podman-coldairnetworks.service` | Cold Air Networks site on port 3012 |
|
||||||
|
| `podman-vnc-desktop.service` | KDE Plasma desktop, noVNC on port 6080 (localhost only) |
|
||||||
|
| `build-vnc-image.service` | Builds the VNC desktop image from `vnc-desktop/`; runs before `podman-vnc-desktop` |
|
||||||
| nginx | Reverse proxy + ACME certs for multiple domains |
|
| nginx | Reverse proxy + ACME certs for multiple domains |
|
||||||
|
|
||||||
## Virtualisation
|
## Virtualisation
|
||||||
|
|
||||||
- **Podman** is used for Forgejo (via Arion). `DOCKER_HOST` for the gitea-runner is set to `unix:///run/podman/podman.sock`.
|
- **Podman** is used for all OCI containers (`virtualisation.oci-containers.backend = "podman"`) — navidrome, nextcloud, blog, VNC desktop, etc. — and for Forgejo via Arion.
|
||||||
- **Docker** is used for the OCI containers (navidrome, nextcloud, etc.) and the riverside Arion stack.
|
- **Docker** is still present for the Riverside Arion stack.
|
||||||
|
- `DOCKER_HOST` for the gitea-runner is set to `unix:///run/podman/podman.sock`.
|
||||||
- The gitea-runner runs docker images for CI jobs, so the `gitea-runner` user is in the `docker` and `podman` supplementary groups.
|
- The gitea-runner runs docker images for CI jobs, so the `gitea-runner` user is in the `docker` and `podman` supplementary groups.
|
||||||
|
|
||||||
|
## VNC desktop
|
||||||
|
|
||||||
|
`podman-vnc-desktop.service` runs a KDE Plasma desktop inside a container, accessible via noVNC at `localhost:6080` (reverse-proxied by nginx). The image is built locally — no registry involved.
|
||||||
|
|
||||||
|
- **Image source**: `vnc-desktop/Dockerfile` (Ubuntu 24.04, TigerVNC, KDE, Firefox, patched Discover)
|
||||||
|
- **Auto-rebuild**: `build-vnc-image.service` runs on boot and on `nixos-rebuild switch` whenever `vnc-desktop/` changes. The trigger is `vncContext = builtins.path { path = ./vnc-desktop; }` — a Nix store path that invalidates when any file in the directory changes.
|
||||||
|
- **Auto-restart**: `podman-vnc-desktop.service` has `restartTriggers = [ vncContext ]`, so the container restarts automatically after a rebuild during `nixos-rebuild switch`.
|
||||||
|
- **Secrets**: `VNC_PASSWORD` and `ROOT_PASSWORD` come from `age.secrets.vnc-password`.
|
||||||
|
- **Discover logging**: `vnc-desktop/discover-logging/` contains a build-time patch (`patch.py`) that instruments `PKTransaction.cpp` with `qWarning` calls to diagnose hanging installs. Logs visible via `podman logs vnc-desktop`.
|
||||||
|
|
||||||
## Networking / DNS
|
## Networking / DNS
|
||||||
|
|
||||||
- Dynamic DNS via `invoke-ddns` (NearlyFreeSpeech provider).
|
- Dynamic DNS via `invoke-ddns` (NearlyFreeSpeech provider).
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,20 @@ alias gb='git branch'
|
||||||
alias gs='git status'
|
alias gs='git status'
|
||||||
alias gpcb='git push origin $(git rev-parse --abbrev-ref HEAD)'
|
alias gpcb='git push origin $(git rev-parse --abbrev-ref HEAD)'
|
||||||
|
|
||||||
|
function git() {
|
||||||
|
if [[ $1 == clone && $# -ge 2 ]]; then
|
||||||
|
local url=$2
|
||||||
|
# expand foo/bar -> https://github.com/foo/bar.git (no protocol, no dots in host part)
|
||||||
|
if [[ $url =~ ^[A-Za-z0-9_-]+/[A-Za-z0-9_.-]+$ ]]; then
|
||||||
|
url="https://github.com/${url%.git}.git"
|
||||||
|
echo "Cloning $url" >&2
|
||||||
|
fi
|
||||||
|
command git clone "$url" "${@:3}"
|
||||||
|
else
|
||||||
|
command git "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
alias u='cd ..'
|
alias u='cd ..'
|
||||||
alias uu='cd ../..'
|
alias uu='cd ../..'
|
||||||
alias uuu='cd ../../..'
|
alias uuu='cd ../../..'
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
services = {
|
services = {
|
||||||
app = {
|
app = {
|
||||||
service = {
|
service = {
|
||||||
image = "forge.quinefoundation.com/ironmagma/riverside@sha256:835a2a407aa3f60193c089e2c5fd26193bd0ac90f3da6aa5e8edaa0789db15aa";
|
image = "forge.quinefoundation.com/ironmagma/riverside@sha256:6ad578b0668ac91f37fc3677ce12960b5eeb23c3ba7238e1ba137d35e60fea58";
|
||||||
container_name = "riverside";
|
container_name = "riverside";
|
||||||
restart = "unless-stopped";
|
restart = "unless-stopped";
|
||||||
networks = [ "riverside" ];
|
networks = [ "riverside" ];
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
nixPkgs = specialArgs.nixPkgs;
|
nixPkgs = specialArgs.nixPkgs;
|
||||||
ourRustVersion = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.complete);
|
ourRustVersion = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.complete);
|
||||||
|
|
||||||
|
vncContext = builtins.path { path = ./vnc-desktop; name = "vnc-desktop-context"; };
|
||||||
|
|
||||||
ourRustPlatform = nixPkgs.makeRustPlatform {
|
ourRustPlatform = nixPkgs.makeRustPlatform {
|
||||||
rustc = ourRustVersion;
|
rustc = ourRustVersion;
|
||||||
cargo = ourRustVersion;
|
cargo = ourRustVersion;
|
||||||
|
|
@ -170,8 +172,8 @@ in {
|
||||||
systemd.services.arion-riverside.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
|
systemd.services.arion-riverside.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
|
||||||
|
|
||||||
# Build the VNC desktop image locally from the Dockerfile — no registry push/pull needed.
|
# Build the VNC desktop image locally from the Dockerfile — no registry push/pull needed.
|
||||||
# Nix copies the build context into the store; the hash changes when Dockerfile or
|
# vncContext is a Nix store path that changes whenever any file under vnc-desktop/ changes,
|
||||||
# start.sh change, triggering a rebuild on the next nixos-rebuild switch.
|
# which causes build-vnc-image to re-run and podman-vnc-desktop to restart on nixos-rebuild.
|
||||||
systemd.services.build-vnc-image = {
|
systemd.services.build-vnc-image = {
|
||||||
description = "Build VNC desktop container image from Dockerfile";
|
description = "Build VNC desktop container image from Dockerfile";
|
||||||
wantedBy = [ "podman-vnc-desktop.service" ];
|
wantedBy = [ "podman-vnc-desktop.service" ];
|
||||||
|
|
@ -181,9 +183,7 @@ in {
|
||||||
RemainAfterExit = true;
|
RemainAfterExit = true;
|
||||||
ExecStart = pkgs.writeShellScript "build-vnc-image" ''
|
ExecStart = pkgs.writeShellScript "build-vnc-image" ''
|
||||||
STAMP=/var/lib/build-vnc-image/context-hash
|
STAMP=/var/lib/build-vnc-image/context-hash
|
||||||
EXPECTED="${builtins.hashString "sha256"
|
EXPECTED="${vncContext}"
|
||||||
(builtins.readFile ./vnc-desktop/Dockerfile +
|
|
||||||
builtins.readFile ./vnc-desktop/start.sh)}"
|
|
||||||
if [ -f "$STAMP" ] && [ "$(cat "$STAMP")" = "$EXPECTED" ]; then
|
if [ -f "$STAMP" ] && [ "$(cat "$STAMP")" = "$EXPECTED" ]; then
|
||||||
echo "VNC image is up to date, skipping build"
|
echo "VNC image is up to date, skipping build"
|
||||||
exit 0
|
exit 0
|
||||||
|
|
@ -191,13 +191,15 @@ in {
|
||||||
echo "Building VNC desktop image..."
|
echo "Building VNC desktop image..."
|
||||||
${pkgs.podman}/bin/podman build \
|
${pkgs.podman}/bin/podman build \
|
||||||
-t forge.quinefoundation.com/ironmagma/vnc-desktop:latest \
|
-t forge.quinefoundation.com/ironmagma/vnc-desktop:latest \
|
||||||
${./vnc-desktop}
|
${vncContext}
|
||||||
mkdir -p "$(dirname "$STAMP")"
|
mkdir -p "$(dirname "$STAMP")"
|
||||||
echo "$EXPECTED" > "$STAMP"
|
echo "$EXPECTED" > "$STAMP"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.podman-vnc-desktop.restartTriggers = [ "${vncContext}" ];
|
||||||
|
|
||||||
services.gitea-actions-runner.instances."ubuntu" = {
|
services.gitea-actions-runner.instances."ubuntu" = {
|
||||||
enable = true;
|
enable = true;
|
||||||
name = "ubuntu";
|
name = "ubuntu";
|
||||||
|
|
@ -345,7 +347,7 @@ in {
|
||||||
|
|
||||||
"blog-quine" = {
|
"blog-quine" = {
|
||||||
autoStart = true;
|
autoStart = true;
|
||||||
image = "quineglobal/blog-quine@sha256:3c2901f772c322d81f843c04d6982b9f50ff0b46d3cc457d9f868a7ff5a15497";
|
image = "quineglobal/blog-quine@sha256:88097e4867a99a375db490bf7a989c122653cdb48bfdf6d9ad5e2f6a0bfb2d38";
|
||||||
volumes = [];
|
volumes = [];
|
||||||
environment = {};
|
environment = {};
|
||||||
ports = ["3010:8080"];
|
ports = ["3010:8080"];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 NFD/vg ZScq11dQhcK72TVjmnwo7OXG8yarNhU6XFqo+n2XvCg
|
-> ssh-ed25519 NFD/vg BPXpLwp8zZADR3vYGht8wEMrKCegRiHBkO22ZgHHrXU
|
||||||
eXK/3Jp5J/kjjl3sRV1L4q0ZY2SEPZSEgTczqkLONJk
|
nh3J6CscPxGpsdaKyL0q1a5EmROX3lVtZv1A7/pvm4s
|
||||||
--- GV0KPgO/rZpKGL+6M/JW9dUzuoNiA0e3Nm2ubBhLgUc
|
--- UGCcZKsSD4opeR41BAsJT1Hi0OPLpkHyiGLuYPH/dwA
|
||||||
sÃCêÎ+ÃyÖÊä²b’ª ÅöÜŒ6ˆ€ö™ÀéÊÛ4€Îcßô–Â#íïeM'½™p¤°ø<C2B0>n
|
±<EFBFBD>´fÑMüØäýN®¡}Êæ¤[¶ÑkøVÍ·ØtÐ"׌qÞfÛ„Æš<C386>ÞF/âÁx£"¸
Läèbe;Ìjõÿ±"^'Zm—X
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
jq
|
jq
|
||||||
killall
|
killall
|
||||||
tmux
|
tmux
|
||||||
|
tree
|
||||||
unzip
|
unzip
|
||||||
vim
|
vim
|
||||||
wget
|
wget
|
||||||
|
|
|
||||||
32
nixos/vnc-desktop/discover-logging/build.sh
Normal file
32
nixos/vnc-desktop/discover-logging/build.sh
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Enable deb-src so apt-get source works
|
||||||
|
sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
|
||||||
|
|
||||||
|
apt-get update -qq
|
||||||
|
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
dpkg-dev \
|
||||||
|
build-essential \
|
||||||
|
devscripts \
|
||||||
|
python3
|
||||||
|
|
||||||
|
apt-get build-dep -y plasma-discover
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
apt-get source plasma-discover
|
||||||
|
|
||||||
|
SRC_DIR=$(ls -d /tmp/plasma-discover-*/)
|
||||||
|
|
||||||
|
# Apply logging patch
|
||||||
|
python3 /discover-logging/patch.py "$SRC_DIR/libdiscover/backends/PackageKitBackend/PKTransaction.cpp"
|
||||||
|
|
||||||
|
cd "$SRC_DIR"
|
||||||
|
DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -b -uc -us -j"$(nproc)"
|
||||||
|
|
||||||
|
# Install the rebuilt packages (packagekit-backend.so lives in plasma-discover_*.deb)
|
||||||
|
dpkg -i /tmp/plasma-discover_*.deb
|
||||||
|
|
||||||
|
# Clean up to keep image lean
|
||||||
|
rm -rf /tmp/plasma-discover-* /tmp/*.deb /tmp/*.dsc /tmp/*.tar.*
|
||||||
84
nixos/vnc-desktop/discover-logging/patch.py
Normal file
84
nixos/vnc-desktop/discover-logging/patch.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Patch PKTransaction.cpp to add verbose logging so we can diagnose
|
||||||
|
why installs hang on "Installing..." status.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def replace_once(src, old, new, label):
|
||||||
|
count = src.count(old)
|
||||||
|
if count != 1:
|
||||||
|
print(f"ERROR: '{label}' matched {count} times (expected 1)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
return src.replace(old, new)
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path) as f:
|
||||||
|
src = f.read()
|
||||||
|
|
||||||
|
# 1. trigger(): log what phase we're entering and with what flags
|
||||||
|
src = replace_once(src,
|
||||||
|
'void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags)\n{',
|
||||||
|
'void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags)\n{\n'
|
||||||
|
' qWarning() << "[DISCOVER] trigger(): flags=" << flags << "role=" << role();',
|
||||||
|
'trigger() header'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. statusChanged(): log the raw PK status instead of the collapsed UI status
|
||||||
|
src = replace_once(src,
|
||||||
|
'void PKTransaction::statusChanged()\n{'
|
||||||
|
'\n setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus);',
|
||||||
|
'void PKTransaction::statusChanged()\n{'
|
||||||
|
'\n qWarning() << "[DISCOVER] statusChanged(): pk_status=" << m_trans->status()'
|
||||||
|
' << "percentage=" << m_trans->percentage()'
|
||||||
|
' << "lastPackage=" << m_trans->lastPackage();'
|
||||||
|
'\n setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus);',
|
||||||
|
'statusChanged() body'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. progressChanged(): log when percentage updates (or fails to)
|
||||||
|
src = replace_once(src,
|
||||||
|
' auto percent = m_trans->percentage();\n if (percent == 101) {\n qWarning() << "percentage cannot be calculated";',
|
||||||
|
' auto percent = m_trans->percentage();\n'
|
||||||
|
' qWarning() << "[DISCOVER] progressChanged(): raw_pct=" << percent << "pk_status=" << m_trans->status();\n'
|
||||||
|
' if (percent == 101) {\n qWarning() << "percentage cannot be calculated";',
|
||||||
|
'progressChanged() body'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. cleanup(): log the exit/cancel/failed/simulate flags
|
||||||
|
src = replace_once(src,
|
||||||
|
'void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime)\n{',
|
||||||
|
'void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime)\n{\n'
|
||||||
|
' const bool _simulate_flag = m_trans && (m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate);\n'
|
||||||
|
' qWarning() << "[DISCOVER] cleanup(): exit=" << exit << "runtime=" << runtime'
|
||||||
|
' << "simulate=" << _simulate_flag'
|
||||||
|
' << "proceedFunctions=" << m_proceedFunctions.size();',
|
||||||
|
'cleanup() header'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. errorFound(): log every error, including ones currently silently swallowed
|
||||||
|
src = replace_once(src,
|
||||||
|
'void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString &error)\n{'
|
||||||
|
'\n if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled'
|
||||||
|
'\n || err == PackageKit::Transaction::ErrorNotAuthorized) {'
|
||||||
|
'\n return;\n }',
|
||||||
|
'void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString &error)\n{'
|
||||||
|
'\n qWarning() << "[DISCOVER] errorFound(): err=" << err << "detail=" << error;'
|
||||||
|
'\n if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled'
|
||||||
|
'\n || err == PackageKit::Transaction::ErrorNotAuthorized) {'
|
||||||
|
'\n return;\n }',
|
||||||
|
'errorFound() body'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6. LocalFilePKResource path: log the .deb path being installed
|
||||||
|
src = replace_once(src,
|
||||||
|
' m_trans = PackageKit::Daemon::installFile(QUrl(app->packageName()).toLocalFile(), flags);',
|
||||||
|
' qWarning() << "[DISCOVER] installFile():" << QUrl(app->packageName()).toLocalFile() << "flags=" << flags;\n'
|
||||||
|
' m_trans = PackageKit::Daemon::installFile(QUrl(app->packageName()).toLocalFile(), flags);',
|
||||||
|
'installFile() call'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(src)
|
||||||
|
|
||||||
|
print(f"Patched {path}")
|
||||||
Loading…
Reference in a new issue