So the trick here is to use the file:// URL protocol:
First set up a paclet server in the standard way (see also my brief explanation here)
Next we need to set up our file:// URL properly. For example, I keep the local version of my paclet server here, before pushing it to the cloud:
serverDir = FileNameJoin@ { $UserBaseDirectory, "ApplicationData", "WebSites", "PacletServer" };
To create an appropriate file URL we'll need to tweak the output of URLBuild like so:
serverURL = StringReplace["file:" -> "file://"]@ URLBuild@ <| "Scheme" -> "file", "Path" -> FileNameSplit[serverDir] |>;
Then we can install a paclet like so:
PacletInstall[ "SiteBuilder", "Site" -> serverURL ] PacletManager`Paclet[ "Name" -> "SiteBuilder", "Version" -> "1.0.8", "Creator" -> "[email protected]", "Description" -> "Implements a configuration scheme for building and \ deploying sites a la pelican.\n Forked from BTools.", "Extensions" -> {{ "Kernel", "Root" -> ".", "Context" -> {"SiteBuilder`"}}}, "Tags" -> {"web"}, "Categories" -> {"Services"}, "Location" -> "~/Library/Mathematica/Paclets/Repository/\ SiteBuilder-1.0.8"]
Note for pre-11.2:
Owing to a bug in the PacletManager that seems to have been fixed in 11.2, the file:// protocol was not appropriately supported. If you're using 11.1 or below you'll need to patch the PacletInstall functions like so:
If[$VersionNumber < 11.2, PacletManager`Services`Private`finishPacletSiteUpdate[ { PacletManager`Private`siteURL_, PacletManager`Private`file_, PacletManager`Private`interactive_, PacletManager`Private`async_, 0} ] := PacletManager`Services`Private`finishPacletSiteUpdate[ { PacletManager`Private`siteURL, PacletManager`Private`file, PacletManager`Private`interactive, PacletManager`Private`async, 200} ]; PacletManager`Package`getTaskData[task_] := Block[{PacletManager`Private`$override = True}, Replace[PacletManager`Package`getTaskData[task], { PacletManager`Private`a_, PacletManager`Private`b_, PacletManager`Private`c_, PacletManager`Private`d_, PacletManager`Private`e_, 0, PacletManager`Private`rest__} :> { PacletManager`Private`a, PacletManager`Private`b, PacletManager`Private`c, PacletManager`Private`d, PacletManager`Private`e, 200, PacletManager`Private`rest}] ] /; ! TrueQ[PacletManager`Private`$override] ]
Helper Function:
We can wrap all of this up into a convenient local-install function:
localPacletPull[pacletName_, dir : (_String | _File)?DirectoryQ] := With[{ serverURL = StringReplace["file:" -> "file://"]@ URLBuild@ <| "Scheme" -> "file", "Path" -> FileNameSplit[dir] |> }, If[$VersionNumber < 11.2, PacletManager`Services`Private`finishPacletSiteUpdate[ { PacletManager`Private`siteURL_, PacletManager`Private`file_, PacletManager`Private`interactive_, PacletManager`Private`async_, 0} ] := PacletManager`Services`Private`finishPacletSiteUpdate[ { PacletManager`Private`siteURL, PacletManager`Private`file, PacletManager`Private`interactive, PacletManager`Private`async, 200} ]; PacletManager`Package`getTaskData[task_] := Block[{PacletManager`Private`$override = True}, Replace[PacletManager`Package`getTaskData[task], { PacletManager`Private`a_, PacletManager`Private`b_, PacletManager`Private`c_, PacletManager`Private`d_, PacletManager`Private`e_, 0, PacletManager`Private`rest__} :> { PacletManager`Private`a, PacletManager`Private`b, PacletManager`Private`c, PacletManager`Private`d, PacletManager`Private`e, 200, PacletManager`Private`rest}] ] /; ! TrueQ[PacletManager`Private`$override] ]; If[Length@PacletFind[pacletName] > 0, PacletUpdate[pacletName, "Site" -> serverURL ], PacletInstall[pacletName, "Site" -> serverURL ] ] ];
So, e.g., if I know my paclet server is at ~/Dropbox/PacletServer, I can do:
localPacletPull[paclet, "~/Dropbox/PacletServer"]
And it will install / update that paclet from my DropBox server