Merge pull request #274 from outis1one/claude/ionos-script-integration-x32ofw

Fix ensure_coturn_user leaving callers in the wrong directory
This commit is contained in:
Outis
2026-08-09 23:21:39 -04:00
committed by GitHub
2 changed files with 104 additions and 12 deletions
+90 -11
View File
@@ -33,28 +33,107 @@ the wizard — the USB can be unplugged once setup starts.
sudo bash bootstrap.sh --pat ghp_xxxxxxxxxxxxxxxxxxxx sudo bash bootstrap.sh --pat ghp_xxxxxxxxxxxxxxxxxxxx
``` ```
Use a fine-grained read-only PAT scoped to just this repo (Contents: Read). Use a fine-grained read-only PAT scoped to just this repo (Contents: Read).
The PAT is stripped from the stored remote URL after cloning.
**Cloud provider install-script / user-data field (IONOS, DigitalOcean, <details>
Hetzner, ...):** these run as root with no terminal attached while the <summary><b>Cloud provider install-script / user-data field (IONOS, DigitalOcean, Hetzner, ...)</b></summary>
image is still being provisioned, so `bootstrap.sh`'s interactive hand-off
doesn't apply yet. Use `cloud-init.sh` instead — it's a plain cloud-init These run as root with no terminal attached while the image is still being
user-data shell script (starts with `#!/bin/bash`, no `#cloud-config` YAML). provisioned, so `bootstrap.sh`'s interactive hand-off doesn't apply yet.
Use `cloud-init.sh` instead — it's a plain cloud-init user-data shell
script (starts with `#!/bin/bash`, no `#cloud-config` YAML).
IONOS's server-creation screen has a **User Data** box under "Scripts" with IONOS's server-creation screen has a **User Data** box under "Scripts" with
a **Script Type** choice of *Cloud Config* or *Shell Script* — pick a **Script Type** choice of *Cloud Config* or *Shell Script* — pick
**Shell Script**, then either click **Import from file** and select **Shell Script**, then either click **Import from file** and select
`cloud-init.sh`, or paste its contents directly. User-data fields run the `cloud-init.sh`, or paste its contents (below) directly. User-data fields
script's own content; they don't fetch a URL, so paste/import the file run the script's own content; they don't fetch a URL, so paste/import the
itself rather than a link to it. (DigitalOcean/Hetzner's plain "User data" file itself rather than a link to it. (DigitalOcean/Hetzner's plain "User
textbox works the same way — paste the script contents in directly.) data" textbox works the same way — paste the script contents in directly.)
It clones the repo in the background during provisioning and installs a It clones the repo in the background during provisioning and installs a
one-shot login hook. The provider boots Ubuntu 24.04, this runs unattended, one-shot login hook. The provider boots Ubuntu 24.04, this runs unattended,
and by the time you SSH in the whiptail service menu is already waiting for and by the time you SSH in the whiptail service menu is already waiting for
you — same experience as `bootstrap.sh`, just already started. Assumes a you — same experience as `bootstrap.sh`, just already started. Assumes a
root login (the default for all three providers above); see the comments in root login (the default for all three providers above); see the comments in
`cloud-init.sh` if you've provisioned a separate sudo user instead. the script if you've provisioned a separate sudo user instead.
The PAT is stripped from the stored remote URL after cloning.
```bash
#!/bin/bash
# cloud-init.sh — payload for a cloud provider's "install script" / user-data
# field (IONOS Cloud Server image deploy, DigitalOcean droplet user-data,
# Hetzner Cloud user-data, etc). The provider runs this as root, unattended,
# with no TTY, while the box is still being provisioned — before you have
# ever logged in.
#
# It deliberately does NOT run the interactive wizard itself (there's no
# terminal for whiptail to talk to yet). Instead it does two things:
#
# 1. Clones this repo to /root/ubuntu-post-install (pulls if already there).
# 2. Installs a one-shot /etc/profile.d hook that launches setup.sh —
# the normal whiptail service menu — the first time you actually log
# in over SSH, then deletes itself so it never fires again.
#
# End result: the provider boots Ubuntu 24.04, this runs in the background,
# and by the time you SSH in the checklist menu is sitting there waiting —
# the same experience as running bootstrap.sh by hand, just already started.
#
# Usage: paste this whole file's contents into the provider's install-script /
# user-data field (or use an "import from file" option if it has one).
# User-data fields run the content you give them directly — they don't fetch
# a URL — so paste the script itself, not a link to it.
#
# Assumes the provider logs you in as root (the default for IONOS Cloud
# Server, DigitalOcean droplets, and Hetzner Cloud server images). If you've
# provisioned a separate non-root sudo user instead, the hook won't reach
# you automatically — SSH in and run:
# sudo bash /root/ubuntu-post-install/setup.sh
set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then
echo "cloud-init.sh must run as root — that's how provider install-script hooks already run it." >&2
exit 1
fi
REPO_URL="https://github.com/outis1one/ubuntu-post-install.git"
DEST="/root/ubuntu-post-install"
MARKER="/root/.ubuntu-post-install-pending"
HOOK="/etc/profile.d/99-ubuntu-post-install.sh"
export DEBIAN_FRONTEND=noninteractive
command -v git >/dev/null 2>&1 || { apt-get update -qq && apt-get install -y git; }
if [ -d "$DEST/.git" ]; then
git -C "$DEST" pull --ff-only || true
else
git clone "$REPO_URL" "$DEST"
fi
touch "$MARKER"
# POSIX sh, not bash — /etc/profile.d/*.sh gets sourced by whatever shell
# the login uses, not necessarily bash.
cat > "$HOOK" << 'EOF'
# Installed by cloud-init.sh — launches the ubuntu-post-install wizard on
# the first interactive login, then removes itself so it never fires again.
MARKER="/root/.ubuntu-post-install-pending"
HOOK="/etc/profile.d/99-ubuntu-post-install.sh"
DEST="/root/ubuntu-post-install"
if [ -f "$MARKER" ] && [ -t 0 ] && [ "$(id -u)" -eq 0 ] && [ -f "$DEST/setup.sh" ]; then
rm -f "$MARKER" "$HOOK"
echo ""
echo "ubuntu-post-install: launching the setup wizard..."
echo ""
bash "$DEST/setup.sh"
fi
EOF
chmod 644 "$HOOK"
echo "cloud-init.sh: repo cloned to $DEST — the setup wizard will launch on first login."
```
</details>
## Usage ## Usage
+14 -1
View File
@@ -810,7 +810,20 @@ ensure_coturn_user() {
if [ ! -d "$DOCKER_DIR/coturn" ]; then if [ ! -d "$DOCKER_DIR/coturn" ]; then
if declare -F install_coturn >/dev/null 2>&1; then if declare -F install_coturn >/dev/null 2>&1; then
log_info "No shared coturn (TURN/STUN) server yet — setting one up for $_consumer..." log_info "No shared coturn (TURN/STUN) server yet — setting one up for $_consumer..."
install_coturn || { log_warning "coturn setup failed — $_consumer will run without TURN."; return 1; } # install_coturn cd's into $DOCKER_DIR/coturn and never cd's back —
# the caller (e.g. asterisk.sh, already cd'd into its own install
# directory) would otherwise return here with the wrong cwd and go
# on to write ITS docker-compose.yml/.env into coturn's directory
# instead of its own. Confirmed live: this clobbered coturn's
# compose file and left the consumer's own directory without one,
# so its later `docker compose up --build` failed with "Dockerfile:
# no such file or directory" (no Dockerfile in coturn's directory).
local _caller_pwd
_caller_pwd="$(pwd)"
install_coturn
local _coturn_rc=$?
cd "$_caller_pwd" || true
[ "$_coturn_rc" -ne 0 ] && { log_warning "coturn setup failed — $_consumer will run without TURN."; return 1; }
else else
log_warning "services/coturn.sh not loaded — $_consumer will run without TURN." log_warning "services/coturn.sh not loaded — $_consumer will run without TURN."
log_warning "Run: sudo ./setup.sh coturn" log_warning "Run: sudo ./setup.sh coturn"