Browse Source
Runs a browser on the seedbox and displays it on the workstation through
waypipe, for the cases FlareSolverr cannot cover: logging into a private
tracker, or reaching an interface only exposed on the seedbox network.
waypipe lives in the container rather than on the Fedora CoreOS host. The
waypipe server has to share a mount namespace with the browser, and putting it
in the image keeps the host a stock, unlayered CoreOS while pinning the waypipe
version -- 0.9 (C) and 0.10+ (Rust) do not interoperate. The entry point is
seedbox-waypipe.sh, passed to the client with --remote-bin, because
"ssh <host> <command>" never sources /etc/profile.d.
LibreWolf because the seedbox is aarch64, which rules out Tor Browser and
Mullvad Browser (no Linux arm64 build). It ships resistFingerprinting, uBlock
Origin and no telemetry out of the box, and disables its own updater, which
suits an image rebuilt nightly by librewolf-build.timer.
Four things that are not obvious from the code:
- SELinux checks "connectto" on a Unix socket against the listening process,
not the socket file, and the listener is the unconfined SSH session. No
label the container could carry is allowed to connect, hence
--security-opt label=disable rather than a system-wide policy module.
- Firefox's content sandbox chroots itself and needs CAP_SYS_CHROOT in the
bounding set, while bubblewrap -- used by the SVG image loader -- refuses to
run when the caller holds capabilities without being setuid. podman puts
--cap-add in the ambient set too, so the image entrypoint drops ambient and
inheritable capabilities with setpriv, leaving the bounding set alone.
- The forwarded socket is bind-mounted by directory, not by file: mounting the
file alone makes podman synthesize a root-owned 0755 parent inside the
container, and waypipe then cannot unlink its own socket on shutdown.
- "waypipe ssh" forwards several of its own options to the server side, and a
repeated flag is a hard error, so the shim only injects what is absent.
The browser deliberately does not use Network=host, unlike the rest of the
stack: a hostile page must not reach the *arr interfaces on localhost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017kXTkLKMkwYSHZET7ckzmd
main
11 changed files with 606 additions and 0 deletions
@ -0,0 +1,49 @@ |
|||||
|
## |
||||
|
## Configuration of the on-demand browser container. |
||||
|
## |
||||
|
## Sourced by /etc/quadlets/seedbox/seedbox-waypipe.sh (as root) and by |
||||
|
## /etc/profile.d/seedbox.sh, so keep it to plain shell assignments. |
||||
|
## |
||||
|
## Every setting uses the ${VAR:-default} form so that an environment variable |
||||
|
## still wins over the file, which makes one-off overrides easy: |
||||
|
## BROWSER_PODMAN_OPTS=--security-opt=seccomp=unconfined seedbox-browser ... |
||||
|
## |
||||
|
## It is owned by root:root on purpose (see the seedbox Makefile): the shim |
||||
|
## sources it with root privileges, so it must not be writable by the seedbox |
||||
|
## user the browser runs as. |
||||
|
## |
||||
|
|
||||
|
# Container image, rebuilt every night by librewolf-build.timer. |
||||
|
BROWSER_IMAGE="${BROWSER_IMAGE:-localhost/librewolf:latest}" |
||||
|
|
||||
|
# The browser runs as the unprivileged seedbox user, like the rest of the stack. |
||||
|
# Changing these means rebuilding the image: the Containerfile creates |
||||
|
# /run/user/<uid> (the browser's XDG_RUNTIME_DIR) with matching ownership. |
||||
|
BROWSER_UID="${BROWSER_UID:-10017}" |
||||
|
BROWSER_GID="${BROWSER_GID:-10000}" |
||||
|
|
||||
|
# Persistent browser profile: bookmarks, cookies, extension state. |
||||
|
# On the production seedbox, point this at the SSD virtiofs mount instead: |
||||
|
# BROWSER_PROFILE_DIR=/var/lib/virtiofs/ssd/librewolf |
||||
|
BROWSER_PROFILE_DIR="${BROWSER_PROFILE_DIR:-/var/lib/quadlets/seedbox/librewolf}" |
||||
|
|
||||
|
# Where downloads land. On the production seedbox, the import directory the |
||||
|
# *arr stack already watches is the obvious candidate: |
||||
|
# BROWSER_DOWNLOAD_DIR=/var/lib/virtiofs/hdd/import |
||||
|
BROWSER_DOWNLOAD_DIR="${BROWSER_DOWNLOAD_DIR:-/var/lib/quadlets/seedbox/downloads}" |
||||
|
|
||||
|
# Firefox-family browsers are multi-process and need a large /dev/shm. |
||||
|
BROWSER_SHM_SIZE="${BROWSER_SHM_SIZE:-1g}" |
||||
|
|
||||
|
# --no-gpu: there is no GPU in this VM, so block wayland-drm and linux-dmabuf |
||||
|
# instead of letting waypipe negotiate them and fail. |
||||
|
# |
||||
|
# These are only added when the client has not passed them already: "waypipe |
||||
|
# ssh" forwards several of its own options to the server side, and a repeated |
||||
|
# flag is a hard error ("cannot be used multiple times"). |
||||
|
BROWSER_WAYPIPE_OPTS="${BROWSER_WAYPIPE_OPTS:---no-gpu}" |
||||
|
|
||||
|
# Escape hatch for extra podman arguments, e.g. if the browser's own sandbox |
||||
|
# ends up needing a looser seccomp profile: |
||||
|
# BROWSER_PODMAN_OPTS="--security-opt seccomp=unconfined" |
||||
|
BROWSER_PODMAN_OPTS="${BROWSER_PODMAN_OPTS:-}" |
||||
@ -0,0 +1,77 @@ |
|||||
|
# LibreWolf, displayed on a remote desktop through waypipe (Wayland over SSH). |
||||
|
# |
||||
|
# waypipe is installed *here* rather than on the Fedora CoreOS host: the waypipe |
||||
|
# server has to share a mount namespace with the browser (it creates the Wayland |
||||
|
# socket the browser connects to), and putting it in the image keeps the host a |
||||
|
# stock, immutable CoreOS with no layered packages. It also pins the waypipe |
||||
|
# version, which matters: waypipe 0.9 (C) and 0.10+ (Rust) do not interoperate, |
||||
|
# and the client and server versions have to match. |
||||
|
FROM quay.io/fedora/fedora:44 |
||||
|
|
||||
|
COPY librewolf.repo /etc/yum.repos.d/librewolf.repo |
||||
|
|
||||
|
# The font set is pinned explicitly and must stay identical across rebuilds: the |
||||
|
# list of installed fonts is a fingerprinting vector, and a set that drifts every |
||||
|
# night would make this browser a little more recognisable every day. |
||||
|
# |
||||
|
# mesa-dri-drivers provides llvmpipe -- there is no GPU in this VM. |
||||
|
# |
||||
|
# The icon theme, the extra gdk-pixbuf loaders and the MIME database are weak |
||||
|
# dependencies, and they are not optional here: without them GTK cannot resolve |
||||
|
# an icon, falls back to image-missing.svg, fails to load that too, and aborts |
||||
|
# the whole browser with |
||||
|
# "Gtk:ERROR:gtkiconhelper.c:495:ensure_surface_for_gicon: assertion failed". |
||||
|
RUN dnf install -y --setopt=install_weak_deps=False \ |
||||
|
librewolf \ |
||||
|
waypipe \ |
||||
|
adwaita-icon-theme \ |
||||
|
hicolor-icon-theme \ |
||||
|
gdk-pixbuf2-modules-extra \ |
||||
|
shared-mime-info \ |
||||
|
util-linux \ |
||||
|
dejavu-sans-fonts dejavu-serif-fonts dejavu-sans-mono-fonts \ |
||||
|
liberation-sans-fonts liberation-serif-fonts liberation-mono-fonts \ |
||||
|
google-noto-color-emoji-fonts \ |
||||
|
mesa-dri-drivers \ |
||||
|
&& dnf clean all \ |
||||
|
&& rm -rf /var/cache/dnf |
||||
|
|
||||
|
# See the header of itix-hardening.cfg for why this is appended to LibreWolf's |
||||
|
# own AutoConfig file instead of living in librewolf.overrides.cfg (which would |
||||
|
# sit in the persistent profile volume and escape the nightly rebuild). |
||||
|
COPY itix-hardening.cfg /tmp/itix-hardening.cfg |
||||
|
RUN cat /tmp/itix-hardening.cfg >> /usr/share/librewolf/librewolf.cfg \ |
||||
|
&& rm -f /tmp/itix-hardening.cfg |
||||
|
|
||||
|
# Two sandboxes fight over CAP_SYS_CHROOT in here, and setpriv (util-linux) is |
||||
|
# what reconciles them: |
||||
|
# |
||||
|
# - Firefox's content sandbox chroots itself. It does so inside a fresh user |
||||
|
# namespace, where it holds every capability still present in the *bounding* |
||||
|
# set -- so CAP_SYS_CHROOT has to be in the bounding set, or content |
||||
|
# processes die with "Sandbox: chroot: EPERM". |
||||
|
# - bubblewrap, which glycin (Fedora's SVG image loader) uses, refuses to run |
||||
|
# at all when the calling process holds capabilities without being setuid: |
||||
|
# "bwrap: Unexpected capabilities but not setuid, old file caps config?". |
||||
|
# GTK then fails to load an icon and aborts the browser. |
||||
|
# |
||||
|
# podman's --cap-add puts the capability in the *ambient* set too when the |
||||
|
# container runs as a non-root user, which is exactly what bubblewrap objects |
||||
|
# to. Dropping ambient and inheritable capabilities needs no privilege, and |
||||
|
# leaves the bounding set untouched, so both sandboxes get what they want. |
||||
|
ENTRYPOINT [ "/usr/bin/setpriv", "--inh-caps=-all", "--ambient-caps=-all", "--" ] |
||||
|
|
||||
|
# XDG_RUNTIME_DIR for the browser user: waypipe creates its Wayland socket in |
||||
|
# there. podman does not mount a tmpfs over /run, so a directory baked into the |
||||
|
# image with the right ownership is writable, and disappears with the container. |
||||
|
# |
||||
|
# The uid/gid are hardcoded, like everywhere else in this cookbook; they have to |
||||
|
# match BROWSER_UID/BROWSER_GID in browser.conf. |
||||
|
RUN install -d -m 0755 -o root -g root /run/user \ |
||||
|
&& install -d -m 0700 -o 10017 -g 10000 /run/user/10017 |
||||
|
|
||||
|
# The profile and the download directory are bind-mounted at run time. |
||||
|
VOLUME /home/browser /downloads |
||||
|
|
||||
|
# The shim runs "waypipe ... server -- librewolf" through the entrypoint above. |
||||
|
CMD [ "librewolf" ] |
||||
@ -0,0 +1,27 @@ |
|||||
|
|
||||
|
/** ------------------------------ |
||||
|
* [CATEGORY] ITIX OVERRIDES |
||||
|
* |
||||
|
* Appended to librewolf.cfg when the image is built (see Containerfile). |
||||
|
* librewolf.cfg is an AutoConfig file, evaluated after defaults/pref/*.js, and |
||||
|
* the last defaultPref() for a given pref is the one that sticks -- which is |
||||
|
* why these overrides are appended rather than dropped in a separate file. |
||||
|
* |
||||
|
* Deliberately short. LibreWolf already enables privacy.resistFingerprinting, |
||||
|
* and RFP works by making every user look *identical*: each pref we flip on top |
||||
|
* of it moves this browser out of that crowd and makes it easier to single out, |
||||
|
* not harder. Only add something here when it corrects for the fact that this |
||||
|
* browser runs in a container displayed over waypipe. |
||||
|
* ------------------------------- */ |
||||
|
|
||||
|
// Letterboxing rounds the content area to a coarse grid of common sizes. |
||||
|
// LibreWolf ships the pref but leaves it off; Tor Browser has it on. Here the |
||||
|
// window is resized to whatever the waypipe client feels like, and window size |
||||
|
// is one of the strongest signals there is, so turn it on. |
||||
|
defaultPref("privacy.resistFingerprinting.letterboxing", true); |
||||
|
|
||||
|
// Downloads land in the bind-mounted /downloads directory (see browser.conf), |
||||
|
// not somewhere inside the container's ephemeral filesystem. |
||||
|
defaultPref("browser.download.folderList", 2); |
||||
|
defaultPref("browser.download.dir", "/downloads"); |
||||
|
defaultPref("browser.download.start_downloads_in_tmp_dir", false); |
||||
@ -0,0 +1,11 @@ |
|||||
|
# LibreWolf is not packaged in Fedora. This is the upstream repository, which |
||||
|
# publishes both x86_64 and aarch64 (the seedbox VM runs on Ampere hardware). |
||||
|
# |
||||
|
# Both the packages and the repository metadata are GPG-checked. |
||||
|
[librewolf] |
||||
|
name=LibreWolf Software Repository |
||||
|
baseurl=https://repo.librewolf.net |
||||
|
gpgcheck=1 |
||||
|
repo_gpgcheck=1 |
||||
|
gpgkey=https://repo.librewolf.net/pubkey.gpg |
||||
|
enabled=1 |
||||
@ -0,0 +1,227 @@ |
|||||
|
#!/bin/bash |
||||
|
# |
||||
|
# waypipe "remote binary" shim for the seedbox browser. |
||||
|
# |
||||
|
# `waypipe ssh` does not run the browser directly. It runs a waypipe *server* on |
||||
|
# the remote side, which connects back to the local waypipe client through an |
||||
|
# SSH-forwarded Unix socket, creates a Wayland socket of its own, and starts the |
||||
|
# program under it. That server has to share a mount namespace with the browser, |
||||
|
# so here it runs *inside* the container -- which is what keeps waypipe and the |
||||
|
# browser off the immutable Fedora CoreOS host entirely. |
||||
|
# |
||||
|
# waypipe calls this script exactly the way it would call the waypipe binary: |
||||
|
# |
||||
|
# seedbox-waypipe.sh --socket <path> server -- librewolf |
||||
|
# |
||||
|
# so the arguments are handed to the in-container waypipe untouched. Wire it in |
||||
|
# from the client with --remote-bin; see the cookbook README for the full |
||||
|
# client-side command line. |
||||
|
# |
||||
|
# Called with no arguments, it opens an interactive shell in the image instead, |
||||
|
# which is handy to inspect what the nightly rebuild produced. |
||||
|
# |
||||
|
set -Eeuo pipefail |
||||
|
|
||||
|
CONFIG_FILE=/etc/quadlets/seedbox/browser.conf |
||||
|
|
||||
|
## |
||||
|
## The containers of this cookbook are started by root and drop to the seedbox |
||||
|
## user themselves (--user below), but ssh logs us in as an unprivileged |
||||
|
## account, so re-exec through sudo first. |
||||
|
## |
||||
|
## sudo scrubs the environment, so any BROWSER_* override the caller set is |
||||
|
## carried across explicitly. That does let the caller influence a podman |
||||
|
## command line that runs as root: only grant sudo on this script to accounts |
||||
|
## that already have full root anyway (on Fedora CoreOS, the wheel group). |
||||
|
## |
||||
|
if [ "$(id -u)" -ne 0 ]; then |
||||
|
overrides=() |
||||
|
while IFS= read -r name; do |
||||
|
overrides+=("${name}=${!name}") |
||||
|
done < <(compgen -v | grep "^BROWSER_" || true) |
||||
|
|
||||
|
exec sudo -n -- /usr/bin/env ${overrides[@]+"${overrides[@]}"} "$0" "$@" |
||||
|
fi |
||||
|
|
||||
|
# Defaults live in browser.conf, which uses the ${VAR:-default} form so that the |
||||
|
# environment still wins; these are the fallbacks if the file is missing. |
||||
|
# shellcheck source=/dev/null |
||||
|
[ -r "$CONFIG_FILE" ] && . "$CONFIG_FILE" |
||||
|
|
||||
|
BROWSER_IMAGE="${BROWSER_IMAGE:-localhost/librewolf:latest}" |
||||
|
BROWSER_UID="${BROWSER_UID:-10017}" |
||||
|
BROWSER_GID="${BROWSER_GID:-10000}" |
||||
|
BROWSER_PROFILE_DIR="${BROWSER_PROFILE_DIR:-/var/lib/quadlets/seedbox/librewolf}" |
||||
|
BROWSER_DOWNLOAD_DIR="${BROWSER_DOWNLOAD_DIR:-/var/lib/quadlets/seedbox/downloads}" |
||||
|
BROWSER_SHM_SIZE="${BROWSER_SHM_SIZE:-1g}" |
||||
|
BROWSER_WAYPIPE_OPTS="${BROWSER_WAYPIPE_OPTS:---no-gpu}" |
||||
|
BROWSER_PODMAN_OPTS="${BROWSER_PODMAN_OPTS:-}" |
||||
|
|
||||
|
## |
||||
|
## Locate the --socket argument waypipe passed us. |
||||
|
## |
||||
|
## Parsing stops at the sub-command: everything after "server" belongs to the |
||||
|
## program being run and may legitimately carry a --socket of its own. |
||||
|
## |
||||
|
socket="" |
||||
|
previous="" |
||||
|
leading_arguments=() |
||||
|
for argument in "$@"; do |
||||
|
case "$argument" in |
||||
|
server|client|ssh|bench) |
||||
|
break |
||||
|
;; |
||||
|
--socket=*) |
||||
|
socket="${argument#--socket=}" |
||||
|
;; |
||||
|
esac |
||||
|
case "$previous" in |
||||
|
-s|--socket) |
||||
|
socket="$argument" |
||||
|
;; |
||||
|
esac |
||||
|
leading_arguments+=("$argument") |
||||
|
previous="$argument" |
||||
|
done |
||||
|
|
||||
|
## |
||||
|
## Work out which of our own waypipe options still need to be added. |
||||
|
## |
||||
|
## "waypipe ssh" forwards several of its options to the server side (--no-gpu |
||||
|
## and --threads at least), and the argument parser rejects a repeated flag |
||||
|
## outright: |
||||
|
## |
||||
|
## error: the argument '--no-gpu' cannot be used multiple times |
||||
|
## |
||||
|
## so anything already on the command line has to be left alone. |
||||
|
## |
||||
|
option_matches() { |
||||
|
case "$1:$2" in |
||||
|
"--no-gpu:-n"|"-n:--no-gpu") return 0 ;; |
||||
|
esac |
||||
|
[ "$1" = "$2" ] |
||||
|
} |
||||
|
|
||||
|
waypipe_options=() |
||||
|
for option in ${BROWSER_WAYPIPE_OPTS}; do |
||||
|
already_given=false |
||||
|
for argument in ${leading_arguments[@]+"${leading_arguments[@]}"}; do |
||||
|
if option_matches "$option" "$argument"; then |
||||
|
already_given=true |
||||
|
break |
||||
|
fi |
||||
|
done |
||||
|
"$already_given" || waypipe_options+=("$option") |
||||
|
done |
||||
|
|
||||
|
socket_options=() |
||||
|
if [ -n "$socket" ]; then |
||||
|
if [ ! -S "$socket" ]; then |
||||
|
echo "$0: '$socket' is not a socket: is the SSH remote forwarding set up?" >&2 |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
# sshd creates the forwarded socket owned by the login user with mode 0600 |
||||
|
# (StreamLocalBindMask 0177), so the unprivileged browser user can neither |
||||
|
# connect() to it nor, on shutdown, remove it: |
||||
|
# |
||||
|
# Error: "src/main.rs:1505: Failed to unlink socket: EACCES: Permission denied" |
||||
|
# |
||||
|
# Handing the socket over outright covers both: it keeps mode 0600, so no |
||||
|
# other account can hijack the Wayland connection, and the sticky bit on the |
||||
|
# parent directory then lets its new owner -- and only its new owner -- |
||||
|
# unlink it. An ACL would grant the connect but not the unlink, since |
||||
|
# deleting a file is governed by the directory and the file's ownership. |
||||
|
chown "${BROWSER_UID}:${BROWSER_GID}" "$socket" |
||||
|
|
||||
|
# The *directory* is mounted, not the socket alone. Bind-mounting the |
||||
|
# socket by itself makes podman synthesize a parent directory inside the |
||||
|
# container -- root:root, mode 0755 -- and the host's permissions on the |
||||
|
# real directory become irrelevant: the browser's uid cannot write to that |
||||
|
# synthetic parent, so waypipe cannot remove its own socket on shutdown: |
||||
|
# |
||||
|
# Error: "src/main.rs:1505: Failed to unlink socket: EACCES: Permission denied" |
||||
|
# |
||||
|
# Mounting the directory gives the container the real one, sticky bit and |
||||
|
# all. Concurrent sessions become visible to each other by name, but their |
||||
|
# sockets stay 0600 and owned by their own uid, so they can be neither |
||||
|
# connected to nor deleted. |
||||
|
socket_directory=$(dirname "$socket") |
||||
|
socket_options+=(--volume "${socket_directory}:${socket_directory}") |
||||
|
fi |
||||
|
|
||||
|
## |
||||
|
## Run the browser. |
||||
|
## |
||||
|
## --userns=host is not decorative: it is what makes the in-container uid the |
||||
|
## same as the host uid, without which the ACL set on the socket above would |
||||
|
## not apply to the browser process. |
||||
|
## |
||||
|
podman_options=( |
||||
|
--rm |
||||
|
--interactive |
||||
|
# Without an init, waypipe is PID 1 and inherits every orphaned browser |
||||
|
# process, logging each one it reaps as an error: |
||||
|
# ERR waypipe-server(1) main.rs:260] Received SIGCHLD for unexpected child |
||||
|
--init |
||||
|
--user "${BROWSER_UID}:${BROWSER_GID}" |
||||
|
--userns=host |
||||
|
--cap-drop=ALL |
||||
|
# Firefox's own content sandbox chroots itself, and cannot get the |
||||
|
# capability back from an empty bounding set: without this, every content |
||||
|
# process dies with "Sandbox: chroot: EPERM" followed by SIGSEGV. This is |
||||
|
# the only capability it needs -- SYS_ADMIN is not required. |
||||
|
# |
||||
|
# podman also puts it in the ambient set, which breaks the *other* sandbox |
||||
|
# in this image (bubblewrap, used by the SVG image loader). The image's |
||||
|
# entrypoint drops the ambient set again; see the Containerfile. |
||||
|
--cap-add=SYS_CHROOT |
||||
|
--security-opt no-new-privileges |
||||
|
# SELinux checks connect() on a Unix socket with "connectto" against the |
||||
|
# *listening process*, not against the socket file, so relabelling the |
||||
|
# socket does not help: |
||||
|
# |
||||
|
# avc: denied { connectto } comm="waypipe" |
||||
|
# path="/run/seedbox/waypipe/....sock" |
||||
|
# scontext=...:container_t:s0:c266,c919 |
||||
|
# tcontext=...:unconfined_t:s0-s0:c0.c1023 |
||||
|
# tclass=unix_stream_socket |
||||
|
# |
||||
|
# The listener is the SSH session (an unconfined user), and no label this |
||||
|
# container could carry is allowed to connect to it. The alternative to |
||||
|
# disabling the label here is a system-wide policy module granting |
||||
|
# "allow container_t unconfined_t:unix_stream_socket connectto", which |
||||
|
# opens that hole for *every* container on the host rather than this one. |
||||
|
# See the README for the trade-off. The container still runs unprivileged |
||||
|
# (--user, --cap-drop=ALL, no-new-privileges) and is thrown away on exit. |
||||
|
--security-opt label=disable |
||||
|
--shm-size "${BROWSER_SHM_SIZE}" |
||||
|
--env "XDG_RUNTIME_DIR=/run/user/${BROWSER_UID}" |
||||
|
--env "HOME=/home/browser" |
||||
|
--env "MOZ_ENABLE_WAYLAND=1" |
||||
|
--volume "${BROWSER_PROFILE_DIR}:/home/browser:z" |
||||
|
--volume "${BROWSER_DOWNLOAD_DIR}:/downloads:z" |
||||
|
) |
||||
|
|
||||
|
if [ "$#" -eq 0 ]; then |
||||
|
# No arguments: interactive shell in the image, for troubleshooting. |
||||
|
exec podman run --tty "${podman_options[@]}" ${BROWSER_PODMAN_OPTS} "${BROWSER_IMAGE}" /bin/bash |
||||
|
fi |
||||
|
|
||||
|
status=0 |
||||
|
# shellcheck disable=SC2086 # BROWSER_PODMAN_OPTS and BROWSER_WAYPIPE_OPTS are word-split on purpose |
||||
|
podman run "${podman_options[@]}" "${socket_options[@]}" ${BROWSER_PODMAN_OPTS} \ |
||||
|
"${BROWSER_IMAGE}" \ |
||||
|
waypipe ${waypipe_options[@]+"${waypipe_options[@]}"} "$@" || status=$? |
||||
|
|
||||
|
## |
||||
|
## waypipe unlinks the forwarded socket itself on a clean shutdown (see the |
||||
|
## chown above), and sshd does not (StreamLocalBindUnlink defaults to no). This |
||||
|
## is the safety net for the cases where waypipe never gets that far -- a crash, |
||||
|
## a killed session -- so that the directory does not fill with dead sockets. |
||||
|
## |
||||
|
if [ -n "$socket" ]; then |
||||
|
rm -f "$socket" |
||||
|
fi |
||||
|
|
||||
|
exit "$status" |
||||
@ -0,0 +1,10 @@ |
|||||
|
[Unit] |
||||
|
Description=Rebuild the LibreWolf browser image |
||||
|
PartOf=seedbox.target |
||||
|
|
||||
|
[Timer] |
||||
|
OnCalendar=daily |
||||
|
Persistent=true |
||||
|
|
||||
|
[Install] |
||||
|
WantedBy=seedbox.target |
||||
@ -0,0 +1,11 @@ |
|||||
|
[Unit] |
||||
|
Description=Build of the LibreWolf browser image |
||||
|
Documentation=https://librewolf.net/ |
||||
|
Wants=network-online.target |
||||
|
After=network-online.target fedora-image.service |
||||
|
Requires=fedora-image.service |
||||
|
|
||||
|
[Build] |
||||
|
File=/etc/quadlets/seedbox/container/Containerfile |
||||
|
ImageTag=localhost/librewolf:latest |
||||
|
SetWorkingDirectory=/etc/quadlets/seedbox/container |
||||
@ -0,0 +1,35 @@ |
|||||
|
## |
||||
|
## Interactive helpers for the seedbox browser container. |
||||
|
## |
||||
|
## Note that this file is NOT what starts the browser for a `waypipe ssh` |
||||
|
## session: `ssh <host> <command>` runs a non-interactive, non-login shell, |
||||
|
## which does not source /etc/profile.d. The entry point in that case is |
||||
|
## /etc/quadlets/seedbox/seedbox-waypipe.sh, passed to waypipe with |
||||
|
## --remote-bin. These helpers are for when you are logged in and want to |
||||
|
## inspect or drive the same container by hand. |
||||
|
## |
||||
|
|
||||
|
if [ -r /etc/quadlets/seedbox/browser.conf ]; then |
||||
|
. /etc/quadlets/seedbox/browser.conf |
||||
|
export BROWSER_IMAGE BROWSER_PROFILE_DIR BROWSER_DOWNLOAD_DIR |
||||
|
fi |
||||
|
|
||||
|
# Run the browser container. Takes the same arguments as the waypipe binary, so |
||||
|
# it can stand in for a manual `waypipe --socket ... server -- librewolf`; with |
||||
|
# no arguments, it opens a shell in the image. |
||||
|
seedbox-browser() { |
||||
|
sudo -n /etc/quadlets/seedbox/seedbox-waypipe.sh "$@" |
||||
|
} |
||||
|
|
||||
|
# Rebuild the image now instead of waiting for tonight's timer. |
||||
|
seedbox-browser-rebuild() { |
||||
|
sudo -n systemctl start librewolf-build.service && |
||||
|
sudo -n journalctl -u librewolf-build.service -n 20 --no-pager |
||||
|
} |
||||
|
|
||||
|
# Print the command line to run on the *client* to get a browser on screen. |
||||
|
seedbox-browser-command() { |
||||
|
printf 'waypipe --remote-bin /etc/quadlets/seedbox/seedbox-waypipe.sh \\\n' |
||||
|
printf ' --remote-socket /run/seedbox/waypipe/wp \\\n' |
||||
|
printf ' --no-gpu ssh %s@%s librewolf\n' "${USER:-nicolas}" "$(hostname -f 2>/dev/null || hostname)" |
||||
|
} |
||||
Loading…
Reference in new issue