131 lines
5.2 KiB
Text
131 lines
5.2 KiB
Text
|
|
module Bootstrap
|
||
|
|
|
||
|
|
open System
|
||
|
|
open System.IO
|
||
|
|
open System.Runtime.InteropServices
|
||
|
|
open System.Security.Cryptography
|
||
|
|
open System.Text
|
||
|
|
|
||
|
|
let digest path =
|
||
|
|
use stream = File.OpenRead path
|
||
|
|
SHA256.HashData stream |> Convert.ToHexString |> _.ToLowerInvariant()
|
||
|
|
|
||
|
|
let write (path: string) (bytes: byte[]) =
|
||
|
|
Directory.CreateDirectory(Path.GetDirectoryName path) |> ignore
|
||
|
|
File.WriteAllBytes(path, bytes)
|
||
|
|
|
||
|
|
let text path value = write path (Encoding.UTF8.GetBytes(value: string))
|
||
|
|
|
||
|
|
let trust source runtime =
|
||
|
|
write (Path.Combine(runtime, "cacerts")) (File.ReadAllBytes source)
|
||
|
|
|
||
|
|
type Paths =
|
||
|
|
{ Data: string
|
||
|
|
State: string
|
||
|
|
Backups: string
|
||
|
|
Files: string }
|
||
|
|
|
||
|
|
type Config =
|
||
|
|
{ Paths: Paths
|
||
|
|
PasswordPath: string
|
||
|
|
Eula: string
|
||
|
|
Args: string
|
||
|
|
Saltatory: string }
|
||
|
|
|
||
|
|
let prefixes =
|
||
|
|
[ "mods", "mods"
|
||
|
|
"config", "config"
|
||
|
|
"defaultconfigs", "defaultconfigs"
|
||
|
|
"kubejs", "kubejs"
|
||
|
|
"libraries", "libraries"
|
||
|
|
"libraries-integratedscripting", "libraries-integratedscripting"
|
||
|
|
"settings", "settings"
|
||
|
|
"tools", "tools" ]
|
||
|
|
|
||
|
|
let parse (value: string) =
|
||
|
|
value.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||
|
|
|> Array.map (fun line ->
|
||
|
|
let line = line.TrimEnd '\r'
|
||
|
|
let hash, path = line.Substring(0, 64), line.Substring(66)
|
||
|
|
hash, path)
|
||
|
|
|
||
|
|
let sync cache prefix inventory includeFile fetchManifest fetch =
|
||
|
|
let root = Path.Combine(cache, prefix)
|
||
|
|
Directory.CreateDirectory root |> ignore
|
||
|
|
let files = fetchManifest inventory |> parse |> Array.filter (snd >> includeFile)
|
||
|
|
|
||
|
|
for hash, name in files do
|
||
|
|
let path = Path.Combine(root, name)
|
||
|
|
Directory.CreateDirectory(Path.GetDirectoryName path) |> ignore
|
||
|
|
fetch (prefix + "/" + name) hash path
|
||
|
|
|
||
|
|
let names = files |> Array.map (snd >> fun name -> Path.GetFullPath(Path.Combine(root, name))) |> Set.ofArray
|
||
|
|
|
||
|
|
for path in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories) do
|
||
|
|
if not (names.Contains(Path.GetFullPath path)) then
|
||
|
|
File.Delete path
|
||
|
|
|
||
|
|
files
|
||
|
|
|
||
|
|
let copyFiles source target files =
|
||
|
|
for _, name in files do
|
||
|
|
let path = Path.Combine(target, name)
|
||
|
|
Directory.CreateDirectory(Path.GetDirectoryName path) |> ignore
|
||
|
|
File.Copy(Path.Combine(source, name), path, true)
|
||
|
|
|
||
|
|
let run config arch fetchManifest fetch fetchSaltatory =
|
||
|
|
let p = config.Paths
|
||
|
|
let cache = Path.Combine(p.State, "cache")
|
||
|
|
Directory.CreateDirectory p.Data |> ignore
|
||
|
|
Directory.CreateDirectory p.Backups |> ignore
|
||
|
|
Directory.CreateDirectory(Path.Combine(p.State, "world")) |> ignore
|
||
|
|
Directory.CreateDirectory(Path.Combine(p.State, "world/data/chunky")) |> ignore
|
||
|
|
|
||
|
|
for prefix, target in prefixes do
|
||
|
|
let includeFile (name: string) =
|
||
|
|
(prefix <> "mods" || not (name.StartsWith("saltatory-", StringComparison.OrdinalIgnoreCase)))
|
||
|
|
&& (prefix <> "config" || not (name.StartsWith("chunky/tasks/", StringComparison.Ordinal)))
|
||
|
|
let files = sync cache prefix (prefix + ".sha256") includeFile fetchManifest fetch
|
||
|
|
let destination = Path.Combine(p.Data, target)
|
||
|
|
if Directory.Exists destination then Directory.Delete(destination, true)
|
||
|
|
copyFiles (Path.Combine(cache, prefix)) destination files
|
||
|
|
|
||
|
|
let saltatory = Path.Combine(cache, "saltatory.jar")
|
||
|
|
fetchSaltatory config.Saltatory saltatory
|
||
|
|
File.Copy(saltatory, Path.Combine(p.Data, "mods", "saltatory.jar"), true)
|
||
|
|
|
||
|
|
for name in [ "ops.json"; "whitelist.json"; "banned-ips.json"; "banned-players.json"; "usercache.json" ] do
|
||
|
|
let path = Path.Combine(p.State, name)
|
||
|
|
if not (File.Exists path) then text path "[]"
|
||
|
|
|
||
|
|
let names = Path.Combine(p.State, "usernamecache.json")
|
||
|
|
if not (File.Exists names) then text names "{}"
|
||
|
|
for name in [ "local"; "journeymap" ] do
|
||
|
|
Directory.CreateDirectory(Path.Combine(p.State, name)) |> ignore
|
||
|
|
|
||
|
|
let world = Path.Combine(p.State, "world")
|
||
|
|
for prefix in [ "datapacks"; "serverconfig" ] do
|
||
|
|
let name = "world/" + prefix
|
||
|
|
let files = sync cache name ("world-" + prefix + ".sha256") (fun _ -> true) fetchManifest fetch
|
||
|
|
copyFiles (Path.Combine(cache, name)) (Path.Combine(world, prefix)) files
|
||
|
|
|
||
|
|
let tools =
|
||
|
|
match arch with
|
||
|
|
| Architecture.X64 -> "amd64"
|
||
|
|
| Architecture.Arm64 -> "arm64"
|
||
|
|
| _ -> invalidOp "unsupported architecture"
|
||
|
|
|
||
|
|
for name in [ "mc-server-runner"; "mc-monitor" ] do
|
||
|
|
let source = Path.Combine(p.Data, "tools", tools, name)
|
||
|
|
let path = Path.Combine(p.Data, name)
|
||
|
|
File.Copy(source, path, true)
|
||
|
|
File.SetUnixFileMode(path, enum<UnixFileMode> 0o755)
|
||
|
|
|
||
|
|
let properties = File.ReadAllText(Path.Combine(p.Files, "server.properties")).TrimEnd()
|
||
|
|
let password = File.ReadAllText(config.PasswordPath).TrimEnd()
|
||
|
|
text (Path.Combine(p.Data, "server.properties")) (properties + "\nrcon.password=" + password + "\n")
|
||
|
|
File.Copy(Path.Combine(p.Data, config.Args), Path.Combine(p.Data, "server_args.txt"), true)
|
||
|
|
let args = File.ReadAllText(Path.Combine(p.Data, "settings", "user_jvm_args.txt")).TrimEnd()
|
||
|
|
text (Path.Combine(p.Data, "user_jvm_args.txt")) (args + "\n-Djavax.net.ssl.trustStore=/data/cacerts\n")
|
||
|
|
text (Path.Combine(p.Data, "eula.txt")) ("eula=" + config.Eula.ToLowerInvariant() + "\n")
|
||
|
|
printfn "Minecraft bootstrap ready"
|