M2.7: Host Game, backups, launchd/systemd samples, hosting and Tailscale guide
This commit is contained in:
@@ -50,6 +50,8 @@ func open_server_world(config: RefCounted) -> Node3D:
|
||||
if id.is_empty():
|
||||
id = "server-world"
|
||||
Saves.current_id = Saves.open_or_create(id, int(config.v("seed")))
|
||||
backup("start-up") # (the state it's about to load, before anyone changes it)
|
||||
_backup_left = maxf(float(config.v("backup_hours")), 0.25) * 3600.0
|
||||
var w: Node3D = load("res://scenes/world.tscn").instantiate()
|
||||
w.server_mode = true
|
||||
w.day_length = float(config.v("day_length_minutes")) * 60.0
|
||||
@@ -97,6 +99,66 @@ func save() -> void:
|
||||
if world != null and is_instance_valid(world) and world.server_mode:
|
||||
world.save_now() # (players too: world.saved)
|
||||
|
||||
# --- backups (M2.7) ----------------------------------------------------------------
|
||||
# A copy of the world (meta, world and every player's file) in
|
||||
# worlds/<world>/backups/<yyyy-mm-dd_hh-mm-ss>/: when the server starts (the
|
||||
# state it's about to load), every backup_hours while it runs, and on
|
||||
# /backup. The newest backups_kept are kept. To restore one, stop the server
|
||||
# and copy its files back up two folders (docs/HOSTING.md).
|
||||
|
||||
var _backup_left := INF
|
||||
|
||||
func world_dir() -> String:
|
||||
return Saves.root_dir.path_join(Saves.current_id)
|
||||
|
||||
## Makes a backup now. Returns its folder name ("" if there was nothing to back up).
|
||||
func backup(reason: String) -> String:
|
||||
var src := world_dir()
|
||||
if not FileAccess.file_exists(src.path_join("world.json")):
|
||||
return ""
|
||||
var stamp := Time.get_datetime_string_from_system(false, true).replace(":", "-").replace(" ", "_")
|
||||
var dst := src.path_join("backups").path_join(stamp)
|
||||
var n := 2
|
||||
while DirAccess.dir_exists_absolute(dst):
|
||||
dst = src.path_join("backups").path_join("%s_%d" % [stamp, n])
|
||||
n += 1
|
||||
DirAccess.make_dir_recursive_absolute(dst.path_join("players"))
|
||||
for f in ["meta.json", "world.json"]:
|
||||
if FileAccess.file_exists(src.path_join(f)):
|
||||
DirAccess.copy_absolute(src.path_join(f), dst.path_join(f))
|
||||
var pd := DirAccess.open(src.path_join("players"))
|
||||
if pd != null:
|
||||
for f in pd.get_files():
|
||||
if f.ends_with(".json"):
|
||||
DirAccess.copy_absolute(src.path_join("players").path_join(f), dst.path_join("players").path_join(f))
|
||||
_prune_backups()
|
||||
print("[server] backup (%s): %s" % [reason, ProjectSettings.globalize_path(dst)])
|
||||
return dst.get_file()
|
||||
|
||||
func list_backups() -> Array:
|
||||
var d := DirAccess.open(world_dir().path_join("backups"))
|
||||
if d == null:
|
||||
return []
|
||||
var out: Array = Array(d.get_directories())
|
||||
out.sort()
|
||||
return out
|
||||
|
||||
func _prune_backups() -> void:
|
||||
var keep := maxi(int(Net.config.v("backups_kept")), 1)
|
||||
var all := list_backups()
|
||||
while all.size() > keep:
|
||||
_rm_tree(world_dir().path_join("backups").path_join(String(all.pop_front())))
|
||||
|
||||
static func _rm_tree(d: String) -> void:
|
||||
var da := DirAccess.open(d)
|
||||
if da == null:
|
||||
return
|
||||
for sub in da.get_directories():
|
||||
_rm_tree(d.path_join(sub))
|
||||
for f in da.get_files():
|
||||
da.remove(f)
|
||||
DirAccess.remove_absolute(d)
|
||||
|
||||
## Something changed: save within a few seconds. Ctrl+C kills Godot without
|
||||
## a chance to save, so this keeps the loss small.
|
||||
func mark_changed() -> void:
|
||||
@@ -139,6 +201,11 @@ func _process(delta: float) -> void:
|
||||
_save_left -= delta
|
||||
if _save_left <= 0.0:
|
||||
save()
|
||||
_backup_left -= delta
|
||||
if _backup_left <= 0.0:
|
||||
_backup_left = maxf(float(Net.config.v("backup_hours")), 0.25) * 3600.0
|
||||
save()
|
||||
backup("every %s h" % Net.config.v("backup_hours"))
|
||||
_blocker_left -= delta
|
||||
if _blocker_left <= 0.0:
|
||||
_blocker_left = 1.0
|
||||
|
||||
Reference in New Issue
Block a user