Breaking changes from previous Syncplify Server! versions
This page outlines the significant changes ("breaking changes") introduced in the latest version of the software, as compared to previous versions. It also provides guidance on how to smoothly adapt to these changes when upgrading to the version described in the current documentation.
GetCurrentVFS() is no longer a member of the Session object
In previous versions of Syncplify Server!, to work with the currently active Virtual File System (VFS) from inside a SyncJS script, you'd call:
var currVfs = Session.GetCurrentVFS();
if (currVfs != null) {
// do something with it
}In Syncplify Server!, GetCurrentVFS() has become a stand-alone function, and is no longer a member of the Session object. You will then need to update all of your scripts using this method, in order to call it like this:
var currVfs = GetCurrentVFS();
if (currVfs != null) {
// do something with it
}Read more about this method here.
VFS' ImportFile() and ExportFile() function signature differences
In previous versions of Syncplify Server! the ImportFile() and ExportFile() methods of a VFS object would take a file path and a directory path as arguments, which lead to all kinds of confusion. These functions signatures have now been uniformed as follows:
function ImportFile(localFilePath, targetVfsFilePath: string) respBase;
function ExportFile(vfsFilePath, localFilePath: string) respBase;Read more on this topic on the specific manual pages for ImportFile and ExportFile.
CtxRelPath() replaces Session.GetRelPath() / Session.GetAbsPath() in event-handler scripts
In Syncplify Server! versions 1 through 6, the only way to retrieve the path of the file associated with a triggered event from inside a script was to call Session.GetRelPath() or Session.GetAbsPath(). This was always subtly unreliable: those methods return the session's live path cursor, which may have already changed by the time the script executes — because the client keeps working concurrently.
Syncplify Server! V7 (and thus also V8 and all subsequent versions) introduced the dedicated CtxRelPath() helper function. The path is captured at the moment the event fires and frozen into the script's execution context for its entire lifetime, regardless of what the client does afterwards.
If you have existing scripts that call Session.GetRelPath() or Session.GetAbsPath() in order to determine which file triggered a file-based event, you should update them to use CtxRelPath() instead:
// Before (V1–V6) — unreliable under concurrent session activity:
var path = Session.GetRelPath();
// After (V7+) — always correct:
var path = CtxRelPath();NOTE
Session.GetRelPath() and Session.GetAbsPath() still exist and are still valid for their original purpose (inspecting the session's current cursor position). Only use them when you genuinely need that live cursor, not when you need the file that triggered the script.
CtxRelTargetPath() replaces Session.GetRelTargetPath() in event-handler scripts as of v8.0.2
This one is far more specific than CtxRelPath(): it gives you the path of the target file before and after a Move/Rename operation. The logic is the same as for CtxRelPath(): the path is captured at the moment the event fires and frozen into the script's execution context for its entire lifetime, regardless of what the client does afterwards.
// Before v8.0.2 — unreliable under concurrent session activity:
var targetPath = Session.GetRelTargetPath();
// After v8.0.2 — always correct:
var targetPath = CtxRelTargetPath();WARNING
CtxRelTargetPath() is not available prior to v8.0.2. Please upgrade to v8.0.2 or newer to use this function.
CtxVFSName() replaces Session.GetCurrentVFSName() in event-handler scripts as of v8.0.4
This function provides the name of the virtual file system (VFS) associated with the current event. As with the other Ctx* functions, the VFS name is captured at the moment the event fires and frozen into the script's execution context for its entire lifetime, ensuring consistent behavior even under concurrent session activity.
// Before v8.0.4 — unreliable under concurrent session activity:
var vfsName = Session.GetCurrentVFSName();
// After v8.0.4 — always correct:
var vfsName = CtxVFSName();Read more about this function here.
WARNING
CtxVFSName() is not available prior to v8.0.4. Please upgrade to v8.0.4 or newer to use this function.
New EventCtx() helper function as of v8.0.4
Alongside CtxVFSName(), v8.0.4 also introduces the EventCtx() helper function. It returns the whole event context as a single object, carrying the same frozen values the individual Ctx* functions expose:
var ctx = EventCtx();
Log("File path: " + ctx.RelPath); // same as CtxRelPath()
Log("Target path: " + ctx.RelTargetPath); // same as CtxRelTargetPath()
Log("VFS name: " + ctx.VFSName); // same as CtxVFSName()This is not a breaking change, existing scripts keep working unmodified. It is worth knowing about because EventCtx() is forward compatible: when future versions enrich the event context with new information, the new fields appear in this object automatically, without waiting for a dedicated Ctx* function.
WARNING
EventCtx() is not available prior to v8.0.4. Please upgrade to v8.0.4 or newer to use this function.
New CtxUsername() helper function as of v8.0.5
v8.0.5 introduces the CtxUsername() helper function, which works exactly like the other Ctx* functions: it returns the username of the user who logged in and started the session that triggered the script, captured at the moment the event fires and frozen into the script's execution context for its entire lifetime. It returns an empty string "" for events that occur before authentication.
var user = CtxUsername();
if (user !== "") {
Log("Event triggered by user: " + user);
}As promised by the forward compatibility of EventCtx(), the same value is also available as the new Username field of the event context object, with no changes needed in scripts that already read it:
var ctx = EventCtx();
Log("File path: " + ctx.RelPath); // same as CtxRelPath()
Log("Target path: " + ctx.RelTargetPath); // same as CtxRelTargetPath()
Log("VFS name: " + ctx.VFSName); // same as CtxVFSName()
Log("Username: " + ctx.Username); // same as CtxUsername(), new in v8.0.5This is not a breaking change, existing scripts keep working unmodified.
WARNING
CtxUsername(), and the Username field of EventCtx(), are not available prior to v8.0.5. Please upgrade to v8.0.5 or newer to use them.
New CtxVirtualSite() helper function as of v8.0.6
v8.0.6 introduces the CtxVirtualSite() helper function, which returns the ID of the virtual site in whose context the event occurred, captured at the moment the event fires and frozen into the script's execution context for its entire lifetime. Unlike the other Ctx* functions, it never returns an empty string: every worker instance serves exactly one virtual site and will not start without one, so there is no event that fires outside a virtual site.
Log("Event fired in virtual site: " + CtxVirtualSite());The same value is also available as the new VirtualSite field of the EventCtx() object:
var ctx = EventCtx();
Log("Username: " + ctx.Username); // same as CtxUsername()
Log("Virtual site: " + ctx.VirtualSite); // same as CtxVirtualSite(), new in v8.0.6This is not a breaking change, existing scripts keep working unmodified. Session.GetVirtualSite() returns the same value and is not deprecated: unlike the session's file cursor or current VFS, the virtual site cannot change during a session, so neither call can go stale.
WARNING
CtxVirtualSite(), and the VirtualSite field of EventCtx(), are not available prior to v8.0.6. Please upgrade to v8.0.6 or newer to use them.
The SSH Shell subsystem is now a virtual shell
Asking an SSH server for a shell, which is what ssh user@host with no arguments does, used to start a real operating system shell on the host: /bin/sh on Unix, cmd.exe on Windows. That child process carried no user credential, so it ran with the identity of the service itself, which is root on Linux and SYSTEM on Windows, and it stepped entirely outside this product's virtual file system boundaries, permissions, quotas and audit log.
The Shell subsystem now runs a virtual shell instead: a fixed, small set of commands interpreted by the server, operating on the session's own virtual file system, with no operating system process involved at any point. The full command set and the reasoning behind the change are documented on the SSH Shell subsystem page.
This is a breaking change for anyone whose automation opened an interactive shell and issued operating system commands. Such automation stops working, deliberately. The ssh2_shell permission itself is unchanged, and clients that ask for sftp, scp or exec are entirely unaffected.
Rewrite affected automation using SFTP or SCP where it is really moving files, or as a script bound to an event handler where it is not. If somebody genuinely needs an operating system shell on the machine, give them an operating system account on that machine; it should not arrive as a side effect of a file transfer permission.
Event handlers with no timeout no longer run indefinitely
An event handler whose Timeout (s) field was left at zero used to be given a timeout of roughly 115 days, which is not a timeout in any practical sense: a handler that hung held its script engine, and everything that engine referenced, until the service was restarted.
A timeout of zero now means one hour for ordinary events, and 30 seconds for events that can fire before a user has authenticated, since those are the events an anonymous stranger can trigger at will just by connecting. A timeout you set explicitly is still honoured exactly as set, in either direction.
This affects you only if you have a handler that relies on running longer than those defaults and has no explicit timeout set. Set the timeout you actually need on that handler and it will behave as before. The Event Handlers page describes both defaults in full.
Asynchronous event handlers now have a concurrency limit
Handlers marked Run asynchronously are started and left to finish on their own. There was previously no limit on how many could be running at once, so a rapid burst of events, which an anonymous client can produce simply by opening connections, could start an unbounded number of script engines.
There is now a limit, set by the new Max concurrent asynchronous scripts field at the top of the Event Handlers page. It defaults to 64 and accepts anything from 1 to 4096. If every slot is busy when an event fires, that asynchronous run is skipped and a warning naming the script and the event is written to the log. Synchronous handlers are never skipped.
This affects you only if you rely on a large number of asynchronous handlers running at the same moment. Raise the value if the log warnings show you need to; the setting takes effect on the next event, with no restart.
OnBlocklistHit fires at most once per 10 seconds per address
When a client that is already on the blocklist tried to connect, the server used to consult the database, write back an updated hit count and ban expiry, and dispatch the OnBlocklistHit event, on every single connection attempt. An address under a ban therefore cost the server more work per connection than a clean one, which is backwards for a control whose whole purpose is to make unwanted traffic cheap to refuse.
The verdict for a refused address is now remembered for 10 seconds. Within that window the address is still refused, immediately, but the database is not consulted again and the event does not fire again. The ban still compounds the longer a client keeps trying; it now does so at a rate the server chooses rather than one the client chooses.
This affects you only if you have an OnBlocklistHit handler that counts events, for example one that tallies attempts or sends a message per hit. It will now see at most one event per 10 seconds per address instead of one per connection attempt. Removing an address from the blocklist, or clearing the blocklist entirely, takes effect immediately and does not wait out the 10 seconds.
A JWT can only be presented in the Authorization header
The worker's HTTP endpoints used to accept a token in a jwt form field on POST requests, as a fallback when the Authorization header was absent. That fallback has been removed: reading the form field meant parsing the request body of an entirely unauthenticated request, buffering it in memory and spilling the excess to the temporary directory, before there was any credential to check.
No Syncplify client has ever sent a token that way, so in practice this affects nothing. If you have your own automation against the WebClient API that puts the token in a form field, move it to the standard header:
Authorization: Bearer <token>New installations generate their own database credential
Every version before this one reached its own database with a service account password that was a constant, identical on every installation ever shipped and compiled into the program. A new installation now generates a random password for itself during setup and keeps it in a protected file next to the installation key. See Protected files on a node.
This is not a breaking change for anyone who is upgrading. An installation that already exists keeps the password its database already has, and the software still knows it. Setup only generates a credential when it creates a database from scratch, which happens on a first installation and never on an upgrade or a repair. There is nothing to do.
Two things are worth knowing anyway:
- The new file,
.ssrv-database.cred, joins.ssrv-installation.keyas something to back up separately from your backup archives. Neither is included in an archive, which is what makes a stolen archive useless on its own. - If setup cannot generate the credential it says so and carries on with the built in password, exactly as before. The installation is complete and works normally.
An R2FS! node may only serve VFS names that exist on the virtual site
When an R2FS! storage node connects, it claims the VFS names it intends to serve. The virtual site used to check that claim against its own list of R2FS! virtual file systems only when that list was not empty. On a virtual site with no R2FS! VFS configured, which is an entirely ordinary configuration, an empty list read as "no restriction" rather than as "accept nothing", so any node holding a valid identity key could claim any name it invented.
Every claim is now checked, including on a virtual site that has no R2FS! virtual file system at all. A name that does not match a configured R2FS! VFS is refused, and the refusal is written to the log naming the claim that was rejected.
This affects you only if an R2FS! node claims a name that no VFS on that virtual site uses. Such a node used to connect and report itself healthy while serving nothing, because there was no VFS to route traffic to it; it is now refused at connection time, which turns a silent misconfiguration into a visible one. Create an R2FS! virtual file system whose R2FS! name matches what the node claims, or correct the name configured on the node, and it connects exactly as before. The check follows configuration changes as you make them, with no restart.
R2FS! connections are encrypted as of Syncplify Server! v8.1 and R2FS! v2.2
The link between a virtual site and its R2FS! storage nodes used to run in the clear. Everything it carried, the identity key included, was readable by anyone on the network path, and a node had no way to tell whether it had reached your server or something answering in its place.
Between a v8.1 server and a v2.2 node that link is now encrypted, and both ends prove possession of the shared identity key over the connection they are actually on, so neither can be impersonated by a machine on the path. There is nothing to configure and nothing to do. No certificate is involved, no new field appears, and the identity key you already copy between the two sides remains the whole of the trust.
This is not a breaking change. A v8.1 server still accepts older R2FS! nodes, and a v2.2 node still reaches older servers, so the two sides can be upgraded in either order and at whatever pace suits you. Once both ends of a given link are new, that link is encrypted automatically without either side being told to do so.
Two settings let you close the door behind you once every node is upgraded: Require R2FS! v2.2+ mutually verified encryption on the server, described on the R2FS! page, and its counterpart in the R2FS! node's own configuration. Both default to off, and both refuse unencrypted links once turned on.
TIP
Before turning either setting on, look for the log entries naming any R2FS! node that connected without encryption. They tell you exactly which nodes still need upgrading.
