feat(updater): per #9, move over synthos updater code

Signed-off-by: zontreck <tarapiccari@gmail.com>
This commit is contained in:
zontreck 2024-10-05 00:56:18 -07:00
parent ec2f61125f
commit d7bd3cf805
4 changed files with 479 additions and 0 deletions

View file

@ -0,0 +1,141 @@
#include "includes/common.lsl"
#define MSGS_0 "Server startup in progress... reading manifest"
#define MSGS_1 "Server startup completed with "
default
{
state_entry()
{
// Startup
llOwnerSay(MSGS_0);
UpdateDSRequest(NULL, llGetNotecardLine("server_manifest", 0), SetDSMeta(["read",0, ""]));
}
changed(integer iChange)
{
if(iChange && CHANGED_INVENTORY)
{
llOwnerSay("Reloading server...");
llResetScript();
}
}
listen(integer c,string n,key i,string m)
{
llWhisper(0, m);
if(llJsonGetValue(m, ["operation"]) == "check_package_servers")
{
llRegionSayTo(i,c,Build("package_server_reply", ["server", g_sServerName]));
}else if(llJsonGetValue(m,["operation"]) == "connect")
{
// This is always going to be successful in reality, so what we do is we send back all package names
// Anything beyond that is package specific
list lPackages = llJson2List(g_sPackages);
integer count = llGetListLength(lPackages)/2;
list lNames= StrideOfList(lPackages, 2,0,-1);
lPackages=[];// Mark for GC
llRegionSayTo(i,c,Build("package_list", ["packages", llList2Json(JSON_ARRAY, lNames)]));
} else if(llJsonGetValue(m,["operation"]) == "check_package")
{
string sPackage = llJsonGetValue(m,["pkg"]);
list lPkgDef = llJson2List(llJsonGetValue(g_sPackages,[sPackage]));
lPkgDef=StrideOfList(lPkgDef,2,0,-1);
llRegionSayTo(i,c,Build("package_versions", [sPackage, llList2Json(JSON_ARRAY, lPkgDef)])); // Send all versions
} else if(llJsonGetValue(m,["operation"]) == "check_version")
{
string sPackage = llJsonGetValue(m,["pkg"]);
string sVer = llJsonGetValue(m,["ver"]);
if(sVer == "latest") sVer = llJsonGetValue(g_sPackages, [sPackage, "latest"]);
string sCurVer = llJsonGetValue(m,["cur"]);
string sCompat = llJsonGetValue(g_sPackages, [sPackage, sVer, "compat"]);
integer iRes=VersionNumberCompare(sCurVer, sVer);
integer iSame;
integer iNewer;
if(iRes == 0)
{
iSame=TRUE;
}else {
iSame=FALSE;
if(iRes>0)iNewer=1;
else iNewer=0;
}
integer iCompat;
iRes = VersionNumberCompare(sCurVer, sCompat);
if(iRes <= 0)iCompat=1;
else iCompat=0;
llRegionSayTo(i,c,Build("version_back", ["same", iSame, "newer", iNewer, "compatible", iCompat]));
} else if(llJsonGetValue(m,["operation"]) == "send")
{
string sPackage = llJsonGetValue(m,["pkg"]);
string sRequest = llJsonGetValue(m,["ver"]);
if(sRequest == "latest")sRequest = llJsonGetValue(g_sPackages, [sPackage, "latest"]);
string sItem = llJsonGetValue(g_sPackages, [sPackage, sRequest, "item"]);
llGiveInventory(llJsonGetValue(m,["dest"]), sItem);
llRegionSayTo(i,c,Build("send_back", ["item", sItem]));
}
}
dataserver( key queryid, string data )
{
if(~HasDSRequest(queryid))
{
list lMeta = GetMetaList(queryid);
string sOp = llList2String(lMeta,0);
if(sOp == "read")
{
integer iLine = (integer)llList2String(lMeta,1);
string sPkg = llList2String(lMeta,2);
if(data==EOF)
{
DeleteDSReq(queryid);
llOwnerSay(MSGS_1 + (string)llGetFreeMemory()+"b free");
g_iServerListener = llListen(PACKAGE_SERVER_CHANNEL, "", "", "");
} else {
iLine++;
list lOpts = llParseStringKeepNulls(data, [" ", "|"], []);
if(llList2String(lOpts,0) == "SERVER_NAME")
{
g_sServerName = llDumpList2String(llList2List(lOpts,1,-1), " ");
}else if(llList2String(lOpts,0) == "BEGIN")
{
sPkg = llDumpList2String(llList2List(lOpts,1,-1), " ");
g_sPackages = llJsonSetValue(g_sPackages, [sPkg], "{}");
} else if(llList2String(lOpts,0) == "LATEST")
{
g_sPackages = llJsonSetValue(g_sPackages, [sPkg,"latest"], llDumpList2String(llList2List(lOpts,1,-1), " "));
}else if(llList2String(lOpts,0) == "END")
{
sPkg="";
}else {
string sVer = llList2String(lOpts,0);
string sItem = llDumpList2String(llList2List(lOpts,1,-2)," ");
// The version number for which this package is backward compatible.
// Backwards compatibility means that at least in theory, someone could even downgrade without issues to at least this version number
// It also means that no middle steps are required for upgrading. The user can upgrade from the version to this package version without issues.
string sBackwardCompat = llList2String(lOpts,2);
g_sPackages = llJsonSetValue(g_sPackages, [sPkg, sVer], llList2Json(JSON_OBJECT, ["item", sItem, "compat", sBackwardCompat]));
}
UpdateDSRequest(queryid, llGetNotecardLine("server_manifest", iLine), SetDSMeta(["read", iLine, sPkg]));
}
}
}
}
}

View file

@ -0,0 +1,53 @@
/*
This file is a temporary placeholder for development purposes to help bootstrap the update system. It is a drop in script to immediately start the update
*/
#include "includes/common.lsl"
default
{
state_entry()
{
// Send the update scan signal
VERSION = "0.0.0.0";
g_iUpdaterListener = llListen(UPDATER_CHANNEL, "", "", "");
llWhisper(UPDATER_CHANNEL, Build("checkupdate", ["myversion", compileVersion()]));
llSetTimerEvent(1);
}
timer(){
if(llGetTime()>=10){
llSay(0, "No updater responded in time, good bye!");
llRemoveInventory(llGetScriptName());
}
}
listen(integer iChan, string sName, key kID, string sMsg)
{
//llSay(0, "DEBUG ["+llDumpList2String([iChan, sName, kID, sMsg], " ~ ")+"]");
integer iStart=0;
if(llJsonGetValue(sMsg,["operation"]) == "available")
{
// Begin stage 2
iStart=1;
}else if(llJsonGetValue(sMsg,["operation"]) == "same")
{
// Impossible as this is the force start. Tell it to start anyway!
iStart=1;
}
if(!iStart)return;
g_iClientPin = llAbs(llRound(llFrand(0xFFFF)));
llSetRemoteScriptAccessPin(g_iClientPin);
llSleep(1);
llRegionSayTo(kID, UPDATER_CHANNEL, Build("stage2", ["pin", g_iClientPin]));
llSay(0, "Updater found, sent pairing code, good bye!");
llRemoveInventory(llGetScriptName());
}
}

129
src/raw/updater/shim.lsl Normal file
View file

@ -0,0 +1,129 @@
#include "includes/common.lsl"
default
{
state_entry()
{
// We are installed by the updater.
// We can now perform update specific preparations
// First off send the update started signal
if(llGetStartParameter()==0)llSetScriptState(llGetScriptName(), FALSE);
if(llGetInventoryType("SynthOS Installer") == INVENTORY_SCRIPT){
llSetScriptState(llGetScriptName(), FALSE);
return;
}
g_kSession = MakeSession(SESSION_UPDATE);
signal(SIGNAL_UPDATE_START, g_kSession);
g_iStartup = llGetStartParameter();
g_iUpdaterListener = llListen(UPDATER_CHANNEL, "", "", "");
}
dataserver( key kID, string sData )
{
if(HasDSRequest(kID)!=-1)
{
list lMeta = GetMetaList(kID);
if(llList2String(lMeta,0) == "read")
{
if(sData == EOF)
{
DeleteDSReq(kID);
llRemoveInventory(llList2String(lMeta,1)); // Delete the BOM file
llRegionSayTo(g_kUpdatePair, UPDATER_CHANNEL, Build("upgrade_security", ["pin", g_iClientPin, "secure_channel", g_iUpdaterSecureChannel]));
}else {
if(sData=="")jump next;
list lTmp = llParseString2List(sData,["|"], []);
string sItem = llList2String(lTmp,1);
if(llGetInventoryType(sItem) == INVENTORY_NONE)jump next;
llRemoveInventory(sItem);
@next;
integer iLine = (integer)llList2String(lMeta,2);
iLine++;
lMeta[2]=iLine;
UpdateDSRequest(kID, llGetNotecardLine(llList2String(lMeta,1), iLine), SetDSMeta(lMeta));
}
}
}
}
listen(integer iChannel, string sName, key kID, string sMsg)
{
//llSay(0, "DEBUG ["+llDumpList2String([iChannel, sName, kID, sMsg], " ~ ")+"]");
// Check the startup number against the pairing code
if(iChannel == UPDATER_CHANNEL)
{
if(llJsonGetValue(sMsg,["operation"]) == "connect")
{
integer iPair = (integer)llJsonGetValue(sMsg,["pair"]);
if(iPair == g_iStartup){
// Upgrade to a secure channel
llListenRemove(g_iUpdaterListener);
g_iUpdaterSecureChannel = llAbs(llRound(llFrand(0xFFFFFF)));
g_iUpdaterSecureListener = llListen(g_iUpdaterSecureChannel, "", kID, "");
g_kUpdatePair = kID;
g_iClientPin = llAbs(llRound(llFrand(0xFFFFFF)));
// Inform the updater that we are ready to upgrade to the secure channel
llSetRemoteScriptAccessPin(g_iClientPin);
string sBOM = llJsonGetValue(sMsg,["bom"]);
if(llGetInventoryType(sBOM)==INVENTORY_NOTECARD)
UpdateDSRequest(NULL, llGetNotecardLine(sBOM,1), SetDSMeta(["read", sBOM, 1])); // Start at line 1 to skip the Version Number
else
llRegionSayTo(g_kUpdatePair, UPDATER_CHANNEL, Build("upgrade_security", ["pin", g_iClientPin, "secure_channel", g_iUpdaterSecureChannel]));
}
}
} else if (iChannel == g_iUpdaterSecureChannel)
{
// This is the secure channel, which means that the handshake was successful
// Here we process update signals from the updater itself
// The packets from the updater will tell us information about the item in question
// Due to SL not telling us UUIDs of things that we do not have full permission to, we must rely on a force update all approach.
// This is not ideal but it is how non-full perm stuff must be updated.
if (llJsonGetValue(sMsg, ["operation"]) == "began")
{
// The updater has begun to send items or install scripts. It'll be quiet for a little bit while the install happens.
// Currently there is nothing here to process
} else if(llJsonGetValue(sMsg, ["operation"]) == "ended")
{
// The updater has sent us everything and it has started to reset itself.
// We can safely assume the update is done. Sleep for about 10 seconds and then dispatch the update done signal
// Sleep for 1 second to let everything settle down
llSleep(1);
integer i=0;
integer end = llGetInventoryNumber(INVENTORY_SCRIPT);
for(i=0;i<end;i++)
{
string sName = llGetInventoryName(INVENTORY_SCRIPT, i);
llSetScriptState(sName, TRUE); // We do not care about resetting them right here. Let them start up, then sleep and send the update done signal
}
llSleep(10);
signal(SIGNAL_UPDATE_DONE, g_kSession);
llSetRemoteScriptAccessPin(0);
signal(SIGNAL_RESET, g_kSession);
llSleep(1);
llSay(0, "Update completed to "+llLinksetDataRead("os.softwareversion"));
llRemoveInventory(llGetScriptName()); // Clean up ourself
} else if(llJsonGetValue(sMsg, ["operation"]) == "prepare")
{
string sItem=llJsonGetValue(sMsg,["details", "name"]);
// This signal is specific to items that need to be removed to make room for the newer item
if(llGetInventoryType(sItem) != INVENTORY_NONE)
{
llRemoveInventory(sItem);
}
// Send back the details packet which will include details like the item name, and type
// We are sending it on the main json payload this time instead of inside a json object ["details"]
llRegionSayTo(kID, iChannel, Build("continue", llJson2List(llJsonGetValue(sMsg,["details"]))));
}
}
}
}

156
src/raw/updater/updater.lsl Normal file
View file

@ -0,0 +1,156 @@
/*
This file is the updater
*/
#include "includes/common.lsl"
integer g_iDelOnFinish=0;
default
{
state_entry()
{
g_iUpdaterListener = llListen(UPDATER_CHANNEL, "", "", "");
// Scan for BOM file
integer i=0;
integer end = llGetInventoryNumber(INVENTORY_NOTECARD);
for(i=0;i<end;i++)
{
string sName = llGetInventoryName(INVENTORY_NOTECARD, i);
if(llSubStringIndex(sName, ".BOM")!=-1)
{
g_sBOM = sName;
}
}
if(g_sBOM == "")
llOwnerSay("/!\\ ALERT /!\\ NO BOM DETECTED. INSTALLER WILL NOT WORK");
else llOwnerSay("Installer is now operational and set to : "+g_sBOM);
if(g_sBOM != "")UpdateDSRequest(NULL, llGetNotecardLine(g_sBOM,0), SetDSMeta(["read_version"]));
}
on_rez(integer t){
g_iDelOnFinish=t;
}
changed(integer iChange)
{
if(iChange & CHANGED_INVENTORY)
{
// Reset ourselves if not update in progress
if(g_iClientPin != 0){
// We're in a update, scream at the object owner
llOwnerSay("/!\\ FATAL /!\\\n \n[ You have changed my contents mid-update. There could be serious problems that arise now. You should repeat the update after this one ends if there are issues ]");
}else
llResetScript();
}
}
listen( integer iChannel, string sName, key kID, string sMsg )
{
//llSay(0, "DEBUG ["+llDumpList2String([iChannel, sName, kID, sMsg], " ~ ")+"]");
if(iChannel == UPDATER_CHANNEL)
{
if(llJsonGetValue(sMsg,["operation"]) == "checkupdate")
{
string sVer = llJsonGetValue(sMsg,["myversion"]);
integer iCompare = VersionNumberCompare(compileVersion(), sVer);
if(iCompare == 0)
{
llRegionSayTo(kID, UPDATER_CHANNEL, Build("same", []));
}else if(iCompare == -1){
// Bingo, we are good to begin
llRegionSayTo(kID, UPDATER_CHANNEL, Build("available", []));
} else if(iCompare == 1)
{
llRegionSayTo(kID, UPDATER_CHANNEL, Build("older", []));
}
} else if(llJsonGetValue(sMsg, ["operation"]) == "stage2")
{
// Send the shim
g_iClientPin = (integer)llJsonGetValue(sMsg, ["pin"]);
g_iStartup = llAbs(llRound(llFrand(0xFFFF))); // Pairing code
llRemoteLoadScriptPin(kID, UPDATER_SHIM, g_iClientPin, TRUE, g_iStartup);
llSleep(5); // Wait for it to start up...and any lag that might be happening
llRegionSayTo(kID, UPDATER_CHANNEL, Build("connect", ["pair", g_iStartup, "bom", g_sBOM]));
} else if(llJsonGetValue(sMsg,["operation"]) == "upgrade_security")
{
llListenControl(g_iUpdaterListener, FALSE);
g_iUpdaterSecureChannel = (integer)llJsonGetValue(sMsg,["secure_channel"]);
g_iClientPin = (integer)llJsonGetValue(sMsg,["pin"]);
g_kUpdatePair = kID;
g_iUpdaterSecureListener = llListen(g_iUpdaterSecureChannel, "", g_kUpdatePair, "");
g_kSession = MakeSession(SESSION_UPDATE);
// Initial plans had there to be a bundle reader here but this can be done in a single script
// Start reading BOM
g_kBundleReader = MakeSession(SESSION_UPDATE);
UpdateDSRequest(NULL, llGetNotecardLine(g_sBOM, 1), SetDSMeta(["read_bom", 1]));
}
} else if(iChannel == g_iUpdaterSecureChannel){
if(llJsonGetValue(sMsg,["operation"]) == "continue")
{
integer iScript = (integer)llJsonGetValue(sMsg,["type"]);
string sScript = llJsonGetValue(sMsg,["name"]);
if(iScript)
{
llOwnerSay("Install: "+sScript);
llRemoteLoadScriptPin(g_kUpdatePair, sScript, g_iClientPin, FALSE, 0);
}else {
llOwnerSay("Give: "+sScript);
llGiveInventory(g_kUpdatePair, sScript);
}
list lMeta = GetMetaList(g_kBundleReader);
UpdateDSRequest(g_kBundleReader, llGetNotecardLine(g_sBOM, (integer)llList2String(lMeta,1)), SetDSMeta(lMeta));
}
}
}
dataserver(key kID, string sData)
{
if(HasDSRequest(kID)!=-1)
{
list lMeta = GetMetaList(kID);
if(llList2String(lMeta,0)=="read_bom")
{
if(sData == EOF)
{
llRegionSayTo(g_kUpdatePair, g_iUpdaterSecureChannel, Build("ended", []));
llGiveInventory(g_kUpdatePair, g_sBOM); // Give the manifest. We're done now
llSay(0, "Update completed!");
llSleep(2);
if(g_iDelOnFinish)llDie();
llResetScript();
}else {
integer iLine = (integer)llList2String(lMeta,1);
iLine++;
lMeta[1]=iLine;
if(sData == "")jump next;
list lParams = llParseString2List(sData,["|"], []);
integer iType = 0;
if(llList2String(lParams,0)=="SCRIPT")iType=1;
string sDetailsPacket = llList2Json(JSON_OBJECT,["name", llList2String(lParams,1), "type", iType]);
UpdateDSRequest(kID, g_kBundleReader, SetDSMeta(lMeta));
llRegionSayTo(g_kUpdatePair, g_iUpdaterSecureChannel, Build("prepare", ["details", sDetailsPacket]));
jump done;
@next;
UpdateDSRequest(kID, llGetNotecardLine(g_sBOM, iLine), SetDSMeta(lMeta));
@done; // Called to await the continue signal
}
} else if(llList2String(lMeta,0) == "read_version")
{
VERSION = sData;
DeleteDSReq(kID);
}
}
}
}