Valheim changes merge to master #1
@@ -0,0 +1,189 @@
|
|||||||
|
# Valheim Server StackScript
|
||||||
|
|
||||||
|
A Linode StackScript that turns a freshly created Debian 13 Linode into a
|
||||||
|
Valheim dedicated server. Running it as a StackScript means everything happens
|
||||||
|
as root during deployment, so no SSH session (and therefore no SSH key) is
|
||||||
|
needed to configure the machine.
|
||||||
|
|
||||||
|
It does four things, in order:
|
||||||
|
|
||||||
|
1. Installs Docker from Debian's own `docker.io` package.
|
||||||
|
2. Mounts the pre-existing Valheim volume (this mirrors `voldirections.txt`;
|
||||||
|
`mkfs.ext4` is **not** run because the filesystem already exists).
|
||||||
|
3. Creates the persistent `config/` and `data/` directories on the volume.
|
||||||
|
4. Starts the Valheim server container with those directories bind-mounted, so
|
||||||
|
worlds, configuration and backups survive destroying and recreating the
|
||||||
|
Linode.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
1. Log in to the Linode Cloud Manager and open **StackScripts**.
|
||||||
|
2. Click **Create StackScript**.
|
||||||
|
3. Set **Label** to something like `valheim-server` and choose **Debian 13**
|
||||||
|
(or "Any") as the **Target Images**.
|
||||||
|
4. Paste the code block below into the **Script** editor.
|
||||||
|
5. Edit the server settings at the top of the script (name, world, password)
|
||||||
|
before saving.
|
||||||
|
6. Leave the **UDF** (User Defined Fields) section empty — the values are
|
||||||
|
hard-coded, so nothing needs to be entered at deploy time.
|
||||||
|
7. Click **Save**. The numeric ID of the StackScript appears in the URL and in
|
||||||
|
the StackScript list; copy it.
|
||||||
|
8. Put that ID in `create-linode.sh` as the `STACKSCRIPT_ID` value near the top
|
||||||
|
of the file.
|
||||||
|
|
||||||
|
After that, `create-linode.sh c <label>` passes the StackScript on the create
|
||||||
|
call and the Linode comes up as a running Valheim server with no further steps.
|
||||||
|
|
||||||
|
## Where things live on the volume
|
||||||
|
|
||||||
|
Everything the container needs to keep is under `/mnt/ValVol202609/valheim`:
|
||||||
|
|
||||||
|
| Host path | Container path | Contents |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `/mnt/ValVol202609/valheim/config` | `/config` | World saves, `adminlist.txt`, `bannedlist.txt`, `permittedlist.txt`, `backups/` |
|
||||||
|
| `/mnt/ValVol202609/valheim/data` | `/opt/valheim` | The ~1 GB downloaded server plus SteamCMD's depot cache |
|
||||||
|
|
||||||
|
Mounting `/opt/valheim` as well means a newly created Linode does not have to
|
||||||
|
re-download the server from Steam, which saves several minutes per instance.
|
||||||
|
The depot cache lives inside that same directory so it stays in sync with the
|
||||||
|
installation it belongs to.
|
||||||
|
|
||||||
|
Worlds are stored in `config/worlds_local/<WORLD_NAME>/` (older releases used a
|
||||||
|
single `<WORLD_NAME>.db`/`.fwl` pair directly in `config/worlds/`). To bring an
|
||||||
|
existing world along, copy it into the `worlds_local` directory on the volume
|
||||||
|
and set `WORLD_NAME` to match.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Ports.** The container publishes `2456-2457/udp` (game and query). Linodes
|
||||||
|
have no inbound firewall by default, so nothing else is required. If you add
|
||||||
|
a Cloud Firewall, allow those two UDP ports.
|
||||||
|
- **Sizing.** The upstream project recommends a high-clocked 4 core / 8 GB
|
||||||
|
machine, which is exactly `g6-standard-4` — the type `create-linode.sh` uses.
|
||||||
|
- **First start.** The container downloads ~1 GB from Steam on first boot and
|
||||||
|
takes several minutes to become joinable.
|
||||||
|
- **Admin.** Once the server has started, `config/adminlist.txt` exists on the
|
||||||
|
volume. Put your SteamID64 in it to get in-game admin commands.
|
||||||
|
- **Backups.** The container writes hourly world backups to `config/backups/`
|
||||||
|
on the volume and prunes them after 3 days.
|
||||||
|
- **Updates.** The container checks for Valheim updates every 15 minutes and
|
||||||
|
restarts the server when one is found, but only while nobody is connected.
|
||||||
|
- **Re-running.** The script is safe to run again: the mount and the `fstab`
|
||||||
|
entry are only created if missing, and any previous container is removed
|
||||||
|
before the new one is started.
|
||||||
|
|
||||||
|
## StackScript
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Linode StackScript: turn a fresh Debian 13 Linode into a Valheim dedicated
|
||||||
|
# server whose worlds, configuration and backups live on the pre-existing
|
||||||
|
# Valheim volume. Runs as root during deployment; no SSH access required.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Server settings - edit these before saving the StackScript.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
SERVER_NAME="Valheim Server"
|
||||||
|
WORLD_NAME="Dedicated"
|
||||||
|
# Must be at least 5 characters, otherwise valheim_server.x86_64 refuses to
|
||||||
|
# start. Anything saved here is readable by anyone with access to the
|
||||||
|
# StackScript, so change it.
|
||||||
|
SERVER_PASS="changeme123"
|
||||||
|
# true lists the server in the in-game community browser.
|
||||||
|
SERVER_PUBLIC="true"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Volume and container settings.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
VOLUME_NAME="ValVol202609"
|
||||||
|
VOLUME_DEVICE="/dev/disk/by-id/scsi-0Linode_Volume_${VOLUME_NAME}"
|
||||||
|
VOLUME_MOUNT="/mnt/${VOLUME_NAME}"
|
||||||
|
|
||||||
|
# Everything the container has to keep across Linode rebuilds lives here.
|
||||||
|
VALHEIM_DIR="${VOLUME_MOUNT}/valheim"
|
||||||
|
CONFIG_DIR="${VALHEIM_DIR}/config"
|
||||||
|
DATA_DIR="${VALHEIM_DIR}/data"
|
||||||
|
|
||||||
|
CONTAINER_NAME="valheim-server"
|
||||||
|
# The upstream project moved to ghcr.io/community-valheim-tools/valheim-server.
|
||||||
|
# The Docker Hub image below is still published and can be swapped for it.
|
||||||
|
IMAGE="lloesche/valheim-server"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Install Docker.
|
||||||
|
# Debian's docker.io package is enough for a single `docker run` and avoids
|
||||||
|
# adding a third-party apt repository. ca-certificates is listed explicitly
|
||||||
|
# because --no-install-recommends would otherwise skip it and image pulls
|
||||||
|
# would fail TLS verification.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends docker.io ca-certificates docker-cli
|
||||||
|
systemctl enable --now docker
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Mount the Valheim volume.
|
||||||
|
# The volume is attached over the Linode API once the instance is running, so
|
||||||
|
# wait for the device to appear. 60 attempts x 5s = 5 minute ceiling.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
if [ -e "$VOLUME_DEVICE" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for ${VOLUME_DEVICE} to appear..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -e "$VOLUME_DEVICE" ]; then
|
||||||
|
echo "ERROR: ${VOLUME_DEVICE} never appeared. Aborting so that the world is not written to the instance's ephemeral disk." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$VOLUME_MOUNT"
|
||||||
|
if ! mountpoint -q "$VOLUME_MOUNT"; then
|
||||||
|
mount "$VOLUME_DEVICE" "$VOLUME_MOUNT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount on every boot, without duplicating the line on re-runs. nofail keeps a
|
||||||
|
# missing volume from blocking boot; if the volume is not there the mountpoint
|
||||||
|
# silently becomes a directory on the instance's own disk, which is why the
|
||||||
|
# check above aborts rather than continuing.
|
||||||
|
if ! grep -q "$VOLUME_DEVICE" /etc/fstab; then
|
||||||
|
echo "${VOLUME_DEVICE} ${VOLUME_MOUNT} ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Create the persistent directories on the volume.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
mkdir -p "$CONFIG_DIR" "$DATA_DIR"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Start the Valheim server.
|
||||||
|
# --restart unless-stopped brings the server back after a reboot.
|
||||||
|
# --stop-timeout 120 gives the server time to save the world on shutdown.
|
||||||
|
# --cap-add=sys_nice lets the Steam library raise its own thread priority.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
docker run -d \
|
||||||
|
--name "$CONTAINER_NAME" \
|
||||||
|
--cap-add=sys_nice \
|
||||||
|
--stop-timeout 120 \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 2456-2457:2456-2457/udp \
|
||||||
|
-v "${CONFIG_DIR}:/config" \
|
||||||
|
-v "${DATA_DIR}:/opt/valheim" \
|
||||||
|
-e SERVER_NAME="$SERVER_NAME" \
|
||||||
|
-e WORLD_NAME="$WORLD_NAME" \
|
||||||
|
-e SERVER_PASS="$SERVER_PASS" \
|
||||||
|
-e SERVER_PUBLIC="$SERVER_PUBLIC" \
|
||||||
|
-e SERVER_ARGS="-modifier resources more" \
|
||||||
|
"$IMAGE"
|
||||||
|
|
||||||
|
echo "Valheim server started."
|
||||||
|
echo "The first start downloads ~1GB from Steam and takes several minutes."
|
||||||
|
echo "Follow progress with: docker logs -f $CONTAINER_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
+60
-13
@@ -13,8 +13,6 @@ source .env
|
|||||||
#ROOT_PASS="StartRootPass5678"
|
#ROOT_PASS="StartRootPass5678"
|
||||||
SCRIPT_ACTION=$1
|
SCRIPT_ACTION=$1
|
||||||
LINODE_LABEL=$2
|
LINODE_LABEL=$2
|
||||||
# oily 2111036
|
|
||||||
WHICH_DOMAIN="2111036"
|
|
||||||
LOGFILE="./linodes.log"
|
LOGFILE="./linodes.log"
|
||||||
|
|
||||||
# Pre-existing Valheim volume, attached to every Linode at creation time.
|
# Pre-existing Valheim volume, attached to every Linode at creation time.
|
||||||
@@ -24,9 +22,19 @@ VOLUME_NAME="ValVol202609"
|
|||||||
VOLUME_DEVICE="/dev/disk/by-id/scsi-0Linode_Volume_$VOLUME_NAME"
|
VOLUME_DEVICE="/dev/disk/by-id/scsi-0Linode_Volume_$VOLUME_NAME"
|
||||||
VOLUME_MOUNT="/mnt/$VOLUME_NAME"
|
VOLUME_MOUNT="/mnt/$VOLUME_NAME"
|
||||||
|
|
||||||
|
# Cloud Firewall applied to every Linode at creation time. It must allow the
|
||||||
|
# Valheim ports (2456-2457/udp) and, if you still need SSH, port 22.
|
||||||
|
FIREWALL_ID="165767433"
|
||||||
|
|
||||||
# StackScript that mounts the volume on first boot (see stackscript.md).
|
# StackScript that mounts the volume on first boot (see stackscript.md).
|
||||||
# Enter the numeric StackScript ID manually after creating it in the Linode UI.
|
# Enter the numeric StackScript ID manually after creating it in the Linode UI.
|
||||||
STACKSCRIPT_ID="2225110"
|
#STACKSCRIPT_ID="2225110"
|
||||||
|
# Normal:
|
||||||
|
#STACKSCRIPT_ID="2225140"
|
||||||
|
# More resources (1.5):
|
||||||
|
#STACKSCRIPT_ID="2225166"
|
||||||
|
# Final, 1.5 resources:
|
||||||
|
STACKSCRIPT_ID="2225201"
|
||||||
|
|
||||||
if [[ "$SCRIPT_ACTION" == "" || "$SCRIPT_ACTION" == "--help" || "$SCRIPT_ACTION" == "-?" ]]; then
|
if [[ "$SCRIPT_ACTION" == "" || "$SCRIPT_ACTION" == "--help" || "$SCRIPT_ACTION" == "-?" ]]; then
|
||||||
echo "Linode helper script: create-linode.sh <action> <label> [args]"
|
echo "Linode helper script: create-linode.sh <action> <label> [args]"
|
||||||
@@ -38,6 +46,7 @@ if [[ "$SCRIPT_ACTION" == "" || "$SCRIPT_ACTION" == "--help" || "$SCRIPT_ACTION"
|
|||||||
echo " create-linode.sh x <label> get <remote source> <local dest>"
|
echo " create-linode.sh x <label> get <remote source> <local dest>"
|
||||||
echo " create-linode.sh x <label> put <local source> <remote dest>"
|
echo " create-linode.sh x <label> put <local source> <remote dest>"
|
||||||
echo "Update record: create-linode.sh a <label> <subdomain>"
|
echo "Update record: create-linode.sh a <label> <subdomain>"
|
||||||
|
echo " (updates the existing A record, creates one if missing)"
|
||||||
echo "List: create-linode.sh l"
|
echo "List: create-linode.sh l"
|
||||||
echo " (lists all Linodes, no label needed)"
|
echo " (lists all Linodes, no label needed)"
|
||||||
fi
|
fi
|
||||||
@@ -60,7 +69,8 @@ if [[ "$SCRIPT_ACTION" == "c" ]]; then
|
|||||||
"image": "'$INSTANCE_IMAGE'",
|
"image": "'$INSTANCE_IMAGE'",
|
||||||
"root_pass": "'$ROOT_PASS'",
|
"root_pass": "'$ROOT_PASS'",
|
||||||
"authorized_users": ["ni7ne_midpriv"],
|
"authorized_users": ["ni7ne_midpriv"],
|
||||||
"stackscript_id": '$STACKSCRIPT_ID'
|
"stackscript_id": '$STACKSCRIPT_ID',
|
||||||
|
"firewall_id": '$FIREWALL_ID'
|
||||||
}' \
|
}' \
|
||||||
https://api.linode.com/v4/linode/instances | jq .
|
https://api.linode.com/v4/linode/instances | jq .
|
||||||
|
|
||||||
@@ -118,6 +128,17 @@ if [[ "$SCRIPT_ACTION" == "d" ]]; then
|
|||||||
INSTANCE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_KEY" https://api.linode.com/v4/linode/instances | jq '.data[] | select(.label == "'$LINODE_LABEL'") | .id' | sed -e "s/\"//g")
|
INSTANCE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_KEY" https://api.linode.com/v4/linode/instances | jq '.data[] | select(.label == "'$LINODE_LABEL'") | .id' | sed -e "s/\"//g")
|
||||||
echo "Destroying ID:$INSTANCE_ID Hit Ctrl-C in the next 10 seconds to cancel."
|
echo "Destroying ID:$INSTANCE_ID Hit Ctrl-C in the next 10 seconds to cancel."
|
||||||
sleep 15
|
sleep 15
|
||||||
|
|
||||||
|
# Shut the Linode down cleanly before deleting it, so systemd stops the
|
||||||
|
# Docker container and the Valheim server gets the chance to save the world
|
||||||
|
# rather than being cut off mid-write.
|
||||||
|
echo "Shutting down ID:$INSTANCE_ID..."
|
||||||
|
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
|
||||||
|
-X POST https://api.linode.com/v4/linode/instances/$INSTANCE_ID/shutdown | jq .
|
||||||
|
|
||||||
|
# Give the shutdown time to finish before the instance is deleted.
|
||||||
|
sleep 30
|
||||||
|
|
||||||
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
|
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
|
||||||
-X DELETE https://api.linode.com/v4/linode/instances/$INSTANCE_ID
|
-X DELETE https://api.linode.com/v4/linode/instances/$INSTANCE_ID
|
||||||
echo "$(date -Iminutes) --- Id:$INSTANCE_ID --- IP:$INSTANCE_IP Destroyed $LINODE_LABEL." >>$LOGFILE
|
echo "$(date -Iminutes) --- Id:$INSTANCE_ID --- IP:$INSTANCE_IP Destroyed $LINODE_LABEL." >>$LOGFILE
|
||||||
@@ -154,30 +175,56 @@ if [[ "$SCRIPT_ACTION" == "a" ]]; then
|
|||||||
INSTANCE_IP=$(curl -s -H "Authorization: Bearer $LINODE_API_KEY" https://api.linode.com/v4/linode/instances | jq '.data[] | select(.label == "'$LINODE_LABEL'") | .ipv4[]' | sed -e "s/\"//g")
|
INSTANCE_IP=$(curl -s -H "Authorization: Bearer $LINODE_API_KEY" https://api.linode.com/v4/linode/instances | jq '.data[] | select(.label == "'$LINODE_LABEL'") | .ipv4[]' | sed -e "s/\"//g")
|
||||||
|
|
||||||
NI_SUBDOMAIN=$3
|
NI_SUBDOMAIN=$3
|
||||||
|
DOMAIN_RECORDS=$(curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
|
||||||
|
https://api.linode.com/v4/domains/$WHICH_DOMAIN/records)
|
||||||
|
|
||||||
|
# Only A records are managed by this action, so filter out the rest
|
||||||
|
# (CNAME, MX, TXT, ...) when showing the current state.
|
||||||
echo "Previous value:"
|
echo "Previous value:"
|
||||||
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
|
echo "$DOMAIN_RECORDS" | jq '.data[] | select(.type == "A")'
|
||||||
https://api.linode.com/v4/domains/$WHICH_DOMAIN/records | jq .
|
|
||||||
|
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
|
# Update the A record that already exists for this subdomain rather than
|
||||||
|
# adding another one; a record is only created when there is nothing to
|
||||||
|
# update. DNS names are case-insensitive, so compare them that way.
|
||||||
|
RECORD_IDS=$(echo "$DOMAIN_RECORDS" | jq -r --arg name "$NI_SUBDOMAIN" \
|
||||||
|
'.data[] | select(.type == "A" and ((.name // "") | ascii_downcase) == ($name | ascii_downcase)) | .id')
|
||||||
|
RECORD_ID=$(echo "$RECORD_IDS" | head -n 1)
|
||||||
|
DUPLICATE_IDS=$(echo "$RECORD_IDS" | tail -n +2)
|
||||||
|
|
||||||
echo "Updating records with ip $INSTANCE_IP..."
|
echo "Updating records with ip $INSTANCE_IP..."
|
||||||
|
|
||||||
#ni7ne:
|
#ni7ne:
|
||||||
|
if [[ -n "$RECORD_ID" ]]; then
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $LINODE_API_KEY" \
|
||||||
|
-X PUT -d '{
|
||||||
|
"type": "A",
|
||||||
|
"name": "'$NI_SUBDOMAIN'",
|
||||||
|
"target": "'$INSTANCE_IP'",
|
||||||
|
"ttl_sec": 14400
|
||||||
|
}' \
|
||||||
|
https://api.linode.com/v4/domains/$WHICH_DOMAIN/records/$RECORD_ID | jq .
|
||||||
|
|
||||||
|
# Duplicates left behind by earlier runs would keep resolving to an old
|
||||||
|
# IP, so remove everything except the record that was just updated.
|
||||||
|
for DUPLICATE_ID in $DUPLICATE_IDS; do
|
||||||
|
echo "Deleting duplicate A record $DUPLICATE_ID for $NI_SUBDOMAIN..."
|
||||||
|
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
|
||||||
|
-X DELETE https://api.linode.com/v4/domains/$WHICH_DOMAIN/records/$DUPLICATE_ID
|
||||||
|
done
|
||||||
|
else
|
||||||
curl -s -H "Content-Type: application/json" \
|
curl -s -H "Content-Type: application/json" \
|
||||||
-H "Authorization: Bearer $LINODE_API_KEY" \
|
-H "Authorization: Bearer $LINODE_API_KEY" \
|
||||||
-X POST -d '{
|
-X POST -d '{
|
||||||
"type": "A",
|
"type": "A",
|
||||||
"name": "'$NI_SUBDOMAIN'",
|
"name": "'$NI_SUBDOMAIN'",
|
||||||
"target": "'$INSTANCE_IP'",
|
"target": "'$INSTANCE_IP'",
|
||||||
"priority": 0,
|
"ttl_sec": 14400
|
||||||
"weight": 0,
|
|
||||||
"port": 0,
|
|
||||||
"service": null,
|
|
||||||
"protocol": null,
|
|
||||||
"ttl_sec": 14400,
|
|
||||||
"tag": null
|
|
||||||
}' \
|
}' \
|
||||||
https://api.linode.com/v4/domains/$WHICH_DOMAIN/records | jq .
|
https://api.linode.com/v4/domains/$WHICH_DOMAIN/records | jq .
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Update complete."
|
echo "Update complete."
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
# Valheim Volume StackScript
|
|
||||||
|
|
||||||
A Linode StackScript that mounts the pre-existing Valheim volume on first boot.
|
|
||||||
Running it as a StackScript means the mount happens as root during deployment,
|
|
||||||
so no SSH session (and therefore no SSH key) is needed to configure it.
|
|
||||||
|
|
||||||
This mirrors the steps in `voldirections.txt`. The filesystem (`mkfs.ext4`) is
|
|
||||||
**not** created here, because that step has already been completed on the
|
|
||||||
volume; the script only creates the mountpoint, mounts the device, and adds the
|
|
||||||
`/etc/fstab` entry.
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. Log in to the Linode Cloud Manager and open **StackScripts**.
|
|
||||||
2. Click **Create StackScript**.
|
|
||||||
3. Set **Label** to something like `valheim-volume-mount` and choose
|
|
||||||
**Debian 13** (or "Any") as the **Target Images**.
|
|
||||||
4. Paste the code block below into the **Script** editor.
|
|
||||||
5. Leave the **UDF** (User Defined Fields) section empty — the values are
|
|
||||||
hard-coded, so nothing needs to be entered at deploy time.
|
|
||||||
6. Click **Save**. The numeric ID of the StackScript appears in the URL and in
|
|
||||||
the StackScript list; copy it.
|
|
||||||
7. Put that ID in `create-linode.sh` as the `STACKSCRIPT_ID` value near the top
|
|
||||||
of the file.
|
|
||||||
|
|
||||||
After that, `create-linode.sh c <label>` passes the StackScript on the create
|
|
||||||
call and the volume is mounted automatically on first boot.
|
|
||||||
|
|
||||||
## StackScript
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# Linode StackScript: mount the pre-existing Valheim volume on first boot.
|
|
||||||
# Runs as root during deployment; no SSH access required.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# The volume already has an ext4 filesystem (see voldirections.txt), so only
|
|
||||||
# the mountpoint, the mount, and the fstab entry are handled here.
|
|
||||||
VOLUME_NAME="ValVol202609"
|
|
||||||
VOLUME_DEVICE="/dev/disk/by-id/scsi-0Linode_Volume_${VOLUME_NAME}"
|
|
||||||
VOLUME_MOUNT="/mnt/${VOLUME_NAME}"
|
|
||||||
|
|
||||||
# The volume is attached by the API after deployment starts, so wait for the
|
|
||||||
# device to show up before trying to mount it.
|
|
||||||
for _ in $(seq 1 30); do
|
|
||||||
[ -e "$VOLUME_DEVICE" ] && break
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ! -e "$VOLUME_DEVICE" ]; then
|
|
||||||
echo "Volume device ${VOLUME_DEVICE} did not appear; skipping mount." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$VOLUME_MOUNT"
|
|
||||||
mount "$VOLUME_DEVICE" "$VOLUME_MOUNT"
|
|
||||||
|
|
||||||
# Mount on every boot, without duplicating the line on re-runs.
|
|
||||||
grep -q "$VOLUME_DEVICE" /etc/fstab || \
|
|
||||||
echo "${VOLUME_DEVICE} ${VOLUME_MOUNT} ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
|
|
||||||
|
|
||||||
echo "Mounted ${VOLUME_DEVICE} at ${VOLUME_MOUNT}."
|
|
||||||
```
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user