Thorn Tech Marketing Ad
Skip to main content
Version: 1.3.0

Running the In-Place Upgrade Script

TLDR

Purpose: Upgrade an existing StorageLink 1.2.x instance to version 1.3.0 in place, without deploying a new instance.

Read first: An in-place upgrade does not give the instance a license. You start on the free 30-day trial, stay at the 10-user default, and keep paying the hourly marketplace fee. Our recommended upgrade is to deploy a new 1.3.0 instance and import your backup (see Upgrade Process for StorageLink); a new marketplace instance is licensed automatically.

Quick Steps: SSH into the VM, then run:

  1. cd ~
  2. curl -sS -O https://thorntech-products.s3.amazonaws.com/storagelink/1.003.00/in-place-upgrade-storagelink.sh
  3. sudo bash ./in-place-upgrade-storagelink.sh

Important:

  • Export a backup file first via Settings → Backup & Recovery
  • Supported on single-instance deployments on AWS, Azure and Google Cloud
  • The upgraded instance comes up without a license. Start the free 30-day trial (10 users) under Settings → License, or non-administrator users cannot sign in and file transfers stay disabled; after the trial you need a purchased license
  • If you use LetsEncrypt or your own SSL certificate, re-apply your hostname and certificate paths to the new website.conf
  • Behind a load balancer or reverse proxy, add its CIDR range to security.client-ip.trusted-proxies in /opt/swiftgw/application.properties
  • The script is safe to run again if it stops partway

Recommended: Deploy a new 1.3.0 instance and import your backup. It is safer, it is licensed automatically, and it ships the current PostgreSQL and operating system image.

Product: StorageLink by Thorn Technologies — cloud storage gateway for secure file sharing

Overview​

This article goes over how to run the in-place upgrade script to upgrade an existing StorageLink 1.2.x instance to version 1.3.0 (1.003.00).

Read this before you upgrade in place

StorageLink 1.3.0 introduces licensing, and that changes what an in-place upgrade means. An instance upgraded in place does not receive a license. It comes up unlicensed, you start the free 30-day trial, and you stay at the 10-user default. After the trial you have to purchase a license to keep using it, while the hourly marketplace fee continues. A new 1.3.0 instance deployed from the marketplace is licensed automatically for 10 users with nothing to activate. Deploying a new instance and importing your backup is the upgrade path we recommend; the in-place script is for deployments that cannot be replaced. See Upgrading from 1.2.1 or earlier below.

The 1.3.0 upgrade changes more than earlier ones, so read this article all the way through before you start:

  • New service definition. The 1.3.0 platform starts differently, so the script replaces the swiftgateway service definition and adds the application properties the new version requires.
  • Licensing. Licensing is new in 1.3.0. An instance upgraded in place comes up without a license. After the upgrade you activate the free 30-day trial or a purchased license in the web admin UI.
  • PostgreSQL stays as it is. StorageLink 1.3.0 has no minimum PostgreSQL version, so the script leaves the database already on your instance in place. A new 1.3.0 instance deployed from the marketplace ships PostgreSQL 18; an instance upgraded in place keeps the version it has, which remains supported and continues to receive security updates from your operating system.

Upgrading from 1.2.1 or earlier: choose your path​

Every StorageLink instance on 1.2.1 or earlier runs without a license, because licensing did not exist before 1.3.0. There are two ways to reach 1.3.0, and they end in different places:

Deploy a new 1.3.0 instance and import your backup (recommended)Upgrade in place with this script
LicenseLicensed automatically by the marketplace for 10 StorageLink users (administrators not counted). Nothing to enter or activate.No license. Start the free 30-day trial (10 users); after the trial you must purchase a license to keep using StorageLink.
User limit10 users10 users on the trial; a purchased license is sized to what you buy
Marketplace feeContinues on the new instanceContinues on the existing instance, in addition to any license you purchase
PostgreSQL18, shipped with the imageThe version already on the instance (15 on 1.2.1 images), still supported
Operating systemFreshly built image with current patchesYour existing image, patched by your normal OS updates
Your configurationRestored from the backup file (users, folders, cloud connections, identity providers, settings); custom properties and SSL certificates re-applied by handKept in place; custom properties and SSL certificates re-applied to the new website.conf
DowntimeNone on the old instance until you cut overStorageLink is unavailable while the script runs, about one to two minutes

Our recommendation is to deploy a new instance. The procedure is in the Upgrade Process article: export a backup, deploy 1.3.0 from the marketplace listing you already use, import the backup, and cut over. The new instance is licensed from first boot and you never see the trial card.

Use the in-place script only when replacing the instance is not an option for you. If you do, plan for the trial: it lasts 30 days, and the only way past the 10-user default afterwards is a purchased license.

If you have more than 10 users, neither path keeps them all on a marketplace instance: from 1.3.0 a marketplace instance is licensed for 10 users, and a backup with more than 10 users imports the first 10 and skips the rest. Deploy from the StorageLink BYOL listing instead, where the license is available in 10-, 100- or 1,000-user tiers. The Standard vs Bring Your Own License (BYOL) article explains the two listings and the move, and Purchasing a License covers buying and activating a license.

Before You Begin​

Export a backup file first​

Before running the script, export a new Backup File containing your Users & Settings under Settings ---> Backup & Recovery ---> Export Backup File. If anything goes wrong, you can deploy a fresh 1.3.0 instance and import this file. The Upgrade Process article describes the export and import steps.

Downtime​

StorageLink is unavailable to users from the moment the script stops the service until the health check passes at the end, so plan a short maintenance window. The first start after the upgrade applies the 1.3.0 schema changes, which can take a few minutes on a small VM.

Network access​

The script downloads the 1.3.0 files from https://thorntech-products.s3.amazonaws.com. If Java 21 or the unzip tool is missing from the instance, it also installs them from your operating system's package repositories. The instance needs outbound HTTPS access for the download. If your instance has no outbound internet access, use the backup-and-restore path in the Upgrade Process article instead.

Supported platforms​

The script supports single-instance StorageLink deployments on AWS (Amazon Linux 2023), Azure and Google Cloud (Ubuntu). It detects the cloud and operating system itself.

If you run a high-availability deployment, do not run this script; contact support@thorntech.com for the upgrade path.

Licensing after the upgrade​

As described above, an instance upgraded in place comes up unlicensed. The script prints this warning at the start of the run:

WARNING: no license found. After this upgrade, activate a license or
trial in the admin UI (Settings -> License) or non-admin users cannot
sign in and transfers/jobs stay disabled.

Until a license or trial is active, administrators can sign in and manage configuration and users, but non-administrator users cannot sign in, and uploads, downloads and transfer jobs are disabled.

After the upgrade, sign in as an administrator and start the free 30-day trial under Settings ---> License, or activate a purchased license if you already have one. The Purchasing a License article walks through both. Transfers are unblocked as soon as the trial or license is active; no restart is needed.

The trial covers 10 StorageLink users. If your instance already has more than 10 users, they keep working, but you cannot add or replace a user until a license tier that covers your user count is active. Before the 30 days run out, purchase a license or move to a new instance as described in Upgrading from 1.2.1 or earlier.

LetsEncrypt and custom SSL certificates​

The script installs a new nginx website.conf and keeps your previous one alongside it as website.conf-<stamp>, where <stamp> is the date and time of the run (a copy is also in the rollback bundle). The file is located at:

Azure & Google Cloud:

/etc/nginx/sites-available/website.conf

AWS:

/etc/nginx/conf.d/website.conf

If you used LetsEncrypt or installed your own certificate, the new file does not contain your hostname or certificate paths. After the upgrade, copy the server_name line and the ssl_certificate and ssl_certificate_key lines from the stamped copy into the new website.conf, then validate and restart nginx:

sudo nginx -t && sudo systemctl restart nginx

The Troubleshooting section of the LetsEncrypt article shows what a complete website.conf looks like with a certificate applied.

Behind a load balancer or reverse proxy​

StorageLink 1.3.0 resolves each user's client IP address against a list of trusted proxies, which by default contains only the loopback addresses (127.0.0.0/8 and ::1/128), that is, the nginx proxy running on the instance itself. If StorageLink sits behind a load balancer, reverse proxy or CDN (an Azure Application Gateway or a Google Cloud load balancer, for example), add the address range your proxy sends traffic from to the security.client-ip.trusted-proxies property in /opt/swiftgw/application.properties, for example:

security.client-ip.trusted-proxies=127.0.0.0/8,::1/128,10.224.1.0/24

For an Azure Application Gateway this is the subnet the gateway is deployed in; for Google's external Application Load Balancer it is Google's proxy ranges, 130.211.0.0/22 and 35.191.0.0/16. Keep the two loopback entries: nginx on the instance forwards requests to StorageLink over loopback, so it must stay trusted. Several ranges can be separated with commas; bare IP addresses are accepted, DNS names are not. Then restart StorageLink with sudo systemctl restart swiftgateway.

Until you do, nothing fails and no error is shown, but audit records and login lockout attribute every user to the load balancer's own address. To confirm the setting, sign in as an administrator: the address your own session resolved to is shown as Seen as <address> in the top navigation bar.

Automated 1.3.0 deployments, such as the high-availability templates, set the same list through the LOAD_BALANCER_ADDRESSES variable in the launch configuration (/opt/swiftgw/launch_config.env). On an upgraded instance, use the application property above.

Deployments where users reach StorageLink directly need no change.

note

Behind an Azure Application Gateway the forwarded address currently includes a changing source port, so login lockout does not yet accumulate per client, and audit records show the port alongside the address. Handling this form is planned for a later release.

Memory settings​

Older in-place upgrade articles asked you to edit the Java memory settings after the upgrade. The 1.3.0 script sizes them for you: it reads the instance's total RAM and sets the minimum heap to about 15% and the maximum heap to about 48% of it in /opt/swiftgw/swiftgateway-1.3.0.conf. You don't need to edit the file unless you resize the VM later. See the Memory Settings article for that case.

Log forwarding​

StorageLink 1.3.0 writes license events to a new log file, /opt/swiftgw/log/license-audit-<date>.log, next to the application and audit logs. Instances launched from a 1.3.0 image on AWS and Google Cloud forward it to CloudWatch or Google Cloud Logging with the other logs. The script does not change the log agent on an upgraded instance, so if you forward StorageLink logs today, add the new file to the same agent configuration that already picks up application-*.log and audit-*.log. On Azure, add it to the Azure Monitor Agent data collection rule you use for the other logs. See the Logging article for the log file locations.

What the Script Does​

The script prints every command as it runs (each appears on a line beginning with +) and stops at the first error. Every step can be repeated, so if it stops partway you simply run it again. It works through these steps in order.

Step 0 - Preflight checks. The script checks whether a license file is already present on the instance and prints the licensing warning shown above if not. It detects your cloud provider and operating system, and installs the unzip tool if the image does not have it. Finally, it validates your current nginx configuration with nginx -t. If that check fails, the script exits before changing anything (see Troubleshooting).

Step 1 - Rollback bundle. The script writes a rollback bundle to /var/backups/slink-upgrade-<stamp>/, where <stamp> is the date and time of the run, so each run gets its own bundle. It contains:

  • pg_dumpall.sql - a full SQL export of the PostgreSQL databases, taken before 1.3.0 applies its schema changes
  • opt-swiftgw.tgz - an archive of /opt/swiftgw (excluding the tmp and log folders)
  • swiftgateway.service - the previous service definition
  • website.conf - the previous nginx site configuration
  • application.properties - a copy taken in step 5, before any properties are changed

Step 2 - Stop StorageLink. The script stops the swiftgateway service. StorageLink is unavailable to users from this point until step 7.

Step 3 - Java 21. The script installs Java 21 if it is not already present. Instances on 1.2.1 already have it, so this step usually does nothing.

Step 4 - Install StorageLink 1.3.0. The script downloads the 1.3.0 assets and installs:

  • the application jar and its conf file into /opt/swiftgw/, with the Java heap sized for this instance's RAM
  • the new web admin UI into /usr/share/nginx/admin-ui (the previous UI is kept as /usr/share/nginx/admin-ui-<stamp>)
  • the new nginx website.conf (the previous one is kept as website.conf-<stamp>). The new file is validated immediately. If it fails nginx -t, the script restores the previous file, keeps the failing one as website.conf-1.3.0-failed, and continues
  • a new webconfig.js for the admin UI that keeps this instance's existing OAuth client settings (the values are not written to the script output)

Step 5 - Application properties. The script adds the properties 1.3.0 requires to /opt/swiftgw/application.properties if they are not already present, and sets one existing property to its new value:

  • features.systemd.notify=true
  • server.forward-headers-strategy=none (replaces the framework value used by 1.2.x; StorageLink 1.3.0 resolves client addresses through security.client-ip.trusted-proxies instead, and reports any other value as an error at startup)
  • security.redirect.base-path=backend/
  • server.max-http-request-header-size=128KB
  • features.api.file-upload-accept-rules= (empty)

Your other properties are not changed.

Step 6 - Service definition. The script replaces /etc/systemd/system/swiftgateway.service with the 1.3.0 unit, which launches Java directly and waits for StorageLink to signal that it is ready. The previous unit is kept as swiftgateway.service-<stamp>. The script also updates the version shown in the SSH login banner.

Step 7 - Restart and health check. The script restarts nginx (only if its configuration validates), reloads systemd, and starts swiftgateway. It then polls http://127.0.0.1:8080/actuator/health every 5 seconds for up to 10 minutes; the first start applies the 1.3.0 schema changes, which takes longest on small VMs. When the health check reports UP, the script prints the completion message. If StorageLink does not become healthy within 10 minutes, the script prints the last 40 lines of the service journal and exits with an error (see Troubleshooting).

Running the Script​

SSH into the VM (see SSH into the VM). Then download and run the script from your home directory:

cd ~
curl -sS -O https://thorntech-products.s3.amazonaws.com/storagelink/1.003.00/in-place-upgrade-storagelink.sh
sudo bash ./in-place-upgrade-storagelink.sh
info

Run the script with sudo bash from your home directory, exactly as shown. /tmp is mounted noexec on the StorageLink image, so running sudo ./in-place-upgrade-storagelink.sh from /tmp fails with Permission denied. You don't need sudo su, chmod +x, or a separate apt-get update; the script updates the package lists itself when it needs to install something.

The script runs with set -x, so it prints each command on a line beginning with + before running it. Expect a lot of output; the messages quoted in this article appear among those trace lines. The final health check is the longest step. Keep your SSH session open until the script finishes.

To keep a copy of the output for support, you can run the last command as sudo bash ./in-place-upgrade-storagelink.sh 2>&1 | tee ~/upgrade-1.3.0.log instead.

Expected output​

Near the end of a successful run, in between the + ... command-trace lines, you will see:

StorageLink 1.3.0 is UP after upgrade.

Upgrade to 1.3.0 complete.
Rollback bundle: /var/backups/slink-upgrade-<stamp>
PostgreSQL was left at its current version; 1.3.0 needs no database upgrade.
Reminder: if this instance has no license, activate one in Settings -> License.

Re-running the script​

If the script stops partway (a dropped SSH session, for example), run it again with the same three commands. Every step can be repeated: the download overwrites the previous copy, properties that are already present are left alone, and each run writes its own rollback bundle and keeps its own copies of the files it replaces, so nothing from the first attempt is lost.

Verify the upgrade​

  1. Refresh the web admin UI in your browser. You should see the updated UI, and the version shown at the bottom of the page should read 1.3.0.

  2. Check that the service is running:

    sudo systemctl status swiftgateway
  3. Check the health endpoint. The response includes "status":"UP":

    curl -s http://127.0.0.1:8080/actuator/health

After the Upgrade​

Work through this checklist once the script reports Upgrade to 1.3.0 complete.

  1. Start the trial or activate a license. Go to Settings ---> License and start the free 30-day trial (10 users) or activate your purchased license. Until you do, non-administrator users cannot sign in and transfers and jobs stay disabled. Put a reminder in your calendar for the trial's end date. See Purchasing a License.

  2. Behind a load balancer or reverse proxy? Add its CIDR range to security.client-ip.trusted-proxies in /opt/swiftgw/application.properties as described in Behind a load balancer or reverse proxy, run sudo systemctl restart swiftgateway, and confirm the Seen as address in the top navigation bar is your own.

  3. Using LetsEncrypt or your own certificate? Re-apply your hostname and certificate paths to the new website.conf, then run sudo nginx -t && sudo systemctl restart nginx.

  4. Verify users and folders. Sign in and confirm your users, folders and cloud connections are all present. Note that in 1.3.0 creating folders is a separate permission from uploading. On upgrade, every user who can upload is also allowed to create folders, matching the previous behavior, so remove the Create Folder permission from any user who should not have it.

  5. Forwarding logs? Add /opt/swiftgw/log/license-audit-<date>.log to the agent configuration that already forwards your application and audit logs.

  6. Tuned S3 upload concurrency? The property features.file-system.s3-max-concurrency was renamed to features.file-system.aws-s3.http-max-concurrency. The old name is ignored, so move your value to the new name in /opt/swiftgw/application.properties and restart StorageLink.

  7. Clean up when you are satisfied. The rollback bundle in /var/backups/ and the stamped copies of the previous admin UI, website.conf and service definition can be removed once you are confident in the upgrade.

Troubleshooting​

The script refuses to start because the nginx configuration fails validation​

If your current nginx configuration does not pass nginx -t, the script exits immediately with:

ERROR: 'nginx -t' fails on this instance's CURRENT nginx configuration.
The upgrade has NOT started and nothing has been changed.
Fix the nginx configuration (run 'sudo nginx -t' to see the error), then re-run this script.

Nothing on the instance has changed. Run sudo nginx -t, read the error, and fix the file it names. If the file is website.conf, the Troubleshooting section of the LetsEncrypt article has a known-good example. Then run the upgrade script again.

The new website.conf failed validation​

If the output contains ERROR: new website.conf fails 'nginx -t' — restoring the previous config., the script has put your previous website.conf back and kept the new one as website.conf-1.3.0-failed in the same directory. The upgrade continues with your previous nginx configuration. Send both files to support@thorntech.com so we can help you apply the 1.3.0 configuration.

"backend did not become healthy in 10 minutes"​

If StorageLink does not report healthy within 10 minutes, the script prints the last 40 lines of the service journal and exits with ERROR: backend did not become healthy in 10 minutes. The service is left running, so it may still finish starting on its own. Check the service status first:

sudo systemctl status swiftgateway

Then look at the service journal and the most recent application log (the date is part of the file name):

sudo journalctl -u swiftgateway --no-pager | tail -100
/opt/swiftgw/log/application-<date>.log

One known cause: StorageLink 1.3.0 refuses to start if security.max-login-failed-attempts or security.failed-login-timeout-seconds is set below 1 in /opt/swiftgw/application.properties, and the startup error names the property. Set a value of 1 or more (the default timeout is 3600), then run sudo systemctl restart swiftgateway. Once the cause is fixed, it is safe to run the upgrade script again.

If the web admin UI stays on the Please wait while StorageLink finishes setting up loading screen after the upgrade, check the service journal and the application log as described above.

Rolling back​

The rollback bundle for each run is in /var/backups/slink-upgrade-<stamp>/; run ls /var/backups/ to find it. It holds a full SQL export of the database taken before the upgrade, an archive of the previous /opt/swiftgw, and the previous service definition and nginx configuration. If you need to roll back, contact support@thorntech.com before making changes and we will walk through it with you.

Contacting support​

If you get stuck, email support@thorntech.com and include the full output of the script (or the ~/upgrade-1.3.0.log file if you kept one), the most recent /opt/swiftgw/log/application-<date>.log, and the output of sudo journalctl -u swiftgateway --no-pager | tail -200.

Script Contents​

Here are the contents of the script for reference:

#!/bin/bash
#
# StorageLink in-place upgrade script — upgrades an existing 1.2.x instance to v1.3.0.
#
# What this upgrade changes on the instance:
# 1. Application files: the 1.3.0 jar and conf, the web admin UI and the nginx
# site config. Spring Boot 4 no longer produces an executable boot jar, so
# the systemd unit is replaced with one that launches `java -jar` with an
# EnvironmentFile (JAVA_OPTS/RUN_ARGS), Type=notify, and
# features.systemd.notify=true in application.properties.
# 2. Application properties: adds the properties 1.3.0 requires and sets
# server.forward-headers-strategy=none (1.3.0 resolves client addresses
# through security.client-ip.trusted-proxies instead).
# 3. Java 21 is installed if it is missing.
#
# PostgreSQL is left as it is. StorageLink 1.3.0 enforces no minimum PostgreSQL
# version, so the database already on the instance keeps running unchanged; a
# new 1.3.0 instance deployed from the marketplace ships PostgreSQL 18.
#
# Every step can be repeated: if the script stops partway, run it again.
#
# Supported: single-instance StorageLink 1.2.x on AWS (Amazon Linux 2023), Azure
# and Google Cloud (Ubuntu). Not for high-availability deployments.
#
# QA mode: if ASSETS_TARBALL is set (a .tgz containing swiftgateway-<ver>.jar,
# swiftgateway-<ver>.conf, admin-ui/ and website.conf), it is used instead of the
# public S3 assets.zip — lets the upgrade run against pre-release builds.
#
# How to run:
# cd ~
# curl -sS -O https://thorntech-products.s3.amazonaws.com/storagelink/1.003.00/in-place-upgrade-storagelink.sh
# sudo bash ./in-place-upgrade-storagelink.sh
#
# Run it with "sudo bash" from your home directory: /tmp is mounted noexec on
# the hardened appliance, so "sudo ./in-place-upgrade-storagelink.sh" from /tmp
# fails with "Permission denied".
#

set -xe

if [[ $(whoami) != "root" ]]; then
echo "Usage: sudo bash $0"
exit 1
fi

TARGET_VERSION="${TARGET_VERSION:-1.3.0}"
TARGET_VERBOSE_VERSION="1.003.00"
# One stamp per run: every backup copy and the rollback bundle carry it, so a
# re-run never overwrites the copies taken before the first attempt.
STAMP=$(date +"%m%d%Y-%H%M%S")
APPLICATION_PROPERTIES="/opt/swiftgw/application.properties"
BACKUP_DIR="/var/backups/slink-upgrade-${STAMP}"

# Wait for any other apt/dpkg holder (e.g. unattended-upgrades) instead of
# failing mid-upgrade, and never let a package replace a locally-modified
# config file via a conffile prompt (noninteractive runs die on those).
APT_OPTS=(-o DPkg::Lock::Timeout=600
-o Dpkg::Options::=--force-confdef
-o Dpkg::Options::=--force-confold)

function extractPropValueFromSourceFile {
local prefix="${1}"
local str=`grep "${prefix}" ${2} 2>/dev/null`
echo "${str#$prefix}" | xargs
}

#
# 0. Preflight
#

# Licensing is new in 1.3.0. A 1.2.x instance has no license, so after this
# upgrade an administrator activates a free trial or a purchased license in the
# admin UI (Settings -> License); until then non-admin users cannot sign in and
# transfers/jobs stay disabled. Warn, don't block.
if [ -d /etc/thorn ] && [ -n "$(ls -A /etc/thorn 2>/dev/null)" ]; then
echo "License file present under /etc/thorn — instance will come up licensed."
else
echo "WARNING: no license found. After this upgrade, activate a license or"
echo "trial in the admin UI (Settings -> License) or non-admin users cannot"
echo "sign in and transfers/jobs stay disabled."
fi

mkdir -p "${BACKUP_DIR}"

# Determine cloud provider (same probe order as prior scripts)
# head -c keeps a wrong-cloud error page (e.g. Google's HTML 404) from
# flooding the trace log — the real values are all short strings.
AWS_DOMAIN=$(curl -s --max-time 2 "http://169.254.169.254/latest/meta-data/services/domain" | head -c 40 || true)
AZURE_DOMAIN=$(curl --noproxy "*" --max-time 2 -s -H 'Metadata: True' "http://169.254.169.254/metadata/instance/compute/azEnvironment?api-version=2019-06-01&format=text" | head -c 40 || true)
GCP_CHECK=$(curl -s --max-time 2 "http://169.254.169.254/computeMetadata/v1/instance/zone" -H "Metadata-Flavor: Google" | head -c 80 || true)
CLOUD_PROVIDER=aws
# GCP's metadata answer contains "zones/..." — other clouds' IMDS return XML/JSON errors here
[[ $GCP_CHECK == *"zones/"* ]] && CLOUD_PROVIDER=gcp
[[ $AWS_DOMAIN == "amazonaws.com" ]] && CLOUD_PROVIDER=aws
[[ $AZURE_DOMAIN == "AzurePublicCloud" ]] && CLOUD_PROVIDER=azure

# Determine OS and nginx layout
if getent passwd www-data > /dev/null 2>&1; then
OS=ubuntu
NGINX_USER=www-data
NGINX_CONF_PATH="/etc/nginx/sites-available"
else
OS=el
NGINX_USER=nginx
NGINX_CONF_PATH="/etc/nginx/conf.d"
fi

# unzip is needed to unpack the assets bundle; not every image ships it.
if ! command -v unzip >/dev/null 2>&1; then
if [ "$OS" == "ubuntu" ]; then
apt-get "${APT_OPTS[@]}" update
DEBIAN_FRONTEND=noninteractive apt-get "${APT_OPTS[@]}" install -y -q unzip
else
yum install -y unzip
fi
fi

# Pre-flight: refuse to start if the CURRENT nginx config already fails
# validation. This upgrade stops the app — never begin that on a box whose
# web tier is already broken.
if ! nginx -t; then
echo "" >&2
echo "ERROR: 'nginx -t' fails on this instance's CURRENT nginx configuration." >&2
echo "The upgrade has NOT started and nothing has been changed." >&2
echo "Fix the nginx configuration (run 'sudo nginx -t' to see the error), then re-run this script." >&2
exit 1
fi

#
# 1. Backups (rollback bundle)
#

# Full logical DB backup — the database itself is not modified by this script,
# but 1.3.0 applies its own schema migrations on first start, so keep a copy
# of the pre-upgrade state.
sudo -u postgres pg_dumpall > "${BACKUP_DIR}/pg_dumpall.sql"

# App + config backups
tar czf "${BACKUP_DIR}/opt-swiftgw.tgz" --exclude='swiftgw/tmp/*' --exclude='swiftgw/log/*' -C /opt swiftgw
cp -a /etc/systemd/system/swiftgateway.service "${BACKUP_DIR}/swiftgateway.service"
cp -a "${NGINX_CONF_PATH}/website.conf" "${BACKUP_DIR}/website.conf" 2>/dev/null || \
cp -a /etc/nginx/sites-enabled/website.conf "${BACKUP_DIR}/website.conf" 2>/dev/null || true
echo "Rollback bundle written to ${BACKUP_DIR}"

#
# 2. Stop the application
#

systemctl stop swiftgateway

#
# 3. Java 21 runtime (1.2.x images already ship it; install if missing)
#

if ! java -version 2>&1 | grep -q 'version "21'; then
if [ "$OS" == "ubuntu" ]; then
apt-get "${APT_OPTS[@]}" update
DEBIAN_FRONTEND=noninteractive apt-get "${APT_OPTS[@]}" install -y -q openjdk-21-jre-headless
update-alternatives --set java /usr/lib/jvm/java-21-openjdk-amd64/bin/java || true
else
yum install -y java-21-amazon-corretto
/usr/sbin/update-alternatives --set java /usr/lib/jvm/java-21-amazon-corretto.x86_64/bin/java
fi
fi

#
# 4. Install StorageLink 1.3.0 files
#

cd /tmp
rm -rf /tmp/slink-upgrade-assets && mkdir /tmp/slink-upgrade-assets && cd /tmp/slink-upgrade-assets
if [ -n "${ASSETS_TARBALL}" ] && [ -f "${ASSETS_TARBALL}" ]; then
tar xzf "${ASSETS_TARBALL}"
else
curl -fsSL -o assets.zip "https://thorntech-products.s3.amazonaws.com/storagelink/${TARGET_VERBOSE_VERSION}/assets.zip"
# Normalize the zip's assets/ folder to the same top-level layout the
# ASSETS_TARBALL path produces — later steps (website.conf install) resolve
# files relative to /tmp/slink-upgrade-assets and would miss assets/.
unzip -o assets.zip && mv assets/* . && rmdir assets
fi

# jar + conf
chmod +x swiftgateway-${TARGET_VERSION}.jar
chown swiftgw:swiftgw swiftgateway-${TARGET_VERSION}.jar swiftgateway-${TARGET_VERSION}.conf
mv swiftgateway-${TARGET_VERSION}.jar swiftgateway-${TARGET_VERSION}.conf /opt/swiftgw/

# Heap sizing: the conf ships with values computed for the build VM; recompute
# for this instance like first-boot does (Xms ~15% / Xmx ~48% of MemTotal).
MEM_MB=$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)
XMS=$((MEM_MB * 15 / 100))
XMX=$((MEM_MB * 48 / 100))
sed -i -E "s/-Xms[0-9]+m/-Xms${XMS}m/; s/-Xmx[0-9]+m/-Xmx${XMX}m/" /opt/swiftgw/swiftgateway-${TARGET_VERSION}.conf

# admin UI (previous UI kept as admin-ui-<stamp>)
if [ -d admin-ui ]; then
tar czf /usr/share/nginx/admin-ui.tar.gz admin-ui
else
mv admin-ui.tar.gz /usr/share/nginx
fi
cd /usr/share/nginx
[ -d admin-ui ] && mv admin-ui "admin-ui-${STAMP}"
tar xzvpf admin-ui.tar.gz && rm -f admin-ui.tar.gz
# Drop macOS AppleDouble metadata files (._*) if the bundle was built on a Mac.
find admin-ui -name '._*' -delete
chown -R ${NGINX_USER}:${NGINX_USER} admin-ui

# nginx site config (backup already taken; certs keep their existing paths)
cd /tmp/slink-upgrade-assets
if [ -f website.conf ]; then
cp "${NGINX_CONF_PATH}/website.conf" "${NGINX_CONF_PATH}/website.conf-${STAMP}" 2>/dev/null || true
chown ${NGINX_USER}:${NGINX_USER} website.conf
mv website.conf "${NGINX_CONF_PATH}/website.conf"
# Validate the new config immediately. Don't abort — restore the previous
# (working) config, keep the failing one for inspection, and continue loudly.
if ! nginx -t; then
echo "ERROR: new website.conf fails 'nginx -t' — restoring the previous config." >&2
mv "${NGINX_CONF_PATH}/website.conf" "${NGINX_CONF_PATH}/website.conf-${TARGET_VERSION}-failed"
cp "${BACKUP_DIR}/website.conf" "${NGINX_CONF_PATH}/website.conf"
nginx -t
fi
fi

# webconfig.js — preserve this instance's OAuth client
# The OAuth client values are served publicly in webconfig.js by design, but
# keep them out of this trace log — customers share these logs with support.
{ set +x; } 2>/dev/null
echo "Writing webconfig.js with this instance's OAuth client (values not logged)"
CLIENT_ID=$(extractPropValueFromSourceFile "security.client-id=" ${APPLICATION_PROPERTIES})
CLIENT_SECRET=$(extractPropValueFromSourceFile "security.client-secret=" ${APPLICATION_PROPERTIES})
(
cat <<EOF
window._env_ = {
"clientid": "$CLIENT_ID",
"clientsecret": "$CLIENT_SECRET",
"cloudProvider": "$CLOUD_PROVIDER",
"version": "${TARGET_VERSION%%-*}"
};
EOF
) | tee /usr/share/nginx/admin-ui/webconfig.js >/dev/null
chown ${NGINX_USER}:${NGINX_USER} /usr/share/nginx/admin-ui/webconfig.js
set -x

#
# 5. application.properties — add the 1.3.0-required properties (idempotent)
#

function ensure_prop {
local key="$1" value="$2"
grep -q "^${key}=" "${APPLICATION_PROPERTIES}" || \
echo "${key}=${value}" >> "${APPLICATION_PROPERTIES}"
}

cp -a "${APPLICATION_PROPERTIES}" "${BACKUP_DIR}/application.properties"
# Required for the Type=notify systemd unit — without it the service never
# signals readiness and systemd kills it at TimeoutSec.
ensure_prop "features.systemd.notify" "true"
# 1.3.0 resolves the client address itself (security.client-ip.trusted-proxies)
# and rebuilds proxied URLs from security.redirect.base-path, so Spring's own
# forwarded-header handling must be off. 1.2.x set this to "framework"; on
# 1.3.0 any value other than "none" is logged as an error at startup and
# bypasses the trusted-proxy resolution, so replace whatever is there.
sed -i '/^server\.forward-headers-strategy=/d' "${APPLICATION_PROPERTIES}"
ensure_prop "server.forward-headers-strategy" "none"
ensure_prop "security.redirect.base-path" "backend/"
ensure_prop "server.max-http-request-header-size" "128KB"
# Upload-path rewrite feature flags (ship-default empty ruleset)
ensure_prop "features.api.file-upload-accept-rules" ""
chown swiftgw:swiftgw "${APPLICATION_PROPERTIES}"

#
# 6. systemd unit — Spring Boot 4 launch pattern
#

cp -a /etc/systemd/system/swiftgateway.service "/etc/systemd/system/swiftgateway.service-${STAMP}"
cat > /etc/systemd/system/swiftgateway.service <<EOF
[Unit]
Description=StorageLink
After=syslog.target network.target
Before=nginx.service

[Service]
Type=notify
User=swiftgw
WorkingDirectory=/opt/swiftgw
EnvironmentFile=-/opt/swiftgw/swiftgateway-${TARGET_VERSION}.conf
ExecStart=/usr/bin/java \$JAVA_OPTS -jar /opt/swiftgw/swiftgateway-${TARGET_VERSION}.jar \$RUN_ARGS
SuccessExitStatus=143
AmbientCapabilities=CAP_NET_BIND_SERVICE
NotifyAccess=all
TimeoutSec=900

[Install]
WantedBy=multi-user.target
EOF

# Login banner version
sed -i "s/1\..*/${TARGET_VERSION}/" /etc/profile.d/login-info.sh 2>/dev/null || true

#
# 7. Restart everything and wait for health
#

if nginx -t; then
systemctl restart nginx
else
echo "WARNING: nginx config fails validation — nginx NOT restarted (see errors above)." >&2
fi
systemctl daemon-reload
systemctl start swiftgateway

# First start after the upgrade applies the 1.3.0 schema migrations, so allow
# up to 10 minutes on small VMs.
for i in $(seq 1 120); do
sleep 5
if curl -sk --max-time 5 http://127.0.0.1:8080/actuator/health | grep -q '"UP"'; then
echo "StorageLink ${TARGET_VERSION} is UP after upgrade."
break
fi
if [ "$i" == "120" ]; then
echo "ERROR: backend did not become healthy in 10 minutes"
echo "The service was left running and may still be starting. Check 'systemctl status swiftgateway'"
echo "and 'journalctl -u swiftgateway'. Once the cause is fixed it is safe to run this script again."
journalctl -u swiftgateway --no-pager | tail -40
exit 1
fi
done

echo
echo "Upgrade to ${TARGET_VERSION} complete."
echo "Rollback bundle: ${BACKUP_DIR}"
echo "PostgreSQL was left at its current version; 1.3.0 needs no database upgrade."
echo "Reminder: if this instance has no license, activate one in Settings -> License."