mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-01 16:58:43 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96eb2ab356 |
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c #v4.2.0
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 #v4.5.1
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
@@ -659,7 +659,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
|
||||
- name: Run opencode
|
||||
uses: anomalyco/opencode/github@e5cc278dec9294a627a7b05f47ce6a564408c1a2 #v1.18.5
|
||||
uses: anomalyco/opencode/github@4da7bb44c84e013fa53e9c5d02ac753d1435c81a #v1.18.9
|
||||
env:
|
||||
ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }}
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
# code. Nothing from the fork is executed in this privileged job; the PR
|
||||
# is only ever read as text.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Gather pull request text
|
||||
env:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue has been inactive for 30 days. Please respond to keep it open."
|
||||
|
||||
@@ -294,14 +294,6 @@ pub struct RunRemoteResponse {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct StopRemoteResponse {
|
||||
pub session_id: String,
|
||||
pub status: String,
|
||||
/// What the session actually cost, in seconds.
|
||||
pub billed_seconds: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
struct RunProfileRequest {
|
||||
url: Option<String>,
|
||||
@@ -601,7 +593,6 @@ impl ApiServer {
|
||||
.routes(routes!(get_profile, update_profile, delete_profile))
|
||||
.routes(routes!(run_profile))
|
||||
.routes(routes!(run_profile_remote))
|
||||
.routes(routes!(stop_remote_session))
|
||||
.routes(routes!(set_profile_cloud_sync))
|
||||
.routes(routes!(open_url_in_profile))
|
||||
.routes(routes!(kill_profile))
|
||||
@@ -2367,43 +2358,6 @@ pub fn remote_launch_precondition(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// API Handler - Stop a REMOTE session started by run-remote
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/v1/remote-sessions/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Remote session ID from run-remote")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Remote session stopped", body = StopRemoteResponse),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "No such remote session"),
|
||||
(status = 429, description = "Automation request rate limit exceeded"),
|
||||
(status = 503, description = "The fleet could not be reached; the session is still running"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
),
|
||||
tag = "profiles"
|
||||
)]
|
||||
async fn stop_remote_session(
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<StopRemoteResponse>, (StatusCode, String)> {
|
||||
// Without this route, `run-remote` hands back a session id nothing can act
|
||||
// on: the only thing that ends a session is the fleet's own two-hour cap, so
|
||||
// every launch bills 7200s no matter how briefly it ran.
|
||||
let outcome = crate::remote_session::end_remote_session(&id)
|
||||
.await
|
||||
.map_err(remote_session_error_response)?;
|
||||
|
||||
Ok(Json(StopRemoteResponse {
|
||||
session_id: outcome.session_id,
|
||||
status: outcome.status,
|
||||
billed_seconds: outcome.billed_seconds,
|
||||
}))
|
||||
}
|
||||
|
||||
fn remote_session_error_response(
|
||||
err: crate::remote_session::RemoteSessionError,
|
||||
) -> (StatusCode, String) {
|
||||
|
||||
@@ -140,57 +140,6 @@ pub async fn start_remote_session(
|
||||
.map_err(|e| classify_error_string(&e))
|
||||
}
|
||||
|
||||
/// What the backend returns when a session is stopped.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EndRemoteSessionOutcome {
|
||||
pub session_id: String,
|
||||
pub status: String,
|
||||
/// What the session actually cost, in seconds.
|
||||
pub billed_seconds: u64,
|
||||
}
|
||||
|
||||
/// Ask donutbrowser-infra to stop a remote session.
|
||||
///
|
||||
/// Without this the only thing that ends a session is the fleet's own two-hour
|
||||
/// cap, so every launch bills the full 7200s however briefly it was used — a
|
||||
/// handful of runs exhausts an allowance meant for a hundred. The backend
|
||||
/// refuses to retire a row it could not stop on the fleet, so a successful
|
||||
/// return here means the browser is really down and the profile lock released.
|
||||
pub async fn end_remote_session(
|
||||
session_id: &str,
|
||||
) -> Result<EndRemoteSessionOutcome, RemoteSessionError> {
|
||||
let endpoint = format!(
|
||||
"{}/api/remote-sessions/{}",
|
||||
crate::cloud_auth::CLOUD_API_URL,
|
||||
urlencoding::encode(session_id)
|
||||
);
|
||||
|
||||
crate::cloud_auth::CLOUD_AUTH
|
||||
.api_call_with_retry(|token| {
|
||||
let endpoint = endpoint.clone();
|
||||
async move {
|
||||
let response = reqwest::Client::new()
|
||||
.delete(&endpoint)
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("reach backend: {e}"))?;
|
||||
|
||||
let status = response.status().as_u16();
|
||||
if !(200..300).contains(&status) {
|
||||
let text = response.text().await.unwrap_or_default();
|
||||
return Err(format!("({status}) {text}"));
|
||||
}
|
||||
response
|
||||
.json::<EndRemoteSessionOutcome>()
|
||||
.await
|
||||
.map_err(|e| format!("decode response: {e}"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| classify_error_string(&e))
|
||||
}
|
||||
|
||||
/// Recover a typed error from `api_call_with_retry`'s string.
|
||||
///
|
||||
/// That helper flattens everything to `String` to do its 401 sniffing, so the
|
||||
@@ -274,31 +223,6 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_stop_response_parses_what_the_backend_actually_sends() {
|
||||
// Pinned against EndRemoteSessionOutcome in donutbrowser-infra
|
||||
// (apps/backend/src/remote-sessions/remote-sessions.service.ts). A field
|
||||
// name that does not match makes every stop fail at the decode step, and
|
||||
// the session then runs to the 2h cap and bills 7200s — the exact defect
|
||||
// this endpoint exists to fix, reintroduced silently.
|
||||
let outcome: EndRemoteSessionOutcome =
|
||||
serde_json::from_str(r#"{"session_id":"sess-1","status":"closed","billed_seconds":42}"#)
|
||||
.expect("the backend's stop payload must deserialize");
|
||||
assert_eq!(outcome.session_id, "sess-1");
|
||||
assert_eq!(outcome.status, "closed");
|
||||
assert_eq!(outcome.billed_seconds, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stop_of_an_unknown_session_is_not_reported_as_capacity() {
|
||||
// The backend 404s a session that is not the caller's. Mapping that to
|
||||
// NoCapacity would tell the user the fleet is busy and invite a retry.
|
||||
assert!(matches!(
|
||||
classify_error_string("(404) No such remote session"),
|
||||
RemoteSessionError::Other(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idempotency_key_is_stable_for_one_attempt_and_distinct_across_attempts() {
|
||||
let a = idempotency_key("p1", "attempt-1");
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::proxy_runner::find_sidecar_executable;
|
||||
#[cfg(unix)]
|
||||
use crate::proxy_storage::is_process_running;
|
||||
use crate::proxy_storage::{process_identity_matches, process_start_time};
|
||||
use crate::proxy_storage::{is_process_running, process_identity_matches, process_start_time};
|
||||
use crate::xray::{build_client_config_json, parse_vless_uri, XrayClientRuntime};
|
||||
use crate::xray_worker_storage::{
|
||||
create_xray_worker_log, delete_xray_worker_config, generate_xray_worker_id,
|
||||
|
||||
Reference in New Issue
Block a user