- Shell 100%
| .forgejo/workflows | ||
| ct | ||
| install | ||
| misc | ||
| scripts/dry-test | ||
| vendor | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
| renovate.json | ||
TeddyTafForge — Proxmox VE LXC helper
One-command deploy of TeddyTafForge — optionally together with TeddyCloud — as an unprivileged LXC on Proxmox VE.
What is TeddyTafForge?
TeddyTafForge is a web UI for building custom Tonie audio files (.taf)
to play on a Toniebox. It runs alongside TeddyCloud (the open-source
Toniebox server) — TafForge handles the audio import, chapter editing and
TAF encoding; TeddyCloud serves the files to the box and provides the admin
UI. You need both pieces, but you can run them in one LXC together or
keep TafForge as a sidecar to an existing TeddyCloud install.
The wrapper bundles the TafForge install/ stack under
vendor/tafforge-install/ so the helper can run without a
secondary fetch — the only thing it pulls at install time is the app
source (backend + frontend + plugin) from the public GitHub mirror.
Quickstart
Run on your Proxmox VE host as root:
bash -c "$(curl -fsSL https://forgejo.diefamiliekramer.de/vr6syncro/teddytafforge-proxmox/raw/branch/main/ct/teddytafforge.sh)"
A series of whiptail dialogs walks you through the choices. When the script
finishes, the URL of the new instance is printed at the end and stored in the
LXC's description field in the Proxmox UI.
Paranoid alternative. If you'd rather review the script before running it:
curl -fsSL <url> -o /tmp/teddytafforge.sh && less /tmp/teddytafforge.sh && bash /tmp/teddytafforge.sh.
What gets installed?
You choose between two scopes in the first dialog:
| Scope | Contents | When to pick |
|---|---|---|
sidecar |
Only TafForge | You already run TeddyCloud somewhere else (another LXC, a VM, a NAS, …) |
all-in-one |
TafForge + TeddyCloud in the same LXC | You're starting fresh and want both on one machine |
In sidecar mode you'll be asked for your existing TeddyCloud's URL and,
optionally, for the host path of its data directory (so the plugin can be
installed cleanly via a bind mount).
Requirements
- Proxmox VE 8.0 or newer (amd64)
- Internet access on the PVE host
- ~16 GB free on a
rootdir-capable storage - A Linux bridge (
vmbr0by default)
Defaults
| Option | sidecar | all-in-one | Override |
|---|---|---|---|
| OS | Debian 13 | Debian 13 | not configurable |
| CPU | 2 cores | 2 cores | whiptail / var_cpu |
| RAM | 2048 MB | 3072 MB | whiptail / var_ram |
| Disk | 8 GB | 16 GB | whiptail / var_disk |
| Unprivileged | yes | yes | hard-coded |
| Features | nesting=1,keyctl=1 |
nesting=1,keyctl=1 |
hard-coded |
| Network | DHCP / vmbr0 | DHCP / vmbr0 | whiptail |
| TafForge port | 3000 | 3000 | edit /etc/tafforge/tafforge.env after install |
| TeddyCloud ports | n/a | 80 / 443 / 8443 | not configurable |
Power-user ENV overrides (set before invoking the curl line):
TAFFORGE_SCOPE=all-in-one \
TAFFORGE_REF_DEFAULT=v0.2.2 \
TC_REF_DEFAULT=v0.6.2 \
CTID=200 RAM_SIZE=4096 DISK_SIZE=24 \
bash -c "$(curl -fsSL …/ct/teddytafforge.sh)"
After installation
- Open the UI at
http://<lxc-ip>:3000/ - Service control (inside the LXC, after
pct enter <CTID>):systemctl status tafforgejournalctl -u tafforge -f- In all-in-one: same with
teddycloud
- Runtime config:
/etc/tafforge/tafforge.env(created by the upstream installer; edit andsystemctl restart tafforgeto apply) - Wrapper marker with the versions that were installed:
/etc/tafforge/proxmox.meta
Update
The authoritative updater lives inside the LXC and is provided by the TafForge project itself — this repo only wraps it.
Recommended path (in the LXC):
pct enter <CTID>
/opt/tafforge/app/install/lxc/update-app.sh
Convenience path (from the PVE host):
bash -c "$(curl -fsSL https://forgejo.diefamiliekramer.de/vr6syncro/teddytafforge-proxmox/raw/branch/main/ct/update.sh)"
There is intentionally no automatic update timer. Pick a moment that works for you.
Sidecar setup details
If you already run TeddyCloud elsewhere and pick sidecar, the wrapper can
mount your TeddyCloud's data directory into the new LXC so TafForge can drop
its plugin into the TC plugins folder. The relevant dialog asks for:
- Host path — the absolute path of the TeddyCloud data directory on the
PVE host (e.g.
/var/lib/teddycloud-data) - LXC mount point — where to mount it inside the LXC (default
/mnt/teddycloud-data)
The mount is set as mp0 with backup=0 so a vzdump backup of the
TafForge LXC won't try to capture your TC data along with it (your TC data
should be backed up at its source).
If you skip the mount you can still use sidecar mode — TafForge will log a warning and you'll have to install the plugin into TC manually.
Uninstall
pct stop <CTID>
pct destroy <CTID>
In sidecar mode the bind mount only points at your TeddyCloud data on
the host — pct destroy removes the LXC but leaves your TC data
untouched. The TafForge plugin file that was dropped into
<TC-data>/www/plugins/ does stay behind; delete it from TeddyCloud's UI
or filesystem if you want a clean state.
In all-in-one mode both TafForge and the TeddyCloud install inside the LXC are removed — back up any custom Tonie data first.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
pct create fails with "no template" |
template list still empty after auto-refresh | pveam update && pveam download local debian-13-standard_*_amd64.tar.zst |
pveam download fails |
host has no internet / DNS | Check /etc/resolv.conf, route, firewall |
| LXC has no IPv4 | DHCP server didn't lease | Wait 30s; if persistent, set static IPv4 in advanced mode |
| TafForge installer warns "UI will be unavailable" | frontend build failed (out of RAM during npm run build) |
bump var_ram to ≥3 GB and re-run, or rebuild manually: pct exec <CTID> -- bash -c 'cd /opt/tafforge/app/frontend && npm ci && npm run build' |
| All-in-one install finishes but TafForge plugin not in TC | TEDDYCLOUD_DATA_DIR pointed at wrong path | check /opt/teddycloud/data/www/plugins/teddytafforge/ exists; if not, re-run installer (this was fixed in commit after 4cb4d3e) |
apt-get update inside LXC hangs |
IPv6 default route broken | Re-run with "Disable IPv6" answered "yes" in the dialog |
| TafForge UI loads but shows "TC unreachable" | Wrong TEDDYCLOUD_URL |
Edit /etc/tafforge/tafforge.env → systemctl restart tafforge |
| TafForge plugin not visible in TC | Plugin dir not writable / not mounted | Sidecar: check mp0 mount; all-in-one: check /opt/teddycloud/data/www/plugins |
| TC build fails | Upstream build layout changed | Pin TC_REF_DEFAULT to a known-good tag (see ENV overrides above) |
| Installer exits silently after the first "Use defaults?" dialog | Pre-2026-05-20 build with the default_settings set -e regression |
Pull branch/main — fixed; re-run with DEBUG=1 if it still aborts |
For TafForge-internal questions (audio import, plugin layout, env vars) see the TeddyTafForge documentation.
Debug mode
If the installer behaves unexpectedly (silent exits, dialogs that don't
advance, pct create failures that don't print why), re-run with DEBUG=1:
DEBUG=1 bash -c "$(curl -fsSL https://forgejo.diefamiliekramer.de/vr6syncro/teddytafforge-proxmox/raw/branch/main/ct/teddytafforge.sh)"
What changes:
- A yellow
! DEBUG MODE — xtrace → /tmp/teddytafforge-trace-<pid>.logbanner prints at the start so you know it's on. - A full
bash -xtrace of every command is written to that log file on a dedicated file descriptor — whiptail's stdio redirections cannot corrupt it. - Human-readable
[DBG …]checkpoints print to your terminal at every meaningful state transition (scope selected, settings collected, pre-flight checks,pctargument vector). - On any failure the ERR handler resets the terminal before printing,
so the red error line is no longer eaten by whiptail's alt-screen repaint.
You get the failing command, the exit code, a
FUNCNAMEstack trace and the path to the xtrace log. - The
DEBUGflag is forwarded into the LXC and propagates into the in-container installer, so the whole chain is traced end-to-end.
When you report a bug, attach both the on-screen stack trace and the last ~50 lines of the trace log. Together they pinpoint the exact command, arguments and variable state at the moment of failure.
Security notes
- The LXC is unprivileged by default. Don't switch to privileged unless you have a specific reason.
nesting=1,keyctl=1is required so systemd inside the LXC behaves correctly. No other privileged features are enabled.- The root password defaults to empty / login disabled — SSH key
authentication is the intended login path. Provide a key in the dialog,
or use
pct enter <CTID>from the host. curl … | bashis convenient but a security trade-off. Review the script first if you don't trust the chain.
What this repo does not do
- It does not back up your LXC — that's
vzdump/ Proxmox Backup Server. - It does not terminate TLS for TafForge — put a reverse proxy (Traefik, Caddy, NGINX, …) in front if you want HTTPS.
- It does not auto-update — see the "Update" section above.
- It does not monitor — bring your own observability.
Versioning
- This repo follows Semantic Versioning. The Quickstart
one-liner pulls
branch/main, which is what most users want. TAFFORGE_REF_DEFAULTandTC_REF_DEFAULTcan be overridden to pin the app versions (see "Power-user ENV overrides" above).- The wrapper version + the refs it installed are written to
/etc/tafforge/proxmox.metainside the LXC.
Contributing
Issues and pull requests welcome at forgejo.diefamiliekramer.de/vr6syncro/teddytafforge-proxmox.
Before submitting a script change, please run shellcheck locally:
shellcheck -x ct/*.sh install/*.sh misc/*.func
CI runs the same check plus a --dry-run smoke test that exercises the
whiptail flow without calling pct.
Local dry test (the full matrix)
The CI smoke test walks the default sidecar path. For a richer local
verification — all four {scope × settings} combinations with title-aware
whiptail and DEBUG=1 xtrace — run:
bash scripts/dry-test/matrix.sh
It mocks every PVE binary (pveversion, pvesm, pveam, pct, pvesh, dpkg, id)
plus whiptail (FD-correct, title-aware so the sidecar mount-loop terminates),
runs start() → build_container() → description() end-to-end under
DRY_RUN=1, and reports PASS/FAIL for each combo. Per-run logs end up in
/tmp/teddytafforge-dry-test-runN.log, dialog traces in
/tmp/teddytafforge-whiptail.log, xtrace in
/tmp/teddytafforge-trace-<pid>.log. Useful when bisecting whether a
helper change broke a non-default path.
Credits & license
- TeddyTafForge © its authors — see the upstream repo
- TeddyCloud © the toniebox-reverse-engineering project
- This wrapper's UX and file layout are inspired by the community-scripts.github.io / tteck Proxmox helper-scripts pattern (MIT). No code is copied — the helpers are an independent re-implementation.
Released under the MIT License.