`navigator.clipboard` only exists in secure contexts, so every copy button
threw on plain-http LAN/IP deployments and falsely reported failure.
Add copyTextToClipboard(), which uses the Clipboard API on secure origins
and falls back to a hidden textarea + `execCommand("copy")` otherwise
(restoring focus/selection and always cleaning the element up). CopyButton
now reports success only when the text actually reached the clipboard.
installClipboardFallback() polyfills a working `navigator.clipboard` on
insecure pages so twui's CodeBlock/CopySlug/Input copy icons work too,
without editing twui sources. It is installed once from AppProviders.
ed4a6f3 rewrote the middle of install-wg-ui.sh and deleted seven function
definitions while leaving every call site in place. The runtime only
stopped at the first one, so `update wg-ui` died with:
install-wg-ui.sh: line 202: clone_or_update: command not found
Fixing that line alone would have moved the failure to
install_dependencies three lines down, and with install_systemd_unit /
install_openrc_unit gone the script could never have written a service
unit again. Restore all seven from 0f94004, keeping the Caddy work that
landed in the same commit:
- clone_or_update, install_dependencies, ensure_env_file,
setup_wireguard, install_wg_quick_helper: byte-identical to 0f94004.
- install_systemd_unit / install_openrc_unit: identical, except wg-ui now
starts after docker, since Caddy ingress reloads go through docker exec.
Add install-wg-ui.test.ts so this class of bug cannot come back silently:
it asserts the script parses, that every invoked function has a
definition (indented calls included, which is how clone_or_update hid),
and that the update flow runs in order. Checked against the pre-fix
script, the definition test fails with exactly the seven missing names.
Caddy previously ran as a host binary in /opt/caddy with a wgui-caddy
systemd/OpenRC unit. Only Caddy is containerized; the app still runs on the
host as root because it shells out to wg, ip and iptables.
- Add caddy-container-commands.ts as the single source of truth for the
run/exec/validate/inspect argument arrays. Every value stays a separate
argv element, the image is pinned to caddy:2.11.4-alpine, and the
entrypoint is set explicitly because some mirrors publish the image with
a null Entrypoint (which made Docker exec "run" as a binary).
- Rewrite install-caddy.sh: require_docker with distro-specific hints,
pull + recreate the container, assert it is running and echo the daemon
error instead of discarding it, normalize the admin socket path in
existing Caddyfiles, and clean up a legacy native install only after the
container is up so the ingress port is never unbound. Add
--purge-legacy / --purge for hosts that want to drop the old install
without installing the container.
- Move the admin socket to /var/lib/wgui/caddy/admin.sock so it lives
inside the mounted Caddy dir and reloads work over docker exec.
- Validate candidate configs in a throwaway container, mounting the TLS
dir read-only as well: provisioning the tls app loads every certificate
referenced by a tls directive, so without that mount a route with a
certificate failed validation and its Caddyfile was never committed.
- Report docker-based installed/version/service_active, skip
validate/reload with a warning when the image is absent, and update the
system setup tool check to look for docker.
- Issue certificates with a docker exec reload command and chown files to
root:root, since the container reads them through a read-only mount.
- Update the Setup UI copy and docs (README, AGENTS.md).
Also drops the unused EmptyContent import and a stray debug log in the
client route form, and stops passing the unsupported cancel_href prop to
ClientRouteForm from the route add/edit pages.
- Load refractor's ~36 common languages plus docker, nginx, toml,
powershell, jsx and tsx (was 6)
- Expand TWUIPrismLanguages to 54 names/aliases for autocomplete;
`language` now also accepts any registered refractor name
- RemoteCodeBlock re-exports the list instead of keeping a stale copy
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Highlight string children with refractor (Prism) when `language` is
set, rendered as React token spans; add ini/bash/json/yaml/ts support
- Add `theme` prop (auto | light | dark); auto follows the `.dark` class.
Palettes are `--twui-code-*` CSS variables (GitHub Light/Dark, all
tokens WCAG AA) so custom themes need no component changes
- Fix layout: real padding, singleBlock copy button no longer covers
the code, clipped rounded corners, wrapperProps.style honoured,
long tokens wrap
- MDX helpers take `codeTheme` and no longer force a black background
- Highlight WireGuard config blocks as ini
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rebuild every page on a shared set of twui-based primitives with a dense
ops-console look, WireGuard red accent and light/dark/system theming.
- Add design tokens (WireGuard red accent, zinc neutrals, theme-flipping
semantic colours) and a no-flash theme cookie + toggle
- Add shared components: page header, panel, sub-nav, data table with
mobile card list, stat tile, status dot, key-value list, copy button,
inline alert, empty state, form section/actions, confirm dialog and
toast providers
- New admin shell (sidebar, sticky header, mobile drawer) and a centered
shell for auth and 404; strip the public template pages
- Unify adding clients at /admin/clients/add with a host picker; tabbed
client detail (Overview / Rules / Config & QR) with config copy/download
- Re-run client setup after rule changes on the detail page so the
firewall matches the DB
- Split Setup into status/maintenance/firewall and Settings into
profile/password subpages
- Replace window.confirm/alert and page reloads with in-app dialogs,
toasts and in-place list updates
- Fix nested rule form submitting the client form, prefilled signup
passwords, hidden submit buttons, fields not updating after "next
free" lookups, and TinyMCE white-on-white in dark mode
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Drop unused email module and media text extraction path
- Remove 12 unused dependencies
- Remove unused imports in server.ts
- Add typecheck and test scripts
- Rewrite AGENTS.md to describe this project
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Non-super-admin PUT updated the client whose id equalled the user's id;
it now updates the requested client only if the user owns it. POST no
longer lets non-super-admins overwrite existing clients by id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Require admin auth on interface-local-ip, network-interfaces,
next-listen-port and public-ip (interface-local-ip and public-ip
allowed unauthenticated command injection as root)
- Validate interface names and call ip/curl via execFileSync
- Write host/client WireGuard configs with fs instead of shell
heredocs, validating IPs, AllowedIPs, endpoint and listen port
- Replace rm -rf/cat/mkdir/chmod/test shell calls with fs APIs
- Refuse private media requested through the public media route
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Import types from @moduletrace/bun-sqlite/types now that the package
has an exports map, and guard optional ctx.props in signup server fn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>