mirror of https://github.com/ospab/ostp.git
fix(relay): explain the 404 — the API lives under the panel's secret webpath
A relay configured with upstream_api_url = "http://HOST:9090" fails every key sync with a bare "API returned HTTP 404", which reads like the server is down or the token is wrong. Neither is true: the management API is nested under the target server's api.webpath (create_api_router mounts it at "/{webpath}/api"), because that secret segment is what keeps the panel from being discoverable by scanners. A bare host:port therefore resolves to a route that does not exist and the token is never even looked at. Nothing said so — the config template, the wizard prompt and the shipped example all suggested exactly the host:port form that cannot work. - sync_keys now reports the full URL and, for 404 specifically, states that the webpath must be included and what the URL should look like. 401 is called out separately as a token mismatch, since the two are otherwise indistinguishable from the log. - The relay config template, the shipped example and the wizard prompt now show the path-bearing form, and the wizard warns when the URL entered has no path segment rather than letting it fail later. Docs under docs/ and the wiki are being rewritten concurrently and are left alone here.
This commit is contained in:
parent
365b4ccbf5
commit
a1c146aff3
|
|
@ -20,9 +20,15 @@
|
||||||
// Адрес следующего узла в цепочке — UDP
|
// Адрес следующего узла в цепочке — UDP
|
||||||
"upstream_udp": "TARGET_SERVER_IP:50000",
|
"upstream_udp": "TARGET_SERVER_IP:50000",
|
||||||
|
|
||||||
// URL API конечного (целевого) сервера для синхронизации access_keys
|
// URL API конечного (целевого) сервера для синхронизации access_keys.
|
||||||
// Должен быть доступен с этого relay-сервера (можно через SSH-туннель)
|
// Должен быть доступен с этого relay-сервера (можно через SSH-туннель).
|
||||||
"upstream_api_url": "http://TARGET_SERVER_IP:9090",
|
//
|
||||||
|
// ВАЖНО: URL обязан включать секретный путь панели (api.webpath целевого
|
||||||
|
// сервера). Management API смонтирован ВНУТРИ этого пути — именно он скрывает
|
||||||
|
// панель от сканеров, — поэтому голый host:port попадает в несуществующий
|
||||||
|
// маршрут, и синхронизация падает с 404 ещё до проверки токена.
|
||||||
|
// Это тот же адрес, по которому вы открываете веб-панель.
|
||||||
|
"upstream_api_url": "http://TARGET_SERVER_IP:9090/TARGET_SERVER_WEBPATH",
|
||||||
|
|
||||||
// Bearer-токен для доступа к API целевого сервера
|
// Bearer-токен для доступа к API целевого сервера
|
||||||
// Должен совпадать с api.token в конфиге target-сервера
|
// Должен совпадать с api.token в конфиге target-сервера
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,29 @@ async fn sync_keys(cfg: &RelayConfig, shared_keys: &SharedKeys) -> Result<usize>
|
||||||
|
|
||||||
let resp = req.send().await?;
|
let resp = req.send().await?;
|
||||||
if !resp.status().is_success() {
|
if !resp.status().is_success() {
|
||||||
anyhow::bail!("API returned HTTP {}", resp.status());
|
// 404 here almost always means the URL is missing the panel's secret
|
||||||
|
// path segment rather than the server being down or the token being
|
||||||
|
// wrong. The management API is not served at /api — it is nested under
|
||||||
|
// the configured `api.webpath` (see create_api_router), which exists to
|
||||||
|
// keep the panel from being discoverable by scanners. A bare
|
||||||
|
// host:port therefore resolves to a route that does not exist, and the
|
||||||
|
// token is never even looked at, which makes "404" a deeply misleading
|
||||||
|
// thing to report on its own.
|
||||||
|
if resp.status() == reqwest::StatusCode::NOT_FOUND {
|
||||||
|
anyhow::bail!(
|
||||||
|
"API returned HTTP 404 for {url}. The management API is served under the \
|
||||||
|
target server's secret `api.webpath`, not at /api — set upstream_api_url \
|
||||||
|
to include it, e.g. \"http://HOST:9090/<webpath>\" (the same path you open \
|
||||||
|
the web panel at). Check `api.webpath` in the target server's config."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if resp.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||||
|
anyhow::bail!(
|
||||||
|
"API returned HTTP 401 for {url}: upstream_api_token does not match the \
|
||||||
|
target server's `api.token`."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
anyhow::bail!("API returned HTTP {} for {url}", resp.status());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -799,8 +799,26 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
||||||
let listen = wizard_prompt("Listen address (host:port)", "0.0.0.0:50000");
|
let listen = wizard_prompt("Listen address (host:port)", "0.0.0.0:50000");
|
||||||
let upstream = wizard_prompt("Upstream server address (host:port)", "");
|
let upstream = wizard_prompt("Upstream server address (host:port)", "");
|
||||||
if upstream.is_empty() { anyhow::bail!("Upstream address cannot be empty."); }
|
if upstream.is_empty() { anyhow::bail!("Upstream address cannot be empty."); }
|
||||||
let api_url = wizard_prompt("Upstream server API URL (e.g. http://1.2.3.4:9090)", "");
|
let api_url = wizard_prompt(
|
||||||
let api_token = wizard_prompt("Upstream API token (leave blank if none)", "");
|
"Upstream API URL, including the panel's secret path (e.g. http://1.2.3.4:9090/bNAzr8Ss)",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
// The management API lives under the target server's api.webpath, so
|
||||||
|
// a bare host:port 404s on every key sync without ever checking the
|
||||||
|
// token. Catch that here instead of leaving it to be debugged from
|
||||||
|
// relay logs.
|
||||||
|
let has_path = api_url
|
||||||
|
.split("://")
|
||||||
|
.nth(1)
|
||||||
|
.map(|rest| rest.contains('/') && !rest.trim_end_matches('/').split('/').nth(1).unwrap_or("").is_empty())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !api_url.is_empty() && !has_path {
|
||||||
|
wizard_warn(
|
||||||
|
"This URL has no path segment. The API is served under the target server's \
|
||||||
|
api.webpath - key sync will fail with 404 unless you append it.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let api_token = wizard_prompt("Upstream API token (must equal api.token on the target server)", "");
|
||||||
|
|
||||||
wizard_step(2, TOTAL, "Saving configuration");
|
wizard_step(2, TOTAL, "Saving configuration");
|
||||||
let relay_json = serde_json::json!({
|
let relay_json = serde_json::json!({
|
||||||
|
|
@ -1213,7 +1231,13 @@ async fn run_app() -> Result<()> {
|
||||||
"listen": "0.0.0.0:50000",
|
"listen": "0.0.0.0:50000",
|
||||||
"upstream_tcp": "TARGET_SERVER_IP:50000",
|
"upstream_tcp": "TARGET_SERVER_IP:50000",
|
||||||
"upstream_udp": "TARGET_SERVER_IP:50000",
|
"upstream_udp": "TARGET_SERVER_IP:50000",
|
||||||
"upstream_api_url": "http://TARGET_SERVER_IP:9090",
|
// MUST include the target server's secret api.webpath. The management API is
|
||||||
|
// nested under it (that path is what hides the panel from scanners), so a
|
||||||
|
// bare host:port hits a route that does not exist and key sync fails with 404
|
||||||
|
// before the token is ever checked. This is the same URL you open the panel
|
||||||
|
// at, e.g. "http://1.2.3.4:9090/bNAzr8Ss".
|
||||||
|
"upstream_api_url": "http://TARGET_SERVER_IP:9090/TARGET_SERVER_WEBPATH",
|
||||||
|
// Must equal api.token on the target server (NOT the panel password).
|
||||||
"upstream_api_token": "YOUR_API_TOKEN_HERE",
|
"upstream_api_token": "YOUR_API_TOKEN_HERE",
|
||||||
"sync_interval_secs": 30,
|
"sync_interval_secs": 30,
|
||||||
"debug": false
|
"debug": false
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue