add upgrade command; reinstall keeps the same version

- terractl upgrade <name> lists the zips an instance can use; with a zip
  or 'latest' it checks the zip, stops the server, backs up worlds and
  configs to instances/.backups/, pins ZIP=, unpacks right away and
  starts the server again. Downgrades need --downgrade.
- reinstall pins the installed zip when ZIP= is blank, so it never
  changes version, and keeps server/.installed (uses a .reinstall flag).
- Resolve terractl's own path before cd /, so a relative $0 works.
- Docs, completion and the daemon's command list; version 0.0.12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-02 21:37:51 +00:00
1 parent ef63609db9
commit 96f7876729
7 files changed
+325 -31

No files matched your search

+65 -13
View File
@@ -87,6 +87,7 @@ On stop, `terractl service-stop` sends `exit` through the pipe so the world is s
│ ├── Mods/ tModLoader mods + enabled.json
│ ├── ModConfigs/ tModLoader mod settings
│ └── banlist.txt
├── .backups/ Backups made by 'terractl upgrade'
└── .deleted/ Backups made by 'terractl delete'
/run/terraria/<name>.stdin Console pipe (exists only while running)
@@ -468,7 +469,7 @@ EXTRA_ARGS=() # extra server arguments, as a bash array
| Setting | Effect |
|---|---|
| `TYPE` | Which server software runs. Changing it re-unpacks `server/` from the other kind of zip on the next start. `data/` and `serverconfig.txt` are kept. |
| `ZIP` | Pins a specific zip from `/var/lib/terraria/dist` (a file name, or an absolute path). Changing it re-unpacks `server/` on the next start. If left blank, the newest matching zip is used at first install and **never upgraded automatically**. |
| `ZIP` | Pins a specific zip from `/var/lib/terraria/dist` (a file name, or an absolute path). Changing it re-unpacks `server/` on the next start; `terractl upgrade` sets it for you (see [Upgrading](#upgrading-or-downgrading-an-instance)). If left blank, the newest matching zip is used at first install and **never upgraded automatically**. |
| `EXTRA_ARGS` | Extra command-line arguments, e.g. `EXTRA_ARGS=(-disableannouncementbox)`. |
This file also marks the directory as an instance: `list` ignores directories without one, and `delete` refuses to remove them.
@@ -563,17 +564,59 @@ Stable tModLoader and the latest vanilla Terraria can be based on different Terr
### Upgrading (or downgrading) an instance
1. Put the new zip in `/var/lib/terraria/dist`, with the version in its name.
2. Set `ZIP=` to that file name in the instance's `instance.conf`.
3. Restart the instance:
Put the new zip in `/var/lib/terraria/dist`, with the version in its name (see [Server zips](#server-zips)). To see which zips an instance can use and which one it runs:
```bash
sudo systemctl restart terraria@calamity
```
$ terractl upgrade calamity
'calamity' (tmodloader) runs: tModLoader-v2026.06.1.0.zip
ZIP NOTE
tModLoader-v2026.06.1.0.zip installed, pinned
tModLoader-v2026.07.3.0.zip newest
Change version with: sudo terractl upgrade calamity <zip>|latest
```
The helper sees that the zip has changed and re-unpacks `server/`. Nothing in `data/` is touched. To move back, set `ZIP=` to the old file.
Then switch to it, by name or with `latest` (the newest zip of the instance's type):
**Pin `ZIP=` on modded instances.** Pinned instances never change version by accident, and you can see at a glance which version each one is on. New tModLoader versions can break older mods, so only upgrade a modded server once its mods support the new version.
```bash
sudo terractl upgrade calamity tModLoader-v2026.07.3.0.zip
sudo terractl upgrade calamity latest
```
`upgrade` applies the change right away, in this order:
1. **Checks the zip** before changing anything: it must be in `dist/`, match the instance's type, be readable by the `terraria` user, contain the server, and pass `unzip -t`. A damaged or half-downloaded zip is refused here, not on the next start.
2. **Stops the server** if it's running. Stopping saves the world.
3. **Backs up** the worlds and configs (not `server/`) to `/var/lib/terraria/instances/.backups/<name>-<date>-<old zip>.tar.gz`, readable only by root. Use `--no-backup` to skip this.
4. **Pins `ZIP=`** in `instance.conf` to the new zip, so the version can't change by accident afterwards.
5. **Unpacks the new zip** into `server/`. For tModLoader, the .NET runtime and logs are kept, as always. If this fails, the old pin is put back and the old `server/` is still in place, so the instance is exactly as it was.
6. **Starts the server again** if it was running.
Nothing in `data/` is changed by `upgrade` itself. But once the new version saves a world, older versions can't open it.
**Downgrades need `--downgrade`.** `upgrade` compares the version in the zip names, and refuses to go to an older one without it:
```bash
sudo terractl upgrade calamity tModLoader-v2026.06.1.0.zip --downgrade
```
Downgrading only changes the server software. If worlds have already been saved by the newer version, restore them from the backup as well (see [Undoing an upgrade](#undoing-an-upgrade)).
**Pin `ZIP=` on modded instances.** `upgrade` always pins, and pinned instances never change version by accident. New tModLoader versions can break older mods, so only upgrade a modded server once its mods support the new version. After upgrading, check that the mods load with `terractl watch calamity`.
You can still change version by hand: set `ZIP=` in `instance.conf` and restart. That skips the checks and the backup, and a bad zip only shows up when the server fails to start.
### Undoing an upgrade
To go back to the old version *and* the worlds as they were before the upgrade, replace the instance with the backup `upgrade` made. The backup is in the same format as `export`, so `import` restores it, pinned to the version it was taken with:
```bash
sudo ls /var/lib/terraria/instances/.backups/
sudo terractl delete calamity -y # keeps its own backup in .deleted/, just in case
sudo terractl import calamity /var/lib/terraria/instances/.backups/calamity-20261002-213553-tModLoader-v2026.06.1.0.tar.gz
sudo terractl enable calamity --now
```
Anything played since the upgrade is lost this way. The backups are never deleted automatically; remove old ones with `sudo rm`.
### Re-unpacking the same version
@@ -581,14 +624,16 @@ If `server/` gets damaged or you want a clean copy:
```bash
sudo terractl reinstall calamity
sudo systemctl restart terraria@calamity
sudo terractl restart calamity
```
`reinstall` unpacks the **same** zip again on the next start; it never changes version. If `ZIP=` is blank, it first pins the zip the instance runs, because otherwise the next start would pick the newest zip in `dist/`. If that zip is no longer in `dist/`, it says so and changes nothing: put the zip back, or use `upgrade`.
For tModLoader, the downloaded .NET runtime and `tModLoader-Logs/` are kept. If you suspect .NET itself is damaged, use `--clean` to delete them too, so .NET is downloaded again on the next start:
```bash
sudo terractl reinstall calamity --clean
sudo systemctl restart terraria@calamity
sudo terractl restart calamity
```
---
@@ -789,7 +834,7 @@ Everything worth keeping is in `data/`, `instance.conf` and `serverconfig.txt`,
30 4 * * * /usr/bin/terractl export calamity /var/backups/terraria/calamity-$(date +\%F).tar.gz && find /var/backups/terraria -name 'calamity-*.tar.gz' -mtime +14 -delete
```
Create the folder first with `sudo mkdir -p /var/backups/terraria`. In a crontab, `%` has a special meaning and must be written as `\%`, as above. Terraria also keeps a `.bak` copy of each world next to the world file, which is useful if the latest save is damaged.
Create the folder first with `sudo mkdir -p /var/backups/terraria`. Separately, `upgrade` backs up an instance's worlds and configs before every version change (see [Undoing an upgrade](#undoing-an-upgrade)). In a crontab, `%` has a special meaning and must be written as `\%`, as above. Terraria also keeps a `.bak` copy of each world next to the world file, which is useful if the latest save is damaged.
---
@@ -807,6 +852,8 @@ The servers always run as the `terraria` user. Administration is simplest with `
| `attach` | root, or members of the `terraria` group | Writes to the console pipe and reads the servers' journal. |
| `cmd` | root, or members of the `terraria` group | Same as `attach`. |
| `watch` | root, or members of the `terraria` group | Reads the servers' journal only. |
| `upgrade <name>` (list) | root, or members of the `terraria` group | |
| `upgrade <name> <zip>` | root only | Checked by the script. Stops and starts the server, and unpacks the zip as `terraria`. |
| `reinstall` | root (or `terraria`) | |
| `fix-perms` | root only | Checked by the script. |
| `mods <name>` (list) | root, or members of the `terraria` group | |
@@ -875,6 +922,7 @@ While a server runs, whether systemd started it or you did, it holds a lock on t
- **A second `run` is refused**, and so is starting the service while a copy started by hand is running. In that case the service keeps retrying every 10 seconds and starts by itself once the other copy exits.
- **`prepare` is refused**, so `server/` can't be replaced under the running server.
- **`reinstall --clean` is refused**, so `.NET` can't be deleted under it. Plain `reinstall` is allowed, because it only takes effect at the next start.
- **`upgrade` stops the service first**, and refuses if a copy started by hand is running.
- **`delete` stops the service**, but refuses to go on if a copy started by hand is still running.
- **`list` shows a copy started by hand** as `running (outside systemd)`.
@@ -950,7 +998,8 @@ Its access was revoked with `daemon revoke`, or the host's `/var/lib/terractl` w
- instance names;
- `vanilla`/`tmodloader` for `new`;
- common server console commands for `cmd` (`save`, `say`, `playing`, `kick`...);
- the options of `enable`, `disable`, `watch`, `reinstall`, `delete`, `export`, `copy` and `move`;
- the options of `enable`, `disable`, `watch`, `upgrade`, `reinstall`, `delete`, `export`, `copy` and `move`;
- server zips for `upgrade`, only those of the instance's type (on a remote, all of its zips);
- remote names after `--host`, and `<remote>:<name>` for `copy` and `move`. Instance names on a remote are asked from its daemon, so they can take a moment;
- `.tar.gz` files for `export` and `import`;
- `mods` actions. For `mods add` it completes `.tmod` files and folders; for `mods enable` and `mods remove`, the installed mods; for `mods disable`, the enabled ones.
@@ -973,7 +1022,10 @@ terractl disable <name> [--now] Don't start at boot (--now: an
terractl attach <name> Interactive console (Ctrl-D detaches)
terractl watch <name> [-n <lines>] [-q] Follow the output, read-only (Ctrl-C quits)
terractl cmd <name> <command...> Send one console command
terractl reinstall <name> [--clean] Re-unpack server/ on next start
terractl upgrade <name> Show the zips it can use, and which it runs
terractl upgrade <name> <zip>|latest [--downgrade] [--no-backup]
Change server version now (backs up first)
terractl reinstall <name> [--clean] Re-unpack the same version on next start
(--clean also re-downloads .NET)
terractl fix-perms <name> Reset the owner and modes of all its files
terractl mods <name> [list] Show installed mods