v0.1.4: keep BindIP open for shared servers before every start, restart button after enabling friends mode

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ДмитрийandClaude Sonnet 5.5 committed 2026-09-30 19:51:07 +05:00
1 parent 4eda03ab63
commit db16d95558
7 files changed
+49 -6

No files matched your search

Generated
+3 -3
View File
@@ -347,7 +347,7 @@ dependencies = [
[[package]]
name = "coa-core"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -375,7 +375,7 @@ dependencies = [
[[package]]
name = "coa-release"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"chrono",
"coa-core",
@@ -384,7 +384,7 @@ dependencies = [
[[package]]
name = "coa-server-manager"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"coa-core",
"serde",
+1 -1
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/coa-core", "crates/coa-release", "src-tauri"]
[workspace.package]
version = "0.1.3"
version = "0.1.4"
edition = "2021"
license = "AGPL-3.0-only"
+21
View File
@@ -109,6 +109,17 @@ pub fn apply_realm_address(root: &Path, host: &str) -> Result<()> {
Ok(())
}
/// Before every server start: when the owner shares the server with friends, make sure the login and world servers
/// listen on all addresses. The launcher's own tools (for example switching detailed logging off) restore the config
/// templates from older copies and would silently put `BindIP` back to 127.0.0.1 while the realm list still advertises
/// the shared address - the game then shows the realm and drops back to the login screen without any message.
pub fn ensure_bind(root: &Path, meta: &Path) -> Result<bool> {
match load(meta).mode {
Mode::Local => Ok(false),
_ => set_open(root, meta, true),
}
}
/// After every server start: put the chosen address back into the realm list (the launcher resets it to 127.0.0.1).
pub fn reapply(root: &Path, meta: &Path) -> Result<()> {
let s = load(meta);
@@ -189,6 +200,16 @@ mod tests {
assert!(fs::read_to_string(root.join("Core/configs/worldserver.conf")).unwrap().contains("BindIP = \"127.0.0.1\""));
}
#[test]
fn a_shared_server_gets_its_bind_address_back_before_start_and_a_local_one_is_left_alone() {
let (_d, root, meta) = setup();
assert!(!ensure_bind(&root, &meta).unwrap(), "local mode: nothing to do");
save(&meta, &Settings { mode: Mode::Private, host: Some("100.64.1.2".into()) }).unwrap();
assert!(ensure_bind(&root, &meta).unwrap(), "templates were reset to 127.0.0.1 -> reopened");
assert!(bind_is_open(&root));
assert!(!ensure_bind(&root, &meta).unwrap(), "already open: idempotent");
}
#[test]
fn settings_round_trip_and_default_to_local() {
let d = tempfile::tempdir().unwrap();
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "coa-server-manager",
"private": true,
"version": "0.1.3",
"version": "0.1.4",
"license": "AGPL-3.0-only",
"type": "module",
"scripts": {
+2
View File
@@ -195,6 +195,8 @@ async fn start_server(state: State<'_, AppState>, id: String) -> std::result::Re
if meta.kind == coa_core::registry::InstallKind::New {
coa_core::config::materialize_module_configs(&root)?;
}
let dir = meta_dir(&root)?;
coa_core::friends::ensure_bind(&root, &dir)?;
Ok::<(), Error>(())
})
.await;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "CoA Server Manager",
"version": "0.1.3",
"version": "0.1.4",
"identifier": "com.coa.servermanager",
"build": {
"beforeDevCommand": "npm run dev",
+20
View File
@@ -55,6 +55,7 @@ export function FriendsPage({ serverId }: { serverId: string }) {
const [error, setError] = useState<UiError | null>(null);
const [note, setNote] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const [needsRestart, setNeedsRestart] = useState(false);
const refresh = useCallback(() => api.friendsStatus(serverId).then(setSt).catch((e) => setError(asUiError(e))), [serverId]);
useEffect(() => {
@@ -84,6 +85,7 @@ export function FriendsPage({ serverId }: { serverId: string }) {
const enable = (mode: FriendsMode) =>
run(`enable-${mode}`, async () => {
const r = await api.friendsEnable(serverId, mode, net?.public_ip ?? undefined, mode === "direct" && !!net?.router_found);
setNeedsRestart(r.restart_required);
return [r.restart_required ? t("fr.restartToApply") : null, r.note].filter(Boolean).join(" ") || t("fr.ready");
});
@@ -199,6 +201,24 @@ export function FriendsPage({ serverId }: { serverId: string }) {
)}
{note && <p className="mt-4 text-sm text-ok" role="status">{note}</p>}
{needsRestart && st.server_running && (
<Button
className="mt-3"
size="sm"
variant="primary"
disabled={!!busy}
onClick={() =>
void run("restart", async () => {
await api.stop(serverId);
await api.start(serverId);
setNeedsRestart(false);
})
}
>
{busy === "restart" && <Loader2 className="h-4 w-4 animate-spin" aria-hidden />}
{t("set.restartNow")}
</Button>
)}
{error && <p className="mt-4 text-sm text-bad" role="alert">{error.human.code === "unknown" ? error.technical : human(error.human).message}</p>}
</div>
);