Compare commits
No commits in common. "64e5ee39654bbc39b4860dc3d5b63a030de83ab6" and "be26313225794e06a4f0374f409ee62ccf9621d6" have entirely different histories.
64e5ee3965
...
be26313225
111 changed files with 700 additions and 16050 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -22,10 +22,6 @@ third_party/iroh-org/*
|
|||
|
||||
# Cloudflare worker local deps / builds
|
||||
deploy/cloudflare-worker/node_modules/
|
||||
cache/
|
||||
out/
|
||||
test-results/
|
||||
.tower-minimal/
|
||||
|
||||
# NEVER commit private keys
|
||||
every_channel_ed25519
|
||||
|
|
|
|||
925
Cargo.lock
generated
925
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,6 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"crates/ec-core",
|
||||
"crates/ec-eth",
|
||||
"crates/ec-moq",
|
||||
"crates/ec-direct",
|
||||
"crates/ec-hdhomerun",
|
||||
|
|
|
|||
77
README.md
77
README.md
|
|
@ -16,7 +16,6 @@ A global, disaggregated mesh of relays that turns local ATSC antennas into a coh
|
|||
- `crates/ec-linux-iptv`: Linux DVB ingest scaffolding.
|
||||
- `crates/ec-iroh`: iroh transport scaffolding.
|
||||
- `crates/ec-crypto`: stream key derivation helpers.
|
||||
- `crates/ec-eth`: Ethereum-compatible protocol representations and commitments.
|
||||
- `crates/ec-ts`: MPEG-TS timing and table parsing.
|
||||
- `crates/ec-chopper`: deterministic ffmpeg chunking scaffolding.
|
||||
- `crates/ec-moq`: MoQ data model and relay scaffolding.
|
||||
|
|
@ -27,7 +26,6 @@ A global, disaggregated mesh of relays that turns local ATSC antennas into a coh
|
|||
- `docs/USAGE.md`: runbook for viewer and ingest pipelines.
|
||||
- `docs/IROH_EXAMPLES.md`: summary of iroh repos/examples used for design.
|
||||
- `docs/`: architecture, roadmap, and MoQ notes.
|
||||
- `contracts/`: Solidity contracts for compact private-chain settlement rails.
|
||||
|
||||
## Development
|
||||
|
||||
|
|
@ -55,19 +53,7 @@ Git hosting topology:
|
|||
cat docs/GIT_HOSTING.md
|
||||
```
|
||||
|
||||
Sovereign forge deploy:
|
||||
|
||||
```sh
|
||||
cat docs/DEPLOY_ECP_FORGE.md
|
||||
```
|
||||
|
||||
OP Stack `ecp-forge` runbook:
|
||||
|
||||
```sh
|
||||
cat docs/OP_STACK_ECP_FORGE.md
|
||||
```
|
||||
|
||||
NUC PXE rollout (UniFi-only or ProxyDHCP):
|
||||
NUC PXE rollout (Unifi + ProxyDHCP):
|
||||
|
||||
```sh
|
||||
cat docs/NUC_UNIFI_NETBOOT.md
|
||||
|
|
@ -143,67 +129,6 @@ cargo run -p ec-node -- control-bridge-web \
|
|||
`control endpoint id` and `control endpoint addr` on startup. Use the `endpoint addr` JSON for
|
||||
`--gossip-peer` when bootstrapping.
|
||||
|
||||
Default discovery identity:
|
||||
|
||||
- If you pass `--stream-id`, that exact value is announced.
|
||||
- If you do not pass `--stream-id`, `ec-node` now prefers a broadcast-scoped ID when the selected
|
||||
channel exposes usable broadcast metadata. HDHomeRun sources also probe early TS packets to fill
|
||||
missing `tsid` / `program_number`, and raw TS inputs can derive identity directly from PAT data.
|
||||
- ATSC PSIP parsing now covers `MGT`, `TVCT/CVCT`, `STT`, `RRT`, `EIT`, and `ETT`. `EIT` and `ETT`
|
||||
are accepted on the PIDs advertised by `MGT`, not only on the base PSIP PID `0x1FFB`.
|
||||
- Discovery identity currently consumes `PAT` plus `VCT` data. `RRT`, `EIT`, `ETT`, and full `STT`
|
||||
parsing are available for inspection and future policy, but they do not currently change the
|
||||
default dedupe key beyond the existing broadcast identity fields.
|
||||
- PAT-derived promotion is intentionally conservative: only single-program streams are promoted. If
|
||||
the probe sees multiple non-zero programs, `ec-node` does not guess and keeps the stream
|
||||
source-scoped.
|
||||
- Sources without usable broadcast metadata still fall back to source-scoped IDs.
|
||||
|
||||
Ethereum rails:
|
||||
|
||||
- `ec-node` keeps BLAKE3 `manifest_id`s and `merkle+blake3` object proofs on the wire, and also
|
||||
emits Ethereum-compatible ABI commitments for settlement.
|
||||
- Manifests can now carry both local Ed25519 signatures and optional secp256k1 EIP-712 signatures
|
||||
over `ManifestBody`. Set `EVERY_CHANNEL_MANIFEST_SIGNING_KEY` for the local signer and
|
||||
`EVERY_CHANNEL_ETH_MANIFEST_SIGNING_KEY` for the Ethereum-native signer.
|
||||
- Stream descriptors and iroh control announcements now carry Keccak commitments so broadcast
|
||||
identity, discovery, and manifest settlement share one vocabulary.
|
||||
- The new `contracts/EveryChannelRail.sol` contract is intentionally storage-light: it stores only
|
||||
latest commitment pointers, while full manifests and media stay off-chain on iroh/relays/archive.
|
||||
- Observation consensus now has dedicated Solidity surfaces:
|
||||
`contracts/EveryChannelWitnessRegistry.sol` and
|
||||
`contracts/EveryChannelObservationLedger.sol`.
|
||||
- `scripts/op-stack/anvil-reality-smoke.sh` validates the observation rail against real archive
|
||||
data pulled from `ecp-forge`, not only synthetic fixtures.
|
||||
- `nix/modules/ec-op-stack.nix` and `docs/OP_STACK_ECP_FORGE.md` define the repo-owned OP Stack
|
||||
operator path for `ecp-forge`.
|
||||
- The dev shell now includes `forge`, `cast`, `anvil`, and `solc`, and the repo ships a
|
||||
top-level `foundry.toml`.
|
||||
|
||||
Real-capture PSIP checks:
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/tsduck/tsduck-test /tmp/tsduck-test
|
||||
EC_REAL_ATSC_SAMPLE=/tmp/tsduck-test/input/test-040.ts \
|
||||
cargo test -p ec-ts real_atsc_sample_matches_known_psip_shape -- --ignored --nocapture
|
||||
EC_REAL_RRT_SAMPLE=/tmp/tsduck-test/input/test-052.ts \
|
||||
cargo test -p ec-ts real_rrt_sample_matches_known_reference -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Ethereum commitment checks:
|
||||
|
||||
```sh
|
||||
cargo test -p ec-eth -p ec-node -- --nocapture
|
||||
nix develop -c bash -lc 'which forge cast anvil solc && solc --bin contracts/EveryChannelRail.sol >/dev/null'
|
||||
```
|
||||
|
||||
Observation rail / OP Stack checks:
|
||||
|
||||
```sh
|
||||
nix shell .#foundry .#solc -c forge test -vv
|
||||
nix shell .#foundry .#solc nixpkgs#jq nixpkgs#openssh nixpkgs#curl -c ./scripts/op-stack/anvil-reality-smoke.sh
|
||||
```
|
||||
|
||||
Coverage:
|
||||
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ blake3.workspace = true
|
|||
ec-crypto = { path = "../../crates/ec-crypto" }
|
||||
ec-core = { path = "../../crates/ec-core" }
|
||||
ec-chopper = { path = "../../crates/ec-chopper" }
|
||||
ec-eth = { path = "../../crates/ec-eth" }
|
||||
ec-hdhomerun = { path = "../../crates/ec-hdhomerun" }
|
||||
ec-linux-iptv = { path = "../../crates/ec-linux-iptv" }
|
||||
ec-iroh = { path = "../../crates/ec-iroh" }
|
||||
ec-moq = { path = "../../crates/ec-moq" }
|
||||
headless_chrome = "1"
|
||||
hex = "0.4"
|
||||
iroh = "0.96"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
|
|
@ -27,16 +25,5 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
|||
tower-http = { version = "0.5", features = ["fs"] }
|
||||
tracing.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block2 = "0.6"
|
||||
objc2 = "0.6"
|
||||
objc2-app-kit = { version = "0.3", features = ["NSBitmapImageRep", "NSImage"] }
|
||||
objc2-core-foundation = "0.3"
|
||||
objc2-foundation = { version = "0.3", features = ["NSData", "NSDictionary", "NSError", "NSString"] }
|
||||
objc2-web-kit = { version = "0.3", features = ["WKSnapshotConfiguration", "WKWebView", "block2", "objc2-app-kit"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[features]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,3 @@
|
|||
[build]
|
||||
dist = "../dist"
|
||||
public_url = "./"
|
||||
public_url = "/"
|
||||
|
|
|
|||
|
|
@ -20,80 +20,8 @@
|
|||
<link data-trunk rel="copy-dir" href="icons" />
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="boot-status"
|
||||
style="
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: #f7f4ef;
|
||||
color: #2b241d;
|
||||
font: 15px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
white-space: pre-wrap;
|
||||
z-index: 9999;
|
||||
"
|
||||
>
|
||||
Loading every.channel…
|
||||
</div>
|
||||
<div id="main"></div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const boot = document.getElementById("boot-status");
|
||||
const main = document.getElementById("main");
|
||||
if (!boot || !main) return;
|
||||
|
||||
const show = (message) => {
|
||||
boot.textContent = message;
|
||||
boot.style.display = "flex";
|
||||
};
|
||||
|
||||
const hide = () => {
|
||||
boot.remove();
|
||||
};
|
||||
|
||||
const mounted = () => {
|
||||
if (main.childElementCount > 0) {
|
||||
hide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
new MutationObserver(() => {
|
||||
mounted();
|
||||
}).observe(main, { childList: true, subtree: true });
|
||||
|
||||
window.addEventListener("error", (event) => {
|
||||
const message =
|
||||
event?.error?.stack ||
|
||||
event?.error?.message ||
|
||||
event?.message ||
|
||||
"Unknown frontend boot error";
|
||||
show(`Frontend boot error\n\n${message}`);
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
const reason = event?.reason;
|
||||
const message =
|
||||
reason?.stack ||
|
||||
reason?.message ||
|
||||
(typeof reason === "string" ? reason : JSON.stringify(reason, null, 2)) ||
|
||||
"Unknown unhandled rejection";
|
||||
show(`Frontend boot rejection\n\n${message}`);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (!mounted()) {
|
||||
show("Loading every.channel is taking longer than expected…");
|
||||
}
|
||||
}, 4000);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Installable app shell (PWA). Keep this tiny and resilient.
|
||||
if ("serviceWorker" in navigator) {
|
||||
|
|
|
|||
|
|
@ -130,24 +130,6 @@ struct ProbeStreamArgs {
|
|||
input: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BootstrapNbcAuthArgs {
|
||||
input: Option<String>,
|
||||
stream_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BootstrapNbcAuthResult {
|
||||
input_url: String,
|
||||
stream_id: Option<String>,
|
||||
hidden_mode: bool,
|
||||
surfaced_auth: bool,
|
||||
data_dir: Option<String>,
|
||||
status: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ManualSourceOptions {
|
||||
|
|
@ -795,13 +777,6 @@ fn App() -> Element {
|
|||
let active_id = selected.read().as_ref().map(|s| s.id.clone());
|
||||
let playback_url = playback.read().as_ref().map(|p| p.url.clone());
|
||||
let now_playing = selected.read().clone();
|
||||
let bootstrap_stream = selected
|
||||
.read()
|
||||
.as_ref()
|
||||
.filter(|stream| stream_source_kind(stream) == "nbc")
|
||||
.cloned();
|
||||
let bootstrap_input_value = add_input.read().clone();
|
||||
let bootstrap_input_is_nbc = looks_like_nbc_input(&bootstrap_input_value);
|
||||
let current_share = share_info.read().clone();
|
||||
let source_list = sources.read().clone();
|
||||
|
||||
|
|
@ -919,82 +894,6 @@ fn App() -> Element {
|
|||
onclick: move |_| refresh_sources(),
|
||||
"Refresh"
|
||||
}
|
||||
if bootstrap_stream.is_some() || bootstrap_input_is_nbc {
|
||||
div { class: "source-menu-divider" }
|
||||
div { class: "source-menu-section",
|
||||
div { class: "source-menu-title", "NBC auth" }
|
||||
div { class: "source-menu-status",
|
||||
"Warm a hidden NBC session. The window only appears if MVPD auth is needed."
|
||||
}
|
||||
if let Some(stream) = bootstrap_stream.clone() {
|
||||
button {
|
||||
class: "source-menu-action",
|
||||
onclick: move |_| {
|
||||
if !tauri_available() {
|
||||
status.set("Tauri backend not available (open the Tauri app)".to_string());
|
||||
return;
|
||||
}
|
||||
let stream_id = stream.id.clone();
|
||||
let stream_title = stream.title.clone();
|
||||
let args = BootstrapNbcAuthArgs {
|
||||
input: None,
|
||||
stream_id: Some(stream_id),
|
||||
};
|
||||
let mut status = status.clone();
|
||||
spawn(async move {
|
||||
status.set(format!("Bootstrapping NBC auth for {}", stream_title));
|
||||
match tauri_invoke::<BootstrapNbcAuthResult, _>("bootstrap_nbc_auth", &args).await {
|
||||
Ok(result) => {
|
||||
if result.surfaced_auth {
|
||||
status.set("NBC auth ready after interactive sign-in".to_string());
|
||||
} else {
|
||||
status.set("NBC auth ready in hidden mode".to_string());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
status.set(format!("NBC bootstrap error: {err}"));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
"Bootstrap selected NBC"
|
||||
}
|
||||
}
|
||||
if bootstrap_input_is_nbc {
|
||||
button {
|
||||
class: "source-menu-action",
|
||||
onclick: move |_| {
|
||||
if !tauri_available() {
|
||||
status.set("Tauri backend not available (open the Tauri app)".to_string());
|
||||
return;
|
||||
}
|
||||
let input = bootstrap_input_value.clone();
|
||||
let args = BootstrapNbcAuthArgs {
|
||||
input: Some(input.clone()),
|
||||
stream_id: None,
|
||||
};
|
||||
let mut status = status.clone();
|
||||
spawn(async move {
|
||||
status.set(format!("Bootstrapping NBC auth for {}", input));
|
||||
match tauri_invoke::<BootstrapNbcAuthResult, _>("bootstrap_nbc_auth", &args).await {
|
||||
Ok(result) => {
|
||||
if result.surfaced_auth {
|
||||
status.set("NBC auth ready after interactive sign-in".to_string());
|
||||
} else {
|
||||
status.set("NBC auth ready in hidden mode".to_string());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
status.set(format!("NBC bootstrap error: {err}"));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
"Bootstrap pasted NBC URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "source-menu-divider" }
|
||||
div { class: "source-menu-section",
|
||||
div { class: "source-menu-title", "Add stream" }
|
||||
|
|
@ -1808,7 +1707,6 @@ fn App() -> Element {
|
|||
option { value: "linux-dvb", "Linux DVB" }
|
||||
option { value: "hls", "HLS" }
|
||||
option { value: "ytdlp", "yt-dlp" }
|
||||
option { value: "nbc", "NBC" }
|
||||
option { value: "moq", "Link" }
|
||||
}
|
||||
button {
|
||||
|
|
@ -1855,8 +1753,6 @@ fn App() -> Element {
|
|||
.map(|id| id == &stream.id)
|
||||
.unwrap_or(false);
|
||||
let card_class = if is_active { "channel-card active" } else { "channel-card" };
|
||||
let channel_subtitle = stream_display_subtitle(stream);
|
||||
let channel_detail = stream_display_detail(stream);
|
||||
let moq_endpoint = stream
|
||||
.metadata
|
||||
.iter()
|
||||
|
|
@ -1939,11 +1835,8 @@ fn App() -> Element {
|
|||
});
|
||||
},
|
||||
div { class: "channel-title", "{stream.title}" }
|
||||
if let Some(channel_subtitle) = channel_subtitle.clone() {
|
||||
div { class: "channel-meta", "{channel_subtitle}" }
|
||||
}
|
||||
if let Some(channel_detail) = channel_detail.clone() {
|
||||
div { class: "channel-detail", "{channel_detail}" }
|
||||
div { class: "channel-meta",
|
||||
{stream.number.clone().unwrap_or_default()}
|
||||
}
|
||||
if !stream.source.is_empty() {
|
||||
div { class: "channel-badge source", "{stream.source}" }
|
||||
|
|
@ -2400,65 +2293,6 @@ fn stream_has_drm(metadata: &[StreamMetadata]) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
fn stream_metadata_value<'a>(stream: &'a StreamDescriptor, key: &str) -> Option<&'a str> {
|
||||
stream
|
||||
.metadata
|
||||
.iter()
|
||||
.find(|entry| entry.key == key)
|
||||
.map(|entry| entry.value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn stream_display_subtitle(stream: &StreamDescriptor) -> Option<String> {
|
||||
if stream_source_kind(stream) == "nbc" {
|
||||
if let Some(program) =
|
||||
stream_metadata_value(stream, "current_program").or_else(|| stream.number.as_deref())
|
||||
{
|
||||
return Some(format!("Now: {}", program.trim()));
|
||||
}
|
||||
if let Some(brand) = stream_metadata_value(stream, "nbc_brand") {
|
||||
return Some(brand.to_string());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
stream
|
||||
.number
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| value.to_string())
|
||||
}
|
||||
|
||||
fn stream_display_detail(stream: &StreamDescriptor) -> Option<String> {
|
||||
if stream_source_kind(stream) != "nbc" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut parts = Vec::new();
|
||||
if let Some(badge) = stream_metadata_value(stream, "badge") {
|
||||
parts.push(badge.to_string());
|
||||
}
|
||||
if let Some(entitlement) = stream_metadata_value(stream, "entitlement") {
|
||||
if !entitlement.eq_ignore_ascii_case("free") {
|
||||
parts.push("Adobe auth".to_string());
|
||||
}
|
||||
} else if stream_has_drm(&stream.metadata) {
|
||||
parts.push("Adobe auth".to_string());
|
||||
}
|
||||
|
||||
if parts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(parts.join(" • "))
|
||||
}
|
||||
}
|
||||
|
||||
fn looks_like_nbc_input(value: &str) -> bool {
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
value.starts_with("https://www.nbc.com/") || value.starts_with("https://nbc.com/")
|
||||
}
|
||||
|
||||
fn stream_source_kind(stream: &StreamDescriptor) -> String {
|
||||
let source = stream.source.trim();
|
||||
if !source.is_empty() {
|
||||
|
|
|
|||
|
|
@ -392,13 +392,6 @@ body::before {
|
|||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.channel-detail {
|
||||
font-size: 11px;
|
||||
color: var(--ink-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.source-status {
|
||||
font-size: 13px;
|
||||
color: var(--ink-muted);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ const HLS_MODULE_URLS = [
|
|||
"https://unpkg.com/hls.js@1.6.2/dist/hls.mjs",
|
||||
];
|
||||
const PUBLIC_STREAMS_PATH = "/api/public-streams";
|
||||
const LIVE_JITTER_MS = 750;
|
||||
let moqWatchModulePromise = null;
|
||||
let hlsModulePromise = null;
|
||||
let disposePlayerSignals = null;
|
||||
|
|
@ -164,7 +163,6 @@ function mountPlayer(relayUrl, name) {
|
|||
watch.setAttribute("path", name);
|
||||
watch.setAttribute("volume", "1");
|
||||
watch.setAttribute("muted", "");
|
||||
watch.setAttribute("jitter", String(LIVE_JITTER_MS));
|
||||
|
||||
// Force WebTransport in-browser; websocket fallback has shown degraded
|
||||
// media behavior (especially audio) against public relay paths.
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {EveryChannelWitnessRegistry} from "./EveryChannelWitnessRegistry.sol";
|
||||
|
||||
/// @title EveryChannelObservationLedger
|
||||
/// @notice Observation consensus ledger for reality-derived every.channel epochs.
|
||||
/// Witnesses attest to the same derived observation hash, and the first candidate to hit quorum
|
||||
/// finalizes the `(stream, epoch)` slot.
|
||||
contract EveryChannelObservationLedger {
|
||||
struct ObservationHeader {
|
||||
bytes32 streamHash;
|
||||
bytes32 epochHash;
|
||||
bytes32 parentObservationHash;
|
||||
bytes32 dataRoot;
|
||||
bytes32 locatorHash;
|
||||
uint64 observedUnixMs;
|
||||
uint64 sequence;
|
||||
}
|
||||
|
||||
struct Candidate {
|
||||
bytes32 slot;
|
||||
address proposer;
|
||||
uint64 observedUnixMs;
|
||||
uint64 sequence;
|
||||
uint32 attestations;
|
||||
bool finalized;
|
||||
}
|
||||
|
||||
EveryChannelWitnessRegistry public immutable witnessRegistry;
|
||||
uint256 public immutable quorum;
|
||||
|
||||
mapping(bytes32 => ObservationHeader) private observationHeaders;
|
||||
mapping(bytes32 => Candidate) public candidates;
|
||||
mapping(bytes32 => bytes32) public finalizedObservationBySlot;
|
||||
mapping(bytes32 => mapping(address => bool)) public hasAttested;
|
||||
|
||||
event ObservationProposed(
|
||||
bytes32 indexed slot,
|
||||
bytes32 indexed observationHash,
|
||||
address indexed proposer,
|
||||
bytes32 streamHash,
|
||||
bytes32 epochHash,
|
||||
bytes32 parentObservationHash,
|
||||
bytes32 dataRoot,
|
||||
bytes32 locatorHash,
|
||||
uint64 observedUnixMs,
|
||||
uint64 sequence
|
||||
);
|
||||
|
||||
event ObservationAttested(
|
||||
bytes32 indexed slot,
|
||||
bytes32 indexed observationHash,
|
||||
address indexed witness,
|
||||
uint32 attestations
|
||||
);
|
||||
|
||||
event ObservationFinalized(
|
||||
bytes32 indexed slot,
|
||||
bytes32 indexed observationHash,
|
||||
uint32 attestations
|
||||
);
|
||||
|
||||
error NotWitness(address caller);
|
||||
error InvalidRegistry(address registry);
|
||||
error InvalidQuorum(uint256 quorum);
|
||||
error UnknownObservation(bytes32 observationHash);
|
||||
error ObservationAlreadyExists(bytes32 observationHash);
|
||||
error ObservationSlotAlreadyFinalized(bytes32 slot, bytes32 observationHash);
|
||||
error DuplicateAttestation(bytes32 observationHash, address witness);
|
||||
|
||||
constructor(address registry, uint256 quorumThreshold) {
|
||||
if (registry == address(0)) revert InvalidRegistry(registry);
|
||||
if (quorumThreshold == 0) revert InvalidQuorum(quorumThreshold);
|
||||
witnessRegistry = EveryChannelWitnessRegistry(registry);
|
||||
quorum = quorumThreshold;
|
||||
}
|
||||
|
||||
modifier onlyWitness() {
|
||||
if (!witnessRegistry.isWitness(msg.sender)) revert NotWitness(msg.sender);
|
||||
_;
|
||||
}
|
||||
|
||||
function observationSlot(
|
||||
bytes32 streamHash,
|
||||
bytes32 epochHash
|
||||
) public pure returns (bytes32) {
|
||||
return keccak256(abi.encode(streamHash, epochHash));
|
||||
}
|
||||
|
||||
function hashObservationHeader(
|
||||
ObservationHeader memory header
|
||||
) public pure returns (bytes32) {
|
||||
return keccak256(
|
||||
abi.encode(
|
||||
header.streamHash,
|
||||
header.epochHash,
|
||||
header.parentObservationHash,
|
||||
header.dataRoot,
|
||||
header.locatorHash,
|
||||
header.observedUnixMs,
|
||||
header.sequence
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getObservationHeader(
|
||||
bytes32 observationHash
|
||||
) external view returns (ObservationHeader memory) {
|
||||
if (candidates[observationHash].slot == bytes32(0)) {
|
||||
revert UnknownObservation(observationHash);
|
||||
}
|
||||
return observationHeaders[observationHash];
|
||||
}
|
||||
|
||||
function proposeObservation(
|
||||
ObservationHeader calldata header
|
||||
) external returns (bytes32 observationHash) {
|
||||
observationHash = hashObservationHeader(header);
|
||||
bytes32 slot = observationSlot(header.streamHash, header.epochHash);
|
||||
bytes32 finalized = finalizedObservationBySlot[slot];
|
||||
if (finalized != bytes32(0) && finalized != observationHash) {
|
||||
revert ObservationSlotAlreadyFinalized(slot, finalized);
|
||||
}
|
||||
if (candidates[observationHash].slot != bytes32(0)) {
|
||||
revert ObservationAlreadyExists(observationHash);
|
||||
}
|
||||
|
||||
observationHeaders[observationHash] = header;
|
||||
candidates[observationHash] = Candidate({
|
||||
slot: slot,
|
||||
proposer: msg.sender,
|
||||
observedUnixMs: header.observedUnixMs,
|
||||
sequence: header.sequence,
|
||||
attestations: 0,
|
||||
finalized: false
|
||||
});
|
||||
|
||||
emit ObservationProposed(
|
||||
slot,
|
||||
observationHash,
|
||||
msg.sender,
|
||||
header.streamHash,
|
||||
header.epochHash,
|
||||
header.parentObservationHash,
|
||||
header.dataRoot,
|
||||
header.locatorHash,
|
||||
header.observedUnixMs,
|
||||
header.sequence
|
||||
);
|
||||
|
||||
if (witnessRegistry.isWitness(msg.sender)) {
|
||||
_attest(observationHash, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
function attestObservation(bytes32 observationHash) external onlyWitness {
|
||||
_attest(observationHash, msg.sender);
|
||||
}
|
||||
|
||||
function _attest(bytes32 observationHash, address witness) internal {
|
||||
Candidate storage candidate = candidates[observationHash];
|
||||
if (candidate.slot == bytes32(0)) revert UnknownObservation(observationHash);
|
||||
if (hasAttested[observationHash][witness]) {
|
||||
revert DuplicateAttestation(observationHash, witness);
|
||||
}
|
||||
|
||||
bytes32 finalized = finalizedObservationBySlot[candidate.slot];
|
||||
if (finalized != bytes32(0) && finalized != observationHash) {
|
||||
revert ObservationSlotAlreadyFinalized(candidate.slot, finalized);
|
||||
}
|
||||
|
||||
hasAttested[observationHash][witness] = true;
|
||||
candidate.attestations += 1;
|
||||
|
||||
emit ObservationAttested(
|
||||
candidate.slot,
|
||||
observationHash,
|
||||
witness,
|
||||
candidate.attestations
|
||||
);
|
||||
|
||||
if (!candidate.finalized && candidate.attestations >= quorum) {
|
||||
candidate.finalized = true;
|
||||
finalizedObservationBySlot[candidate.slot] = observationHash;
|
||||
emit ObservationFinalized(
|
||||
candidate.slot,
|
||||
observationHash,
|
||||
candidate.attestations
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
/// @title EveryChannelRail
|
||||
/// @notice Storage-light Ethereum rails for every.channel stream identity, manifest, and
|
||||
/// transport attestations. The intended deployment target is a private Ethereum-compatible
|
||||
/// network where iroh remains the transport/control plane and this contract carries compact
|
||||
/// settlement metadata only.
|
||||
contract EveryChannelRail {
|
||||
struct ManifestPointer {
|
||||
bytes32 streamIdHash;
|
||||
bytes32 epochIdHash;
|
||||
bytes32 manifestId;
|
||||
bytes32 bodyCommitment;
|
||||
bytes32 dataRoot;
|
||||
uint64 createdUnixMs;
|
||||
address announcer;
|
||||
}
|
||||
|
||||
struct AnnouncementPointer {
|
||||
bytes32 streamIdHash;
|
||||
bytes32 announcementCommitment;
|
||||
uint64 updatedUnixMs;
|
||||
uint64 ttlMs;
|
||||
address announcer;
|
||||
}
|
||||
|
||||
mapping(bytes32 => ManifestPointer) public latestManifestByEpoch;
|
||||
mapping(bytes32 => AnnouncementPointer) public latestAnnouncementByStream;
|
||||
|
||||
event ManifestCommitted(
|
||||
bytes32 indexed streamIdHash,
|
||||
bytes32 indexed epochIdHash,
|
||||
bytes32 indexed manifestId,
|
||||
bytes32 bodyCommitment,
|
||||
bytes32 dataRoot,
|
||||
uint64 createdUnixMs,
|
||||
address announcer
|
||||
);
|
||||
|
||||
event TransportAnnounced(
|
||||
bytes32 indexed streamIdHash,
|
||||
bytes32 indexed announcementCommitment,
|
||||
uint64 updatedUnixMs,
|
||||
uint64 ttlMs,
|
||||
address announcer
|
||||
);
|
||||
|
||||
function commitManifest(
|
||||
string calldata streamId,
|
||||
string calldata epochId,
|
||||
bytes32 manifestId,
|
||||
bytes32 bodyCommitment,
|
||||
bytes32 dataRoot,
|
||||
uint64 createdUnixMs
|
||||
) external {
|
||||
bytes32 streamIdHash = keccak256(bytes(streamId));
|
||||
bytes32 epochIdHash = keccak256(bytes(epochId));
|
||||
bytes32 slot = keccak256(abi.encode(streamIdHash, epochIdHash));
|
||||
|
||||
latestManifestByEpoch[slot] = ManifestPointer({
|
||||
streamIdHash: streamIdHash,
|
||||
epochIdHash: epochIdHash,
|
||||
manifestId: manifestId,
|
||||
bodyCommitment: bodyCommitment,
|
||||
dataRoot: dataRoot,
|
||||
createdUnixMs: createdUnixMs,
|
||||
announcer: msg.sender
|
||||
});
|
||||
|
||||
emit ManifestCommitted(
|
||||
streamIdHash,
|
||||
epochIdHash,
|
||||
manifestId,
|
||||
bodyCommitment,
|
||||
dataRoot,
|
||||
createdUnixMs,
|
||||
msg.sender
|
||||
);
|
||||
}
|
||||
|
||||
function announceTransport(
|
||||
string calldata streamId,
|
||||
bytes32 announcementCommitment,
|
||||
uint64 updatedUnixMs,
|
||||
uint64 ttlMs
|
||||
) external {
|
||||
bytes32 streamIdHash = keccak256(bytes(streamId));
|
||||
|
||||
latestAnnouncementByStream[streamIdHash] = AnnouncementPointer({
|
||||
streamIdHash: streamIdHash,
|
||||
announcementCommitment: announcementCommitment,
|
||||
updatedUnixMs: updatedUnixMs,
|
||||
ttlMs: ttlMs,
|
||||
announcer: msg.sender
|
||||
});
|
||||
|
||||
emit TransportAnnounced(
|
||||
streamIdHash,
|
||||
announcementCommitment,
|
||||
updatedUnixMs,
|
||||
ttlMs,
|
||||
msg.sender
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
/// @title EveryChannelWitnessRegistry
|
||||
/// @notice Minimal registry-backed witness set for observation consensus. This is intentionally
|
||||
/// simple for the first testnet tranche: explicit membership, no staking, no slashing.
|
||||
contract EveryChannelWitnessRegistry {
|
||||
address public owner;
|
||||
mapping(address => bool) public isWitness;
|
||||
uint256 public witnessCount;
|
||||
|
||||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
||||
event WitnessAdded(address indexed witness);
|
||||
event WitnessRemoved(address indexed witness);
|
||||
|
||||
error NotOwner();
|
||||
error ZeroAddress();
|
||||
error WitnessAlreadyRegistered(address witness);
|
||||
error WitnessNotRegistered(address witness);
|
||||
|
||||
constructor(address initialOwner) {
|
||||
if (initialOwner == address(0)) revert ZeroAddress();
|
||||
owner = initialOwner;
|
||||
emit OwnershipTransferred(address(0), initialOwner);
|
||||
}
|
||||
|
||||
modifier onlyOwner() {
|
||||
if (msg.sender != owner) revert NotOwner();
|
||||
_;
|
||||
}
|
||||
|
||||
function transferOwnership(address newOwner) external onlyOwner {
|
||||
if (newOwner == address(0)) revert ZeroAddress();
|
||||
address previous = owner;
|
||||
owner = newOwner;
|
||||
emit OwnershipTransferred(previous, newOwner);
|
||||
}
|
||||
|
||||
function addWitness(address witness) external onlyOwner {
|
||||
if (witness == address(0)) revert ZeroAddress();
|
||||
if (isWitness[witness]) revert WitnessAlreadyRegistered(witness);
|
||||
isWitness[witness] = true;
|
||||
witnessCount += 1;
|
||||
emit WitnessAdded(witness);
|
||||
}
|
||||
|
||||
function removeWitness(address witness) external onlyOwner {
|
||||
if (!isWitness[witness]) revert WitnessNotRegistered(witness);
|
||||
isWitness[witness] = false;
|
||||
witnessCount -= 1;
|
||||
emit WitnessRemoved(witness);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {EveryChannelObservationLedger} from "../EveryChannelObservationLedger.sol";
|
||||
import {EveryChannelWitnessRegistry} from "../EveryChannelWitnessRegistry.sol";
|
||||
|
||||
contract WitnessActor {
|
||||
function propose(
|
||||
EveryChannelObservationLedger ledger,
|
||||
EveryChannelObservationLedger.ObservationHeader calldata header
|
||||
) external returns (bytes32) {
|
||||
return ledger.proposeObservation(header);
|
||||
}
|
||||
|
||||
function attest(
|
||||
EveryChannelObservationLedger ledger,
|
||||
bytes32 observationHash
|
||||
) external {
|
||||
ledger.attestObservation(observationHash);
|
||||
}
|
||||
}
|
||||
|
||||
contract ObservationLedgerTest {
|
||||
function _header(
|
||||
bytes32 streamHash,
|
||||
bytes32 epochHash,
|
||||
uint64 sequence,
|
||||
bytes32 dataRoot
|
||||
) internal pure returns (EveryChannelObservationLedger.ObservationHeader memory) {
|
||||
return EveryChannelObservationLedger.ObservationHeader({
|
||||
streamHash: streamHash,
|
||||
epochHash: epochHash,
|
||||
parentObservationHash: bytes32(0),
|
||||
dataRoot: dataRoot,
|
||||
locatorHash: keccak256(abi.encodePacked(streamHash, epochHash, sequence)),
|
||||
observedUnixMs: 1_772_001_256_329,
|
||||
sequence: sequence
|
||||
});
|
||||
}
|
||||
|
||||
function test_finalizes_when_quorum_is_reached() public {
|
||||
EveryChannelWitnessRegistry registry = new EveryChannelWitnessRegistry(address(this));
|
||||
WitnessActor witnessA = new WitnessActor();
|
||||
WitnessActor witnessB = new WitnessActor();
|
||||
registry.addWitness(address(witnessA));
|
||||
registry.addWitness(address(witnessB));
|
||||
|
||||
EveryChannelObservationLedger ledger = new EveryChannelObservationLedger(
|
||||
address(registry),
|
||||
2
|
||||
);
|
||||
|
||||
EveryChannelObservationLedger.ObservationHeader memory header = _header(
|
||||
keccak256("la-cbs:video0.m4s"),
|
||||
keccak256("epoch-229"),
|
||||
229,
|
||||
keccak256("58cd13f693debd000d995c9a5574e8b9274cc4d3399eb6f1f22393af1ba7407d")
|
||||
);
|
||||
|
||||
bytes32 observationHash = witnessA.propose(ledger, header);
|
||||
bytes32 slot = ledger.observationSlot(header.streamHash, header.epochHash);
|
||||
|
||||
assert(ledger.finalizedObservationBySlot(slot) == bytes32(0));
|
||||
|
||||
witnessB.attest(ledger, observationHash);
|
||||
|
||||
assert(ledger.finalizedObservationBySlot(slot) == observationHash);
|
||||
EveryChannelObservationLedger.ObservationHeader memory storedHeader = ledger
|
||||
.getObservationHeader(observationHash);
|
||||
assert(storedHeader.streamHash == header.streamHash);
|
||||
assert(storedHeader.epochHash == header.epochHash);
|
||||
assert(storedHeader.dataRoot == header.dataRoot);
|
||||
assert(storedHeader.sequence == header.sequence);
|
||||
}
|
||||
|
||||
function test_rejects_duplicate_attestation() public {
|
||||
EveryChannelWitnessRegistry registry = new EveryChannelWitnessRegistry(address(this));
|
||||
WitnessActor witnessA = new WitnessActor();
|
||||
registry.addWitness(address(witnessA));
|
||||
|
||||
EveryChannelObservationLedger ledger = new EveryChannelObservationLedger(
|
||||
address(registry),
|
||||
1
|
||||
);
|
||||
|
||||
EveryChannelObservationLedger.ObservationHeader memory header = _header(
|
||||
keccak256("la-nbc:catalog.json"),
|
||||
keccak256("epoch-5"),
|
||||
5,
|
||||
keccak256("f6ea0793fd00e29ced670d586e0e5f7f3d0f5edfc016c03f80710bd4bed587ec")
|
||||
);
|
||||
|
||||
bytes32 observationHash = witnessA.propose(ledger, header);
|
||||
assert(
|
||||
ledger.finalizedObservationBySlot(
|
||||
ledger.observationSlot(header.streamHash, header.epochHash)
|
||||
) == observationHash
|
||||
);
|
||||
|
||||
(bool ok, ) = address(witnessA).call(
|
||||
abi.encodeWithSelector(
|
||||
WitnessActor.attest.selector,
|
||||
ledger,
|
||||
observationHash
|
||||
)
|
||||
);
|
||||
assert(!ok);
|
||||
}
|
||||
|
||||
function test_competing_candidates_only_one_slot_can_finalize() public {
|
||||
EveryChannelWitnessRegistry registry = new EveryChannelWitnessRegistry(address(this));
|
||||
WitnessActor witnessA = new WitnessActor();
|
||||
WitnessActor witnessB = new WitnessActor();
|
||||
WitnessActor witnessC = new WitnessActor();
|
||||
registry.addWitness(address(witnessA));
|
||||
registry.addWitness(address(witnessB));
|
||||
registry.addWitness(address(witnessC));
|
||||
|
||||
EveryChannelObservationLedger ledger = new EveryChannelObservationLedger(
|
||||
address(registry),
|
||||
2
|
||||
);
|
||||
|
||||
bytes32 streamHash = keccak256("la-cbs:video0.m4s");
|
||||
bytes32 epochHash = keccak256("epoch-230");
|
||||
EveryChannelObservationLedger.ObservationHeader memory candidateA = _header(
|
||||
streamHash,
|
||||
epochHash,
|
||||
230,
|
||||
keccak256("1130c51b3ce428505dbc3c3294678f76e3200221d853fc9b02c8ed603ecc8b8c")
|
||||
);
|
||||
EveryChannelObservationLedger.ObservationHeader memory candidateB = _header(
|
||||
streamHash,
|
||||
epochHash,
|
||||
230,
|
||||
keccak256("deadbeef")
|
||||
);
|
||||
|
||||
bytes32 hashA = witnessA.propose(ledger, candidateA);
|
||||
bytes32 hashB = witnessB.propose(ledger, candidateB);
|
||||
bytes32 slot = ledger.observationSlot(streamHash, epochHash);
|
||||
|
||||
assert(hashA != hashB);
|
||||
assert(ledger.finalizedObservationBySlot(slot) == bytes32(0));
|
||||
|
||||
witnessC.attest(ledger, hashA);
|
||||
assert(ledger.finalizedObservationBySlot(slot) == hashA);
|
||||
|
||||
(bool ok, ) = address(witnessB).call(
|
||||
abi.encodeWithSelector(
|
||||
WitnessActor.attest.selector,
|
||||
ledger,
|
||||
hashB
|
||||
)
|
||||
);
|
||||
assert(!ok);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,8 +19,6 @@ use std::process::{Child, Command, Stdio};
|
|||
#[cfg(feature = "ffmpeg-ffi")]
|
||||
use std::time::Duration;
|
||||
|
||||
pub const LIVE_AUDIO_RESAMPLE_FILTER: &str = "aresample=async=1000:min_hard_comp=0.100:first_pts=0";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamProbe {
|
||||
pub index: usize,
|
||||
|
|
@ -806,18 +804,12 @@ pub fn deterministic_h264_profile() -> DeterminismProfile {
|
|||
encoder_args: vec![
|
||||
"-c:a".to_string(),
|
||||
"aac".to_string(),
|
||||
"-profile:a".to_string(),
|
||||
"aac_low".to_string(),
|
||||
"-b:a".to_string(),
|
||||
"128k".to_string(),
|
||||
"-ac".to_string(),
|
||||
"2".to_string(),
|
||||
"-ar".to_string(),
|
||||
"48000".to_string(),
|
||||
"-af".to_string(),
|
||||
LIVE_AUDIO_RESAMPLE_FILTER.to_string(),
|
||||
"-max_muxing_queue_size".to_string(),
|
||||
"2048".to_string(),
|
||||
"-pix_fmt".to_string(),
|
||||
"yuv420p".to_string(),
|
||||
"-g".to_string(),
|
||||
|
|
@ -884,15 +876,6 @@ mod tests {
|
|||
assert!(args.iter().any(|a| a == "1"));
|
||||
assert!(args.iter().any(|a| a == "+bitexact"));
|
||||
assert!(args.iter().any(|a| a == "libx264"));
|
||||
assert!(args
|
||||
.windows(2)
|
||||
.any(|w| w[0] == "-profile:a" && w[1] == "aac_low"));
|
||||
assert!(args
|
||||
.windows(2)
|
||||
.any(|w| w[0] == "-af" && w[1] == LIVE_AUDIO_RESAMPLE_FILTER));
|
||||
assert!(args
|
||||
.windows(2)
|
||||
.any(|w| w[0] == "-max_muxing_queue_size" && w[1] == "2048"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -8,5 +8,3 @@ license.workspace = true
|
|||
serde.workspace = true
|
||||
blake3.workspace = true
|
||||
serde_json.workspace = true
|
||||
hex = "0.4"
|
||||
sha3 = "0.10"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
//! Core types shared across every.channel.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha3::{Digest, Keccak256};
|
||||
use std::fmt;
|
||||
|
||||
pub const MANIFEST_ID_ALG_BLAKE3: &str = "blake3";
|
||||
pub const MANIFEST_ID_ALG_KECCAK256: &str = "keccak256";
|
||||
pub const MERKLE_PROOF_ALG_BLAKE3: &str = "merkle+blake3";
|
||||
pub const MERKLE_PROOF_ALG_KECCAK256: &str = "merkle+keccak256";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ChannelId(pub String);
|
||||
|
||||
|
|
@ -18,13 +12,6 @@ pub struct DeviceId(pub String);
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct StreamId(pub String);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ChainCommitment {
|
||||
pub chain: String,
|
||||
pub scheme: String,
|
||||
pub digest: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamDescriptor {
|
||||
pub id: StreamId,
|
||||
|
|
@ -32,8 +19,6 @@ pub struct StreamDescriptor {
|
|||
pub number: Option<String>,
|
||||
pub source: String,
|
||||
pub metadata: Vec<StreamMetadata>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub commitments: Vec<ChainCommitment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -47,7 +32,6 @@ pub struct BroadcastId {
|
|||
pub standard: String,
|
||||
pub transport_stream_id: Option<u16>,
|
||||
pub program_number: Option<u16>,
|
||||
pub virtual_channel: Option<String>,
|
||||
pub callsign: Option<String>,
|
||||
pub region: Option<String>,
|
||||
pub frequency: Option<String>,
|
||||
|
|
@ -70,30 +54,6 @@ pub struct StreamKey {
|
|||
}
|
||||
|
||||
impl StreamKey {
|
||||
pub fn for_channel_or_source(
|
||||
channel: Option<&Channel>,
|
||||
standard: Option<&str>,
|
||||
source: SourceId,
|
||||
profile: Option<String>,
|
||||
variant: Option<String>,
|
||||
) -> Self {
|
||||
let broadcast = channel
|
||||
.and_then(|channel| standard.and_then(|standard| channel.broadcast_id(standard)));
|
||||
let source = if broadcast.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(source)
|
||||
};
|
||||
|
||||
Self {
|
||||
version: 1,
|
||||
broadcast,
|
||||
source,
|
||||
profile,
|
||||
variant,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_stream_id(&self) -> StreamId {
|
||||
let mut parts = vec![
|
||||
"ec".to_string(),
|
||||
|
|
@ -110,9 +70,6 @@ impl StreamKey {
|
|||
if let Some(program) = broadcast.program_number {
|
||||
parts.push(format!("program-{program}"));
|
||||
}
|
||||
if let Some(channel) = &broadcast.virtual_channel {
|
||||
parts.push(format!("channel-{}", sanitize(channel)));
|
||||
}
|
||||
if let Some(callsign) = &broadcast.callsign {
|
||||
parts.push(format!("callsign-{}", sanitize(callsign)));
|
||||
}
|
||||
|
|
@ -175,90 +132,6 @@ pub enum ChannelMetadata {
|
|||
Extra(String, String),
|
||||
}
|
||||
|
||||
impl BroadcastId {
|
||||
pub fn is_usable(&self) -> bool {
|
||||
self.transport_stream_id.is_some()
|
||||
|| self.program_number.is_some()
|
||||
|| self
|
||||
.virtual_channel
|
||||
.as_ref()
|
||||
.is_some_and(|value| !value.trim().is_empty())
|
||||
|| self
|
||||
.callsign
|
||||
.as_ref()
|
||||
.is_some_and(|value| !value.trim().is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn broadcast_id(&self, standard: &str) -> Option<BroadcastId> {
|
||||
let standard = standard.trim().to_ascii_lowercase();
|
||||
if standard.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let callsign = channel_metadata_value(&self.metadata, "callsign").or_else(|| {
|
||||
let name = self.name.trim();
|
||||
(!name.is_empty()).then(|| name.to_string())
|
||||
});
|
||||
let region = channel_metadata_value(&self.metadata, "region");
|
||||
let frequency = channel_metadata_value(&self.metadata, "frequency");
|
||||
let transport_stream_id = channel_metadata_u16(&self.metadata, "transport_stream_id")
|
||||
.or_else(|| channel_metadata_u16(&self.metadata, "tsid"));
|
||||
let program_number = self
|
||||
.program_id
|
||||
.or_else(|| channel_metadata_u16(&self.metadata, "program_number"))
|
||||
.or_else(|| channel_metadata_u16(&self.metadata, "program_id"));
|
||||
let virtual_channel = self.number.as_ref().and_then(|value| {
|
||||
let trimmed = value.trim();
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_string())
|
||||
});
|
||||
|
||||
let broadcast = BroadcastId {
|
||||
standard,
|
||||
transport_stream_id,
|
||||
program_number,
|
||||
virtual_channel,
|
||||
callsign,
|
||||
region,
|
||||
frequency,
|
||||
};
|
||||
|
||||
broadcast.is_usable().then_some(broadcast)
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_metadata_value(metadata: &[ChannelMetadata], key: &str) -> Option<String> {
|
||||
for item in metadata {
|
||||
match item {
|
||||
ChannelMetadata::Callsign(value) if key == "callsign" => {
|
||||
return Some(value.trim().to_string())
|
||||
}
|
||||
ChannelMetadata::Region(value) if key == "region" => {
|
||||
return Some(value.trim().to_string())
|
||||
}
|
||||
ChannelMetadata::Frequency(value) if key == "frequency" => {
|
||||
return Some(value.trim().to_string())
|
||||
}
|
||||
ChannelMetadata::Network(value) if key == "network" => {
|
||||
return Some(value.trim().to_string())
|
||||
}
|
||||
ChannelMetadata::Extra(extra_key, value) if extra_key.eq_ignore_ascii_case(key) => {
|
||||
let trimmed = value.trim_matches('"').trim().to_string();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn channel_metadata_u16(metadata: &[ChannelMetadata], key: &str) -> Option<u16> {
|
||||
channel_metadata_value(metadata, key).and_then(|value| value.parse().ok())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PacketDigest {
|
||||
pub algorithm: String,
|
||||
|
|
@ -334,8 +207,6 @@ pub struct StreamControlAnnouncement {
|
|||
pub updated_unix_ms: u64,
|
||||
/// Suggested freshness window for this announcement.
|
||||
pub ttl_ms: u64,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub commitments: Vec<ChainCommitment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -347,8 +218,6 @@ pub struct ManifestSummary {
|
|||
pub chunk_start_index: u64,
|
||||
pub encoder_profile_id: String,
|
||||
pub signed_by: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub commitments: Vec<ChainCommitment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -399,8 +268,6 @@ pub struct Manifest {
|
|||
pub body: ManifestBody,
|
||||
pub manifest_id: String,
|
||||
pub signatures: Vec<ManifestSignature>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub commitments: Vec<ChainCommitment>,
|
||||
}
|
||||
|
||||
impl Manifest {
|
||||
|
|
@ -417,7 +284,6 @@ impl Manifest {
|
|||
.iter()
|
||||
.map(|sig| sig.signer_id.clone())
|
||||
.collect(),
|
||||
commitments: self.commitments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -441,96 +307,44 @@ impl std::error::Error for ManifestError {}
|
|||
|
||||
impl ManifestBody {
|
||||
pub fn manifest_id(&self) -> Result<String, serde_json::Error> {
|
||||
self.manifest_id_blake3()
|
||||
}
|
||||
|
||||
pub fn manifest_id_blake3(&self) -> Result<String, serde_json::Error> {
|
||||
let bytes = serde_json::to_vec(self)?;
|
||||
Ok(blake3::hash(&bytes).to_hex().to_string())
|
||||
}
|
||||
|
||||
pub fn manifest_id_keccak256(&self) -> Result<String, serde_json::Error> {
|
||||
let bytes = serde_json::to_vec(self)?;
|
||||
Ok(hex::encode(keccak256(&bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hash32(value: &str) -> Result<[u8; 32], ManifestError> {
|
||||
let trimmed = value.trim().strip_prefix("0x").unwrap_or(value.trim());
|
||||
let bytes = hex::decode(trimmed).map_err(|_| ManifestError::InvalidHash(value.to_string()))?;
|
||||
if bytes.len() != 32 {
|
||||
return Err(ManifestError::InvalidHash(value.to_string()));
|
||||
}
|
||||
let mut out = [0u8; 32];
|
||||
out.copy_from_slice(&bytes);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn keccak256(bytes: &[u8]) -> [u8; 32] {
|
||||
let mut hasher = Keccak256::new();
|
||||
hasher.update(bytes);
|
||||
let digest = hasher.finalize();
|
||||
let mut out = [0u8; 32];
|
||||
out.copy_from_slice(&digest);
|
||||
out
|
||||
}
|
||||
|
||||
fn blake3_pair_hash(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
hasher.update(left);
|
||||
hasher.update(right);
|
||||
*hasher.finalize().as_bytes()
|
||||
}
|
||||
|
||||
fn keccak_pair_hash(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(left);
|
||||
merged[32..].copy_from_slice(right);
|
||||
keccak256(&merged)
|
||||
}
|
||||
|
||||
fn merkle_root_from_hashes_with(
|
||||
hashes: &[String],
|
||||
pair_hash: fn(&[u8; 32], &[u8; 32]) -> [u8; 32],
|
||||
) -> Result<String, ManifestError> {
|
||||
pub fn merkle_root_from_hashes(hashes: &[String]) -> Result<String, ManifestError> {
|
||||
if hashes.is_empty() {
|
||||
return Err(ManifestError::Empty);
|
||||
}
|
||||
let mut nodes: Vec<[u8; 32]> = Vec::with_capacity(hashes.len());
|
||||
let mut nodes: Vec<blake3::Hash> = Vec::with_capacity(hashes.len());
|
||||
for hash in hashes {
|
||||
nodes.push(parse_hash32(hash)?);
|
||||
let parsed = blake3::Hash::from_hex(hash.as_bytes())
|
||||
.map_err(|_| ManifestError::InvalidHash(hash.clone()))?;
|
||||
nodes.push(parsed);
|
||||
}
|
||||
while nodes.len() > 1 {
|
||||
if nodes.len() % 2 == 1 {
|
||||
if let Some(last) = nodes.last().copied() {
|
||||
if let Some(last) = nodes.last().cloned() {
|
||||
nodes.push(last);
|
||||
}
|
||||
}
|
||||
let mut parents = Vec::with_capacity(nodes.len() / 2);
|
||||
for pair in nodes.chunks(2) {
|
||||
parents.push(pair_hash(&pair[0], &pair[1]));
|
||||
let left = pair[0].as_bytes();
|
||||
let right = pair[1].as_bytes();
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(left);
|
||||
merged[32..].copy_from_slice(right);
|
||||
parents.push(blake3::hash(&merged));
|
||||
}
|
||||
nodes = parents;
|
||||
}
|
||||
Ok(hex::encode(nodes[0]))
|
||||
Ok(nodes[0].to_hex().to_string())
|
||||
}
|
||||
|
||||
pub fn merkle_root_from_hashes(hashes: &[String]) -> Result<String, ManifestError> {
|
||||
blake3_merkle_root_from_hashes(hashes)
|
||||
}
|
||||
|
||||
pub fn blake3_merkle_root_from_hashes(hashes: &[String]) -> Result<String, ManifestError> {
|
||||
merkle_root_from_hashes_with(hashes, blake3_pair_hash)
|
||||
}
|
||||
|
||||
pub fn keccak_merkle_root_from_hashes(hashes: &[String]) -> Result<String, ManifestError> {
|
||||
merkle_root_from_hashes_with(hashes, keccak_pair_hash)
|
||||
}
|
||||
|
||||
fn merkle_proof_for_index_with(
|
||||
pub fn merkle_proof_for_index(
|
||||
hashes: &[String],
|
||||
index: usize,
|
||||
pair_hash: fn(&[u8; 32], &[u8; 32]) -> [u8; 32],
|
||||
) -> Result<Vec<String>, ManifestError> {
|
||||
if hashes.is_empty() {
|
||||
return Err(ManifestError::Empty);
|
||||
|
|
@ -541,16 +355,18 @@ fn merkle_proof_for_index_with(
|
|||
)));
|
||||
}
|
||||
|
||||
let mut nodes: Vec<[u8; 32]> = Vec::with_capacity(hashes.len());
|
||||
let mut nodes: Vec<blake3::Hash> = Vec::with_capacity(hashes.len());
|
||||
for hash in hashes {
|
||||
nodes.push(parse_hash32(hash)?);
|
||||
let parsed = blake3::Hash::from_hex(hash.as_bytes())
|
||||
.map_err(|_| ManifestError::InvalidHash(hash.clone()))?;
|
||||
nodes.push(parsed);
|
||||
}
|
||||
|
||||
let mut proof = Vec::new();
|
||||
let mut pos = index;
|
||||
while nodes.len() > 1 {
|
||||
if nodes.len() % 2 == 1 {
|
||||
if let Some(last) = nodes.last().copied() {
|
||||
if let Some(last) = nodes.last().cloned() {
|
||||
nodes.push(last);
|
||||
}
|
||||
}
|
||||
|
|
@ -559,11 +375,16 @@ fn merkle_proof_for_index_with(
|
|||
let sibling = nodes
|
||||
.get(sibling_index)
|
||||
.ok_or_else(|| ManifestError::InvalidHash("missing sibling".to_string()))?;
|
||||
proof.push(hex::encode(sibling));
|
||||
proof.push(sibling.to_hex().to_string());
|
||||
|
||||
let mut parents = Vec::with_capacity(nodes.len() / 2);
|
||||
for pair in nodes.chunks(2) {
|
||||
parents.push(pair_hash(&pair[0], &pair[1]));
|
||||
let left = pair[0].as_bytes();
|
||||
let right = pair[1].as_bytes();
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(left);
|
||||
merged[32..].copy_from_slice(right);
|
||||
parents.push(blake3::hash(&merged));
|
||||
}
|
||||
nodes = parents;
|
||||
pos /= 2;
|
||||
|
|
@ -572,39 +393,17 @@ fn merkle_proof_for_index_with(
|
|||
Ok(proof)
|
||||
}
|
||||
|
||||
pub fn merkle_proof_for_index(
|
||||
hashes: &[String],
|
||||
index: usize,
|
||||
) -> Result<Vec<String>, ManifestError> {
|
||||
blake3_merkle_proof_for_index(hashes, index)
|
||||
}
|
||||
|
||||
pub fn blake3_merkle_proof_for_index(
|
||||
hashes: &[String],
|
||||
index: usize,
|
||||
) -> Result<Vec<String>, ManifestError> {
|
||||
merkle_proof_for_index_with(hashes, index, blake3_pair_hash)
|
||||
}
|
||||
|
||||
pub fn keccak_merkle_proof_for_index(
|
||||
hashes: &[String],
|
||||
index: usize,
|
||||
) -> Result<Vec<String>, ManifestError> {
|
||||
merkle_proof_for_index_with(hashes, index, keccak_pair_hash)
|
||||
}
|
||||
|
||||
fn verify_merkle_proof_with(
|
||||
pub fn verify_merkle_proof(
|
||||
leaf_hash: &str,
|
||||
mut index: usize,
|
||||
branch: &[String],
|
||||
expected_root: &str,
|
||||
pair_hash: fn(&[u8; 32], &[u8; 32]) -> [u8; 32],
|
||||
) -> bool {
|
||||
let Ok(mut acc) = parse_hash32(leaf_hash) else {
|
||||
let Ok(mut acc) = blake3::Hash::from_hex(leaf_hash.as_bytes()) else {
|
||||
return false;
|
||||
};
|
||||
for sibling_hex in branch {
|
||||
let Ok(sibling) = parse_hash32(sibling_hex) else {
|
||||
let Ok(sibling) = blake3::Hash::from_hex(sibling_hex.as_bytes()) else {
|
||||
return false;
|
||||
};
|
||||
let (left, right) = if index % 2 == 0 {
|
||||
|
|
@ -612,40 +411,13 @@ fn verify_merkle_proof_with(
|
|||
} else {
|
||||
(sibling, acc)
|
||||
};
|
||||
acc = pair_hash(&left, &right);
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(left.as_bytes());
|
||||
merged[32..].copy_from_slice(right.as_bytes());
|
||||
acc = blake3::hash(&merged);
|
||||
index /= 2;
|
||||
}
|
||||
match parse_hash32(expected_root) {
|
||||
Ok(root) => acc == root,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_merkle_proof(
|
||||
leaf_hash: &str,
|
||||
index: usize,
|
||||
branch: &[String],
|
||||
expected_root: &str,
|
||||
) -> bool {
|
||||
verify_blake3_merkle_proof(leaf_hash, index, branch, expected_root)
|
||||
}
|
||||
|
||||
pub fn verify_blake3_merkle_proof(
|
||||
leaf_hash: &str,
|
||||
index: usize,
|
||||
branch: &[String],
|
||||
expected_root: &str,
|
||||
) -> bool {
|
||||
verify_merkle_proof_with(leaf_hash, index, branch, expected_root, blake3_pair_hash)
|
||||
}
|
||||
|
||||
pub fn verify_keccak_merkle_proof(
|
||||
leaf_hash: &str,
|
||||
index: usize,
|
||||
branch: &[String],
|
||||
expected_root: &str,
|
||||
) -> bool {
|
||||
verify_merkle_proof_with(leaf_hash, index, branch, expected_root, keccak_pair_hash)
|
||||
acc.to_hex().to_string() == expected_root
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -674,39 +446,11 @@ mod tests {
|
|||
assert_ne!(id1, id2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_id_defaults_to_blake3() {
|
||||
let body = ManifestBody {
|
||||
stream_id: StreamId("s".to_string()),
|
||||
epoch_id: "e".to_string(),
|
||||
chunk_duration_ms: 2000,
|
||||
total_chunks: 1,
|
||||
chunk_start_index: 0,
|
||||
encoder_profile_id: "p".to_string(),
|
||||
merkle_root: "00".repeat(32),
|
||||
created_unix_ms: 1,
|
||||
metadata: Vec::new(),
|
||||
chunk_hashes: vec!["11".repeat(32)],
|
||||
variants: None,
|
||||
};
|
||||
let bytes = serde_json::to_vec(&body).unwrap();
|
||||
assert_eq!(
|
||||
body.manifest_id().unwrap(),
|
||||
blake3::hash(&bytes).to_hex().to_string()
|
||||
);
|
||||
assert_ne!(
|
||||
body.manifest_id().unwrap(),
|
||||
body.manifest_id_keccak256().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merkle_root_single_is_leaf() {
|
||||
let leaf = blake3::hash(b"leaf").to_hex().to_string();
|
||||
let root = merkle_root_from_hashes(&[leaf.clone()]).unwrap();
|
||||
assert_eq!(root, leaf);
|
||||
let keccak_root = keccak_merkle_root_from_hashes(&[leaf.clone()]).unwrap();
|
||||
assert_eq!(keccak_root, leaf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -732,23 +476,6 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keccak_merkle_proof_roundtrip_small_sets() {
|
||||
for size in 1..=9usize {
|
||||
let leaves = (0..size)
|
||||
.map(|i| blake3::hash(&[i as u8]).to_hex().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let root = keccak_merkle_root_from_hashes(&leaves).unwrap();
|
||||
for idx in 0..size {
|
||||
let proof = keccak_merkle_proof_for_index(&leaves, idx).unwrap();
|
||||
assert!(
|
||||
verify_keccak_merkle_proof(&leaves[idx], idx, &proof, &root),
|
||||
"size {size} idx {idx} failed"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merkle_proof_detects_tampering() {
|
||||
let leaves = (0..4usize)
|
||||
|
|
@ -759,90 +486,4 @@ mod tests {
|
|||
proof[0] = blake3::hash(b"evil").to_hex().to_string();
|
||||
assert!(!verify_merkle_proof(&leaves[2], 2, &proof, &root));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn channel_broadcast_id_uses_typed_and_extra_metadata() {
|
||||
let channel = Channel {
|
||||
id: ChannelId("kcbs".to_string()),
|
||||
name: "KCBS-HD".to_string(),
|
||||
number: Some("2.1".to_string()),
|
||||
program_id: Some(3),
|
||||
metadata: vec![
|
||||
ChannelMetadata::Callsign("KCBS".to_string()),
|
||||
ChannelMetadata::Region("los-angeles".to_string()),
|
||||
ChannelMetadata::Extra("tsid".to_string(), "42".to_string()),
|
||||
ChannelMetadata::Extra("frequency".to_string(), "573000000".to_string()),
|
||||
],
|
||||
};
|
||||
|
||||
let broadcast = channel.broadcast_id("ATSC").unwrap();
|
||||
assert_eq!(broadcast.standard, "atsc");
|
||||
assert_eq!(broadcast.transport_stream_id, Some(42));
|
||||
assert_eq!(broadcast.program_number, Some(3));
|
||||
assert_eq!(broadcast.virtual_channel.as_deref(), Some("2.1"));
|
||||
assert_eq!(broadcast.callsign.as_deref(), Some("KCBS"));
|
||||
assert_eq!(broadcast.region.as_deref(), Some("los-angeles"));
|
||||
assert_eq!(broadcast.frequency.as_deref(), Some("573000000"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_key_prefers_broadcast_scope_when_channel_identity_exists() {
|
||||
let channel = Channel {
|
||||
id: ChannelId("kcbs".to_string()),
|
||||
name: "KCBS-HD".to_string(),
|
||||
number: Some("2.1".to_string()),
|
||||
program_id: None,
|
||||
metadata: vec![ChannelMetadata::Callsign("KCBS".to_string())],
|
||||
};
|
||||
let source = SourceId {
|
||||
kind: "hdhr".to_string(),
|
||||
device_id: Some("ABCDEF01".to_string()),
|
||||
channel: Some("2.1".to_string()),
|
||||
};
|
||||
|
||||
let key = StreamKey::for_channel_or_source(
|
||||
Some(&channel),
|
||||
Some("atsc"),
|
||||
source,
|
||||
Some("chunk-2000ms".to_string()),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(key.broadcast.is_some());
|
||||
assert!(key.source.is_none());
|
||||
assert_eq!(
|
||||
key.to_stream_id().0,
|
||||
"ec/stream/v1/broadcast/atsc/channel-2_1/callsign-kcbs/profile-chunk-2000ms"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_key_falls_back_to_source_scope_without_channel_identity() {
|
||||
let channel = Channel {
|
||||
id: ChannelId("unknown".to_string()),
|
||||
name: "".to_string(),
|
||||
number: None,
|
||||
program_id: None,
|
||||
metadata: Vec::new(),
|
||||
};
|
||||
let source = SourceId {
|
||||
kind: "ts".to_string(),
|
||||
device_id: None,
|
||||
channel: Some("file.ts".to_string()),
|
||||
};
|
||||
|
||||
let key = StreamKey::for_channel_or_source(
|
||||
Some(&channel),
|
||||
Some("atsc"),
|
||||
source,
|
||||
Some("chunk-2000ms".to_string()),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(key.broadcast.is_none());
|
||||
assert_eq!(
|
||||
key.to_stream_id().0,
|
||||
"ec/stream/v1/source/ts/channel-file_ts/profile-chunk-2000ms"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,5 @@ license.workspace = true
|
|||
blake3 = "1"
|
||||
chacha20poly1305 = "0.10"
|
||||
ed25519-dalek = { version = "2", features = ["pkcs8"] }
|
||||
ec-eth = { path = "../ec-eth" }
|
||||
hex = "0.4"
|
||||
ec-core = { path = "../ec-core" }
|
||||
k256 = { version = "0.13", features = ["ecdsa"] }
|
||||
sha3 = "0.10"
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
//! Cryptographic helpers for every.channel.
|
||||
|
||||
use chacha20poly1305::{aead::Aead, KeyInit, XChaCha20Poly1305, XNonce};
|
||||
use ec_core::{ManifestBody, ManifestSignature};
|
||||
use ec_eth::{manifest_body_eip712_signing_hash, ETH_MANIFEST_SIG_ALG};
|
||||
use ec_core::ManifestSignature;
|
||||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use k256::ecdsa::{
|
||||
RecoveryId as SecpRecoveryId, Signature as SecpSignature, SigningKey as SecpSigningKey,
|
||||
VerifyingKey as SecpVerifyingKey,
|
||||
};
|
||||
use sha3::{Digest, Keccak256};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
pub const MANIFEST_SIG_ALG: &str = "ed25519";
|
||||
pub const ETH_MANIFEST_SIGNING_KEY_ENV: &str = "EVERY_CHANNEL_ETH_MANIFEST_SIGNING_KEY";
|
||||
|
||||
pub const ENCRYPTION_ALG: &str = "xchacha20poly1305";
|
||||
|
||||
|
|
@ -90,29 +83,19 @@ pub struct ManifestKeypair {
|
|||
pub verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EthereumManifestKeypair {
|
||||
pub signing_key: SecpSigningKey,
|
||||
pub verifying_key: SecpVerifyingKey,
|
||||
}
|
||||
|
||||
fn decode_env_hex_or_file(value: &str) -> Result<Vec<u8>, String> {
|
||||
let trimmed = value.trim();
|
||||
if std::path::Path::new(trimmed).exists() {
|
||||
let text = fs::read_to_string(trimmed).map_err(|err| err.to_string())?;
|
||||
hex::decode(text.trim().trim_start_matches("0x")).map_err(|err| err.to_string())
|
||||
} else {
|
||||
hex::decode(trimmed.trim_start_matches("0x")).map_err(|err| err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_manifest_keypair_from_env() -> Result<Option<ManifestKeypair>, String> {
|
||||
let value = match env::var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY") {
|
||||
Ok(value) => value,
|
||||
Err(env::VarError::NotPresent) => return Ok(None),
|
||||
Err(err) => return Err(err.to_string()),
|
||||
};
|
||||
let key_bytes = decode_env_hex_or_file(&value)?;
|
||||
let trimmed = value.trim();
|
||||
let key_bytes = if std::path::Path::new(trimmed).exists() {
|
||||
let text = fs::read_to_string(trimmed).map_err(|err| err.to_string())?;
|
||||
hex::decode(text.trim()).map_err(|err| err.to_string())?
|
||||
} else {
|
||||
hex::decode(trimmed).map_err(|err| err.to_string())?
|
||||
};
|
||||
let bytes = if key_bytes.len() == 32 {
|
||||
key_bytes
|
||||
} else if key_bytes.len() == 64 {
|
||||
|
|
@ -130,36 +113,10 @@ pub fn load_manifest_keypair_from_env() -> Result<Option<ManifestKeypair>, Strin
|
|||
}))
|
||||
}
|
||||
|
||||
pub fn load_ethereum_manifest_keypair_from_env() -> Result<Option<EthereumManifestKeypair>, String>
|
||||
{
|
||||
let value = match env::var(ETH_MANIFEST_SIGNING_KEY_ENV) {
|
||||
Ok(value) => value,
|
||||
Err(env::VarError::NotPresent) => return Ok(None),
|
||||
Err(err) => return Err(err.to_string()),
|
||||
};
|
||||
let key_bytes = decode_env_hex_or_file(&value)?;
|
||||
if key_bytes.len() != 32 {
|
||||
return Err("ethereum manifest signing key must be exactly 32 hex bytes".to_string());
|
||||
}
|
||||
let signing_key =
|
||||
SecpSigningKey::from_bytes((&key_bytes[..]).into()).map_err(|err| err.to_string())?;
|
||||
let verifying_key = *signing_key.verifying_key();
|
||||
Ok(Some(EthereumManifestKeypair {
|
||||
signing_key,
|
||||
verifying_key,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn signer_id_from_key(key: &VerifyingKey) -> String {
|
||||
format!("ed25519:{}", hex::encode(key.to_bytes()))
|
||||
}
|
||||
|
||||
pub fn ethereum_signer_id_from_key(key: &SecpVerifyingKey) -> String {
|
||||
let encoded = key.to_encoded_point(false);
|
||||
let digest = Keccak256::digest(&encoded.as_bytes()[1..]);
|
||||
format!("eth:0x{}", hex::encode(&digest[12..]))
|
||||
}
|
||||
|
||||
pub fn sign_manifest_id(manifest_id: &str, keypair: &ManifestKeypair) -> ManifestSignature {
|
||||
let signature: Signature = keypair.signing_key.sign(manifest_id.as_bytes());
|
||||
ManifestSignature {
|
||||
|
|
@ -169,25 +126,6 @@ pub fn sign_manifest_id(manifest_id: &str, keypair: &ManifestKeypair) -> Manifes
|
|||
}
|
||||
}
|
||||
|
||||
pub fn sign_manifest_body_eip712(
|
||||
body: &ManifestBody,
|
||||
keypair: &EthereumManifestKeypair,
|
||||
) -> Result<ManifestSignature, String> {
|
||||
let digest = manifest_body_eip712_signing_hash(body).map_err(|err| err.to_string())?;
|
||||
let (signature, recovery_id) = keypair
|
||||
.signing_key
|
||||
.sign_prehash_recoverable(digest.as_slice())
|
||||
.map_err(|err| err.to_string())?;
|
||||
let mut bytes = Vec::with_capacity(65);
|
||||
bytes.extend_from_slice(&signature.to_bytes());
|
||||
bytes.push(recovery_id.to_byte());
|
||||
Ok(ManifestSignature {
|
||||
signer_id: ethereum_signer_id_from_key(&keypair.verifying_key),
|
||||
alg: ETH_MANIFEST_SIG_ALG.to_string(),
|
||||
signature: hex::encode(bytes),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_manifest_signature(manifest_id: &str, sig: &ManifestSignature) -> bool {
|
||||
if sig.alg != MANIFEST_SIG_ALG {
|
||||
return false;
|
||||
|
|
@ -218,57 +156,6 @@ pub fn verify_manifest_signature(manifest_id: &str, sig: &ManifestSignature) ->
|
|||
.is_ok()
|
||||
}
|
||||
|
||||
fn normalize_eth_signer_id(value: &str) -> Option<String> {
|
||||
let trimmed = value
|
||||
.strip_prefix("eth:")
|
||||
.or_else(|| value.strip_prefix("ETH:"))
|
||||
.unwrap_or(value)
|
||||
.trim();
|
||||
let address = trimmed.trim_start_matches("0x");
|
||||
if address.len() != 40 || !address.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return None;
|
||||
}
|
||||
Some(format!("eth:0x{}", address.to_ascii_lowercase()))
|
||||
}
|
||||
|
||||
pub fn verify_manifest_body_eip712_signature(body: &ManifestBody, sig: &ManifestSignature) -> bool {
|
||||
if sig.alg != ETH_MANIFEST_SIG_ALG {
|
||||
return false;
|
||||
}
|
||||
let Some(expected_signer_id) = normalize_eth_signer_id(&sig.signer_id) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(sig_bytes) = hex::decode(sig.signature.trim().trim_start_matches("0x")) else {
|
||||
return false;
|
||||
};
|
||||
if sig_bytes.len() != 65 {
|
||||
return false;
|
||||
}
|
||||
let Ok(signature) = SecpSignature::from_slice(&sig_bytes[..64]) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(recovery_id) = SecpRecoveryId::try_from(sig_bytes[64]) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(digest) = manifest_body_eip712_signing_hash(body) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(verifying_key) =
|
||||
SecpVerifyingKey::recover_from_prehash(digest.as_slice(), &signature, recovery_id)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
ethereum_signer_id_from_key(&verifying_key) == expected_signer_id
|
||||
}
|
||||
|
||||
pub fn verify_manifest_signature_with_body(
|
||||
manifest_id: &str,
|
||||
body: &ManifestBody,
|
||||
sig: &ManifestSignature,
|
||||
) -> bool {
|
||||
verify_manifest_signature(manifest_id, sig) || verify_manifest_body_eip712_signature(body, sig)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -325,35 +212,6 @@ mod tests {
|
|||
assert!(!verify_manifest_signature("evil", &sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ethereum_manifest_sign_verify_roundtrip() {
|
||||
let body = ManifestBody {
|
||||
stream_id: ec_core::StreamId("stream".to_string()),
|
||||
epoch_id: "epoch-1".to_string(),
|
||||
chunk_duration_ms: 2000,
|
||||
total_chunks: 1,
|
||||
chunk_start_index: 0,
|
||||
encoder_profile_id: "p".to_string(),
|
||||
merkle_root: "11".repeat(32),
|
||||
created_unix_ms: 1,
|
||||
metadata: Vec::new(),
|
||||
chunk_hashes: vec!["22".repeat(32)],
|
||||
variants: None,
|
||||
};
|
||||
let secret = [7u8; 32];
|
||||
let signing_key = SecpSigningKey::from_bytes((&secret).into()).unwrap();
|
||||
let verifying_key = *signing_key.verifying_key();
|
||||
let keypair = EthereumManifestKeypair {
|
||||
signing_key,
|
||||
verifying_key,
|
||||
};
|
||||
let sig = sign_manifest_body_eip712(&body, &keypair).unwrap();
|
||||
assert!(verify_manifest_body_eip712_signature(&body, &sig));
|
||||
let mut tampered = body.clone();
|
||||
tampered.created_unix_ms = 2;
|
||||
assert!(!verify_manifest_body_eip712_signature(&tampered, &sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_keypair_from_env_hex() {
|
||||
let prev = env::var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY").ok();
|
||||
|
|
@ -366,17 +224,4 @@ mod tests {
|
|||
None => env::remove_var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_ethereum_keypair_from_env_hex() {
|
||||
let prev = env::var(ETH_MANIFEST_SIGNING_KEY_ENV).ok();
|
||||
env::set_var(ETH_MANIFEST_SIGNING_KEY_ENV, "01".repeat(32));
|
||||
let loaded = load_ethereum_manifest_keypair_from_env().unwrap().unwrap();
|
||||
let id = ethereum_signer_id_from_key(&loaded.verifying_key);
|
||||
assert!(id.starts_with("eth:0x"));
|
||||
match prev {
|
||||
Some(value) => env::set_var(ETH_MANIFEST_SIGNING_KEY_ENV, value),
|
||||
None => env::remove_var(ETH_MANIFEST_SIGNING_KEY_ENV),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "ec-eth"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
alloy-primitives = "1.5.7"
|
||||
alloy-sol-types = "1.5.7"
|
||||
blake3 = "1"
|
||||
ec-core = { path = "../ec-core" }
|
||||
hex = "0.4"
|
||||
|
|
@ -1,736 +0,0 @@
|
|||
//! Ethereum-compatible representations and commitments for every.channel core types.
|
||||
|
||||
use alloy_primitives::{keccak256, B256};
|
||||
use alloy_sol_types::{eip712_domain, sol, Eip712Domain, SolStruct, SolValue};
|
||||
use ec_core::{
|
||||
BroadcastId, ChainCommitment, ChunkId, Manifest, ManifestBody, ManifestSignature,
|
||||
ManifestVariant, SourceId, StreamControlAnnouncement, StreamDescriptor, StreamKey,
|
||||
StreamMetadata, StreamTransportDescriptor,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
pub const ETHEREUM_CHAIN: &str = "ethereum";
|
||||
pub const SCHEME_STREAM_ID_KECCAK: &str = "stream-id-keccak256-v1";
|
||||
pub const SCHEME_STREAM_DESCRIPTOR_ABI: &str = "stream-descriptor-abi-keccak256-v1";
|
||||
pub const SCHEME_CONTROL_ANNOUNCEMENT_ABI: &str = "control-announcement-abi-keccak256-v1";
|
||||
pub const SCHEME_MANIFEST_DATA_ROOT: &str = "manifest-data-merkle-keccak256-v1";
|
||||
pub const SCHEME_MANIFEST_BODY_ABI: &str = "manifest-body-abi-keccak256-v1";
|
||||
pub const SCHEME_MANIFEST_ENVELOPE_ABI: &str = "manifest-envelope-abi-keccak256-v1";
|
||||
pub const ETH_MANIFEST_SIG_ALG: &str = "secp256k1-eip712-manifest-body-v1";
|
||||
pub const ZERO_B256_HEX: &str =
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
sol! {
|
||||
struct EthStreamMetadata {
|
||||
string key;
|
||||
string value;
|
||||
}
|
||||
|
||||
struct EthBroadcastId {
|
||||
string standard;
|
||||
bool hasTransportStreamId;
|
||||
uint16 transportStreamId;
|
||||
bool hasProgramNumber;
|
||||
uint16 programNumber;
|
||||
string virtualChannel;
|
||||
string callsign;
|
||||
string region;
|
||||
string frequency;
|
||||
}
|
||||
|
||||
struct EthSourceId {
|
||||
string kind;
|
||||
string deviceId;
|
||||
string channel;
|
||||
}
|
||||
|
||||
struct EthStreamKey {
|
||||
uint16 version;
|
||||
bool hasBroadcast;
|
||||
EthBroadcastId broadcast;
|
||||
bool hasSource;
|
||||
EthSourceId source;
|
||||
string profile;
|
||||
string variant;
|
||||
}
|
||||
|
||||
struct EthStreamDescriptor {
|
||||
string id;
|
||||
string title;
|
||||
string number;
|
||||
string source;
|
||||
EthStreamMetadata[] metadata;
|
||||
}
|
||||
|
||||
struct EthStreamTransportDescriptor {
|
||||
uint8 kind;
|
||||
string url;
|
||||
string endpoint;
|
||||
string broadcastName;
|
||||
string trackName;
|
||||
}
|
||||
|
||||
struct EthStreamControlAnnouncement {
|
||||
EthStreamDescriptor stream;
|
||||
EthStreamTransportDescriptor[] transports;
|
||||
uint64 updatedUnixMs;
|
||||
uint64 ttlMs;
|
||||
}
|
||||
|
||||
struct EthChunkId {
|
||||
string streamId;
|
||||
string epochId;
|
||||
uint64 chunkIndex;
|
||||
bytes32 chunkHash;
|
||||
}
|
||||
|
||||
struct EthManifestVariant {
|
||||
string variantId;
|
||||
string streamId;
|
||||
uint64 chunkStartIndex;
|
||||
uint64 totalChunks;
|
||||
bytes32 merkleRoot;
|
||||
bytes32[] chunkHashes;
|
||||
EthStreamMetadata[] metadata;
|
||||
}
|
||||
|
||||
struct EthManifestBody {
|
||||
string streamId;
|
||||
string epochId;
|
||||
uint64 chunkDurationMs;
|
||||
uint64 totalChunks;
|
||||
uint64 chunkStartIndex;
|
||||
string encoderProfileId;
|
||||
bytes32 merkleRoot;
|
||||
uint64 createdUnixMs;
|
||||
EthStreamMetadata[] metadata;
|
||||
bytes32[] chunkHashes;
|
||||
EthManifestVariant[] variants;
|
||||
}
|
||||
|
||||
struct EthManifestSignature {
|
||||
string signerId;
|
||||
string alg;
|
||||
bytes signature;
|
||||
}
|
||||
|
||||
struct EthManifest {
|
||||
EthManifestBody body;
|
||||
bytes32 manifestId;
|
||||
EthManifestSignature[] signatures;
|
||||
}
|
||||
|
||||
struct EthObservationHeader {
|
||||
bytes32 streamHash;
|
||||
bytes32 epochHash;
|
||||
bytes32 parentObservationHash;
|
||||
bytes32 dataRoot;
|
||||
bytes32 locatorHash;
|
||||
uint64 observedUnixMs;
|
||||
uint64 sequence;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EthCommitmentError {
|
||||
Empty,
|
||||
InvalidHex(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for EthCommitmentError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
EthCommitmentError::Empty => write!(f, "no hashes supplied"),
|
||||
EthCommitmentError::InvalidHex(value) => {
|
||||
write!(f, "invalid 32-byte hex value: {value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EthCommitmentError {}
|
||||
|
||||
fn commitment(scheme: &str, digest: B256) -> ChainCommitment {
|
||||
ChainCommitment {
|
||||
chain: ETHEREUM_CHAIN.to_string(),
|
||||
scheme: scheme.to_string(),
|
||||
digest: b256_hex(digest),
|
||||
}
|
||||
}
|
||||
|
||||
fn abi_commitment<T: SolValue>(scheme: &str, value: &T) -> ChainCommitment {
|
||||
commitment(scheme, keccak256(value.abi_encode()))
|
||||
}
|
||||
|
||||
pub fn b256_hex(value: B256) -> String {
|
||||
format!("0x{}", hex::encode(value))
|
||||
}
|
||||
|
||||
pub fn keccak256_bytes_hex(value: &[u8]) -> String {
|
||||
b256_hex(keccak256(value))
|
||||
}
|
||||
|
||||
pub fn parse_b256(value: &str) -> Result<B256, EthCommitmentError> {
|
||||
let trimmed = value.trim().strip_prefix("0x").unwrap_or(value.trim());
|
||||
let bytes =
|
||||
hex::decode(trimmed).map_err(|_| EthCommitmentError::InvalidHex(value.to_string()))?;
|
||||
if bytes.len() != 32 {
|
||||
return Err(EthCommitmentError::InvalidHex(value.to_string()));
|
||||
}
|
||||
let mut out = [0u8; 32];
|
||||
out.copy_from_slice(&bytes);
|
||||
Ok(B256::from(out))
|
||||
}
|
||||
|
||||
fn parse_bytes(value: &str) -> Vec<u8> {
|
||||
let trimmed = value.trim().strip_prefix("0x").unwrap_or(value.trim());
|
||||
hex::decode(trimmed).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn manifest_eip712_domain() -> Eip712Domain {
|
||||
eip712_domain! {
|
||||
name: "every.channel",
|
||||
version: "1",
|
||||
}
|
||||
}
|
||||
|
||||
fn eth_metadata(items: &[StreamMetadata]) -> Vec<EthStreamMetadata> {
|
||||
items
|
||||
.iter()
|
||||
.map(|item| EthStreamMetadata {
|
||||
key: item.key.clone(),
|
||||
value: item.value.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn eth_broadcast_id(value: &BroadcastId) -> EthBroadcastId {
|
||||
EthBroadcastId {
|
||||
standard: value.standard.clone(),
|
||||
hasTransportStreamId: value.transport_stream_id.is_some(),
|
||||
transportStreamId: value.transport_stream_id.unwrap_or_default(),
|
||||
hasProgramNumber: value.program_number.is_some(),
|
||||
programNumber: value.program_number.unwrap_or_default(),
|
||||
virtualChannel: value.virtual_channel.clone().unwrap_or_default(),
|
||||
callsign: value.callsign.clone().unwrap_or_default(),
|
||||
region: value.region.clone().unwrap_or_default(),
|
||||
frequency: value.frequency.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_source_id(value: &SourceId) -> EthSourceId {
|
||||
EthSourceId {
|
||||
kind: value.kind.clone(),
|
||||
deviceId: value.device_id.clone().unwrap_or_default(),
|
||||
channel: value.channel.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_key(value: &StreamKey) -> EthStreamKey {
|
||||
EthStreamKey {
|
||||
version: value.version,
|
||||
hasBroadcast: value.broadcast.is_some(),
|
||||
broadcast: value
|
||||
.broadcast
|
||||
.as_ref()
|
||||
.map(eth_broadcast_id)
|
||||
.unwrap_or_else(|| EthBroadcastId {
|
||||
standard: String::new(),
|
||||
hasTransportStreamId: false,
|
||||
transportStreamId: 0,
|
||||
hasProgramNumber: false,
|
||||
programNumber: 0,
|
||||
virtualChannel: String::new(),
|
||||
callsign: String::new(),
|
||||
region: String::new(),
|
||||
frequency: String::new(),
|
||||
}),
|
||||
hasSource: value.source.is_some(),
|
||||
source: value
|
||||
.source
|
||||
.as_ref()
|
||||
.map(eth_source_id)
|
||||
.unwrap_or_else(|| EthSourceId {
|
||||
kind: String::new(),
|
||||
deviceId: String::new(),
|
||||
channel: String::new(),
|
||||
}),
|
||||
profile: value.profile.clone().unwrap_or_default(),
|
||||
variant: value.variant.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_descriptor(value: &StreamDescriptor) -> EthStreamDescriptor {
|
||||
EthStreamDescriptor {
|
||||
id: value.id.0.clone(),
|
||||
title: value.title.clone(),
|
||||
number: value.number.clone().unwrap_or_default(),
|
||||
source: value.source.clone(),
|
||||
metadata: eth_metadata(&value.metadata),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_transport_descriptor(
|
||||
value: &StreamTransportDescriptor,
|
||||
) -> EthStreamTransportDescriptor {
|
||||
match value {
|
||||
StreamTransportDescriptor::RelayMoq {
|
||||
url,
|
||||
broadcast_name,
|
||||
track_name,
|
||||
} => EthStreamTransportDescriptor {
|
||||
kind: 0,
|
||||
url: url.clone(),
|
||||
endpoint: String::new(),
|
||||
broadcastName: broadcast_name.clone(),
|
||||
trackName: track_name.clone(),
|
||||
},
|
||||
StreamTransportDescriptor::IrohDirect {
|
||||
endpoint,
|
||||
broadcast_name,
|
||||
track_name,
|
||||
} => EthStreamTransportDescriptor {
|
||||
kind: 1,
|
||||
url: String::new(),
|
||||
endpoint: endpoint.clone(),
|
||||
broadcastName: broadcast_name.clone(),
|
||||
trackName: track_name.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_control_announcement(
|
||||
value: &StreamControlAnnouncement,
|
||||
) -> EthStreamControlAnnouncement {
|
||||
EthStreamControlAnnouncement {
|
||||
stream: eth_stream_descriptor(&value.stream),
|
||||
transports: value
|
||||
.transports
|
||||
.iter()
|
||||
.map(eth_stream_transport_descriptor)
|
||||
.collect(),
|
||||
updatedUnixMs: value.updated_unix_ms,
|
||||
ttlMs: value.ttl_ms,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_chunk_id(value: &ChunkId) -> Result<EthChunkId, EthCommitmentError> {
|
||||
Ok(EthChunkId {
|
||||
streamId: value.stream_id.0.clone(),
|
||||
epochId: value.epoch_id.clone(),
|
||||
chunkIndex: value.chunk_index,
|
||||
chunkHash: parse_b256(&value.chunk_hash)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eth_manifest_variant(
|
||||
value: &ManifestVariant,
|
||||
) -> Result<EthManifestVariant, EthCommitmentError> {
|
||||
Ok(EthManifestVariant {
|
||||
variantId: value.variant_id.clone(),
|
||||
streamId: value.stream_id.0.clone(),
|
||||
chunkStartIndex: value.chunk_start_index,
|
||||
totalChunks: value.total_chunks,
|
||||
merkleRoot: parse_b256(&value.merkle_root)?,
|
||||
chunkHashes: value
|
||||
.chunk_hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
metadata: eth_metadata(&value.metadata),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eth_manifest_body(value: &ManifestBody) -> Result<EthManifestBody, EthCommitmentError> {
|
||||
let variants = value
|
||||
.variants
|
||||
.as_ref()
|
||||
.map(|variants| {
|
||||
variants
|
||||
.iter()
|
||||
.map(eth_manifest_variant)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(EthManifestBody {
|
||||
streamId: value.stream_id.0.clone(),
|
||||
epochId: value.epoch_id.clone(),
|
||||
chunkDurationMs: value.chunk_duration_ms,
|
||||
totalChunks: value.total_chunks,
|
||||
chunkStartIndex: value.chunk_start_index,
|
||||
encoderProfileId: value.encoder_profile_id.clone(),
|
||||
merkleRoot: parse_b256(&value.merkle_root)?,
|
||||
createdUnixMs: value.created_unix_ms,
|
||||
metadata: eth_metadata(&value.metadata),
|
||||
chunkHashes: value
|
||||
.chunk_hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn manifest_body_eip712_signing_hash(value: &ManifestBody) -> Result<B256, EthCommitmentError> {
|
||||
Ok(eth_manifest_body(value)?.eip712_signing_hash(&manifest_eip712_domain()))
|
||||
}
|
||||
|
||||
pub fn eth_manifest_signature(value: &ManifestSignature) -> EthManifestSignature {
|
||||
EthManifestSignature {
|
||||
signerId: value.signer_id.clone(),
|
||||
alg: value.alg.clone(),
|
||||
signature: parse_bytes(&value.signature).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_manifest(value: &Manifest) -> Result<EthManifest, EthCommitmentError> {
|
||||
Ok(EthManifest {
|
||||
body: eth_manifest_body(&value.body)?,
|
||||
manifestId: parse_b256(&value.manifest_id)?,
|
||||
signatures: value
|
||||
.signatures
|
||||
.iter()
|
||||
.map(eth_manifest_signature)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn keccak_merkle_root(leaves: &[B256]) -> Result<B256, EthCommitmentError> {
|
||||
if leaves.is_empty() {
|
||||
return Err(EthCommitmentError::Empty);
|
||||
}
|
||||
let mut nodes = leaves.to_vec();
|
||||
while nodes.len() > 1 {
|
||||
if nodes.len() % 2 == 1 {
|
||||
if let Some(last) = nodes.last().copied() {
|
||||
nodes.push(last);
|
||||
}
|
||||
}
|
||||
let mut parents = Vec::with_capacity(nodes.len() / 2);
|
||||
for pair in nodes.chunks(2) {
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(pair[0].as_slice());
|
||||
merged[32..].copy_from_slice(pair[1].as_slice());
|
||||
parents.push(keccak256(merged));
|
||||
}
|
||||
nodes = parents;
|
||||
}
|
||||
Ok(nodes[0])
|
||||
}
|
||||
|
||||
pub fn ethereum_merkle_root_from_hashes(hashes: &[String]) -> Result<String, EthCommitmentError> {
|
||||
let leaves = hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(format!("0x{}", hex::encode(keccak_merkle_root(&leaves)?)))
|
||||
}
|
||||
|
||||
pub fn ethereum_merkle_proof_for_index(
|
||||
hashes: &[String],
|
||||
index: usize,
|
||||
) -> Result<Vec<String>, EthCommitmentError> {
|
||||
if hashes.is_empty() {
|
||||
return Err(EthCommitmentError::Empty);
|
||||
}
|
||||
if index >= hashes.len() {
|
||||
return Err(EthCommitmentError::InvalidHex(format!(
|
||||
"index {index} out of bounds"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut nodes = hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut proof = Vec::new();
|
||||
let mut pos = index;
|
||||
while nodes.len() > 1 {
|
||||
if nodes.len() % 2 == 1 {
|
||||
if let Some(last) = nodes.last().copied() {
|
||||
nodes.push(last);
|
||||
}
|
||||
}
|
||||
let sibling_index = if pos % 2 == 0 { pos + 1 } else { pos - 1 };
|
||||
let sibling = nodes[sibling_index];
|
||||
proof.push(format!("0x{}", hex::encode(sibling)));
|
||||
|
||||
let mut parents = Vec::with_capacity(nodes.len() / 2);
|
||||
for pair in nodes.chunks(2) {
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(pair[0].as_slice());
|
||||
merged[32..].copy_from_slice(pair[1].as_slice());
|
||||
parents.push(keccak256(merged));
|
||||
}
|
||||
nodes = parents;
|
||||
pos /= 2;
|
||||
}
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
pub fn verify_ethereum_merkle_proof(
|
||||
leaf_hash: &str,
|
||||
mut index: usize,
|
||||
branch: &[String],
|
||||
expected_root: &str,
|
||||
) -> bool {
|
||||
let Ok(mut acc) = parse_b256(leaf_hash) else {
|
||||
return false;
|
||||
};
|
||||
for sibling_hex in branch {
|
||||
let Ok(sibling) = parse_b256(sibling_hex) else {
|
||||
return false;
|
||||
};
|
||||
let (left, right) = if index % 2 == 0 {
|
||||
(acc, sibling)
|
||||
} else {
|
||||
(sibling, acc)
|
||||
};
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(left.as_slice());
|
||||
merged[32..].copy_from_slice(right.as_slice());
|
||||
acc = keccak256(merged);
|
||||
index /= 2;
|
||||
}
|
||||
match parse_b256(expected_root) {
|
||||
Ok(root) => acc == root,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream_id_commitment(stream_id: &str) -> ChainCommitment {
|
||||
commitment(SCHEME_STREAM_ID_KECCAK, keccak256(stream_id.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn broadcast_id_commitment(value: &BroadcastId) -> ChainCommitment {
|
||||
abi_commitment("broadcast-id-abi-keccak256-v1", ð_broadcast_id(value))
|
||||
}
|
||||
|
||||
pub fn stream_key_commitment(value: &StreamKey) -> ChainCommitment {
|
||||
abi_commitment("stream-key-abi-keccak256-v1", ð_stream_key(value))
|
||||
}
|
||||
|
||||
pub fn stream_descriptor_commitments(value: &StreamDescriptor) -> Vec<ChainCommitment> {
|
||||
vec![
|
||||
stream_id_commitment(&value.id.0),
|
||||
abi_commitment(SCHEME_STREAM_DESCRIPTOR_ABI, ð_stream_descriptor(value)),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn control_announcement_commitments(value: &StreamControlAnnouncement) -> Vec<ChainCommitment> {
|
||||
vec![abi_commitment(
|
||||
SCHEME_CONTROL_ANNOUNCEMENT_ABI,
|
||||
ð_stream_control_announcement(value),
|
||||
)]
|
||||
}
|
||||
|
||||
fn manifest_data_root_commitment(
|
||||
body: &ManifestBody,
|
||||
) -> Result<ChainCommitment, EthCommitmentError> {
|
||||
let root = if let Some(variants) = body
|
||||
.variants
|
||||
.as_ref()
|
||||
.filter(|variants| !variants.is_empty())
|
||||
{
|
||||
let roots = variants
|
||||
.iter()
|
||||
.map(|variant| variant.merkle_root.clone())
|
||||
.collect::<Vec<_>>();
|
||||
ethereum_merkle_root_from_hashes(&roots)?
|
||||
} else {
|
||||
ethereum_merkle_root_from_hashes(&body.chunk_hashes)?
|
||||
};
|
||||
Ok(ChainCommitment {
|
||||
chain: ETHEREUM_CHAIN.to_string(),
|
||||
scheme: SCHEME_MANIFEST_DATA_ROOT.to_string(),
|
||||
digest: root,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn manifest_commitments(value: &Manifest) -> Result<Vec<ChainCommitment>, EthCommitmentError> {
|
||||
let eth_body = eth_manifest_body(&value.body)?;
|
||||
let eth_manifest = eth_manifest(value)?;
|
||||
Ok(vec![
|
||||
manifest_data_root_commitment(&value.body)?,
|
||||
abi_commitment(SCHEME_MANIFEST_BODY_ABI, ð_body),
|
||||
abi_commitment(SCHEME_MANIFEST_ENVELOPE_ABI, ð_manifest),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn manifest_commitment_digest(
|
||||
value: &Manifest,
|
||||
scheme: &str,
|
||||
) -> Result<Option<String>, EthCommitmentError> {
|
||||
Ok(manifest_commitments(value)?
|
||||
.into_iter()
|
||||
.find(|commitment| commitment.scheme == scheme)
|
||||
.map(|commitment| commitment.digest))
|
||||
}
|
||||
|
||||
pub fn manifest_observation_header(
|
||||
value: &Manifest,
|
||||
parent_observation_hash: Option<&str>,
|
||||
locator_hash: &str,
|
||||
sequence: u64,
|
||||
) -> Result<EthObservationHeader, EthCommitmentError> {
|
||||
let data_root = manifest_commitment_digest(value, SCHEME_MANIFEST_DATA_ROOT)?
|
||||
.ok_or(EthCommitmentError::Empty)?;
|
||||
|
||||
Ok(EthObservationHeader {
|
||||
streamHash: keccak256(value.body.stream_id.0.as_bytes()),
|
||||
epochHash: keccak256(value.body.epoch_id.as_bytes()),
|
||||
parentObservationHash: parse_b256(parent_observation_hash.unwrap_or(ZERO_B256_HEX))?,
|
||||
dataRoot: parse_b256(&data_root)?,
|
||||
locatorHash: parse_b256(locator_hash)?,
|
||||
observedUnixMs: value.body.created_unix_ms,
|
||||
sequence,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn observation_header_hash(value: &EthObservationHeader) -> String {
|
||||
b256_hex(keccak256(value.abi_encode()))
|
||||
}
|
||||
|
||||
pub fn observation_slot_hash(
|
||||
stream_hash: &str,
|
||||
epoch_hash: &str,
|
||||
) -> Result<String, EthCommitmentError> {
|
||||
let stream_hash = parse_b256(stream_hash)?;
|
||||
let epoch_hash = parse_b256(epoch_hash)?;
|
||||
Ok(b256_hex(keccak256((stream_hash, epoch_hash).abi_encode())))
|
||||
}
|
||||
|
||||
pub fn manifest_commitments_match(value: &Manifest) -> Result<bool, EthCommitmentError> {
|
||||
let present = value
|
||||
.commitments
|
||||
.iter()
|
||||
.filter(|item| item.chain == ETHEREUM_CHAIN)
|
||||
.collect::<Vec<_>>();
|
||||
if present.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
let expected = manifest_commitments(value)?;
|
||||
Ok(present.into_iter().all(|actual| expected.contains(actual)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ec_core::{ManifestBody, StreamId};
|
||||
|
||||
fn sample_body() -> ManifestBody {
|
||||
let chunk_hashes = vec![
|
||||
blake3::hash(b"chunk0").to_hex().to_string(),
|
||||
blake3::hash(b"chunk1").to_hex().to_string(),
|
||||
];
|
||||
let merkle_root = ec_core::merkle_root_from_hashes(&chunk_hashes).unwrap();
|
||||
ManifestBody {
|
||||
stream_id: StreamId("ec/stream/v1/broadcast/atsc/tsid-42/program-3".to_string()),
|
||||
epoch_id: "epoch-1".to_string(),
|
||||
chunk_duration_ms: 2000,
|
||||
total_chunks: 2,
|
||||
chunk_start_index: 10,
|
||||
encoder_profile_id: "deterministic-h264-aac".to_string(),
|
||||
merkle_root,
|
||||
created_unix_ms: 1234,
|
||||
metadata: vec![StreamMetadata {
|
||||
key: "callsign".to_string(),
|
||||
value: "KCBS".to_string(),
|
||||
}],
|
||||
chunk_hashes,
|
||||
variants: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keccak_merkle_root_and_proof_roundtrip() {
|
||||
let body = sample_body();
|
||||
let root = ethereum_merkle_root_from_hashes(&body.chunk_hashes).unwrap();
|
||||
let proof = ethereum_merkle_proof_for_index(&body.chunk_hashes, 1).unwrap();
|
||||
assert!(verify_ethereum_merkle_proof(
|
||||
&body.chunk_hashes[1],
|
||||
1,
|
||||
&proof,
|
||||
&root
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_commitments_are_stable_and_match_present_values() {
|
||||
let body = sample_body();
|
||||
let manifest_id = body.manifest_id().unwrap();
|
||||
let mut manifest = Manifest {
|
||||
body,
|
||||
manifest_id,
|
||||
signatures: Vec::new(),
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
let commitments = manifest_commitments(&manifest).unwrap();
|
||||
assert_eq!(commitments.len(), 3);
|
||||
manifest.commitments = commitments.clone();
|
||||
assert!(manifest_commitments_match(&manifest).unwrap());
|
||||
manifest.commitments[0].digest = "0xdeadbeef".to_string();
|
||||
assert!(!manifest_commitments_match(&manifest).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_body_eip712_hash_is_stable() {
|
||||
let body = sample_body();
|
||||
let h1 = manifest_body_eip712_signing_hash(&body).unwrap();
|
||||
let h2 = manifest_body_eip712_signing_hash(&body).unwrap();
|
||||
assert_eq!(h1, h2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_observation_header_uses_manifest_data_root() {
|
||||
let body = sample_body();
|
||||
let manifest_id = body.manifest_id().unwrap();
|
||||
let mut manifest = Manifest {
|
||||
body,
|
||||
manifest_id,
|
||||
signatures: Vec::new(),
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
manifest.commitments = manifest_commitments(&manifest).unwrap();
|
||||
|
||||
let locator_hash = keccak256_bytes_hex(b"locator");
|
||||
let header = manifest_observation_header(&manifest, None, &locator_hash, 7).unwrap();
|
||||
assert_eq!(header.sequence, 7);
|
||||
assert_eq!(header.observedUnixMs, manifest.body.created_unix_ms);
|
||||
assert_eq!(
|
||||
b256_hex(header.dataRoot),
|
||||
manifest_commitment_digest(&manifest, SCHEME_MANIFEST_DATA_ROOT)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
observation_slot_hash(&b256_hex(header.streamHash), &b256_hex(header.epochHash))
|
||||
.unwrap(),
|
||||
b256_hex(keccak256(
|
||||
(header.streamHash, header.epochHash).abi_encode()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_descriptor_commitments_include_stream_id_and_descriptor_hashes() {
|
||||
let descriptor = StreamDescriptor {
|
||||
id: StreamId("ec/stream/v1/source/test/device-a/channel-b".to_string()),
|
||||
title: "Test".to_string(),
|
||||
number: Some("2.1".to_string()),
|
||||
source: "control".to_string(),
|
||||
metadata: vec![StreamMetadata {
|
||||
key: "broadcast".to_string(),
|
||||
value: "la-nbc".to_string(),
|
||||
}],
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
let commitments = stream_descriptor_commitments(&descriptor);
|
||||
assert_eq!(commitments.len(), 2);
|
||||
assert_eq!(commitments[0].scheme, SCHEME_STREAM_ID_KECCAK);
|
||||
assert_eq!(commitments[1].scheme, SCHEME_STREAM_DESCRIPTOR_ABI);
|
||||
}
|
||||
}
|
||||
|
|
@ -555,39 +555,9 @@ fn lineup_from_json_value(json: &Value, device_id: Option<&DeviceId>) -> Result<
|
|||
));
|
||||
}
|
||||
|
||||
if let Some(callsign) = json_string_field(entry, &["CallSign", "Callsign", "CallSignRaw"]) {
|
||||
metadata.push(ChannelMetadata::Callsign(callsign));
|
||||
}
|
||||
if let Some(network) = json_string_field(entry, &["Network"]) {
|
||||
metadata.push(ChannelMetadata::Network(network));
|
||||
}
|
||||
if let Some(region) = json_string_field(entry, &["Region", "Market"]) {
|
||||
metadata.push(ChannelMetadata::Region(region));
|
||||
}
|
||||
if let Some(frequency) = json_string_field(entry, &["Frequency", "FrequencyHz"]) {
|
||||
metadata.push(ChannelMetadata::Frequency(frequency));
|
||||
}
|
||||
|
||||
let program_id = json_u16_field(entry, &["ProgramNumber", "ProgramID", "Program"]);
|
||||
|
||||
if let Some(obj) = entry.as_object() {
|
||||
for (key, value) in obj.iter() {
|
||||
if key == "GuideNumber"
|
||||
|| key == "GuideName"
|
||||
|| key == "Tags"
|
||||
|| key == "URL"
|
||||
|| key == "CallSign"
|
||||
|| key == "Callsign"
|
||||
|| key == "CallSignRaw"
|
||||
|| key == "Network"
|
||||
|| key == "Region"
|
||||
|| key == "Market"
|
||||
|| key == "Frequency"
|
||||
|| key == "FrequencyHz"
|
||||
|| key == "ProgramNumber"
|
||||
|| key == "ProgramID"
|
||||
|| key == "Program"
|
||||
{
|
||||
if key == "GuideNumber" || key == "GuideName" || key == "Tags" || key == "URL" {
|
||||
continue;
|
||||
}
|
||||
metadata.push(ChannelMetadata::Extra(key.clone(), value.to_string()));
|
||||
|
|
@ -598,7 +568,7 @@ fn lineup_from_json_value(json: &Value, device_id: Option<&DeviceId>) -> Result<
|
|||
id,
|
||||
name: guide_name,
|
||||
number: parsed.guide_number,
|
||||
program_id,
|
||||
program_id: None,
|
||||
metadata,
|
||||
};
|
||||
|
||||
|
|
@ -613,44 +583,6 @@ fn lineup_from_json_value(json: &Value, device_id: Option<&DeviceId>) -> Result<
|
|||
Ok(output)
|
||||
}
|
||||
|
||||
fn json_string_field(value: &Value, keys: &[&str]) -> Option<String> {
|
||||
let obj = value.as_object()?;
|
||||
for key in keys {
|
||||
let Some(value) = obj.get(*key) else {
|
||||
continue;
|
||||
};
|
||||
let text = match value {
|
||||
Value::String(text) => text.trim().to_string(),
|
||||
Value::Number(number) => number.to_string(),
|
||||
_ => continue,
|
||||
};
|
||||
if !text.is_empty() {
|
||||
return Some(text);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn json_u16_field(value: &Value, keys: &[&str]) -> Option<u16> {
|
||||
let obj = value.as_object()?;
|
||||
for key in keys {
|
||||
let Some(value) = obj.get(*key) else {
|
||||
continue;
|
||||
};
|
||||
let parsed = match value {
|
||||
Value::Number(number) => number
|
||||
.as_u64()
|
||||
.and_then(|number| u16::try_from(number).ok()),
|
||||
Value::String(text) => text.trim().parse::<u16>().ok(),
|
||||
_ => None,
|
||||
};
|
||||
if parsed.is_some() {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -716,9 +648,6 @@ mod tests {
|
|||
"GuideName": "KCBS-HD",
|
||||
"Tags": "drm,encrypted,",
|
||||
"URL": "http://hdhr/auto/v2.1",
|
||||
"CallSign": "KCBS",
|
||||
"ProgramNumber": "3",
|
||||
"Frequency": "573000000",
|
||||
"Foo": "Bar"
|
||||
},
|
||||
{
|
||||
|
|
@ -733,17 +662,8 @@ mod tests {
|
|||
assert_eq!(entries[0].channel.id.0, "hdhr:ABCDEF01:2.1");
|
||||
assert_eq!(entries[0].channel.name, "KCBS-HD");
|
||||
assert_eq!(entries[0].channel.number.as_deref(), Some("2.1"));
|
||||
assert_eq!(entries[0].channel.program_id, Some(3));
|
||||
assert_eq!(entries[0].stream_url, "http://hdhr/auto/v2.1");
|
||||
assert!(entries[0].tags.iter().any(|t| t == "drm"));
|
||||
assert!(entries[0].channel.metadata.iter().any(|m| match m {
|
||||
ChannelMetadata::Callsign(value) => value == "KCBS",
|
||||
_ => false,
|
||||
}));
|
||||
assert!(entries[0].channel.metadata.iter().any(|m| match m {
|
||||
ChannelMetadata::Frequency(value) => value == "573000000",
|
||||
_ => false,
|
||||
}));
|
||||
assert!(entries[0].channel.metadata.iter().any(|m| match m {
|
||||
ChannelMetadata::Extra(key, value) => key == "guide_number" && value == "2.1",
|
||||
_ => false,
|
||||
|
|
|
|||
|
|
@ -730,7 +730,6 @@ mod tests {
|
|||
},
|
||||
manifest_id: "m".to_string(),
|
||||
signatures: Vec::new(),
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
let bytes = encode_manifest_frame(&manifest).unwrap();
|
||||
let decoded = decode_manifest_frame(&bytes).unwrap();
|
||||
|
|
@ -769,7 +768,6 @@ mod tests {
|
|||
body,
|
||||
manifest_id: manifest_id.clone(),
|
||||
signatures: vec![sig],
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
let bytes = encode_manifest_frame(&manifest).unwrap();
|
||||
let decoded = decode_manifest_frame(&bytes).unwrap();
|
||||
|
|
|
|||
|
|
@ -13,11 +13,9 @@ ec-crypto = { path = "../ec-crypto" }
|
|||
ec-direct = { path = "../ec-direct" }
|
||||
ec-moq = { path = "../ec-moq" }
|
||||
ec-chopper = { path = "../ec-chopper" }
|
||||
ec-eth = { path = "../ec-eth" }
|
||||
ec-hdhomerun = { path = "../ec-hdhomerun" }
|
||||
ec-iroh = { path = "../ec-iroh" }
|
||||
ec-linux-iptv = { path = "../ec-linux-iptv" }
|
||||
ec-ts = { path = "../ec-ts" }
|
||||
hex = "0.4"
|
||||
iroh = "0.96"
|
||||
just-webrtc = "0.2"
|
||||
|
|
@ -40,9 +38,8 @@ hang = "0.14.0"
|
|||
moq-mux = "0.2.1"
|
||||
moq-lite = "0.14.0"
|
||||
moq-native = { version = "0.13.1", default-features = true }
|
||||
headless_chrome = "1"
|
||||
tokio-util = "0.7"
|
||||
url = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
headless_chrome = "1"
|
||||
which = "6"
|
||||
|
|
|
|||
|
|
@ -1,220 +0,0 @@
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use ec_core::Manifest;
|
||||
use ec_eth::{
|
||||
b256_hex, keccak256_bytes_hex, manifest_observation_header, observation_header_hash,
|
||||
observation_slot_hash,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tokio::process::Command;
|
||||
|
||||
pub const OBSERVATION_RPC_URL_ENV: &str = "EVERY_CHANNEL_OBSERVATION_RPC_URL";
|
||||
pub const OBSERVATION_LEDGER_ENV: &str = "EVERY_CHANNEL_OBSERVATION_LEDGER";
|
||||
pub const OBSERVATION_PRIVATE_KEY_ENV: &str = "EVERY_CHANNEL_OBSERVATION_PRIVATE_KEY";
|
||||
pub const OBSERVATION_PRIVATE_KEY_FILE_ENV: &str = "EVERY_CHANNEL_OBSERVATION_PRIVATE_KEY_FILE";
|
||||
pub const OBSERVATION_PARENT_HASH_ENV: &str = "EVERY_CHANNEL_OBSERVATION_PARENT_HASH";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObservationSinkOptions {
|
||||
pub rpc_url: Option<String>,
|
||||
pub ledger: Option<String>,
|
||||
pub private_key: Option<String>,
|
||||
pub private_key_file: Option<PathBuf>,
|
||||
pub parent_hash: Option<String>,
|
||||
pub timeout_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObservationSink {
|
||||
rpc_url: String,
|
||||
ledger: String,
|
||||
private_key: String,
|
||||
parent_hash: Option<String>,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ManifestObservationLocator {
|
||||
pub transport: String,
|
||||
pub broadcast_name: String,
|
||||
pub track_name: String,
|
||||
pub manifest_track: String,
|
||||
pub stream_id: String,
|
||||
pub epoch_id: String,
|
||||
pub manifest_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmittedObservation {
|
||||
pub observation_hash: String,
|
||||
pub slot_hash: String,
|
||||
pub stream_hash: String,
|
||||
pub epoch_hash: String,
|
||||
pub data_root: String,
|
||||
pub locator_hash: String,
|
||||
pub sequence: u64,
|
||||
}
|
||||
|
||||
fn env_value(name: &str) -> Option<String> {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn normalize_private_key(value: &str) -> Result<String> {
|
||||
let trimmed = value.trim().strip_prefix("0x").unwrap_or(value.trim());
|
||||
let bytes = hex::decode(trimmed).context("observation private key must be hex")?;
|
||||
if bytes.len() != 32 {
|
||||
return Err(anyhow!("observation private key must be 32 bytes"));
|
||||
}
|
||||
Ok(format!("0x{}", hex::encode(bytes)))
|
||||
}
|
||||
|
||||
fn read_private_key_file(path: &PathBuf) -> Result<String> {
|
||||
fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read observation private key {}", path.display()))
|
||||
.and_then(|value| normalize_private_key(&value))
|
||||
}
|
||||
|
||||
impl ObservationSink {
|
||||
pub fn from_options(options: ObservationSinkOptions) -> Result<Option<Self>> {
|
||||
let rpc_url = options
|
||||
.rpc_url
|
||||
.or_else(|| env_value(OBSERVATION_RPC_URL_ENV));
|
||||
let ledger = options.ledger.or_else(|| env_value(OBSERVATION_LEDGER_ENV));
|
||||
let private_key = options
|
||||
.private_key
|
||||
.or_else(|| env_value(OBSERVATION_PRIVATE_KEY_ENV));
|
||||
let private_key_file = options
|
||||
.private_key_file
|
||||
.or_else(|| env_value(OBSERVATION_PRIVATE_KEY_FILE_ENV).map(PathBuf::from));
|
||||
let parent_hash = options
|
||||
.parent_hash
|
||||
.or_else(|| env_value(OBSERVATION_PARENT_HASH_ENV));
|
||||
|
||||
if rpc_url.is_none()
|
||||
&& ledger.is_none()
|
||||
&& private_key.is_none()
|
||||
&& private_key_file.is_none()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let rpc_url = rpc_url.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"set --observation-rpc-url or {} for observation submission",
|
||||
OBSERVATION_RPC_URL_ENV
|
||||
)
|
||||
})?;
|
||||
let ledger = ledger.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"set --observation-ledger or {} for observation submission",
|
||||
OBSERVATION_LEDGER_ENV
|
||||
)
|
||||
})?;
|
||||
let private_key = match (private_key, private_key_file) {
|
||||
(Some(value), _) => normalize_private_key(&value)?,
|
||||
(None, Some(path)) => read_private_key_file(&path)?,
|
||||
(None, None) => {
|
||||
return Err(anyhow!(
|
||||
"set --observation-private-key, --observation-private-key-file, {}, or {}",
|
||||
OBSERVATION_PRIVATE_KEY_ENV,
|
||||
OBSERVATION_PRIVATE_KEY_FILE_ENV
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(Self {
|
||||
rpc_url,
|
||||
ledger,
|
||||
private_key,
|
||||
parent_hash,
|
||||
timeout: Duration::from_millis(options.timeout_ms.max(1_000)),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn submit_manifest(
|
||||
&self,
|
||||
manifest: &Manifest,
|
||||
locator: ManifestObservationLocator,
|
||||
) -> Result<SubmittedObservation> {
|
||||
let locator_json =
|
||||
serde_json::to_string(&locator).context("failed to encode observation locator")?;
|
||||
let locator_hash = keccak256_bytes_hex(locator_json.as_bytes());
|
||||
let sequence = manifest.body.chunk_start_index;
|
||||
let header = manifest_observation_header(
|
||||
manifest,
|
||||
self.parent_hash.as_deref(),
|
||||
&locator_hash,
|
||||
sequence,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
|
||||
let stream_hash = b256_hex(header.streamHash);
|
||||
let epoch_hash = b256_hex(header.epochHash);
|
||||
let data_root = b256_hex(header.dataRoot);
|
||||
let observation_hash = observation_header_hash(&header);
|
||||
let slot_hash =
|
||||
observation_slot_hash(&stream_hash, &epoch_hash).map_err(|err| anyhow!(err))?;
|
||||
let tuple = format!(
|
||||
"({},{},{},{},{},{},{})",
|
||||
stream_hash,
|
||||
epoch_hash,
|
||||
b256_hex(header.parentObservationHash),
|
||||
data_root,
|
||||
locator_hash,
|
||||
header.observedUnixMs,
|
||||
header.sequence
|
||||
);
|
||||
|
||||
let mut cmd = Command::new("cast");
|
||||
cmd.arg("send")
|
||||
.arg(&self.ledger)
|
||||
.arg("proposeObservation((bytes32,bytes32,bytes32,bytes32,bytes32,uint64,uint64))")
|
||||
.arg(&tuple)
|
||||
.arg("--rpc-url")
|
||||
.arg(&self.rpc_url)
|
||||
.arg("--private-key")
|
||||
.arg(&self.private_key);
|
||||
|
||||
let output = tokio::time::timeout(self.timeout, cmd.output())
|
||||
.await
|
||||
.context("timed out submitting observation")?
|
||||
.context("failed to run cast for observation submission")?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"cast observation submission failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(SubmittedObservation {
|
||||
observation_hash,
|
||||
slot_hash,
|
||||
stream_hash,
|
||||
epoch_hash,
|
||||
data_root,
|
||||
locator_hash,
|
||||
sequence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn normalize_private_key_accepts_prefixed_and_plain_hex() {
|
||||
let plain = "11".repeat(32);
|
||||
assert_eq!(normalize_private_key(&plain).unwrap(), format!("0x{plain}"));
|
||||
assert_eq!(
|
||||
normalize_private_key(&format!("0x{plain}")).unwrap(),
|
||||
format!("0x{plain}")
|
||||
);
|
||||
assert!(normalize_private_key("abcd").is_err());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,9 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use clap::ValueEnum;
|
||||
use ec_chopper::{deterministic_h264_profile, ffmpeg_profile_args};
|
||||
use ec_core::{BroadcastId, SourceId};
|
||||
use ec_hdhomerun::{find_lineup_entry_by_name, find_lineup_entry_by_number, LineupEntry};
|
||||
use ec_core::SourceId;
|
||||
use ec_hdhomerun::{find_lineup_entry_by_name, find_lineup_entry_by_number};
|
||||
use ec_linux_iptv::LinuxDvbConfig;
|
||||
use ec_ts::probe_transport_stream_identity;
|
||||
use std::io::Read;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::thread;
|
||||
|
|
@ -12,9 +11,6 @@ use std::thread;
|
|||
pub trait StreamSource: Send {
|
||||
fn open_stream(&self) -> Result<Box<dyn Read + Send>>;
|
||||
fn source_id(&self) -> SourceId;
|
||||
fn broadcast_id(&self) -> Result<Option<BroadcastId>> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -28,8 +24,20 @@ pub struct HdhrSource {
|
|||
|
||||
impl StreamSource for HdhrSource {
|
||||
fn open_stream(&self) -> Result<Box<dyn Read + Send>> {
|
||||
let entry = resolve_hdhr_lineup_entry(self)?;
|
||||
Ok(Box::new(ec_hdhomerun::open_stream_entry(&entry, None)?))
|
||||
let device = resolve_hdhr_device(self)?;
|
||||
let lineup = ec_hdhomerun::fetch_lineup(&device)?;
|
||||
let entry = if let Some(channel) = &self.channel {
|
||||
find_lineup_entry_by_number(&lineup, channel)
|
||||
.or_else(|| find_lineup_entry_by_name(&lineup, channel))
|
||||
.ok_or_else(|| anyhow!("channel not found: {channel}"))?
|
||||
} else if let Some(name) = &self.name {
|
||||
find_lineup_entry_by_name(&lineup, name)
|
||||
.ok_or_else(|| anyhow!("channel not found: {name}"))?
|
||||
} else {
|
||||
return Err(anyhow!("--channel or --name required for hdhr"));
|
||||
};
|
||||
|
||||
Ok(Box::new(ec_hdhomerun::open_stream_entry(entry, None)?))
|
||||
}
|
||||
|
||||
fn source_id(&self) -> SourceId {
|
||||
|
|
@ -40,21 +48,6 @@ impl StreamSource for HdhrSource {
|
|||
channel: self.channel.clone().or_else(|| self.name.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn broadcast_id(&self) -> Result<Option<BroadcastId>> {
|
||||
let entry = resolve_hdhr_lineup_entry(self)?;
|
||||
let mut broadcast = entry.channel.broadcast_id("atsc");
|
||||
if broadcast.as_ref().is_none_or(|identity| {
|
||||
identity.transport_stream_id.is_none() || identity.program_number.is_none()
|
||||
}) {
|
||||
let probe = ec_hdhomerun::open_stream_entry(&entry, Some(2))?;
|
||||
broadcast = merge_broadcast_identity(
|
||||
broadcast,
|
||||
probe_transport_stream_broadcast(Box::new(probe), Some("atsc"))?,
|
||||
);
|
||||
}
|
||||
Ok(broadcast)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_hdhr_device(source: &HdhrSource) -> Result<ec_hdhomerun::HdhomerunDevice> {
|
||||
|
|
@ -79,23 +72,6 @@ fn resolve_hdhr_device(source: &HdhrSource) -> Result<ec_hdhomerun::HdhomerunDev
|
|||
.ok_or_else(|| anyhow!("no HDHomeRun devices found"))
|
||||
}
|
||||
|
||||
fn resolve_hdhr_lineup_entry(source: &HdhrSource) -> Result<LineupEntry> {
|
||||
let device = resolve_hdhr_device(source)?;
|
||||
let lineup = ec_hdhomerun::fetch_lineup(&device)?;
|
||||
let entry = if let Some(channel) = &source.channel {
|
||||
find_lineup_entry_by_number(&lineup, channel)
|
||||
.or_else(|| find_lineup_entry_by_name(&lineup, channel))
|
||||
.ok_or_else(|| anyhow!("channel not found: {channel}"))?
|
||||
} else if let Some(name) = &source.name {
|
||||
find_lineup_entry_by_name(&lineup, name)
|
||||
.ok_or_else(|| anyhow!("channel not found: {name}"))?
|
||||
} else {
|
||||
return Err(anyhow!("--channel or --name required for hdhr"));
|
||||
};
|
||||
|
||||
Ok(entry.clone())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LinuxDvbSource {
|
||||
pub adapter: u32,
|
||||
|
|
@ -150,16 +126,6 @@ impl StreamSource for TsSource {
|
|||
channel: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn broadcast_id(&self) -> Result<Option<BroadcastId>> {
|
||||
if self.input.starts_with("http://") || self.input.starts_with("https://") {
|
||||
let reader = ec_hdhomerun::open_stream_url(&self.input, Some(2))?;
|
||||
probe_transport_stream_broadcast(Box::new(reader), None)
|
||||
} else {
|
||||
let reader = std::fs::File::open(&self.input)?;
|
||||
probe_transport_stream_broadcast(Box::new(reader), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
|
|
@ -231,67 +197,6 @@ impl StreamSource for HlsSource {
|
|||
}
|
||||
}
|
||||
|
||||
fn probe_transport_stream_broadcast(
|
||||
reader: Box<dyn Read + Send>,
|
||||
fallback_standard: Option<&str>,
|
||||
) -> Result<Option<BroadcastId>> {
|
||||
let Some(identity) = probe_transport_stream_identity(reader, 256)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let virtual_channel = match (identity.major_channel_number, identity.minor_channel_number) {
|
||||
(Some(major), Some(minor)) => Some(format!("{major}.{minor}")),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(Some(BroadcastId {
|
||||
standard: identity
|
||||
.standard
|
||||
.or_else(|| fallback_standard.map(|value| value.to_string()))
|
||||
.unwrap_or_else(|| "mpegts".to_string()),
|
||||
transport_stream_id: Some(identity.transport_stream_id),
|
||||
program_number: identity.program_number,
|
||||
virtual_channel,
|
||||
callsign: identity.short_name,
|
||||
region: None,
|
||||
frequency: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn merge_broadcast_identity(
|
||||
base: Option<BroadcastId>,
|
||||
probed: Option<BroadcastId>,
|
||||
) -> Option<BroadcastId> {
|
||||
match (base, probed) {
|
||||
(None, other) => other,
|
||||
(some, None) => some,
|
||||
(Some(mut base), Some(probed)) => {
|
||||
if base.standard.trim().is_empty() {
|
||||
base.standard = probed.standard;
|
||||
}
|
||||
if base.transport_stream_id.is_none() {
|
||||
base.transport_stream_id = probed.transport_stream_id;
|
||||
}
|
||||
if base.program_number.is_none() {
|
||||
base.program_number = probed.program_number;
|
||||
}
|
||||
if base.virtual_channel.is_none() {
|
||||
base.virtual_channel = probed.virtual_channel;
|
||||
}
|
||||
if base.callsign.is_none() {
|
||||
base.callsign = probed.callsign;
|
||||
}
|
||||
if base.region.is_none() {
|
||||
base.region = probed.region;
|
||||
}
|
||||
if base.frequency.is_none() {
|
||||
base.frequency = probed.frequency;
|
||||
}
|
||||
Some(base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FfmpegChildStream {
|
||||
child: Child,
|
||||
stdout: std::process::ChildStdout,
|
||||
|
|
|
|||
|
|
@ -1,275 +0,0 @@
|
|||
use std::fs;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const ANVIL_PK0: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
|
||||
const ANVIL_PK1: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
||||
|
||||
fn env_required(key: &str) -> Option<String> {
|
||||
std::env::var(key)
|
||||
.ok()
|
||||
.map(|v| v.trim().to_string())
|
||||
.filter(|v| !v.is_empty())
|
||||
}
|
||||
|
||||
fn looks_drm(value: &str) -> bool {
|
||||
let value = value.to_lowercase();
|
||||
value.contains("drm")
|
||||
|| value.contains("encrypted")
|
||||
|| value.contains("protected")
|
||||
|| value.contains("copy")
|
||||
|| value.contains("widevine")
|
||||
}
|
||||
|
||||
fn autodiscover_hdhr_host_and_channel() -> Option<(String, String)> {
|
||||
let devices = ec_hdhomerun::discover().ok()?;
|
||||
let device = devices.into_iter().next()?;
|
||||
let lineup = ec_hdhomerun::fetch_lineup(&device).ok()?;
|
||||
let entry = lineup.iter().find(|e| {
|
||||
let tag_drm = e.tags.iter().any(|t| looks_drm(t));
|
||||
let raw_drm = e
|
||||
.raw
|
||||
.as_object()
|
||||
.map(|obj| {
|
||||
obj.iter()
|
||||
.any(|(k, v)| looks_drm(k) || looks_drm(&v.to_string()))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
!tag_drm && !raw_drm && e.channel.number.as_deref().unwrap_or("").trim() != ""
|
||||
})?;
|
||||
let host = device.ip.clone();
|
||||
let channel = entry
|
||||
.channel
|
||||
.number
|
||||
.clone()
|
||||
.or_else(|| Some(entry.channel.name.clone()))
|
||||
.unwrap_or_else(|| "2.1".to_string());
|
||||
Some((host, channel))
|
||||
}
|
||||
|
||||
fn ec_node_path() -> std::path::PathBuf {
|
||||
if let Ok(value) = std::env::var("EC_NODE_BIN") {
|
||||
return value.into();
|
||||
}
|
||||
if let Ok(value) = std::env::var("CARGO_BIN_EXE_ec_node") {
|
||||
return value.into();
|
||||
}
|
||||
if let Ok(value) = std::env::var("CARGO_BIN_EXE_ec-node") {
|
||||
return value.into();
|
||||
}
|
||||
let exe = std::env::current_exe().expect("current_exe");
|
||||
let debug_dir = exe
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.expect("expected target/debug/deps");
|
||||
debug_dir.join("ec-node")
|
||||
}
|
||||
|
||||
fn repo_root() -> std::path::PathBuf {
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.expect("workspace root")
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
fn require_tools() -> bool {
|
||||
["anvil", "cast", "forge", "ffmpeg"]
|
||||
.into_iter()
|
||||
.all(|tool| which::which(tool).is_ok())
|
||||
}
|
||||
|
||||
fn wait_for_anvil(rpc_url: &str) {
|
||||
let deadline = Instant::now() + Duration::from_secs(20);
|
||||
while Instant::now() < deadline {
|
||||
let status = Command::new("cast")
|
||||
.arg("block-number")
|
||||
.arg("--rpc-url")
|
||||
.arg(rpc_url)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
if matches!(status, Ok(status) if status.success()) {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(250));
|
||||
}
|
||||
panic!("anvil did not become ready");
|
||||
}
|
||||
|
||||
struct ChildGuard {
|
||||
child: Option<Child>,
|
||||
}
|
||||
|
||||
impl ChildGuard {
|
||||
fn new(child: Child) -> Self {
|
||||
Self { child: Some(child) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ChildGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(child) = self.child.as_mut() {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_field(line: &str, field: &str) -> Option<String> {
|
||||
let prefix = format!("{field}=");
|
||||
line.split_whitespace()
|
||||
.find_map(|part| part.strip_prefix(&prefix).map(|value| value.to_string()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn e2e_hdhr_manifest_observation_finalizes_on_anvil() {
|
||||
if !require_tools() {
|
||||
return;
|
||||
}
|
||||
|
||||
let host = env_required("EVERY_CHANNEL_E2E_HDHR_HOST");
|
||||
let channel = env_required("EVERY_CHANNEL_E2E_HDHR_CHANNEL");
|
||||
let (host, channel) = match (host, channel) {
|
||||
(Some(host), Some(channel)) => (host, channel),
|
||||
_ => match autodiscover_hdhr_host_and_channel() {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
},
|
||||
};
|
||||
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
let tmp = std::env::temp_dir().join(format!("ec-e2e-hdhr-chain-{ts}"));
|
||||
fs::create_dir_all(&tmp).unwrap();
|
||||
let port = 18_545 + (ts % 10_000) as u16;
|
||||
let rpc_url = format!("http://127.0.0.1:{port}");
|
||||
let anvil_log = fs::File::create(tmp.join("anvil.log")).unwrap();
|
||||
let _anvil = ChildGuard::new(
|
||||
Command::new("anvil")
|
||||
.arg("--port")
|
||||
.arg(port.to_string())
|
||||
.stdout(anvil_log)
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("failed to spawn anvil"),
|
||||
);
|
||||
|
||||
wait_for_anvil(&rpc_url);
|
||||
|
||||
let owner_file = tmp.join("owner.key");
|
||||
fs::write(&owner_file, ANVIL_PK0).unwrap();
|
||||
let deploy_json = tmp.join("observation-ledger-deploy.json");
|
||||
let deploy = Command::new(repo_root().join("scripts/op-stack/deploy-observation-ledger.sh"))
|
||||
.current_dir(repo_root())
|
||||
.env("EVERY_CHANNEL_RPC_URL", &rpc_url)
|
||||
.env("EVERY_CHANNEL_PRIVATE_KEY_FILE", &owner_file)
|
||||
.env("EVERY_CHANNEL_OBSERVATION_QUORUM", "1")
|
||||
.env("EVERY_CHANNEL_OBSERVATION_DEPLOY_OUT", &deploy_json)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect("failed to deploy observation ledger");
|
||||
assert!(deploy.success(), "deploy failed with {deploy}");
|
||||
|
||||
let deploy_value: serde_json::Value =
|
||||
serde_json::from_slice(&fs::read(&deploy_json).unwrap()).unwrap();
|
||||
let registry = deploy_value["registry"].as_str().unwrap();
|
||||
let ledger = deploy_value["ledger"].as_str().unwrap();
|
||||
|
||||
let witness = Command::new("cast")
|
||||
.arg("wallet")
|
||||
.arg("address")
|
||||
.arg("--private-key")
|
||||
.arg(ANVIL_PK1)
|
||||
.output()
|
||||
.expect("failed to derive witness address");
|
||||
assert!(witness.status.success());
|
||||
let witness = String::from_utf8(witness.stdout).unwrap();
|
||||
let witness = witness.trim();
|
||||
|
||||
let add_witness = Command::new("cast")
|
||||
.arg("send")
|
||||
.arg(registry)
|
||||
.arg("addWitness(address)")
|
||||
.arg(witness)
|
||||
.arg("--rpc-url")
|
||||
.arg(&rpc_url)
|
||||
.arg("--private-key")
|
||||
.arg(ANVIL_PK0)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect("failed to add witness");
|
||||
assert!(
|
||||
add_witness.success(),
|
||||
"add witness failed with {add_witness}"
|
||||
);
|
||||
|
||||
let ec_node = ec_node_path();
|
||||
let broadcast_name = format!("every.channel/e2e/blockchain/{ts}");
|
||||
let mut publisher = Command::new(&ec_node);
|
||||
publisher
|
||||
.env("EVERY_CHANNEL_MANIFEST_SIGNING_KEY", "11".repeat(32))
|
||||
.arg("moq-publish")
|
||||
.arg("--publish-manifests")
|
||||
.arg("--epoch-chunks")
|
||||
.arg("1")
|
||||
.arg("--max-chunks")
|
||||
.arg("1")
|
||||
.arg("--chunk-ms")
|
||||
.arg("2000")
|
||||
.arg("--broadcast-name")
|
||||
.arg(&broadcast_name)
|
||||
.arg("--observation-rpc-url")
|
||||
.arg(&rpc_url)
|
||||
.arg("--observation-ledger")
|
||||
.arg(ledger)
|
||||
.arg("--observation-private-key")
|
||||
.arg(ANVIL_PK1)
|
||||
.arg("--chunk-dir")
|
||||
.arg(tmp.join("chunks"))
|
||||
.arg("hdhr")
|
||||
.arg("--host")
|
||||
.arg(&host)
|
||||
.arg("--channel")
|
||||
.arg(&channel)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let mut child = publisher.spawn().expect("failed to spawn publisher");
|
||||
let stderr = child.stderr.take().expect("publisher stderr missing");
|
||||
let lines = BufReader::new(stderr)
|
||||
.lines()
|
||||
.filter_map(|line| line.ok())
|
||||
.collect::<Vec<_>>();
|
||||
let status = child.wait().expect("failed to wait for publisher");
|
||||
assert!(
|
||||
status.success(),
|
||||
"publisher exited with {status}: {lines:?}"
|
||||
);
|
||||
|
||||
let observation_line = lines
|
||||
.iter()
|
||||
.find(|line| line.starts_with("observation submitted:"))
|
||||
.expect("publisher did not submit an observation");
|
||||
let observation_hash = parse_field(observation_line, "observation_hash").unwrap();
|
||||
let slot_hash = parse_field(observation_line, "slot_hash").unwrap();
|
||||
|
||||
let finalized = Command::new("cast")
|
||||
.arg("call")
|
||||
.arg(ledger)
|
||||
.arg("finalizedObservationBySlot(bytes32)(bytes32)")
|
||||
.arg(slot_hash)
|
||||
.arg("--rpc-url")
|
||||
.arg(&rpc_url)
|
||||
.output()
|
||||
.expect("failed to read finalized slot");
|
||||
assert!(finalized.status.success());
|
||||
let finalized = String::from_utf8(finalized.stdout).unwrap();
|
||||
assert_eq!(finalized.trim(), observation_hash);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -24,24 +24,13 @@
|
|||
- Each chunk becomes a MoQ object in a group.
|
||||
- Objects are named and addressed deterministically.
|
||||
|
||||
5. Settlement rails
|
||||
|
||||
- Ethereum-compatible commitments mirror stream identity, manifests, and transport announcements.
|
||||
- Observation consensus is a separate rail on top of those commitments: a reality-derived
|
||||
`ObservationHeader` is hashed, witnessed, and finalized per `(stream, epoch)` slot.
|
||||
- The chain stores compact commitment pointers only; media bytes and full manifests remain on iroh,
|
||||
relays, and archive storage.
|
||||
- OP Stack is the current private-chain operator target, with `ecp-forge` as the head node for the
|
||||
first Sepolia-anchored testnet tranche.
|
||||
- Private-chain operation is a protocol extension, not a replacement for transport.
|
||||
|
||||
6. Relay mesh
|
||||
5. Relay mesh
|
||||
|
||||
- Relays cache objects and announce tracks.
|
||||
- iroh provides programmable topology and peer routing.
|
||||
- Multiple relays can serve identical objects.
|
||||
|
||||
7. Playback
|
||||
6. Playback
|
||||
|
||||
- Desktop: Tauri app that subscribes to tracks.
|
||||
- CLI: debugging, inspection, and headless clients.
|
||||
|
|
@ -54,30 +43,15 @@
|
|||
- Relay: stores and forwards MoQ objects.
|
||||
- Manager: configures nodes and applies policy.
|
||||
- Provisioner: bootstraps nodes and manages deployment.
|
||||
- Witness: attests to a reality-derived observation hash for a stream epoch.
|
||||
|
||||
## Determinism
|
||||
|
||||
- The same input with the same profile should yield identical chunks.
|
||||
- Chunk hashes are the primitive for availability and de-duplication.
|
||||
- Deterministic names allow relays to converge without coordination.
|
||||
- Observation consensus derives a second deterministic summary from the archive/manifests layer:
|
||||
`streamHash`, `epochHash`, `dataRoot`, and `locatorHash` become the on-chain observation header.
|
||||
- Local manifests keep BLAKE3 `manifest_id`s and `merkle+blake3` proofs, while the Ethereum rail
|
||||
adds Keccak ABI/data commitments and optional secp256k1 EIP-712 body signatures for settlement.
|
||||
- Discovery identity should prefer broadcast-scoped channel identity when available and only fall
|
||||
back to source-scoped IDs when the ingest path cannot yet prove a usable broadcast identity.
|
||||
- PAT-derived identity is accepted only when the stream exposes a single non-zero program; ambiguous
|
||||
multi-program TS inputs remain source-scoped to avoid accidental convergence on the wrong channel.
|
||||
- `ec-ts` parses ATSC PSIP at the table layer (`MGT`, `TVCT/CVCT`, `STT`, `RRT`, `EIT`, `ETT`),
|
||||
including `EIT` / `ETT` on the PIDs advertised by `MGT`.
|
||||
- Current discovery promotion uses `PAT` plus `VCT` fields; the rest of PSIP is parsed and preserved
|
||||
for inspection, validation, and future policy rather than guessed into the stream key.
|
||||
|
||||
## Time synchronization
|
||||
|
||||
- Chunk boundaries are derived from PCR and, when available, broadcast UTC (ATSC STT / DVB TDT/TOT).
|
||||
- ATSC STT is interpreted as GPS time plus offset correction, then converted to Unix time for chunk
|
||||
anchoring and diagnostics.
|
||||
- Unsynced sources remain source-scoped until broadcast time is present.
|
||||
- Discontinuities force a new chunk group boundary.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
Optional overrides:
|
||||
|
||||
```sh
|
||||
EVERY_CHANNEL_FORGE_HOST=https://git.every.channel \
|
||||
EVERY_CHANNEL_FORGE_HOST=https://forge.every.channel \
|
||||
EVERY_CHANNEL_FORGE_REPO=every-channel/every.channel \
|
||||
EVERY_CHANNEL_PROTECTED_BRANCH=main \
|
||||
EVERY_CHANNEL_REQUIRED_CHECKS="ci-gates / checks" \
|
||||
|
|
@ -31,7 +31,7 @@ EVERY_CHANNEL_REQUIRED_APPROVALS=1 \
|
|||
Token source order:
|
||||
|
||||
1. `EVERY_CHANNEL_FORGE_TOKEN` / `FORGE_TOKEN` / `CODEBERG_TOKEN` env var
|
||||
2. `secrets/forgejo-api-token.age` (preferred) via `agenix` or `age`
|
||||
3. `secrets/forge-token.age` or `secrets/codeberg-token.age` (compat) via `agenix` or `age`
|
||||
2. `secrets/forge-token.age` (preferred) via `agenix` or `age`
|
||||
3. `secrets/codeberg-token.age` (compat) via `agenix` or `age`
|
||||
|
||||
The token must have repository admin scope to edit branch protection.
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
# Sovereign Deploy: `ecp-forge`
|
||||
|
||||
This repository owns deployment of `git.every.channel` (Hetzner 300TB host).
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH access to `root@git.every.channel`.
|
||||
- Local key that matches host `authorized_keys` (default: `~/.ssh/id_ed25519`).
|
||||
- `nix` with flakes enabled.
|
||||
- For emergency Hetzner recovery, Robot Webservice credentials in 1Password item `Hetzner Robot`
|
||||
or `EVERY_CHANNEL_ROBOT_USER` / `EVERY_CHANNEL_ROBOT_PASSWORD`.
|
||||
|
||||
## Deploy
|
||||
|
||||
```sh
|
||||
./scripts/deploy-ecp-forge.sh
|
||||
```
|
||||
|
||||
For the OP Stack operator path and observation-rail validation, see:
|
||||
|
||||
```sh
|
||||
cat docs/OP_STACK_ECP_FORGE.md
|
||||
```
|
||||
|
||||
Equivalent:
|
||||
|
||||
```sh
|
||||
NIX_SSHOPTS="-o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519" \
|
||||
nix run nixpkgs#nixos-rebuild -- \
|
||||
--flake .#ecp-forge \
|
||||
--target-host root@git.every.channel \
|
||||
--build-host root@git.every.channel \
|
||||
--use-remote-sudo \
|
||||
switch
|
||||
```
|
||||
|
||||
## Overrides
|
||||
|
||||
- `EVERY_CHANNEL_FORGE_TARGET_HOST` (default `root@git.every.channel`)
|
||||
- `EVERY_CHANNEL_FORGE_BUILD_HOST` (default same as target)
|
||||
- `EVERY_CHANNEL_FORGE_SSH_IDENTITY` (default `~/.ssh/id_ed25519`)
|
||||
|
||||
## Emergency Robot recovery
|
||||
|
||||
Use this only when both Forge HTTPS and SSH are unreachable. The dedicated host is server
|
||||
`2800441` at `95.216.114.54`.
|
||||
|
||||
```sh
|
||||
./scripts/hetzner-robot-forge.sh probe
|
||||
```
|
||||
|
||||
If the probe confirms outage, sign in to 1Password CLI so the wrapper can read the existing Robot
|
||||
Webservice item at runtime:
|
||||
|
||||
```sh
|
||||
op signin
|
||||
./scripts/hetzner-robot-forge.sh status
|
||||
```
|
||||
|
||||
To boot the host into Hetzner Rescue and issue a hardware reset:
|
||||
|
||||
```sh
|
||||
./scripts/hetzner-robot-forge.sh recover
|
||||
./scripts/hetzner-robot-forge.sh wait-ssh
|
||||
```
|
||||
|
||||
The wrapper masks Robot-generated rescue passwords by default and tries to attach the local SSH key
|
||||
fingerprint when activating rescue. Set `EVERY_CHANNEL_ROBOT_AUTHORIZED_KEY_FINGERPRINT` if Robot
|
||||
uses a different uploaded key fingerprint. Set `EVERY_CHANNEL_ROBOT_PRINT_SENSITIVE=1` only when
|
||||
password-based rescue login is required.
|
||||
|
||||
If production boots but public SSH and HTTPS still time out, inspect the previous boot from Rescue.
|
||||
The known recovery check is host-wide VPN state: `mullvad-daemon.service` must not be active on
|
||||
`ecp-forge`, because its firewall policy can block public Forge ingress even when Robot and the
|
||||
NixOS firewall allow the ports. If a not-yet-redeployed generation still starts Mullvad and the
|
||||
mutable cached target state is rewritten to `secured`, back up `/boot/grub/grub.cfg`, append
|
||||
`systemd.mask=mullvad-daemon.service systemd.mask=mullvad-early-boot-blocking.service` to the
|
||||
default Linux line, and reboot production. After public SSH returns, deploy this repo's NixOS config
|
||||
so the bootloader is regenerated without the emergency mask.
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o IdentityAgent=none -i ~/.ssh/id_ed25519 root@git.every.channel \
|
||||
'hostnamectl --static; systemctl is-active forgejo caddy every-channel-netboot-stage every-channel-netboot'
|
||||
```
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
# Ethereum Nodes on `ecp-forge`
|
||||
|
||||
This runbook covers the dual-network Ethereum full-node surface introduced by [ECP-0104](/Users/conradev/Projects/every.channel/evolution/proposals/ECP-0104-ecp-forge-dual-ethereum-full-nodes-on-dedicated-nvme-zfs.md).
|
||||
|
||||
## Scope
|
||||
|
||||
- `ecp-forge` owns a dedicated NVMe-backed ZFS pool for Ethereum state.
|
||||
- Ethereum mainnet and Sepolia both run as full nodes.
|
||||
- Execution uses Reth and consensus uses Lighthouse.
|
||||
- Public HTTPS on `eth.every.channel` exposes sync and finality status only.
|
||||
- Raw execution RPC, Engine API, and Beacon API stay bound to `127.0.0.1` on `ecp-forge`.
|
||||
|
||||
## Deploy
|
||||
|
||||
```sh
|
||||
./scripts/deploy-ecp-forge.sh
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
Storage:
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@git.every.channel \
|
||||
'zpool list eth && zfs list -r eth'
|
||||
```
|
||||
|
||||
Core services:
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@git.every.channel \
|
||||
'systemctl is-active every-channel-ethereum-storage podman-every-channel-ethereum-mainnet-reth podman-every-channel-ethereum-mainnet-lighthouse podman-every-channel-ethereum-sepolia-reth podman-every-channel-ethereum-sepolia-lighthouse'
|
||||
```
|
||||
|
||||
Public sync status:
|
||||
|
||||
```sh
|
||||
curl -fsS https://eth.every.channel/mainnet/sync | jq .
|
||||
curl -fsS https://eth.every.channel/sepolia/sync | jq .
|
||||
```
|
||||
|
||||
## Local-only endpoints on `ecp-forge`
|
||||
|
||||
Mainnet:
|
||||
|
||||
- execution HTTP: `127.0.0.1:8545`
|
||||
- execution WS: `127.0.0.1:8546`
|
||||
- execution Engine API: `127.0.0.1:8551`
|
||||
- beacon API: `127.0.0.1:5052`
|
||||
|
||||
Sepolia:
|
||||
|
||||
- execution HTTP: `127.0.0.1:18545`
|
||||
- execution WS: `127.0.0.1:18546`
|
||||
- execution Engine API: `127.0.0.1:18551`
|
||||
- beacon API: `127.0.0.1:15052`
|
||||
|
||||
## Notes
|
||||
|
||||
- The dedicated Ethereum pool is `eth`, not `tank`.
|
||||
- Both networks are configured for full sync, not archive mode.
|
||||
- Lighthouse is configured to permit full beacon sync from genesis via
|
||||
`--allow-insecure-genesis-sync` instead of checkpoint bootstrap.
|
||||
- The first public host surface is intentionally sync and finality only; extend `eth.every.channel`
|
||||
later if authenticated RPC is required.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Primary host:
|
||||
|
||||
- Forgejo (`origin`, `git.every.channel`)
|
||||
- Forgejo (`origin`)
|
||||
|
||||
Mirrors (push-only):
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ Codeberg and GitHub are distribution mirrors only. CI/actions should run on Forg
|
|||
|
||||
Defaults:
|
||||
|
||||
- `origin`: `ssh://forgejo@git.every.channel:2222/every-channel/every.channel.git`
|
||||
- `origin`: `git@forge.every.channel:every-channel/every.channel.git`
|
||||
- `mirror-codeberg`: `git@codeberg.org:every-channel/every.channel.git`
|
||||
- `mirror-github`: `git@github.com:every-channel/every.channel.git`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,88 +1,35 @@
|
|||
# NUC Fleet Netboot (Unifi)
|
||||
# NUC Fleet Netboot (Unifi + ProxyDHCP)
|
||||
|
||||
This runbook provisions x86_64 NUCs from runner netboot artifacts without USB image flashing.
|
||||
|
||||
Supported modes:
|
||||
It uses:
|
||||
|
||||
- ProxyDHCP mode: recommended when you want automatic stage-1/2 iPXE handling.
|
||||
- UniFi-only mode: DHCP options 66/67 in UniFi, no ProxyDHCP.
|
||||
- Unifi DHCP for IP leases.
|
||||
- Local `dnsmasq` ProxyDHCP for PXE/iPXE bootfile logic.
|
||||
- Local HTTP + TFTP service for boot artifacts.
|
||||
|
||||
## Why ProxyDHCP
|
||||
|
||||
iPXE commonly needs two boot stages:
|
||||
|
||||
1. firmware PXE -> `ipxe.efi`
|
||||
2. iPXE -> `netboot.ipxe`
|
||||
|
||||
If DHCP always returns `ipxe.efi`, clients can loop forever. ProxyDHCP handles stage-specific boot responses cleanly while leaving Unifi as the DHCP lease server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Linux boot server on the same VLAN/L2 domain as the NUCs.
|
||||
- Unifi network with DHCP enabled.
|
||||
- A Linux boot server on the same VLAN/L2 domain as the NUCs.
|
||||
- Unifi network with normal DHCP enabled.
|
||||
- Local DNS record on that VLAN: `boot.every.channel -> <boot-server-ip>`.
|
||||
- `curl`, `tar`, `python3`, `dnsmasq` installed on the boot server.
|
||||
- For UniFi-only mode with reliable chainloading: `git` and `make` to build embedded iPXE.
|
||||
- `openssl` (or equivalent) if you want generated chain tokens.
|
||||
- Runner netboot artifact published to Forgejo Releases (or available as local tarball).
|
||||
- Runner netboot artifact already published to Forgejo Releases (or available as a local tarball).
|
||||
|
||||
## Persistent NixOS service (recommended)
|
||||
## 1) Stage artifacts
|
||||
|
||||
Instead of running scripts manually, use the exported NixOS module and keep netboot
|
||||
staging/serving declarative:
|
||||
|
||||
```nix
|
||||
{
|
||||
imports = [ every-channel.nixosModules.ec-netboot ];
|
||||
|
||||
services.every-channel.netboot = {
|
||||
enable = true;
|
||||
listenIP = "10.20.30.2";
|
||||
interface = "enp195s0";
|
||||
hostname = "boot.every.channel";
|
||||
tftpBootFilename = "ec-ipxe.efi";
|
||||
httpAllowedCIDRs = [ "10.20.30.0/24" ];
|
||||
chainTokenFile = "/run/agenix/every-channel-netboot-chain-token";
|
||||
|
||||
# UniFi-only mode by default (no ProxyDHCP):
|
||||
proxyDhcp.enable = false;
|
||||
|
||||
release.host = "https://git.every.channel";
|
||||
release.repo = "every-channel/every.channel";
|
||||
# release.tag = "boot-v2026.03.02"; # optional pin
|
||||
# release.tokenFile = "/run/agenix/forgejo-api-token"; # optional private repo token
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Operational commands:
|
||||
From repository root on the boot server:
|
||||
|
||||
```sh
|
||||
sudo systemctl start every-channel-netboot-stage.service
|
||||
sudo systemctl restart every-channel-netboot.service
|
||||
sudo systemctl status every-channel-netboot.service
|
||||
```
|
||||
|
||||
If you prefer ProxyDHCP mode:
|
||||
|
||||
```nix
|
||||
services.every-channel.netboot.proxyDhcp.enable = true;
|
||||
services.every-channel.netboot.proxyDhcp.subnet = "10.20.30.0/24";
|
||||
```
|
||||
|
||||
## 1) Build embedded iPXE (UniFi-only mode)
|
||||
|
||||
This removes iPXE boot loops without requiring ProxyDHCP.
|
||||
|
||||
```sh
|
||||
EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel \
|
||||
EVERY_CHANNEL_NETBOOT_HTTP_PORT=8080 \
|
||||
EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN="$(openssl rand -hex 16)" \
|
||||
./scripts/netboot-build-ipxe.sh
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
- `tmp/netboot/tftp/ec-ipxe.efi` (use this as DHCP option 67 filename)
|
||||
|
||||
## 2) Stage runner netboot artifacts
|
||||
|
||||
```sh
|
||||
EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel \
|
||||
EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN="<same-token-as-step-1>" \
|
||||
EVERY_CHANNEL_NETBOOT_IPXE_EFI_PATH=tmp/netboot/tftp/ec-ipxe.efi \
|
||||
EVERY_CHANNEL_NETBOOT_IPXE_EFI_FILENAME=ec-ipxe.efi \
|
||||
./scripts/netboot-stage.sh
|
||||
```
|
||||
|
||||
|
|
@ -91,31 +38,16 @@ Optional inputs:
|
|||
- `EVERY_CHANNEL_NETBOOT_RELEASE_TAG=boot-v2026.02.28`
|
||||
- `EVERY_CHANNEL_NETBOOT_TARBALL=/path/to/ec-runner-x86_64-netboot-....tar.gz`
|
||||
- `EVERY_CHANNEL_FORGE_TOKEN=<token>` for private releases
|
||||
- `EVERY_CHANNEL_NETBOOT_ALLOW_REMOTE_IPXE=true` only if you intentionally want to download iPXE from URL
|
||||
- `EVERY_CHANNEL_IPXE_EFI_SHA256=<sha256>` to pin iPXE binary integrity
|
||||
- `EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel`
|
||||
|
||||
This stages:
|
||||
|
||||
- `tmp/netboot/http/{kernel,initrd,netboot.ipxe}`
|
||||
- `tmp/netboot/tftp/ec-ipxe.efi`
|
||||
- `tmp/netboot/tftp/ipxe.efi`
|
||||
|
||||
## 3) Serve HTTP + TFTP
|
||||
## 2) Serve HTTP + TFTP + ProxyDHCP
|
||||
|
||||
UniFi-only mode (no ProxyDHCP):
|
||||
|
||||
```sh
|
||||
sudo \
|
||||
EVERY_CHANNEL_NETBOOT_LISTEN_IP=10.20.30.2 \
|
||||
EVERY_CHANNEL_NETBOOT_INTERFACE=eth0 \
|
||||
EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel \
|
||||
EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN="<same-token-as-step-1>" \
|
||||
EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS=10.20.30.0/24 \
|
||||
EVERY_CHANNEL_NETBOOT_PROXY_DHCP=false \
|
||||
EVERY_CHANNEL_NETBOOT_TFTP_BOOT_FILENAME=ec-ipxe.efi \
|
||||
./scripts/netboot-serve.sh
|
||||
```
|
||||
|
||||
ProxyDHCP mode:
|
||||
Example (replace values for your VLAN):
|
||||
|
||||
```sh
|
||||
sudo \
|
||||
|
|
@ -123,40 +55,48 @@ sudo \
|
|||
EVERY_CHANNEL_NETBOOT_INTERFACE=eth0 \
|
||||
EVERY_CHANNEL_NETBOOT_PROXY_SUBNET=10.20.30.0/24 \
|
||||
EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel \
|
||||
EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN="<same-token-as-step-1>" \
|
||||
EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS=10.20.30.0/24 \
|
||||
EVERY_CHANNEL_NETBOOT_PROXY_DHCP=true \
|
||||
EVERY_CHANNEL_NETBOOT_TFTP_BOOT_FILENAME=ec-ipxe.efi \
|
||||
./scripts/netboot-serve.sh
|
||||
```
|
||||
|
||||
## 4) UniFi settings (you do this)
|
||||
Notes:
|
||||
|
||||
UniFi-only mode:
|
||||
- Keep this process running during provisioning.
|
||||
- Do not set Unifi DHCP bootfile options while this proxy mode is active.
|
||||
- Ensure `boot.every.channel` resolves to the boot server IP from NUC clients.
|
||||
|
||||
- `Network Boot`: enabled
|
||||
- `Server`: `boot.every.channel` (or boot server IP)
|
||||
- `Filename`: `ec-ipxe.efi`
|
||||
- `TFTP Server`: `boot.every.channel`
|
||||
## 3) Unifi / NUC settings
|
||||
|
||||
ProxyDHCP mode:
|
||||
Unifi:
|
||||
|
||||
- leave UniFi boot/TFTP options unset.
|
||||
- Keep DHCP enabled for the provisioning VLAN.
|
||||
- Leave DHCP boot/TFTP overrides unset when using `netboot-serve.sh`.
|
||||
- Create/verify local DNS host override: `boot.every.channel -> <boot-server-ip>`.
|
||||
|
||||
NUC BIOS:
|
||||
|
||||
- Enable UEFI PXE boot.
|
||||
- Disable Legacy/CSM where possible.
|
||||
- Put network boot first for initial install.
|
||||
- Enable UEFI network boot (IPv4 PXE).
|
||||
- Disable Legacy/CSM if possible.
|
||||
- Put network boot before disk for first install cycle.
|
||||
|
||||
## Security hardening
|
||||
## 4) Provision the fleet
|
||||
|
||||
- Keep provisioning on an isolated VLAN.
|
||||
- Allow only required ports from NUC VLAN to boot server: UDP 69, TCP 8080 (and DHCP if ProxyDHCP mode).
|
||||
- Keep provisioning services up only during rollout, then stop them.
|
||||
- Use `EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS` to limit HTTP artifact access to NUC subnet(s).
|
||||
- Use `EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN` so only tokened iPXE chain requests receive `netboot.ipxe`.
|
||||
- Use checksum verification in `netboot-stage.sh` (enabled by default when release has `SHA256SUMS.txt`).
|
||||
- `netboot-stage.sh` now defaults to local iPXE binaries; remote URL download requires explicit opt-in.
|
||||
- Prefer embedded `ec-ipxe.efi` with fixed chain target over generic unsigned internet binaries.
|
||||
- If Secure Boot is required, use signed boot chain and keys for your environment (outside this basic runbook).
|
||||
1. Boot each NUC on the provisioning VLAN.
|
||||
2. PXE will chainload into iPXE and then runner `netboot.ipxe`.
|
||||
3. Complete install/bootstrap flow on each node.
|
||||
4. After successful install, switch boot order back to local disk.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Symptom: iPXE loop (`ipxe.efi` repeatedly)
|
||||
- Cause: static DHCP bootfile without iPXE-aware logic.
|
||||
- Fix: use ProxyDHCP flow (`netboot-serve.sh`) or set conditional DHCP rules.
|
||||
- Symptom: NUC gets IP but never downloads boot artifacts
|
||||
- Verify firewall allows UDP 67/68, UDP 69, and TCP 8080 between NUCs and boot server.
|
||||
- Symptom: no `dnsmasq` offers seen
|
||||
- Verify `EVERY_CHANNEL_NETBOOT_INTERFACE` and `EVERY_CHANNEL_NETBOOT_PROXY_SUBNET`.
|
||||
|
||||
## Security / networking
|
||||
|
||||
- Tailscale is not required for provisioning.
|
||||
- Keep the provisioning VLAN isolated from regular clients.
|
||||
- Stop `netboot-serve.sh` when rollout is complete.
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
# OP Stack on `ecp-forge`
|
||||
|
||||
This runbook covers the repo-owned OP Stack testnet surface introduced by [ECP-0093](/Users/conradev/Projects/every.channel/evolution/proposals/ECP-0093-ecp-forge-op-stack-sepolia-observation-testnet.md).
|
||||
|
||||
## Scope
|
||||
|
||||
- `ecp-forge` runs the every.channel OP Stack services with pinned official container images.
|
||||
- The chain is Sepolia-anchored and private by default on the RPC side.
|
||||
- Application-level consensus lives in the observation rail:
|
||||
- [EveryChannelWitnessRegistry.sol](/Users/conradev/Projects/every.channel/contracts/EveryChannelWitnessRegistry.sol)
|
||||
- [EveryChannelObservationLedger.sol](/Users/conradev/Projects/every.channel/contracts/EveryChannelObservationLedger.sol)
|
||||
|
||||
## Required inputs
|
||||
|
||||
- `secrets/op-stack-sepolia-private-key.age`
|
||||
- Sepolia operator key for `op-deployer`, `op-node`, `op-batcher`, and `op-proposer`.
|
||||
- `secrets/op-stack-challenger-prestate.bin.gz.age`
|
||||
- Cannon absolute prestate artifact for `op-challenger`.
|
||||
|
||||
If the private key secret is absent, `services.every-channel.op-stack.enable = false` on `ecp-forge`.
|
||||
If the prestate artifact is absent, `op-challenger` and `op-dispute-mon` stay disabled even when the
|
||||
core rollup services are enabled.
|
||||
|
||||
## Local validation
|
||||
|
||||
Contracts:
|
||||
|
||||
```sh
|
||||
nix shell .#foundry .#solc -c forge test -vv
|
||||
```
|
||||
|
||||
Real archive data through Anvil:
|
||||
|
||||
```sh
|
||||
nix shell .#foundry .#solc nixpkgs#jq nixpkgs#openssh nixpkgs#curl -c ./scripts/op-stack/anvil-reality-smoke.sh
|
||||
```
|
||||
|
||||
The smoke script:
|
||||
|
||||
- deploys the witness registry and observation ledger to Anvil,
|
||||
- reads a real archive JSONL entry from `root@git.every.channel`,
|
||||
- derives `stream_hash`, `epoch_hash`, `locator_hash`, and `observation_hash`,
|
||||
- finalizes the observation with two Anvil witnesses.
|
||||
|
||||
Default remote source:
|
||||
|
||||
- `/var/lib/every-channel/manifests/la-cbs/video0.m4s.jsonl`
|
||||
|
||||
Output artifact:
|
||||
|
||||
- `test-results/anvil-reality-smoke.json`
|
||||
|
||||
## Deploy
|
||||
|
||||
```sh
|
||||
./scripts/deploy-ecp-forge.sh
|
||||
```
|
||||
|
||||
The host bootstrap service is:
|
||||
|
||||
- `every-channel-op-stack-bootstrap.service`
|
||||
|
||||
It writes runtime state under:
|
||||
|
||||
- `/var/lib/every-channel/op-stack`
|
||||
|
||||
Key generated artifacts:
|
||||
|
||||
- `/var/lib/every-channel/op-stack/deployment.json`
|
||||
- `/var/lib/every-channel/op-stack/sequencer/genesis.json`
|
||||
- `/var/lib/every-channel/op-stack/sequencer/rollup.json`
|
||||
|
||||
## Verify
|
||||
|
||||
Core services:
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@git.every.channel \
|
||||
'systemctl is-active every-channel-op-stack-bootstrap podman-every-channel-op-geth podman-every-channel-op-node podman-every-channel-op-batcher podman-every-channel-op-proposer'
|
||||
```
|
||||
|
||||
Full stack including challenger/dispute monitor:
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@git.every.channel \
|
||||
'systemctl is-active podman-every-channel-op-challenger podman-every-channel-op-dispute-mon'
|
||||
```
|
||||
|
||||
Bootstrap outputs:
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@git.every.channel \
|
||||
'jq . /var/lib/every-channel/op-stack/deployment.json'
|
||||
```
|
||||
|
||||
## Contract deployment on the rollup
|
||||
|
||||
Once the rollup RPC is live, deploy the observation rail to the L2 RPC:
|
||||
|
||||
```sh
|
||||
EVERY_CHANNEL_RPC_URL=http://127.0.0.1:28545 \
|
||||
EVERY_CHANNEL_PRIVATE_KEY_FILE=/path/to/private-key \
|
||||
./scripts/op-stack/deploy-observation-ledger.sh
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `op-geth` and `op-node` RPC surfaces bind to `127.0.0.1` on `ecp-forge`.
|
||||
- The OP Stack L2 execution RPC defaults to `127.0.0.1:28545`, not `8545`; `8545` is reserved for
|
||||
the full Ethereum mainnet node on the same host.
|
||||
- `op-geth` P2P uses `28549`, not the Ethereum default `30303`; `30303` is reserved for the host
|
||||
full Ethereum node.
|
||||
- The public firewall opening is only for the `op-node` P2P port.
|
||||
- The bootstrap uses `op-deployer/v0.6.0-rc.3` by default and OP Labs runtime images aligned to the
|
||||
generated rollup schema.
|
||||
|
|
@ -9,12 +9,6 @@ This repo exports reproducible NixOS runner configurations via flake outputs:
|
|||
- `nixosConfigurations.ec-runner-x86_64-iso`
|
||||
- `nixosConfigurations.ec-runner-aarch64-sdimage`
|
||||
|
||||
It also exports reusable NixOS modules:
|
||||
|
||||
- `nixosModules.ec-runner`
|
||||
- `nixosModules.ec-node`
|
||||
- `nixosModules.ec-netboot` (persistent HTTP/TFTP netboot stage+serve service)
|
||||
|
||||
The runner OS exposes this repo's flake source inside the system at:
|
||||
|
||||
- `/etc/every-channel/flake`
|
||||
|
|
|
|||
107
docs/USAGE.md
107
docs/USAGE.md
|
|
@ -11,13 +11,6 @@ cd apps/tauri
|
|||
cargo tauri dev
|
||||
```
|
||||
|
||||
If you want to run the desktop app directly from Cargo against the bundled frontend instead of the
|
||||
dev server, run:
|
||||
|
||||
```sh
|
||||
EVERY_CHANNEL_ROOT=$PWD cargo run -p ec-tauri --features custom-protocol
|
||||
```
|
||||
|
||||
If you want deterministic transcoding instead of stream copy:
|
||||
|
||||
```sh
|
||||
|
|
@ -38,65 +31,6 @@ EVERY_CHANNEL_IROH_DISCOVERY=dht,mdns cargo tauri dev
|
|||
|
||||
In the Tauri app, use **Add stream** to add an HDHomeRun host, a direct HLS URL, or a yt-dlp supported URL (e.g. YouTube Live). The flow rejects non-live sources.
|
||||
|
||||
`https://www.nbc.com/watch/...` URLs are also supported in the Tauri app. This path is
|
||||
browser-backed:
|
||||
|
||||
- on macOS, the app first opens an in-app Tauri webview backed by `WKWebView`
|
||||
- NBC / Adobe Pass authentication stays in that native app window, including popup sign-in flows
|
||||
- if native playback cannot become ready, the app falls back to the existing external Chrome path
|
||||
- once playback is live, the app captures rendered video frames and feeds them into the existing
|
||||
ffmpeg ladder
|
||||
|
||||
Notes:
|
||||
|
||||
- the first run may require you to finish your MVPD login in the native app window or, if native
|
||||
playback falls back, in the launched Chrome window
|
||||
- on macOS, the default native webview data directory is app-local; override it with
|
||||
`EVERY_CHANNEL_NBC_WEBVIEW_DATA_DIR=/path/to/webview-data`
|
||||
- for future unattended runs with a warm session, set `EVERY_CHANNEL_NBC_HIDE_WINDOWS=1` to keep
|
||||
the native NBC webviews hidden; if interactive auth is needed, the app will surface the window
|
||||
instead of silently hanging
|
||||
- the desktop app also exposes `bootstrap_nbc_auth`; in the Add menu, use `Bootstrap selected NBC`
|
||||
or `Bootstrap pasted NBC URL` to warm the hidden session before later playback runs
|
||||
- the fallback Chrome profile directory is app-local; override it with
|
||||
`EVERY_CHANNEL_NBC_PROFILE_DIR=/path/to/profile`
|
||||
- override the Chrome binary with `EVERY_CHANNEL_NBC_CHROME_PATH=/path/to/chrome`
|
||||
- when `EVERY_CHANNEL_NBC_HIDE_WINDOWS=1` is set, the app refuses visible Chrome fallback if the
|
||||
native path fails
|
||||
- the app also pulls NBC's public live guide before auth so browseable NBC channel rows can
|
||||
appear in the Channels list; override that guide shaping with
|
||||
`EVERY_CHANNEL_NBC_PUBLIC_TIMEZONE`, `EVERY_CHANNEL_NBC_PUBLIC_NBC_AFFILIATE`,
|
||||
`EVERY_CHANNEL_NBC_PUBLIC_TELEMUNDO_AFFILIATE`, and
|
||||
`EVERY_CHANNEL_NBC_PUBLIC_BROADCAST_TYPE`
|
||||
- capture is currently video-first; audio is not guaranteed in the first cut
|
||||
- adjust startup timeout / capture rate with `EVERY_CHANNEL_NBC_CAPTURE_TIMEOUT_SECS`,
|
||||
`EVERY_CHANNEL_NBC_CAPTURE_FPS`, and `EVERY_CHANNEL_NBC_CAPTURE_QUALITY`
|
||||
|
||||
On Linux / forge hosts, the equivalent worker path lives in `ec-node`:
|
||||
|
||||
- warm auth with
|
||||
`ec-node nbc-bootstrap --source-url 'https://www.nbc.com/live?brand=nbc-sports-philadelphia'`
|
||||
- publish with
|
||||
`ec-node nbc-wt-publish --url https://cdn.moq.dev/anon --name forge-nbc-sports-philly --source-url 'https://www.nbc.com/live?brand=nbc-sports-philadelphia'`
|
||||
- for unattended hosts, persist the Chrome profile with `EVERY_CHANNEL_NBC_PROFILE_DIR=/path/to/profile`
|
||||
- to automate a Verizon popup on Linux / forge, pass MVPD credentials via env or file paths:
|
||||
`EVERY_CHANNEL_NBC_MVPD_USERNAME`, `EVERY_CHANNEL_NBC_MVPD_PASSWORD`,
|
||||
`EVERY_CHANNEL_NBC_MVPD_USERNAME_FILE`, `EVERY_CHANNEL_NBC_MVPD_PASSWORD_FILE`
|
||||
- the NixOS module can point the Linux worker at root-managed credential files with
|
||||
`services.every-channel.ec-node.nbc.mvpdUsernameFile` and
|
||||
`services.every-channel.ec-node.nbc.mvpdPasswordFile`
|
||||
- for forge-style isolation, the NixOS module can keep only the NBC publisher inside a rootless
|
||||
user+network namespace backed by `slirp4netns` with
|
||||
`services.every-channel.ec-node.nbc.isolateWithUserNetns = true`
|
||||
- pair that with `services.every-channel.ec-node.nbc.requireMullvad = true` to block worker startup
|
||||
until the host Mullvad daemon is connected; optionally pin a region/country family with
|
||||
`services.every-channel.ec-node.nbc.mullvadLocation = "USA"`
|
||||
- the NixOS module exposes `services.every-channel.ec-node.nbc.*` for a persistent Xvfb display plus
|
||||
an optional local-only VNC bridge so MVPD auth can be completed only when the session is cold
|
||||
- on Linux virtual displays, the worker disables Chrome GPU acceleration by default; only set
|
||||
`EVERY_CHANNEL_NBC_ENABLE_GPU=1` if the host has a real GL-capable display path
|
||||
- the forge path is also currently video-first; audio is still a follow-up item
|
||||
|
||||
Linux DVB sources can be added with a URL like:
|
||||
|
||||
```
|
||||
|
|
@ -127,47 +61,6 @@ Requires Nix (so `ac-ffmpeg` finds FFmpeg headers):
|
|||
./scripts/e2e-hdhr.sh --host <HDHR_HOST> --channel <CHANNEL>
|
||||
```
|
||||
|
||||
## HDHomeRun + Observation Chain E2E Test
|
||||
|
||||
This runs a local Anvil chain, deploys the observation registry/ledger, publishes one HDHomeRun
|
||||
manifest epoch, and verifies that the manifest-derived observation finalizes on-chain.
|
||||
|
||||
Requires Nix, Foundry, and a reachable local HDHomeRun:
|
||||
|
||||
```sh
|
||||
./scripts/e2e-hdhr-blockchain.sh --host <HDHR_HOST> --channel <CHANNEL>
|
||||
```
|
||||
|
||||
## Local HDHomeRun Publisher Against Remote Observation Chain
|
||||
|
||||
The remote OP Stack RPC on `ecp-forge` is intentionally local-only. From the local publisher box,
|
||||
tunnel it first:
|
||||
|
||||
```sh
|
||||
ssh -N -L 9545:127.0.0.1:28545 root@git.every.channel
|
||||
```
|
||||
|
||||
Then run a local HDHomeRun publisher with observation submission enabled:
|
||||
|
||||
```sh
|
||||
cargo run -p ec-node -- moq-publish \
|
||||
--publish-manifests \
|
||||
--epoch-chunks 1 \
|
||||
--broadcast-name local-hdhr-8-1 \
|
||||
--observation-rpc-url http://127.0.0.1:9545 \
|
||||
--observation-ledger <OBSERVATION_LEDGER_ADDRESS> \
|
||||
--observation-private-key-file /path/to/witness.key \
|
||||
hdhr --host <HDHR_HOST> --channel <CHANNEL>
|
||||
```
|
||||
|
||||
Environment fallbacks are also supported:
|
||||
|
||||
- `EVERY_CHANNEL_OBSERVATION_RPC_URL`
|
||||
- `EVERY_CHANNEL_OBSERVATION_LEDGER`
|
||||
- `EVERY_CHANNEL_OBSERVATION_PRIVATE_KEY`
|
||||
- `EVERY_CHANNEL_OBSERVATION_PRIVATE_KEY_FILE`
|
||||
- `EVERY_CHANNEL_OBSERVATION_PARENT_HASH`
|
||||
|
||||
## Mesh E2E Test (Split Sources)
|
||||
|
||||
This runs two publishers over the same broadcast:
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
Status: Implemented
|
||||
|
||||
Note: Persistent declarative host operation is specified in ECP-0083.
|
||||
|
||||
## Context
|
||||
|
||||
Runner netboot artifacts now publish from CI, but there is no repository-native operating path for fleet provisioning on common prosumer networks (for example Unifi VLANs).
|
||||
|
|
@ -19,12 +17,6 @@ Unifi DHCP can expose next-server/bootfile settings, but iPXE chainloading often
|
|||
2. Keep Unifi DHCP as the IP authority; use ProxyDHCP only to supply bootfile logic.
|
||||
3. Document a concrete NUC rollout sequence for same-VLAN provisioning.
|
||||
4. Keep dependencies minimal (`curl`, `tar`, `python3`, `dnsmasq`) and avoid requiring image flashing workflows.
|
||||
5. Support an optional UniFi-only mode by providing an embedded-script iPXE build path (`ec-ipxe.efi`) so clients can chainload without DHCP conditional logic.
|
||||
6. Verify release artifact integrity during staging when `SHA256SUMS.txt` is published.
|
||||
7. Harden serving/staging defaults:
|
||||
- default to local iPXE artifacts (remote iPXE download requires explicit opt-in),
|
||||
- support optional chain token protection for `netboot.ipxe`,
|
||||
- support HTTP CIDR allowlists for artifact serving.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
# ECP-0083: Declarative Netboot Service Module
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Context
|
||||
|
||||
ECP-0082 added script-driven netboot staging and serving for UniFi/ProxyDHCP fleets. That path works, but it is still operator-session driven (`tmux`, manual env vars, manual restart order), which is fragile for sustained fleet bring-up.
|
||||
|
||||
The constitution favors explicit, reviewable infrastructure definitions. Netboot delivery should be operated as a normal NixOS service with stable options, systemd lifecycle, and auditable host config.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Add a reusable NixOS module at `nix/modules/ec-netboot.nix` exported as `nixosModules.ec-netboot`.
|
||||
2. Define a first-class `services.every-channel.netboot` option tree for:
|
||||
- UniFi-only mode (default, no ProxyDHCP),
|
||||
- optional ProxyDHCP mode,
|
||||
- release source pinning (host/repo/tag/local tarball/token file),
|
||||
- iPXE strategy (embedded build, local file, or explicit remote download),
|
||||
- security controls (chain token file, HTTP CIDR allowlist).
|
||||
3. Run persistent systemd units:
|
||||
- `every-channel-netboot-ipxe` (oneshot, optional embedded EFI build),
|
||||
- `every-channel-netboot-stage` (oneshot artifact staging),
|
||||
- `every-channel-netboot` (long-running HTTP+TFTP service).
|
||||
4. Add tmpfiles and firewall wiring in-module so host configs remain concise and reversible.
|
||||
5. Keep existing scripts as execution primitives to avoid duplicate logic and preserve local/manual fallback operations.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep scripts only. Rejected because startup order, secret injection, and restart behavior remain ad-hoc.
|
||||
- Implement host-specific module logic only in `key.store`. Rejected because this behavior is core `every.channel` netboot operations and should be reusable across hosts.
|
||||
- Replace scripts with a brand new daemon immediately. Rejected to keep rollout incremental and avoid avoidable regressions.
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
- Rollout:
|
||||
- import `every-channel.nixosModules.ec-netboot` on the boot host,
|
||||
- set `services.every-channel.netboot.*` options,
|
||||
- activate and verify `every-channel-netboot-stage` then `every-channel-netboot`.
|
||||
- Teardown:
|
||||
- disable `services.every-channel.netboot.enable`,
|
||||
- remove host option stanza,
|
||||
- fall back to manual script operation from `docs/NUC_UNIFI_NETBOOT.md` if needed.
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# ECP-0084: Sovereign `ecp-forge` Host Deploy from every.channel
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Context
|
||||
|
||||
`git.every.channel` (Hetzner 300TB host) has been operated from external infra repos. That creates coupling and weakens operational independence for every.channel infrastructure changes, especially netboot/PXE and archive workflows.
|
||||
|
||||
The constitutional direction is explicit repository ownership over its infrastructure path. every.channel should be able to deploy its own forge host from this repository, with age/agenix material stored here.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Add a sovereign `nixosConfigurations.ecp-forge` target to this repository.
|
||||
2. Keep the forge role (`services.forgejo`, `services.caddy`) and archive role (`services.every-channel.ec-node`) in that host target.
|
||||
3. Enable persistent netboot from this repository using `services.every-channel.netboot`, with local sovereign tarball staging as the default source path.
|
||||
4. Keep UniFi-only mode as default (`proxyDhcp.enable = false`) to avoid cross-domain DHCP coupling.
|
||||
5. Store host-consumed runtime secrets in this repository (`secrets/*.age`) and decrypt on-host via `agenix`.
|
||||
6. Deploy directly from this repository to `git.every.channel`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Continue deploying `git.every.channel` from shared infra repos. Rejected due ownership/coupling drift.
|
||||
- Keep runtime-only netboot scripts on host. Rejected because boot resilience should survive reboot and config rebuilds.
|
||||
- Move to ProxyDHCP-first by default. Rejected for now to keep DHCP authority in UniFi.
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
- Rollout:
|
||||
- build/evaluate `.#nixosConfigurations.ecp-forge`,
|
||||
- deploy from every.channel to `git.every.channel`,
|
||||
- verify `every-channel-netboot-stage` and `every-channel-netboot`.
|
||||
- Teardown:
|
||||
- disable `services.every-channel.netboot.enable` in `nix/nixos/ecp-forge.nix`,
|
||||
- redeploy,
|
||||
- fall back to manual script flow (`docs/NUC_UNIFI_NETBOOT.md`) if required.
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# ECP-0085: Enable Archive Auto-Worker on Sovereign `ecp-forge`
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Context
|
||||
|
||||
`ecp-forge` currently runs Forgejo and netboot services from `every.channel`, but archival ingestion is not active. Historical archive/manifests exist on host storage, yet no `every-channel-wt-archive*` unit is running, so stream persistence stalls.
|
||||
|
||||
The constitutional direction favors an operational archive network. The sovereign forge host should continuously ingest public streams into durable storage.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Enable `services.every-channel.ec-node` on `ecp-forge`.
|
||||
2. Run archive ingestion mode (`archive.enable = true`) with no broadcast publish role.
|
||||
3. Use:
|
||||
- `archive.outputDir = "/tank/every-channel/archive"` (large durable ZFS storage),
|
||||
- `archive.manifestDir = "/var/lib/every-channel/manifests"` (existing manifest path continuity).
|
||||
4. Keep `archive.serve.enable = false` for now; this change is ingest-only.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep archive workers manual/ephemeral. Rejected because ingestion must survive reboot/redeploy.
|
||||
- Move manifests to `/tank` in this change. Deferred to avoid migration risk while restoring ingestion first.
|
||||
- Enable replay serving (`wt-archive-serve`) immediately. Rejected for scope control; ingest health first.
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
- Rollout:
|
||||
- deploy `.#nixosConfigurations.ecp-forge`,
|
||||
- verify `every-channel-wt-archive-auto` is active,
|
||||
- verify journal shows polling/worker spawn and manifests update.
|
||||
- Teardown:
|
||||
- set `services.every-channel.ec-node.archive.enable = false`,
|
||||
- deploy,
|
||||
- preserve existing on-disk archive/manifests data.
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# ECP-0086: Web Watcher Jitter Budget Override
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Problem
|
||||
|
||||
Browser live playback on `every.channel` stays connected but still cuts in and out. A Chrome browser repro against `https://every.channel/watch?url=https%3A%2F%2Fcdn.moq.dev%2Fanon&name=la-nbc` showed no remounts or request failures, but the `@moq/watch@0.2.0` player emitted continuous `skipping slow group` warnings plus frequent small seek corrections.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Keep the existing `@moq/watch` integration and WebTransport-only path.
|
||||
- Avoid forking upstream player code for a site-level playback tuning change.
|
||||
- Keep the fix reversible and local to the web app wiring.
|
||||
|
||||
## Decision
|
||||
|
||||
Set the web watcher's `jitter` attribute to `750` milliseconds in `apps/web/app.js` before connect.
|
||||
|
||||
Browser evidence for the same live stream showed:
|
||||
|
||||
- default `jitter=100`: `2762` warnings over the sample, including `324` seek corrections, final `readyState=1`
|
||||
- forced `jitter=750`: `2482` warnings over the sample, including `75` seek corrections, final `readyState=4`
|
||||
|
||||
This does not eliminate upstream `skipping slow group` churn entirely, but it substantially reduces the seek-thrash that most directly surfaces as visible cut-in/cut-out playback.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Leave the default `100ms` jitter budget. Rejected because the browser repro showed sustained seek churn under live playback.
|
||||
- Raise jitter further (for example `1500ms`). Rejected for now because it reduced some warnings but regressed playback advancement more than `750ms` in the same sample.
|
||||
- Fork `@moq/watch`. Rejected because the exposed `jitter` control was sufficient for a first mitigation.
|
||||
|
||||
## Rollout / Teardown
|
||||
|
||||
- Deploy the `jitter=750` override and validate live watch continuity in Chrome against public relay streams.
|
||||
- If the extra latency is unacceptable or upstream player behavior changes, remove the override and fall back to the library default.
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
# ECP-0087: LAN-Capable iPXE/QEMU VM Module
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Context
|
||||
|
||||
`every.channel` already has reusable runner and netboot modules, but hosts that want a continuously-running iPXE test VM still have to carry ad hoc QEMU glue. That makes boot-path verification and downstream reuse harder, and it prevents hosts from declaratively opting into a LAN-visible guest for tuner discovery.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Add `nixosModules.ec-ipxe-qemu` at `nix/modules/ec-ipxe-qemu.nix`.
|
||||
2. Add a dedicated publisher guest module/output pair:
|
||||
- `nixosModules.ec-publisher-guest`
|
||||
- `nixosConfigurations.ec-publisher-x86_64`
|
||||
- `nixosConfigurations.ec-publisher-x86_64-netboot`
|
||||
This keeps the VM path explicitly publisher-oriented while leaving host-specific tuner/broadcast choices to downstream configs.
|
||||
3. Define `services.every-channel.ipxe-qemu.*` options for:
|
||||
- persistent qcow2/state directory handling,
|
||||
- user-mode iPXE boot networking that chains to a configurable internet boot URL,
|
||||
- optional second NIC via `macvtap` for non-disruptive LAN presence,
|
||||
- guest sizing and raw QEMU argument overrides.
|
||||
4. Run the guest as a persistent systemd service (`every-channel-ipxe-qemu`) with restart-on-exit semantics so host restarts or config switches naturally refresh the in-memory booted VM.
|
||||
5. Enable one conservative instance on `ecp-forge` using the user-mode boot path only, so the module is exercised in-repo without assuming a local tuner LAN on the forge host.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep host-specific shell glue outside `every.channel`. Rejected because downstream hosts cannot reuse or review the boot path as a first-class module.
|
||||
- Require a Linux bridge on the host for LAN access. Rejected for now because it is more disruptive than `macvtap` and unnecessary for an initial deployment.
|
||||
- Boot only with QEMU user networking. Rejected because tuner discovery needs a real LAN attachment on some hosts.
|
||||
|
||||
## Rollout / Teardown
|
||||
|
||||
- Rollout:
|
||||
- import `nixosModules.ec-ipxe-qemu`,
|
||||
- enable `services.every-channel.ipxe-qemu`,
|
||||
- set `lan.enable = true` plus `lan.macvtap.interface` on hosts that need LAN discovery.
|
||||
- Teardown:
|
||||
- disable `services.every-channel.ipxe-qemu.enable`,
|
||||
- remove host options,
|
||||
- fall back to ad hoc QEMU or direct host publishers if needed.
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
# ECP-0088: Public RPC/NFS Hardening for `ecp-forge`
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Context
|
||||
|
||||
`ecp-forge` exports `/tank` over NFS for private consumers, but the host firewall also exposed `rpcbind` (`111/tcp,udp`) and NFS (`2049/tcp,udp`) on the public Hetzner address. CERT-Bund flagged the host because public `rpcbind` allowed internet enumeration of registered RPC services.
|
||||
|
||||
The current exports already constrain clients to private address space, so the exposure is a firewall boundary issue rather than a requirement for public access.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Remove public firewall allowances for `111/tcp,udp` and `2049/tcp,udp` on `ecp-forge`.
|
||||
2. Keep NFS enabled for trusted/private paths, including the existing `tailscale0` trusted interface and private-source exports.
|
||||
3. Treat public RPC/NFS exposure on forge hosts as an anti-pattern unless a later ECP explicitly justifies it.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Disable NFS entirely. Rejected because `/tank` export remains useful for private consumers.
|
||||
- Keep public ports open and rely only on `/etc/exports` CIDR restrictions. Rejected because `rpcbind` enumeration is itself enough to trigger abuse notifications and increases attack surface.
|
||||
- Add bespoke public-interface firewall exceptions per private CIDR. Rejected because `ecp-forge` already has a trusted overlay path and does not need public-interface exposure for NFS.
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
- Rollout:
|
||||
- evaluate `.#nixosConfigurations.ecp-forge`,
|
||||
- deploy `ecp-forge`,
|
||||
- verify `rpcbind`/NFS are no longer reachable on the public IP.
|
||||
- Teardown:
|
||||
- restore the public firewall allowances only with a replacement ECP that documents the requirement and compensating controls.
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
# ECP-0089: Broadcast-Scoped Discovery Identity For Gossip And DHT
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Decision
|
||||
|
||||
When `ec-node` needs to synthesize a default `stream_id` for publish/announce flows, it will prefer a broadcast-scoped identifier derived from channel metadata over a source-scoped identifier.
|
||||
|
||||
Current rule:
|
||||
|
||||
- If the selected channel yields a usable broadcast identity, generate `ec/stream/v1/broadcast/...`.
|
||||
- Otherwise keep the existing source-scoped fallback `ec/stream/v1/source/...`.
|
||||
- Explicit `--stream-id` continues to override both behaviors.
|
||||
|
||||
For HDHomeRun lineup sources, the usable broadcast identity is assembled from the channel record when present:
|
||||
|
||||
- `standard = atsc`
|
||||
- `program_number` from lineup `ProgramNumber`/`ProgramID` when available
|
||||
- `virtual_channel` from `GuideNumber`
|
||||
- `callsign` from typed lineup metadata or channel name fallback
|
||||
- optional `region` / `frequency` hints when present
|
||||
|
||||
## Motivation
|
||||
|
||||
Today identical OTA channels announced from different ingest nodes often diverge because the default identity is source-scoped (`hdhr`, device id, local channel selection). That defeats gossip/DHT dedupe and makes the network treat equivalent broadcasts as unrelated streams.
|
||||
|
||||
We already have a long-term architectural direction in ECP-0004: broadcast identity is the primary convergence key and source identity is a fallback. This change applies that rule to the current discovery boundary instead of waiting for deeper ingest metadata work.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- Channel-to-broadcast identity helper in `ec-core`
|
||||
- Typed lineup metadata extraction in `ec-hdhomerun`
|
||||
- Default `stream_id` synthesis updates in `ec-node`
|
||||
- Unit coverage for identity derivation and lineup parsing
|
||||
- Operator docs describing the precedence rules
|
||||
|
||||
Out of scope:
|
||||
|
||||
- Deep MPEG-TS/PSIP extraction of TSID/program data from live transport packets
|
||||
- Automatic migration of already-running source-scoped publishers
|
||||
- Cryptographic anti-spoofing or admission control for gossip/DHT announcements
|
||||
|
||||
## Constraints
|
||||
|
||||
- Must remain additive and reversible
|
||||
- Must not break explicit operator-provided `--stream-id`
|
||||
- Must keep non-broadcast-aware sources working unchanged
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep source-scoped defaults until full PSIP parsing exists. Rejected because it preserves a known convergence flaw in the current network behavior.
|
||||
- Force all operators to manually specify canonical `--stream-id`. Rejected because it is error-prone and defeats zero-config dedupe.
|
||||
- Collapse purely onto channel number. Rejected because channel number alone is too weak without additional hints.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- Additive change: only synthesized defaults change, not explicit IDs.
|
||||
- Roll back by restoring source-scoped default synthesis.
|
||||
- Existing automation can pin old behavior with explicit `--stream-id` if needed during transition.
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# ECP-0090: PAT-Derived Broadcast Identity Promotion
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Decision
|
||||
|
||||
Extend default discovery identity synthesis to probe early MPEG-TS packets for Program Association Table (PAT) data and use that to strengthen broadcast-scoped stream IDs.
|
||||
|
||||
Current rule:
|
||||
|
||||
- If source/channel metadata already yields a usable broadcast identity, use it.
|
||||
- If transport probing finds PAT data, fill missing `transport_stream_id` and `program_number`.
|
||||
- If PAT shows exactly one non-zero program, that program is eligible for broadcast-scoped convergence.
|
||||
- If PAT is ambiguous (multiple non-zero programs), do not guess; keep `program_number` unset and fall back to source scope when needed.
|
||||
- If the probe sees ATSC PSIP traffic, label the standard as `atsc`; otherwise use a generic `mpegts` fallback when PAT is the only signal.
|
||||
|
||||
## Motivation
|
||||
|
||||
ECP-0089 fixed the first duplication bug by preferring channel metadata over source-local IDs. That still left a hole:
|
||||
|
||||
- raw TS sources without lineup metadata could not converge,
|
||||
- channel metadata could be incomplete or inconsistent,
|
||||
- and weak identity inputs risked accidental convergence on the wrong broadcast.
|
||||
|
||||
PAT-derived identity gives us a stronger on-wire fingerprint without waiting for full PSIP/VCT parsing.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- PAT parsing in `ec-ts`
|
||||
- bounded transport probing for `tsid` / single-program extraction
|
||||
- source integration for raw TS inputs and HDHomeRun enrichment
|
||||
- tests that enforce "single program promotes, ambiguous stream does not"
|
||||
|
||||
Out of scope:
|
||||
|
||||
- full ATSC VCT/MGT descriptor parsing
|
||||
- DVB service table parsing
|
||||
- cryptographic origin authentication for discovery announcements
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep trusting channel metadata only. Rejected because it leaves raw TS paths weak and incomplete.
|
||||
- Always promote from PAT even with multiple programs. Rejected because it guesses the channel and increases junk-collision risk.
|
||||
- Require manual `--stream-id` for TS inputs. Rejected because it pushes canonicalization burden onto operators.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- Additive and reversible: explicit `--stream-id` still overrides everything.
|
||||
- Roll back by removing PAT probing and returning to metadata-only promotion.
|
||||
- Future PSIP/VCT work can refine the broadcast identity without changing the override surface.
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
# ECP-0091: Full ATSC PSIP Parsing and Real-Sample Validation
|
||||
|
||||
Status: Implemented
|
||||
|
||||
## Decision
|
||||
|
||||
Extend `ec-ts` from PAT-only plus partial PSIP awareness into table-level ATSC PSIP parsing for:
|
||||
|
||||
- `MGT`
|
||||
- `TVCT` / `CVCT`
|
||||
- `STT`
|
||||
- `RRT`
|
||||
- `EIT`
|
||||
- `ETT`
|
||||
|
||||
Additional rules:
|
||||
|
||||
- `EIT` and `ETT` are parsed on their MGT-assigned PIDs, not only on base PID `0x1FFB`.
|
||||
- Raw descriptor bytes are preserved in parsed tables where we do not yet expose typed descriptor
|
||||
structs.
|
||||
- ATSC STT is converted to Unix time using GPS epoch semantics instead of the earlier placeholder
|
||||
arithmetic.
|
||||
- Real-data checks are added as ignored tests using external ATSC captures rather than vendoring
|
||||
large TS fixtures into the repo.
|
||||
|
||||
## Motivation
|
||||
|
||||
ECP-0089 and ECP-0090 fixed the first discovery-identity hole by preferring broadcast metadata and
|
||||
then strengthening it with PAT probing. That still left two gaps:
|
||||
|
||||
- PSIP coverage was incomplete, so the parser could detect “this is ATSC” without understanding the
|
||||
rest of the broadcast metadata surface.
|
||||
- Validation was synthetic-only, which is not enough for transport tables that are notoriously easy
|
||||
to parse incorrectly while still passing fixture tests.
|
||||
|
||||
We need both: fuller PSIP coverage and a rerunnable path against known real captures.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- full table parsing for `MGT`, `TVCT/CVCT`, `STT`, `RRT`, `EIT`, and `ETT`
|
||||
- multiple-string structure parsing with conservative text decoding for common uncompressed modes
|
||||
- non-base PID handling for `EIT` / `ETT`
|
||||
- corrected STT to Unix-time conversion
|
||||
- ignored real-sample tests against `tsduck-test` ATSC captures
|
||||
|
||||
Out of scope:
|
||||
|
||||
- semantic parsing of all ATSC descriptor payloads
|
||||
- DVB/ISDB parity in this change
|
||||
- using `RRT`, `EIT`, or `ETT` directly in the default discovery key
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep partial PSIP parsing and rely on PAT + lineup hints only. Rejected because it leaves too much
|
||||
unverified broadcast metadata on the floor.
|
||||
- Vendor large real TS captures into the repo. Rejected because it bloats the tree and makes review
|
||||
worse.
|
||||
- Parse `EIT` / `ETT` only on `0x1FFB`. Rejected because live ATSC streams carry them on the PIDs
|
||||
advertised by `MGT`.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- Additive for existing callers: explicit `--stream-id` still overrides discovery identity.
|
||||
- If needed, roll back by reverting the new table parsers while keeping PAT-based identity promotion.
|
||||
- The ignored real-sample tests remain opt-in and do not make normal CI depend on external downloads.
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
# ECP-0092: Ethereum rails and private settlement for every.channel
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`every.channel` already has useful anti-junk and manifest integrity primitives, but they stop at
|
||||
repo-local formats: custom Merkle trees, JSON body hashes, and Ed25519 signatures. That is enough
|
||||
for local validation, not for the broader direction now requested:
|
||||
|
||||
- stream identity, broadcast de-duplication, manifests, and transport announcements should all have
|
||||
Ethereum-compatible representations,
|
||||
- private-chain settlement should reuse existing Ethereum tooling instead of inventing a custom
|
||||
ledger,
|
||||
- iroh should remain the transport substrate,
|
||||
- storage on chain must stay compact, and
|
||||
- `ecp-forge` should become the high-bandwidth head node for sequencing, archival, and bootstrap.
|
||||
|
||||
This supersedes the specific ECP-0022 alternative that rejected “global consensus / blockchain for
|
||||
manifests” as overkill. The new direction is not a public general-purpose chain. It is a private,
|
||||
Ethereum-compatible settlement rail for every.channel commitments.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a dual-plane architecture:
|
||||
|
||||
- `iroh` remains the transport, discovery, and media movement layer.
|
||||
- Local protocol integrity remains first-class:
|
||||
- `manifest_id` stays BLAKE3 over the manifest body,
|
||||
- object membership proofs stay `merkle+blake3`,
|
||||
- Ed25519 manifest signatures remain supported.
|
||||
- Ethereum-compatible commitments become an additive settlement/mirroring layer for stream
|
||||
identity, transport announcements, and manifests.
|
||||
- Ethereum-native signatures are added alongside local signatures:
|
||||
- manifests may carry secp256k1 EIP-712 signatures over `ManifestBody`,
|
||||
- no existing local signature path is removed as part of this proposal.
|
||||
- On-chain state remains compact:
|
||||
- manifests store only stream/epoch ids plus commitment hashes,
|
||||
- control announcements store only stream ids plus announcement commitments,
|
||||
- full media bytes, manifests, PSIP tables, and chunk inventories remain off-chain.
|
||||
- The chain is private and purpose-built for every.channel use cases:
|
||||
- no arbitrary application compute in the near term,
|
||||
- no fraud-proof system in the near term,
|
||||
- permissionless submission as far as operationally possible,
|
||||
- pseudonymous participation preferred over account-heavy identity.
|
||||
|
||||
## Details
|
||||
|
||||
### Canonical Ethereum representations
|
||||
|
||||
Introduce Solidity-compatible struct mirrors for the protocol surfaces we actually ship:
|
||||
|
||||
- `BroadcastId`
|
||||
- `SourceId`
|
||||
- `StreamKey`
|
||||
- `StreamDescriptor`
|
||||
- `StreamControlAnnouncement`
|
||||
- `ChunkId`
|
||||
- `ManifestVariant`
|
||||
- `ManifestBody`
|
||||
- `ManifestSignature`
|
||||
- `Manifest`
|
||||
|
||||
These are encoded via Solidity ABI rules and hashed with `keccak256`. They mirror the shipped
|
||||
protocol types; they do not replace the local wire format or local hashing rules.
|
||||
|
||||
### Manifest commitments
|
||||
|
||||
Publisher-generated manifests may carry a set of Ethereum commitments as first-class fields:
|
||||
|
||||
- `manifest-data-merkle-keccak256-v1`
|
||||
- Keccak Merkle root over ordered chunk hashes, or over ordered variant roots for multi-variant
|
||||
manifests.
|
||||
- `manifest-body-abi-keccak256-v1`
|
||||
- `keccak256(abi.encode(EthManifestBody))`
|
||||
- `manifest-envelope-abi-keccak256-v1`
|
||||
- `keccak256(abi.encode(EthManifest))`
|
||||
|
||||
`manifest_id` and `merkle_root` remain the local BLAKE3-backed protocol fields. Ethereum
|
||||
commitments are explicit, separately named, and never inferred from those local field names alone.
|
||||
|
||||
### Signature rails
|
||||
|
||||
Manifest signatures become dual-rail:
|
||||
|
||||
- local signatures continue to sign the local BLAKE3 `manifest_id` with Ed25519,
|
||||
- Ethereum-native signatures sign the typed `EthManifestBody` with secp256k1 EIP-712,
|
||||
- both signature families may appear on the same manifest.
|
||||
|
||||
The Ethereum signature target is `ManifestBody`, not the full manifest envelope, so signatures do
|
||||
not become self-referential when more signatures are appended.
|
||||
|
||||
### Stream and control commitments
|
||||
|
||||
Stream descriptors and control announcements may also carry Ethereum-compatible commitments:
|
||||
|
||||
- `stream-id-keccak256-v1`
|
||||
- `stream-descriptor-abi-keccak256-v1`
|
||||
- `control-announcement-abi-keccak256-v1`
|
||||
|
||||
This ties broadcast-scoped discovery identity to the same settlement vocabulary used by manifests
|
||||
without forcing transport-time consumers to abandon the local vocabulary they already use.
|
||||
|
||||
### Solidity rail contract
|
||||
|
||||
Add a minimal contract that stores only the latest compact pointers:
|
||||
|
||||
- latest manifest pointer per `stream_id + epoch_id`
|
||||
- latest transport announcement pointer per `stream_id`
|
||||
- event emissions for historical indexing
|
||||
|
||||
The contract must stay event-heavy and storage-light. Full manifests stay in iroh / relay / archive
|
||||
storage.
|
||||
|
||||
### `ecp-forge` role
|
||||
|
||||
`ecp-forge` becomes the maximal head node for this network:
|
||||
|
||||
- private-chain bootstrap and packaging,
|
||||
- archival indexer for settlement events,
|
||||
- high-bandwidth manifest and control announcer,
|
||||
- optional sequencer / execution-client host for the private network.
|
||||
|
||||
This role is operational, not constitutional. Other nodes must still be able to validate, mirror,
|
||||
and submit commitments without going through a closed control plane.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep custom BLAKE3-only manifests forever. Rejected because it strands every.channel outside the
|
||||
tooling and audit surface of the Ethereum ecosystem.
|
||||
- Replace BLAKE3 and Ed25519 immediately. Rejected because it weakens the local protocol’s existing
|
||||
integrity path before the private rail is operational and discards useful cryptographic
|
||||
properties for no near-term operational win.
|
||||
- Build a custom blockchain. Rejected because existing Rust + Solidity stacks are already good
|
||||
enough and reduce implementation risk.
|
||||
- Use public Ethereum mainnet / arbitrary public L2s directly. Rejected for now because cost,
|
||||
privacy, and operational control do not fit the current network shape.
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Add `ec-eth` with canonical Solidity-compatible representations and Keccak commitments.
|
||||
2. Preserve the local BLAKE3/Ed25519 rail as the default transport integrity path.
|
||||
3. Add optional secp256k1 EIP-712 manifest-body signatures alongside Ed25519 signatures.
|
||||
4. Add the minimal Solidity rail contract for compact settlement pointers.
|
||||
5. Teach publishers/indexers to mirror manifests and announcements onto the private chain.
|
||||
6. Stand up the private Ethereum-compatible network on `ecp-forge` and companion nodes.
|
||||
|
||||
Teardown:
|
||||
|
||||
- back out the Ethereum rail changes explicitly in code; the preserved local rail remains as the
|
||||
fallback integrity path.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Which execution stack should back the private network first: a slim Reth-based node, or a more
|
||||
minimal Rust EVM deployment?
|
||||
- When the private rail becomes live, do we require both signature families for publisher policy, or
|
||||
is one valid rail-specific signature enough for a given transport or settlement action?
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
# ECP-0093: `ecp-forge` OP Stack Sepolia testnet and observation consensus rail
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`every.channel` now has additive Ethereum-compatible commitments and signatures, but it does not yet
|
||||
have:
|
||||
|
||||
- a concrete chain-operator path for bringing up a real OP Stack testnet on `ecp-forge`,
|
||||
- a repo-owned bootstrap workflow that matches the current official OP Stack deployment guidance,
|
||||
- an application-level consensus surface for "reality-derived" blocks where witnesses attest to the
|
||||
same observed broadcast epoch, or
|
||||
- a reproducible smoke path that pushes real every.channel archive data through the rail.
|
||||
|
||||
The current `EveryChannelRail` contract is useful as a compact pointer store, but it does not
|
||||
capture the consensus shape requested by the founder: nodes independently discover an observation
|
||||
from reality, then witnesses sign off on that shared observation hash.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a two-part rollout:
|
||||
|
||||
1. Stand up an OP Stack Sepolia-anchored testnet on `ecp-forge` using the official OP Stack stack:
|
||||
`op-deployer`, `op-geth`, `op-node`, `op-batcher`, `op-proposer`, `op-challenger`, and
|
||||
optional `op-dispute-mon`.
|
||||
2. Introduce an application-level observation consensus rail for every.channel:
|
||||
- an `ObservationHeader` is derived from real stream epoch data,
|
||||
- witnesses attest to the derived observation hash,
|
||||
- the observation is finalized when quorum is reached,
|
||||
- full media bytes remain off-chain.
|
||||
|
||||
This keeps Ethereum as the ordering/finality substrate while making the application-level "block"
|
||||
come from reality, not from arbitrary contract writes.
|
||||
|
||||
## Details
|
||||
|
||||
### `ecp-forge` OP Stack deployment
|
||||
|
||||
- Use the official OP Stack deployment topology described in the current Optimism chain-operator
|
||||
tutorial:
|
||||
- Sepolia L1 contracts via `op-deployer`,
|
||||
- `op-geth` + `op-node` sequencer,
|
||||
- `op-batcher`,
|
||||
- `op-proposer`,
|
||||
- `op-challenger`,
|
||||
- optional `op-dispute-mon`.
|
||||
- Host these components on `ecp-forge` via repo-owned NixOS services and pinned official container
|
||||
images.
|
||||
- Keep L2 RPC surfaces private by default; expose P2P only as required for testnet participation.
|
||||
- Default L1 RPC / beacon endpoints may use public Sepolia providers, but deployment and operation
|
||||
require a repo-managed Sepolia private key secret with sufficient ETH.
|
||||
- `op-challenger` and `op-dispute-mon` require a repo-managed Cannon absolute prestate artifact.
|
||||
If the prestate is absent, the core rollup may still run, but the dispute path is intentionally
|
||||
gated off instead of starting with incomplete configuration.
|
||||
|
||||
### Observation consensus
|
||||
|
||||
Introduce a new application-level surface alongside `EveryChannelRail`:
|
||||
|
||||
- `ObservationHeader`
|
||||
- `streamHash`
|
||||
- `epochHash`
|
||||
- `parentObservationHash`
|
||||
- `dataRoot`
|
||||
- `locatorHash`
|
||||
- `observedUnixMs`
|
||||
- `sequence`
|
||||
- `ObservationHash`
|
||||
- `keccak256(abi.encode(ObservationHeader))`
|
||||
- `ObservationSlot`
|
||||
- `keccak256(abi.encode(streamHash, epochHash))`
|
||||
|
||||
Witnesses attest to an `ObservationHash`, not directly to arbitrary payloads. This lets the chain
|
||||
finalize the winning observation for a `(stream, epoch)` slot without storing the full manifest,
|
||||
chunk list, PSIP data, or media bytes.
|
||||
|
||||
### Witness model
|
||||
|
||||
For the first testnet tranche:
|
||||
|
||||
- witness membership is registry-backed,
|
||||
- quorum is fixed and explicit,
|
||||
- no staking or slashing is required,
|
||||
- sending an attestation transaction from a witness address is sufficient "sign off".
|
||||
|
||||
Off-chain EIP-712 witness signatures may be added later for relayed submission, but they are not a
|
||||
prerequisite for the first live testnet.
|
||||
|
||||
### Real-data validation
|
||||
|
||||
Use real every.channel archive data already present on `ecp-forge` as the smoke source:
|
||||
|
||||
- derive observation headers from actual archive JSONL entries,
|
||||
- submit them to a local Anvil deployment of the observation ledger,
|
||||
- prove quorum/finalization against production-shaped data before attempting Sepolia rollout.
|
||||
|
||||
## Components
|
||||
|
||||
- `contracts/EveryChannelRail.sol`
|
||||
- compact manifest / transport pointer store (existing rail)
|
||||
- `contracts/EveryChannelWitnessRegistry.sol`
|
||||
- witness membership for observation consensus
|
||||
- `contracts/EveryChannelObservationLedger.sol`
|
||||
- candidate observation storage, attestation tracking, and finalization
|
||||
- `scripts/op-stack/download-op-deployer.sh`
|
||||
- pinned `op-deployer` fetch helper
|
||||
- `scripts/op-stack/setup-rollup.sh`
|
||||
- repo-owned OP Stack bootstrap/config generation
|
||||
- `scripts/op-stack/anvil-reality-smoke.sh`
|
||||
- local smoke against Anvil using real `ecp-forge` archive data
|
||||
- `nix/modules/ec-op-stack.nix`
|
||||
- NixOS service/module surface for `ecp-forge`
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Use Base Sepolia directly as the only test environment. Rejected because it validates contract
|
||||
compatibility, but not operation of an every.channel-owned chain.
|
||||
- Wait for a future custom consensus implementation before any chain bring-up. Rejected because it
|
||||
delays operator learning and hides infrastructure problems until late.
|
||||
- Put media bytes or full manifests on chain. Rejected because it is operationally wasteful and not
|
||||
required for observation finalization.
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Add the observation-ledger contracts and Foundry tests.
|
||||
2. Add Anvil + real-archive smoke scripts.
|
||||
3. Add an `ec-op-stack` Nix module and `ecp-forge` integration points.
|
||||
4. Wire secrets for a Sepolia deployment key on `ecp-forge`.
|
||||
5. Bring up the OP Stack services on `ecp-forge`.
|
||||
6. Deploy observation-ledger contracts onto the new chain.
|
||||
7. Submit real archive-derived observations and verify finalization.
|
||||
|
||||
Teardown:
|
||||
|
||||
- disable `services.every-channel.op-stack`,
|
||||
- stop and remove the OP Stack containers/state directories,
|
||||
- preserve the local Anvil smoke path and the additive Ethereum rail code.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should `ecp-forge` be the only sequencer for the first tranche, or should a second verifier-only
|
||||
peer be provisioned immediately?
|
||||
- When we move beyond a registry-backed witness set, do we want bonds, reputation, or both?
|
||||
- Should finalized observation slots bridge back into the existing manifest/transport pointer
|
||||
contract automatically, or remain a separate ledger surface for the first rollout?
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# ECP-0094: NBC Browser-Backed Source with Adobe Auth
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
Add `nbc.com/watch/...` as a manual source in the Tauri app by treating it as a browser-backed
|
||||
capture source instead of a direct manifest source.
|
||||
|
||||
The desktop app will:
|
||||
|
||||
- recognize NBC watch URLs as a distinct `nbc` source kind
|
||||
- launch Chrome with a persistent profile so Adobe Pass / MVPD state can survive across runs
|
||||
- wait for the Adobe/NBC entitlement sequence seen in `intake/`
|
||||
- capture the rendered video element from the authenticated browser tab and feed those frames into
|
||||
the existing ffmpeg CMAF ladder path
|
||||
|
||||
## Motivation
|
||||
|
||||
The intake traces show NBC live playback is not exposed as a plain live HLS or clear DASH input:
|
||||
|
||||
- page metadata comes from `friendship.nbc.com`
|
||||
- entitlement is gated by Adobe Pass on `sp.auth.adobe.com`
|
||||
- authorization is validated through NBC's `tokenverifier`
|
||||
- playout resolves to MediaTailor DASH plus Widevine license requests
|
||||
|
||||
That means the existing source model cannot ingest NBC by simply resolving a URL for ffmpeg. We
|
||||
need a source path that can execute the browser login/auth/player flow first.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- `nbc.com/watch/...` detection in the Tauri add-stream flow
|
||||
- persistent manual-source storage for the new kind
|
||||
- Chrome launch/profile handling for NBC playback
|
||||
- readiness checks tied to the Adobe/session/authorize/token-verifier path observed in the traces
|
||||
- browser-frame capture into the existing live transcode/publish pipeline
|
||||
|
||||
Out of scope:
|
||||
|
||||
- generic DRM extraction or CDM key handling
|
||||
- unattended credential entry for every MVPD provider
|
||||
- CLI ingest support in `ec-node`
|
||||
- perfect parity with native audio capture in the first cut
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Treat NBC as a normal HLS/DASH source. Rejected because the trace shows DRM-gated DASH that
|
||||
ffmpeg cannot ingest directly.
|
||||
- Re-implement the full Adobe + MVPD HTTP flow in-process. Rejected for the first cut because the
|
||||
real browser/player state is already required for playout and provider flows vary.
|
||||
- Leave NBC unsupported. Rejected because the traces are sufficient to define a pragmatic desktop
|
||||
path now.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The change is additive and isolated to the desktop app's manual-source flow.
|
||||
- Roll back by removing the `nbc` source kind and browser capture path.
|
||||
- Existing HDHomeRun, HLS, Linux DVB, and yt-dlp flows remain unchanged.
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# ECP-0095: Native macOS Webview First for NBC Auth and Playback
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
On macOS, the Tauri app should attempt NBC playback through an in-app `WebviewWindow` backed by
|
||||
`WKWebView` before launching an external browser.
|
||||
|
||||
The native path will:
|
||||
|
||||
- create a dedicated NBC auth/player window inside the app with a persistent webview data
|
||||
directory
|
||||
- keep Adobe Pass / MVPD interaction inside the native app window, including popup sign-in flows
|
||||
- use native `WKWebView` JavaScript evaluation and snapshot APIs to drive readiness checks and
|
||||
capture rendered frames for ffmpeg
|
||||
|
||||
The existing Chrome-backed path remains as a fallback when native playback cannot become ready.
|
||||
|
||||
## Motivation
|
||||
|
||||
The first NBC implementation proved that browser-backed capture is the right source model, but it
|
||||
still pushes authentication into an external Chrome session. For the desktop app, that is a worse
|
||||
operator experience than a native in-app window and makes session management less coherent.
|
||||
|
||||
Tauri 2 already exposes the platform webview and, on macOS, gives access to the underlying
|
||||
`WKWebView`. That is enough to move the login and capture loop in-process without redesigning the
|
||||
rest of the CMAF ladder or MoQ publish flow.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- macOS-only `WKWebView` session creation for NBC sources
|
||||
- popup/new-window handling for MVPD login flows
|
||||
- native JavaScript probing plus native snapshot capture for the existing frame pipeline
|
||||
- automatic fallback to the existing Chrome path when native playback fails to get ready
|
||||
|
||||
Out of scope:
|
||||
|
||||
- removing the Chrome fallback entirely
|
||||
- guaranteeing DRM parity with Chrome across every NBC playback variant
|
||||
- audio capture changes beyond the current video-first path
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep the external Chrome-only path. Rejected because the native app can host the auth/player
|
||||
surface directly on macOS.
|
||||
- Replace the entire source implementation with custom CEF/Chromium embedding. Rejected because it
|
||||
is materially heavier than using Tauri's existing native webview APIs.
|
||||
- Use an in-app webview only for login, then continue playback in Chrome. Rejected for the first
|
||||
pass because it still splits operator state across two browser stacks.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The change is additive and scoped to NBC handling in the Tauri app on macOS.
|
||||
- If native playback proves unreliable for a given stream/auth path, the app falls back to the
|
||||
existing Chrome implementation automatically.
|
||||
- Roll back by removing the native webview path and retaining the Chrome-backed implementation from
|
||||
ECP-0094.
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
# ECP-0096: Public NBC Guide Before Auth
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
Add a public NBC guide feed to the Tauri app so browseable NBC linear channels appear before any
|
||||
Adobe or MVPD authentication happens.
|
||||
|
||||
The desktop app will:
|
||||
|
||||
- fetch the unauthenticated `friendship.nbc.com` live guide shelf seen in `intake/`
|
||||
- materialize those rows as normal `StreamDescriptor` entries in the existing channel list
|
||||
- route each discovered row to a stable `nbc.com/live?brand=...` URL so selecting a card enters
|
||||
the existing NBC auth and playback path only when the user chooses to watch
|
||||
|
||||
## Motivation
|
||||
|
||||
The NBC capture already proved that playback is auth-gated, but the same trace also contains a
|
||||
public guide layer with channel titles, current programs, branding, and stream access names. That
|
||||
is enough to support browse-before-auth behavior without exposing entitlement or playout tokens.
|
||||
|
||||
This keeps discovery public while preserving the existing browser-backed auth boundary from
|
||||
ECP-0094 and ECP-0095.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- unauthenticated NBC public guide fetches for channel browsing
|
||||
- conversion of live guide rows into `nbc` stream descriptors and sources
|
||||
- Tauri UI updates needed to surface `nbc` rows in the channel list
|
||||
|
||||
Out of scope:
|
||||
|
||||
- reverse-engineering or exposing entitlement responses
|
||||
- direct manifest playback without the existing auth flow
|
||||
- a full public sports-event catalog beyond the live channel guide in this cut
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep NBC URL-only. Rejected because it hides browseable public metadata that the trace already
|
||||
exposes.
|
||||
- Expose MediaTailor or token-verifier outputs in discovery. Rejected because those belong to the
|
||||
authenticated playback path.
|
||||
- Build a separate NBC-only discovery UI. Rejected because the existing channel list can host the
|
||||
guide with less surface area.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The change is additive to the desktop app and only affects discovery presentation.
|
||||
- Roll back by removing the public guide fetch and the synthesized NBC discovery entries.
|
||||
- Existing NBC auth and playback handling remains the same once a user chooses to watch.
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# ECP-0097: Tauri Desktop Bundle Boot via Relative Assets and Custom Protocol
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
Make the Tauri desktop frontend boot from bundled assets reliably by:
|
||||
|
||||
- building the Tauri UI with relative Trunk asset URLs
|
||||
- exposing the standard `custom-protocol` cargo feature so direct Cargo runs can load bundled
|
||||
assets instead of the dev server URL
|
||||
- showing a minimal boot-status overlay until the Dioxus app mounts, so startup failures are no
|
||||
longer silent blank windows
|
||||
|
||||
## Motivation
|
||||
|
||||
The desktop shell was starting as a blank window even though the frontend bundle itself rendered
|
||||
correctly over HTTP. Two separate issues caused that failure mode:
|
||||
|
||||
- the Trunk bundle used root-absolute asset paths, which are fragile for desktop asset loading
|
||||
- `cargo run` without the Tauri custom-protocol feature followed the dev-server path instead of the
|
||||
bundled `dist/` assets
|
||||
|
||||
This made local desktop verification ambiguous and hid useful error information from operators.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- Tauri UI bundling for desktop
|
||||
- direct Cargo run behavior for the desktop app
|
||||
- a minimal visible frontend boot diagnostic
|
||||
|
||||
Out of scope:
|
||||
|
||||
- redesigning the viewer UI
|
||||
- changing the web deployment path for non-Tauri surfaces
|
||||
- full developer tooling around webview devtools
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep root-absolute Trunk paths and require `cargo tauri dev` only. Rejected because desktop
|
||||
bundle verification remains fragile and blank-window failures remain opaque.
|
||||
- Add only the boot overlay. Rejected because diagnostics help, but the bundle/load path still
|
||||
needs correction.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The change is local to the Tauri desktop app.
|
||||
- Roll back by restoring the prior Trunk public URL and removing the package feature / boot
|
||||
overlay.
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
# ECP-0098: NBC Native Auth Completion and Explicit Guide Program Labels
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
Tighten the macOS-native NBC flow so Adobe Pass background-login completion in popup windows is
|
||||
treated as a successful auth handoff, and expose explicit current-program metadata for NBC public
|
||||
guide rows so the desktop channel list can render live program info directly.
|
||||
|
||||
The desktop app will:
|
||||
|
||||
- treat Adobe's `completeBackgroundLogin.html` popup landing as a native auth completion signal
|
||||
- refocus and reload the main NBC player webview once that signal arrives so playback readiness can
|
||||
continue in the same in-app session
|
||||
- reload the main NBC player webview once when it is already back on a fully loaded NBC watch
|
||||
surface but still has not materialized a video element
|
||||
- persist the current live program as first-class guide metadata instead of relying only on the
|
||||
generic `number` field
|
||||
|
||||
## Motivation
|
||||
|
||||
The first native `WKWebView` pass gets the user into Verizon login, but the Adobe handoff can end
|
||||
inside a short-lived popup instead of automatically returning control to the main player window.
|
||||
Without explicit completion handling, the native flow appears to stall after successful login.
|
||||
|
||||
Separately, NBC public discovery already carries current-program titles, but the desktop list was
|
||||
rendering only the generic `number` slot. That makes guide rows look incomplete even when the
|
||||
metadata is already available.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- native popup completion handling for Adobe background login on macOS
|
||||
- readiness-loop updates needed to continue playback after popup completion or an already-authenticated return to the main watch surface
|
||||
- explicit current-program metadata and guide-row subtitle rendering for NBC discovery rows
|
||||
|
||||
Out of scope:
|
||||
|
||||
- a broader redesign of the channel list for all source types
|
||||
- removal of the Chrome NBC fallback path
|
||||
- changes to NBC entitlement semantics beyond surfacing existing discovery metadata
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep waiting on the main player page only. Rejected because Adobe can finish inside a popup and
|
||||
leave the main page idle.
|
||||
- Continue storing NBC program info only in `StreamDescriptor.number`. Rejected because it obscures
|
||||
intent and makes guide rendering brittle.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The auth-completion change is additive to the native macOS NBC path and falls back to Chrome if
|
||||
playback still does not become ready.
|
||||
- The guide-label change is additive metadata plus UI presentation.
|
||||
- Roll back by removing the popup completion hook and the explicit program-label rendering.
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
# ECP-0099: NBC Hidden Native Worker Mode Instead of True Headless Chrome
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
Do not target true Chrome headless mode for NBC playback in `every.channel`.
|
||||
|
||||
Instead, add a macOS hidden native-worker mode for the existing `WKWebView` path so NBC playback
|
||||
can run without a visible browser window when the session is already authenticated.
|
||||
|
||||
The desktop app will:
|
||||
|
||||
- allow the native NBC webview windows to start hidden behind `EVERY_CHANNEL_NBC_HIDE_WINDOWS=1`
|
||||
- surface those hidden windows only when the navigation flow reaches an interactive auth page
|
||||
- refuse the visible Chrome fallback when hidden native mode is explicitly requested
|
||||
|
||||
## Motivation
|
||||
|
||||
On this Mac, local Google Chrome in true headless mode does not expose EME / `requestMediaKeySystemAccess`,
|
||||
which makes Widevine-backed NBC playback non-viable in a real headless browser process.
|
||||
|
||||
That means the practical path to future unattended playback is not “headless Chrome,” but an
|
||||
invisible native browser surface with a warm authenticated session and explicit surfacing only
|
||||
when auth has expired.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- macOS hidden-window behavior for the existing NBC `WKWebView` path
|
||||
- auth-page detection that reveals the hidden window when user interaction is required
|
||||
- blocking visible Chrome fallback when hidden mode is requested
|
||||
|
||||
Out of scope:
|
||||
|
||||
- automating Verizon credential entry in this cut
|
||||
- making Chrome headless support Widevine / EME
|
||||
- guaranteeing unattended runs when the MVPD session has expired
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Use true Chrome headless. Rejected because local testing showed no EME path.
|
||||
- Keep only the visible native/Chrome flows. Rejected because a warm session should be able to run
|
||||
without putting browser chrome on screen.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The change is opt-in behind `EVERY_CHANNEL_NBC_HIDE_WINDOWS=1`.
|
||||
- Roll back by removing the hidden-window flag and retaining the visible native flow from ECP-0098.
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# ECP-0100: NBC Auth Bootstrap Command for Hidden Worker Sessions
|
||||
|
||||
Status: Proposed
|
||||
|
||||
## Decision
|
||||
|
||||
Add an explicit `bootstrap_nbc_auth` command to the Tauri app so operators can warm an NBC /
|
||||
Adobe session ahead of hidden playback runs.
|
||||
|
||||
The command will:
|
||||
|
||||
- accept either an existing NBC stream id or a raw `nbc.com/watch/...` or `nbc.com/live?...` URL
|
||||
- run the macOS native NBC path in hidden mode by default
|
||||
- surface the auth window only when MVPD interaction is actually required
|
||||
- close the bootstrap window after the session is ready so later hidden playback can reuse the
|
||||
persisted session state
|
||||
|
||||
## Motivation
|
||||
|
||||
Hidden worker mode makes unattended playback plausible only when the session is already warm.
|
||||
Without an explicit bootstrap surface, the operator has to guess which playback attempt should be
|
||||
used to re-authenticate the session.
|
||||
|
||||
An explicit bootstrap command gives `every.channel` a clean operator primitive: warm auth now,
|
||||
then let later hidden playback reuse that session without additional visible browser chrome.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- a Tauri backend command for NBC auth bootstrap
|
||||
- a small operator affordance in the desktop UI to invoke that command
|
||||
- reusing the same persisted native webview data directory as hidden playback
|
||||
|
||||
Out of scope:
|
||||
|
||||
- a full standalone CLI lifecycle for bootstrap without the Tauri app runtime
|
||||
- storing Verizon credentials in the repo
|
||||
- automating expired-auth recovery beyond surfacing the interactive window when needed
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Reuse ordinary playback attempts as implicit bootstrap. Rejected because it obscures operator
|
||||
intent and complicates hidden-run automation.
|
||||
- Build a separate bootstrap-only app. Rejected because the existing Tauri runtime already hosts
|
||||
the relevant webview/session state.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- The command is additive.
|
||||
- Roll back by removing the command and UI affordance while retaining ECP-0099 hidden native mode.
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# ECP-0101: Decode Archived Object Frames Before HLS Replay
|
||||
|
||||
## Why
|
||||
|
||||
`wt-archive` stores raw MoQ object frames in CAS. `wt-archive-serve` was replaying those framed bytes directly as `init.mp4` and `.m4s`, which yields HLS playlists that look valid but cannot be decoded by players.
|
||||
|
||||
## Decision
|
||||
|
||||
Decode archived object frames back into their media payload bytes before serving replay responses. If archived bytes are already raw payloads, pass them through unchanged.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Local and forge replay can hand archived CMAF data to normal HLS consumers.
|
||||
- Existing archives remain usable without rewriting CAS objects.
|
||||
- Replay failures now point at genuinely bad archive contents rather than envelope bytes leaking over HTTP.
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# ECP-0102: Linux Widevine NBC Worker on `ecp-forge`
|
||||
|
||||
## Why
|
||||
|
||||
NBC live playback currently exists only in the desktop app:
|
||||
|
||||
- macOS native `WKWebView` for in-app auth and playback capture
|
||||
- local Chrome fallback for browser-driven playback capture
|
||||
|
||||
`ecp-forge` cannot originate an NBC live stream today because `ec-node` has no NBC source worker, no Adobe auth bootstrap flow, and no Linux browser runtime with Widevine.
|
||||
|
||||
## Decision
|
||||
|
||||
Build a forge-capable NBC source worker around Linux Google Chrome plus a persistent authenticated profile.
|
||||
|
||||
1. Use Google Chrome on `x86_64` Linux, not bare Chromium, as the browser runtime for NBC on forge.
|
||||
2. Run Chrome in a virtual display session on `ecp-forge` so DRM playback remains in a normal browser path even when no physical display is attached.
|
||||
Keep the forge worker on a dedicated display number and disable GPU acceleration by default under Xvfb.
|
||||
3. Persist the NBC/Adobe browser profile on forge so auth warmup is amortized across runs.
|
||||
4. Add an explicit bootstrap path that surfaces interactive auth only when the stored session is cold or expired.
|
||||
5. Launch Chrome as an external process and attach over DevTools rather than relying on the crate-managed Chrome launcher path.
|
||||
6. Publish the resulting captured stream through the existing `ec-node` relay path so archive, replay, and downstream nodes stay unchanged.
|
||||
7. Expose the forge operator surface as `ec-node nbc-bootstrap` and `ec-node nbc-wt-publish`, with a persistent virtual display on `ecp-forge`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- NBC live origin becomes possible from forge without depending on a user’s macOS desktop session.
|
||||
- We keep the existing every.channel transport/archive model and only replace the source worker.
|
||||
- The first forge implementation should be browser-frame capture first; audio and full unattended auth renewal can follow once the Linux worker is stable.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- Reuse the macOS `WKWebView` path on forge: impossible on Linux.
|
||||
- Depend on bare Chromium only: rejected because Widevine support is the central requirement.
|
||||
- Require true headless-only DRM playback from the start: rejected because the safer first implementation is a normal Chrome session inside a virtual display.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# ECP-0103: Mullvad Philadelphia Egress for Forge NBC Philadelphia
|
||||
|
||||
## Why
|
||||
|
||||
The forge-side NBC worker is currently dependent on a reverse-tunneled proxy for US egress.
|
||||
That is enough to prove the geo-boundary, but it is the wrong long-term operator shape for `NBC Sports Philadelphia`.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Enable the Mullvad daemon on `ecp-forge`.
|
||||
2. Keep the Mullvad account number out of committed Nix configuration; log in operationally from founder-provided material.
|
||||
3. Use a Philadelphia Mullvad relay for `NBC Sports Philadelphia` work on forge.
|
||||
4. Start the forge NBC publish worker after the Mullvad daemon is available.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Forge NBC egress becomes self-contained instead of depending on a local reverse proxy.
|
||||
- The account credential stays operational-only rather than being copied into repo config.
|
||||
- Relay choice remains runtime-controlled, so it can be swapped if a specific Philadelphia host degrades.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- Keep relying on the reverse-tunneled local proxy: rejected because it couples forge origin to a founder workstation.
|
||||
- Commit the Mullvad account number into NixOS config: rejected because it expands secret exposure for no benefit.
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# ECP-0104: `ecp-forge` dual Ethereum full nodes on dedicated NVMe ZFS
|
||||
|
||||
## Why
|
||||
|
||||
`every.channel` now has OP Stack and Ethereum contract rails on `ecp-forge`, but it still depends on
|
||||
public L1 RPC and beacon providers for Ethereum itself.
|
||||
|
||||
The forge host has enough raw capacity in `tank`, but `tank` is a large HDD `raidz3` pool that is
|
||||
well suited for archive bytes, not for the random-read/write pattern of concurrent Ethereum
|
||||
execution and consensus sync. The same host also has a free 7 TB NVMe device, which is the right
|
||||
media class for a self-hosted node.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Create a dedicated single-device ZFS pool for Ethereum on the free NVMe in `ecp-forge`.
|
||||
2. Run two full-sync Ethereum node pairs on that pool:
|
||||
- Ethereum mainnet
|
||||
- Ethereum Sepolia
|
||||
3. Use Reth for execution and Lighthouse for consensus.
|
||||
4. Keep raw execution and beacon RPC local-only on `ecp-forge` for the first tranche.
|
||||
5. Publish sync and finality status on `https://eth.every.channel`.
|
||||
6. Because this tranche is explicitly full-sync from genesis, run Lighthouse with
|
||||
`--allow-insecure-genesis-sync` instead of checkpoint bootstrap.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `every.channel` gets repo-owned L1 execution and consensus rails for both mainnet and Sepolia.
|
||||
- Ethereum state no longer competes with `tank` archive I/O.
|
||||
- The first public surface is intentionally conservative: health and sync visibility, not
|
||||
unauthenticated public JSON-RPC.
|
||||
- The dedicated Ethereum pool is single-device NVMe, so it optimizes for node performance rather
|
||||
than storage redundancy.
|
||||
- Consensus sync starts from genesis without checkpoint assistance, which is slower but matches the
|
||||
requested full-sync posture.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- Put Ethereum state on `tank`: rejected because the HDD `raidz3` pool is the wrong latency profile
|
||||
for concurrent execution and consensus sync.
|
||||
- Keep using only public upstream Ethereum providers: rejected because the OP Stack and settlement
|
||||
rails should not depend on third-party RPC availability.
|
||||
- Expose raw JSON-RPC on `eth.every.channel` immediately: rejected for the first tranche because it
|
||||
creates an unauthenticated public abuse surface before auth and rate policy exists.
|
||||
- Use checkpoint sync for Lighthouse: rejected for this tranche because the requested posture is
|
||||
full sync from genesis on both networks.
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# ECP-0105: Stable home directory for forge NBC browser workers
|
||||
|
||||
## Why
|
||||
|
||||
The forge NBC worker runs Chrome under a dedicated service user and a persistent profile, but the
|
||||
publish unit was not explicitly setting `HOME`.
|
||||
|
||||
That leaves browser helper processes free to derive state paths from ambient defaults instead of the
|
||||
intended service home. On a long-running forge host, that makes the browser worker more vulnerable
|
||||
to stale locks, crash-report paths, and profile contamination from out-of-band debugging sessions.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Set `HOME=/var/lib/every-channel` on NBC `wt-publish` units, not only on the Xvfb helper units.
|
||||
2. Keep the persistent NBC profile and auth artifacts under `/var/lib/every-channel`.
|
||||
3. Treat the forge NBC browser runtime as a single-service home/profile domain so cleanup and
|
||||
troubleshooting stay deterministic.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Forge NBC launches use the same home directory across the display service and publish service.
|
||||
- Chrome helper processes no longer need to infer state roots from ambient defaults.
|
||||
- Manual debugging sessions must either reuse the service home intentionally or use an isolated
|
||||
profile path to avoid poisoning the live worker profile.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- Keep only `--user-data-dir` and leave `HOME` implicit: rejected because browser helper processes
|
||||
still derive ancillary paths outside the intended service state root.
|
||||
- Give the publish unit a separate home from the display unit: rejected because it makes the forge
|
||||
browser runtime harder to reason about and recover.
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# ECP-0106: Forge NBC workers need `/tmp` and search-driven MVPD selection
|
||||
|
||||
## Why
|
||||
|
||||
The forge NBC worker reached two distinct failure domains:
|
||||
|
||||
1. Chrome failed during early startup under the hardened `wt-publish` unit even though the same
|
||||
browser launch worked outside the systemd sandbox.
|
||||
2. Once the browser launch succeeded, the MVPD picker automation could reach the provider gate but
|
||||
still mis-clicked broad page containers instead of the intended provider search result.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Allow NBC `wt-publish` units to write to `/tmp` in addition to the persistent profile and auth
|
||||
directories.
|
||||
2. Treat the NBC MVPD picker as a search-first flow:
|
||||
- type the configured provider name
|
||||
- submit the search explicitly
|
||||
- prefer short, actionable provider-result nodes over generic container matches
|
||||
3. Keep the provider name configurable through `EVERY_CHANNEL_NBC_MVPD_PROVIDER`, with `Verizon Fios`
|
||||
remaining the default.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Forge NBC workers align better with Chrome's actual startup needs under systemd hardening.
|
||||
- MVPD automation becomes less likely to click the whole picker page or other non-provider chrome.
|
||||
- Future provider integrations should extend the same search-first DOM strategy instead of adding
|
||||
brittle page-wide text matches.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- Disable most systemd hardening for NBC units entirely: rejected because `/tmp` write access is the
|
||||
smallest validated change that unblocks Chrome startup.
|
||||
- Keep broad `div` and `span` provider scans: rejected because they can match large container nodes
|
||||
whose text merely happens to include the provider name.
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# ECP-0109: Local HDHomeRun publishers submit observation rail commitments
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`ecp-forge` has the Ethereum / OP Stack direction and observation ledger contracts, while local
|
||||
nodes have the HDHomeRun tuners and can already produce verified manifests. The missing bridge is a
|
||||
publisher path that can run on the local LAN, observe real tuner-derived epochs, and submit compact
|
||||
observation headers to the remote chain without moving media bytes on chain.
|
||||
|
||||
## Decision
|
||||
|
||||
Add an optional observation-rail sink to `ec-node moq-publish`:
|
||||
|
||||
- each published manifest epoch can become one `EveryChannelObservationLedger.ObservationHeader`,
|
||||
- `streamHash` is `keccak256(stream_id)`,
|
||||
- `epochHash` is `keccak256(epoch_id)`,
|
||||
- `dataRoot` is the manifest's Ethereum data-root commitment,
|
||||
- `locatorHash` commits to a compact JSON locator for the manifest and MoQ broadcast,
|
||||
- `observedUnixMs` and `sequence` come from the manifest body, and
|
||||
- submission uses a configured RPC URL, ledger address, and witness private key.
|
||||
|
||||
The sink is disabled unless explicitly configured. It is intended for a local publisher talking to
|
||||
the remote every.channel chain through the remote host's local-only RPC surface, typically via an
|
||||
SSH tunnel. The OP Stack L2 RPC uses a distinct local port from the full Ethereum nodes on the same
|
||||
host so publisher submissions do not accidentally target mainnet or Sepolia L1 RPC.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Local HDHomeRun boxes can act as reality witnesses without running the full chain locally.
|
||||
- The chain stores compact observation commitments only; media segments and full manifests remain
|
||||
on MoQ / iroh / archive storage.
|
||||
- The first implementation uses Foundry `cast` for transaction submission so the repo can validate
|
||||
end-to-end with Anvil before committing to an embedded Rust transaction signer.
|
||||
- A quorum greater than one still requires additional witnesses to attest; the local publisher only
|
||||
proposes and self-attests when the configured key is a registry witness.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Run the full chain locally next to the HDHomeRuns. Rejected because the desired validation target
|
||||
is the remote every.channel chain, and a local chain would hide remote reachability/configuration
|
||||
failures.
|
||||
- Push full media or manifests on chain. Rejected because the observation rail only needs compact
|
||||
commitments and locators.
|
||||
- Add an embedded Rust transaction signer immediately. Deferred until the end-to-end rail proves
|
||||
useful with Foundry tooling.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
1. Add manifest-to-observation derivation in `ec-eth`.
|
||||
2. Add optional `ec-node moq-publish` flags and environment fallbacks for observation submission.
|
||||
3. Add an ignored HDHomeRun + Anvil E2E test and a wrapper script.
|
||||
4. Point local publishers at the remote RPC once the remote chain is reachable.
|
||||
|
||||
Teardown is simply disabling the observation options; local manifest publication remains unchanged.
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
# ECP-0110: `ecp-forge` Hetzner Robot recovery wrapper
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`git.every.channel` is a single dedicated Hetzner host. When SSH and HTTPS are both unreachable,
|
||||
the blockchain and Forgejo validation path stalls before repo-owned deployment tools can connect.
|
||||
Robot can recover the host, but browser-only recovery is hard to repeat and easy to lose across
|
||||
agent handoffs.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a repo-local Robot wrapper for `ecp-forge` recovery:
|
||||
|
||||
- default to server `2800441` / `95.216.114.54`,
|
||||
- read Robot Webservice credentials from environment variables or the existing 1Password item at
|
||||
runtime,
|
||||
- avoid storing Robot passwords in git or shell profiles,
|
||||
- expose explicit status, rescue, reset, recover, and reachability-probe commands, and
|
||||
- mask Robot-generated rescue passwords unless the operator explicitly opts into printing them.
|
||||
|
||||
The wrapper treats rescue activation and reset as operational recovery steps, not deployment. Once
|
||||
the host is reachable again, `scripts/deploy-ecp-forge.sh` remains the source of truth for the
|
||||
NixOS system state.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Future agents can recover the Forge after a local 1Password CLI sign-in without asking for pasted
|
||||
Robot secrets.
|
||||
- The host identity and Robot server number are documented in the repo instead of being rediscovered
|
||||
from the browser UI.
|
||||
- Recovery actions remain explicit commands; ordinary probes never mutate Robot state.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Continue browser-only Robot recovery. Rejected because it is too stateful for repeated agent
|
||||
handoffs and does not leave a repo-owned runbook.
|
||||
- Store Robot credentials in a repo-local file. Rejected because Robot credentials are operational
|
||||
secrets and should stay in 1Password or the caller's environment.
|
||||
- Move recovery into the deploy script. Rejected because Robot rescue/reset is a host-recovery action,
|
||||
while `deploy-ecp-forge.sh` should remain the NixOS deployment entrypoint.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
1. Add `scripts/hetzner-robot-forge.sh`.
|
||||
2. Document the emergency path in `docs/DEPLOY_ECP_FORGE.md`.
|
||||
3. Use `probe` first, then `status`, then `recover` only when the Forge is unreachable.
|
||||
|
||||
Teardown is removing the wrapper and returning to browser-only Robot operations.
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
# ECP-0111: Disable Host Mullvad for Forge Public Recovery
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`git.every.channel` must stay reachable on public SSH and HTTPS so blockchain validation, deploys,
|
||||
and Forgejo review can proceed. The current `ecp-forge` boot reaches Forgejo, Caddy, and SSH socket
|
||||
activation, but the host becomes unreachable once the host-wide Mullvad daemon connects and applies
|
||||
its firewall policy.
|
||||
|
||||
## Decision
|
||||
|
||||
Disable host-wide Mullvad on `ecp-forge` and stop making forge NBC workers wait for host Mullvad.
|
||||
The public Forge host stays on the Hetzner interface. NBC egress that needs Mullvad should return
|
||||
through a process-scoped or namespace-scoped design that does not install a host-wide kill switch.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `git.every.channel` can serve SSH, HTTPS, and ACME challenges on the public Hetzner address.
|
||||
- Forge recovery no longer depends on manual Mullvad split-tunnel state.
|
||||
- Forge NBC Philadelphia publishing loses the host-wide Mullvad egress assumption until a narrower
|
||||
worker-only egress path lands.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep host-wide Mullvad and rely on split-tunnel exceptions. Rejected because production logs show
|
||||
public SSH and HTTPS time out while Mullvad's firewall policy is active.
|
||||
- Keep Mullvad enabled but mask only Caddy or SSH from the tunnel. Rejected because the daemon's
|
||||
firewall policy still governs inbound public reachability at the host level.
|
||||
- Disable the whole `ec-node` service. Rejected because archive and blockchain workers should remain
|
||||
independent of the NBC egress incident.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
1. From Rescue, inspect the previous boot and confirm Forgejo/Caddy start before Mullvad applies its
|
||||
firewall policy.
|
||||
2. If Mullvad rewrites its cached target state back to `secured`, temporarily append
|
||||
`systemd.mask=mullvad-daemon.service systemd.mask=mullvad-early-boot-blocking.service` to the
|
||||
default GRUB entry and reboot production.
|
||||
3. Deploy the NixOS config that keeps host-wide Mullvad disabled, which regenerates the bootloader
|
||||
without the emergency mask.
|
||||
4. Verify `ssh`, `https://git.every.channel/`, Forgejo, and Caddy.
|
||||
|
||||
Teardown is re-enabling host Mullvad only after a tested design preserves public inbound Forge
|
||||
traffic.
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# ECP-0112: Match Nested OP Deployer Intent Schema
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`ecp-forge` OP Stack bootstrap failed with `missing key id` even though
|
||||
`/var/lib/every-channel/op-stack/deployer/.deployer/intent.toml` contained an `id` field. After that
|
||||
was repaired, bootstrap also found a placeholder `state.json` whose deployment fields were still
|
||||
null. The current `op-deployer` intent format writes chain and role values under nested TOML
|
||||
sections, while the bootstrap helper only matched keys at the start of a line and treated any
|
||||
`state.json` as completed state.
|
||||
|
||||
## Decision
|
||||
|
||||
Update the OP Stack bootstrap helper to replace TOML keys after optional indentation, preserve that
|
||||
indentation when writing the replacement value, and run `op-deployer apply` unless the state file has
|
||||
non-null applied deployment fields.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The existing `op-deployer/v0.6.0-rc.3` intent file can be repaired in place.
|
||||
- The bootstrap service can generate sequencer, batcher, proposer, challenger, and dispute monitor
|
||||
runtime config from the existing deployment state.
|
||||
- Placeholder `state.json` files no longer block the apply step.
|
||||
- The change stays compatible with flat TOML keys if `op-deployer` changes the layout again.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Regenerate the deployment state from scratch. Rejected because a surgical config repair is safer
|
||||
for an already deployed OP Stack root.
|
||||
- Keep matching only top-level keys. Rejected because it does not match the live `op-deployer`
|
||||
schema on `ecp-forge`.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
Deploy the updated bootstrap helper, restart `every-channel-op-stack-bootstrap.service`, and then
|
||||
restart the dependent OP Stack containers. Teardown is reverting this helper change and regenerating
|
||||
the OP Stack root with a known-flat intent schema.
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
# ECP-0113: Keep OP Stack Runtime Compatible With Forge Host Services
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
`ecp-forge` now runs the OP Stack bootstrap far enough to produce `deployment.json`, `genesis.json`,
|
||||
and `rollup.json`, but the runtime containers still failed to stay up. `op-geth` tried to bind the
|
||||
default Ethereum P2P port `30303`, already owned by the host full Ethereum node. The pinned
|
||||
`op-node:v1.13.5` rejected current `op-deployer/v0.6.0-rc.3` rollup fields such as `minBaseFee`.
|
||||
After aligning to `op-node:v1.14.0`, that image still rejected the newer
|
||||
`genesis.system_config.daFootprintGasScalar` field. The generated rollup config also carried
|
||||
`eip1559Params = 0x0000000000000000` even though the genesis `extraData` and chain config encode
|
||||
denominator `250` and elasticity `6`; that zero value caused `op-geth` to panic when the sequencer
|
||||
requested the first payload. `op-batcher:v1.14.0` also no longer accepts `--batch-inbox-address`.
|
||||
Isolated compatibility probes showed `op-node:v1.16.6` paired with `op-geth:v1.101702.0-rc.1` can
|
||||
run against the generated genesis hash and produce L2 blocks.
|
||||
|
||||
## Decision
|
||||
|
||||
Assign `op-geth` a repo-owned L2 P2P port in the existing `285xx` range, align `op-node` to the
|
||||
probed `v1.16.6` runtime, move `op-geth` to the probed
|
||||
`v1.101702.0-rc.1` image, remove the stale batcher inbox-address flag, delete only
|
||||
`genesis.system_config.daFootprintGasScalar` from generated rollup configs, and derive zero
|
||||
`eip1559Params` from the generated `chain_op_config`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The host Ethereum node can keep `30303` without blocking OP Stack startup.
|
||||
- The OP Stack RPC and P2P port assignments stay documented in repo config.
|
||||
- Runtime image compatibility is explicit in Nix config.
|
||||
- The rollup JSON normalization is intentionally narrow: it removes the exact field rejected by the
|
||||
older `op-node:v1.14.0` parser and repairs only the zero EIP-1559 params that caused the live
|
||||
`op-geth` payload panic.
|
||||
- The `op-geth` image is an explicit release-candidate tag because the previously pinned image
|
||||
panicked against the current deployer output.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Stop the host full Ethereum node. Rejected because the OP Stack should coexist with the existing
|
||||
Ethereum services.
|
||||
- Strip all newer-looking fields from `rollup.json`. Rejected because `op-node:v1.14.0` accepts the
|
||||
other generated fields tested during recovery; broad deletion would hide schema drift.
|
||||
- Leave zero `eip1559Params` in place. Rejected because the live sequencer/geth pair panicked before
|
||||
the first L2 block could be built.
|
||||
- Keep `op-geth:v1.101511.1`. Rejected because it reproducibly panics on first payload construction
|
||||
for this generated chain config.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
Deploy the updated NixOS module and bootstrap helper, reset failed OP Stack units, and verify L2 RPC
|
||||
and rollup RPC locally on `ecp-forge`. Teardown is reverting the port assignment and rollup JSON
|
||||
normalization, then regenerating runtime files with a mutually compatible deployer/runtime image set.
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
# ECP-0114: Live Playback Audio Stability
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
Local HDHomeRun playback can sound choppy even while video continues. The current desktop bridge starts a full three-rung CMAF/HLS ladder for local watching, and each variant carries its own AAC encode. OTA MPEG-TS timestamps can also jitter enough that straight AAC transcoding preserves audible gaps or corrections.
|
||||
|
||||
## Decision
|
||||
|
||||
Use a playback-specific encoding profile for local watching:
|
||||
|
||||
- local desktop playback encodes only the 720p rendition instead of the full ABR ladder;
|
||||
- all live AAC transcode paths force AAC-LC stereo and resample with timestamp compensation;
|
||||
- keep the full multi-variant ladder for publishing and sharing paths.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Local watching spends less CPU and avoids variant-switch audio discontinuities.
|
||||
- AAC output gets a continuous 48 kHz stereo timeline even when OTA timestamps jitter.
|
||||
- Published streams remain multi-variant and manifest-compatible.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep full ABR for local playback. Rejected because the local player does not need ABR to watch a LAN tuner and the separate AAC timelines make audible switching more likely.
|
||||
- Fix only the player buffer. Rejected because the source fragments should already have stable audio timestamps before they reach HLS or MSE.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
Remove the playback-specific variant selection and the audio resample filter from the ffmpeg profiles.
|
||||
61
flake.nix
61
flake.nix
|
|
@ -13,11 +13,6 @@
|
|||
nixosModules = rec {
|
||||
ec-node = import ./nix/modules/ec-node.nix;
|
||||
ec-runner = import ./nix/modules/ec-runner.nix;
|
||||
ec-netboot = import ./nix/modules/ec-netboot.nix;
|
||||
ec-ipxe-qemu = import ./nix/modules/ec-ipxe-qemu.nix;
|
||||
ec-ethereum = import ./nix/modules/ec-ethereum.nix;
|
||||
ec-op-stack = import ./nix/modules/ec-op-stack.nix;
|
||||
ec-publisher-guest = import ./nix/modules/ec-publisher-guest.nix;
|
||||
default = ec-node;
|
||||
};
|
||||
in
|
||||
|
|
@ -34,36 +29,11 @@
|
|||
./nix/nixos/ec-runner.nix
|
||||
] ++ extraModules;
|
||||
};
|
||||
mkPublisher = system: extraModules:
|
||||
nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
specialArgs = { inherit self; };
|
||||
modules = [
|
||||
./nix/nixos/ec-runner.nix
|
||||
self.nixosModules.ec-publisher-guest
|
||||
] ++ extraModules;
|
||||
};
|
||||
in
|
||||
{
|
||||
# Base runner system (for normal installs).
|
||||
ec-runner-aarch64 = mkRunner "aarch64-linux" [ ];
|
||||
ec-runner-x86_64 = mkRunner "x86_64-linux" [ ];
|
||||
ec-publisher-x86_64 = mkPublisher "x86_64-linux" [ ];
|
||||
|
||||
# Sovereign forge host (git.every.channel) managed from every.channel.
|
||||
ecp-forge = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs = { inherit self; };
|
||||
modules = [
|
||||
agenix.nixosModules.default
|
||||
self.nixosModules.ec-node
|
||||
self.nixosModules.ec-netboot
|
||||
self.nixosModules.ec-ipxe-qemu
|
||||
self.nixosModules.ec-ethereum
|
||||
self.nixosModules.ec-op-stack
|
||||
./nix/nixos/ecp-forge.nix
|
||||
];
|
||||
};
|
||||
|
||||
# Netboot artifacts (iPXE/PXE).
|
||||
ec-runner-aarch64-netboot = mkRunner "aarch64-linux" [
|
||||
|
|
@ -116,31 +86,6 @@
|
|||
})
|
||||
];
|
||||
|
||||
ec-publisher-x86_64-netboot = mkPublisher "x86_64-linux" [
|
||||
({ modulesPath, ... }: {
|
||||
imports = [ (modulesPath + "/installer/netboot/netboot-minimal.nix") ];
|
||||
})
|
||||
({ ... }: {
|
||||
services.every-channel.runner.overlayRoot.enable = false;
|
||||
})
|
||||
({ config, pkgs, ... }: {
|
||||
system.build.netboot = pkgs.linkFarm "ec-publisher-netboot" [
|
||||
{
|
||||
name = "kernel";
|
||||
path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
|
||||
}
|
||||
{
|
||||
name = "initrd";
|
||||
path = "${config.system.build.netbootRamdisk}/initrd";
|
||||
}
|
||||
{
|
||||
name = "netboot.ipxe";
|
||||
path = "${config.system.build.netbootIpxeScript}/netboot.ipxe";
|
||||
}
|
||||
];
|
||||
})
|
||||
];
|
||||
|
||||
# Installer ISO (primarily for x86_64 bring-up).
|
||||
ec-runner-x86_64-iso = mkRunner "x86_64-linux" [
|
||||
({ modulesPath, ... }: {
|
||||
|
|
@ -193,8 +138,6 @@
|
|||
packages = {
|
||||
agenix = agenixPkg;
|
||||
fj = pkgs.forgejo-cli;
|
||||
foundry = pkgs.foundry;
|
||||
solc = pkgs.solc;
|
||||
ec-node = pkgs.callPackage ./nix/pkgs/ec-node.nix { };
|
||||
ec-cli = pkgs.callPackage ./nix/pkgs/ec-cli.nix { };
|
||||
};
|
||||
|
|
@ -214,8 +157,6 @@
|
|||
nodePackages.wrangler
|
||||
agenixPkg
|
||||
forgejo-cli
|
||||
foundry
|
||||
solc
|
||||
uv
|
||||
git
|
||||
just
|
||||
|
|
@ -247,8 +188,6 @@
|
|||
ffmpeg
|
||||
agenixPkg
|
||||
forgejo-cli
|
||||
foundry
|
||||
solc
|
||||
git
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
[profile.default]
|
||||
src = "contracts"
|
||||
test = "contracts/test"
|
||||
script = "contracts/script"
|
||||
out = "out"
|
||||
libs = []
|
||||
Binary file not shown.
394
intake/soup.csv
394
intake/soup.csv
|
|
@ -1,394 +0,0 @@
|
|||
Id,URL,Host,Path,Status,Status Code,Method,Content Type,Client Name,Client Address,Remote Address,Request Start Time,Request End Time,Request Duration (ms),Response Start Time,Response End Time,Response Duration (ms),Duration (ms),Time Complete,Request Body Size (bytes),Response Body Size (bytes),Compressed Request Size (bytes),Compressed Response Size (bytes),Comment,Server IP Address,Server Certificate - Common Name,Server Certificate - DNSs,GraphQL Query Name
|
||||
3121,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3105.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3105.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55241,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:43.410,16:23:43.410,0,16:23:43.560,16:23:43.588,27,178,16:23:43.588,0,3756303,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3120,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3105.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3105.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55238,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:43.359,16:23:43.359,0,16:23:43.467,16:23:43.615,148,256,16:23:43.615,0,64411,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3116,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?audio=all&subtitle=all&forcedNarrative=true&aws.sessionId=02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,GET,application/dash+xml,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:42.931,16:23:42.931,0,16:23:43.019,16:23:43.019,0,88,16:23:43.019,0,279613,0,9714,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3113,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=10&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=8&ns_st_sp=1&ns_st_sc=2&ns_st_psq=3&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=2&ns_st_spc=2&ns_st_cn=1&ns_st_ev=hb&ns_st_po=20004&ns_st_cl=0&ns_st_hc=2&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=0&ns_st_li=1&ns_st_ci=12014223&ns_st_pt=20004&ns_st_dpt=20004&ns_st_ipt=10002&ns_st_ap=20004&ns_st_dap=20004&ns_st_et=20004&ns_st_det=20004&ns_st_upc=20004&ns_st_dupc=12047&ns_st_iupc=10002&ns_st_upa=20004&ns_st_dupa=20004&ns_st_iupa=10002&ns_st_lpc=20004&ns_st_dlpc=12047&ns_st_lpa=20004&ns_st_dlpa=20004&ns_st_pa=32203&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999420774&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=0&ns_st_dpc=0&ns_st_pp=2&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=vc13&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55199,sb.scorecardresearch.com - 99.84.215.5:443,16:23:40.776,16:23:40.776,0,16:23:40.917,16:23:40.917,0,141,16:23:40.917,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3112,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/tracking/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/tracking/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/02e128cb-1f94-42eb-9db4-a832507d00ce,Completed,200,GET,application/json,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:39.402,16:23:39.402,0,16:23:39.453,16:23:39.453,0,50,16:23:39.453,0,28178,0,4171,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3111,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3104.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3104.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55238,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:39.281,16:23:39.281,0,16:23:39.385,16:23:39.397,12,116,16:23:39.397,0,3900497,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3110,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3104.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3104.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55241,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:39.129,16:23:39.129,0,16:23:39.331,16:23:39.350,19,222,16:23:39.350,0,64388,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3109,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?audio=all&subtitle=all&forcedNarrative=true&aws.sessionId=02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,GET,application/dash+xml,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:38.930,16:23:38.930,0,16:23:39.028,16:23:39.028,0,98,16:23:39.028,0,279613,0,9711,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3108,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3103.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3103.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55241,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:36.048,16:23:36.048,0,16:23:36.124,16:23:36.124,0,76,16:23:36.124,0,64262,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3107,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3103.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3103.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55241,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:35.171,16:23:35.172,0,16:23:35.197,16:23:35.272,75,101,16:23:35.272,0,3942471,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3106,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?audio=all&subtitle=all&forcedNarrative=true&aws.sessionId=02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,GET,application/dash+xml,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:34.931,16:23:34.931,0,16:23:35.056,16:23:35.056,0,125,16:23:35.056,0,279613,0,9715,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3103,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3102.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3102.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55241,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:32.010,16:23:32.010,0,16:23:32.055,16:23:32.166,111,155,16:23:32.166,0,3523828,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3102,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3102.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3102.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55238,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:31.997,16:23:31.998,0,16:23:32.020,16:23:32.042,22,44,16:23:32.042,0,64609,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3099,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/tracking/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/tracking/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/02e128cb-1f94-42eb-9db4-a832507d00ce,Completed,200,GET,application/json,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:31.399,16:23:31.399,0,16:23:31.453,16:23:31.453,0,54,16:23:31.453,0,28178,0,4169,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3098,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?audio=all&subtitle=all&forcedNarrative=true&aws.sessionId=02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,GET,application/dash+xml,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:30.931,16:23:30.931,0,16:23:31.017,16:23:31.017,0,87,16:23:31.017,0,279613,0,9715,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3097,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=9&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=8&ns_st_sp=1&ns_st_sc=2&ns_st_psq=3&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=2&ns_st_spc=2&ns_st_cn=1&ns_st_ev=hb&ns_st_po=10002&ns_st_cl=0&ns_st_hc=1&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=0&ns_st_li=1&ns_st_ci=12014223&ns_st_pt=10002&ns_st_dpt=10002&ns_st_ipt=10002&ns_st_ap=10002&ns_st_dap=10002&ns_st_et=10002&ns_st_det=10002&ns_st_upc=10002&ns_st_dupc=2045&ns_st_iupc=2045&ns_st_upa=10002&ns_st_dupa=10002&ns_st_iupa=10002&ns_st_lpc=10002&ns_st_dlpc=2045&ns_st_lpa=10002&ns_st_dlpa=10002&ns_st_pa=22201&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999410772&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=0&ns_st_dpc=0&ns_st_pp=2&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=vc13&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55199,sb.scorecardresearch.com - 99.84.215.5:443,16:23:30.774,16:23:30.775,1,16:23:30.861,16:23:30.861,0,87,16:23:30.861,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3096,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:55203,map.mp.nbc.com - 151.101.130.49:443,16:23:30.111,16:23:30.111,0,16:23:30.147,16:23:30.147,0,36,16:23:30.147,4986,42,0,62,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3095,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3101.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-07item_Segment-3101.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55241,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:27.882,16:23:27.936,53,16:23:27.965,16:23:27.988,24,106,16:23:27.988,0,64420,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3094,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3101.mp4,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/1774987260757item-06item_Segment-3101.mp4,Completed,200,GET,video/mp4,Google Chrome,127.0.0.1:55238,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:27.798,16:23:27.843,45,16:23:27.863,16:23:27.999,136,201,16:23:27.999,0,3627642,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,*.pcdn03.cssott.com,*.pcdn03.cssott.com; *.stream.peacocktv.com; *.cdn.peacocktv.com; g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,
|
||||
3093,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?audio=all&subtitle=all&forcedNarrative=true&aws.sessionId=02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,GET,application/dash+xml,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:26.931,16:23:26.931,0,16:23:27.011,16:23:27.011,0,80,16:23:27.011,0,279607,0,9715,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3091,https://g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com,,Completed,200,CONNECT,,Google Chrome,127.0.0.1:55228,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,16:23:23.733,16:23:23.753,21,16:23:23.753,16:23:23.753,0,21,16:23:23.753,0,0,0,0,,g001-sle-us-cmaf-prd-cf.pcdn03.cssott.com - 2600:9000:2199:3600:f:ca39:f7c0:93a1:443,,,
|
||||
3090,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/tracking/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/tracking/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/02e128cb-1f94-42eb-9db4-a832507d00ce,Completed,200,GET,application/json,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:23.398,16:23:23.398,0,16:23:23.519,16:23:23.519,0,121,16:23:23.519,0,28178,0,4168,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3089,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?audio=all&subtitle=all&forcedNarrative=true&aws.sessionId=02e128cb-1f94-42eb-9db4-a832507d00ce,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,/v1/dash/7f34bf1814de6fddce84b1e6c296b7a70243b88f/oneapp-atp-dash-sle-4s-generic/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,GET,application/dash+xml,Google Chrome,127.0.0.1:55223,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:23.149,16:23:23.242,93,16:23:23.395,16:23:23.395,0,246,16:23:23.395,0,279589,0,9711,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,mediatailor.us-west-2.amazonaws.com,mediatailor.us-west-2.amazonaws.com; *.mediatailor.us-west-2.amazonaws.com; 69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,
|
||||
3087,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55202,map.mp.nbc.com - 151.101.130.49:443,16:23:20.791,16:23:20.915,124,16:23:20.949,16:23:20.949,0,158,16:23:20.949,1182,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3086,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55203,map.mp.nbc.com - 151.101.130.49:443,16:23:20.791,16:23:20.924,132,16:23:20.967,16:23:20.967,0,176,16:23:20.967,1234,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3085,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55200,map.mp.nbc.com - 151.101.130.49:443,16:23:20.791,16:23:20.924,133,16:23:20.958,16:23:20.958,0,167,16:23:20.958,2250,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3084,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55201,map.mp.nbc.com - 151.101.130.49:443,16:23:20.791,16:23:20.924,133,16:23:20.956,16:23:20.956,0,165,16:23:20.956,2268,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3083,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=8&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=7&ns_st_sp=1&ns_st_sc=2&ns_st_psq=3&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=2&ns_st_spc=2&ns_st_cn=1&ns_st_ev=play&ns_st_po=0&ns_st_cl=0&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=0&ns_st_li=1&ns_st_ci=12014223&ns_st_pt=0&ns_st_dpt=0&ns_st_ipt=0&ns_st_ap=0&ns_st_dap=0&ns_st_et=0&ns_st_det=0&ns_st_upc=7957&ns_st_dupc=0&ns_st_iupc=0&ns_st_upa=0&ns_st_dupa=0&ns_st_iupa=0&ns_st_lpc=7957&ns_st_dlpc=0&ns_st_lpa=0&ns_st_dlpa=0&ns_st_pa=12199&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999400770&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=0&ns_st_dpc=0&ns_st_pp=2&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=vc13&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55199,sb.scorecardresearch.com - 99.84.215.5:443,16:23:20.791,16:23:20.906,115,16:23:20.941,16:23:20.942,0,151,16:23:20.942,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3082,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=262144&cn=videoView&et=i&uxnw=169843&uxss=vg2400781&uxct=4&init=1&vcid2=874355218828044267,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55196,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,16:23:20.791,16:23:20.968,177,16:23:21.015,16:23:21.015,0,225,16:23:21.015,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3081,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=7&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=6&ns_st_sp=1&ns_st_sc=1&ns_st_psq=2&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=1&ns_st_spc=1&ns_st_cn=2&ns_st_ev=end&ns_st_po=4242&ns_st_cl=4238&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=1&ns_st_ad=1&ns_st_ci=12014223&ns_st_pt=4242&ns_st_dpt=0&ns_st_ipt=0&ns_st_ap=4242&ns_st_dap=0&ns_st_et=4244&ns_st_det=2&ns_st_upc=4242&ns_st_dupc=0&ns_st_iupc=0&ns_st_upa=4242&ns_st_dupa=0&ns_st_iupa=0&ns_st_lpc=4242&ns_st_dlpc=0&ns_st_lpa=4242&ns_st_dlpa=0&ns_st_pa=12199&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999400770&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=1&ns_st_dpc=0&ns_st_pp=2&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_an=1&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=va12&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&ns_st_amg=92830326&ns_st_ami=214161967&ns_st_amp=92845689&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=&cs_ad_tu=https%3A%2F%2Fsb.scorecardresearch.com%2Fp%3Fc1%3D3%26c2%3D28881558%26c3%3D92830326%26c4%3D214161967%26c5%3D92845689%26c12%3D%26ns_ad_vevent%3Dv_start%26ns_ad_pcd%3D4%26ns__t%3D1801516585%26ns__p%3D1801516585%26ns_st_pr%3DNBCS%3A%20CSNPhilly%3A%20Baseball%3A%20Live%3A%20MLB%3A%20Men%26ns_st_ge%3D%26ns_st_pu%3DNBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Web%3A%20Live%20Event%26ns_st_ep%3DNBCS%3A%20Live%3A%20CSNPhilly%3A%20Baseball%3A%20MLB%3A%20Non-Broadcast%3A%20Philadelphia%20Phillies%3A%20Washington%20Nationals%3A%20nbc_dtc_12014222%5E%26ns_st_ct%3DNBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Computer%3A%20Web%3A%20Live%20Event%26cs_vp_sv%3D1%26rn%3D1801516585%26ccr%3D1%26ccrsdk%3D1%26c6%3Dmidroll%26ns_ap_device%3D%26ns_ap_pn%3D,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55198,sb.scorecardresearch.com - 99.84.215.5:443,16:23:20.791,16:23:20.901,111,16:23:20.936,16:23:20.936,0,145,16:23:20.936,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3080,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=262144&cn=slotEnd&et=i&tpos=0&async=0&init=1&slid=0,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55197,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:20.790,16:23:20.967,177,16:23:21.011,16:23:21.011,0,221,16:23:21.011,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3087,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=6&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=5&ns_st_sp=1&ns_st_sc=1&ns_st_psq=2&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=1&ns_st_spc=1&ns_st_cn=2&ns_st_ev=pause&ns_st_po=4242&ns_st_cl=4238&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=1&ns_st_ad=1&ns_st_ci=12014223&ns_st_pt=4242&ns_st_dpt=4242&ns_st_ipt=4242&ns_st_ap=4242&ns_st_dap=4242&ns_st_et=4242&ns_st_det=4242&ns_st_upc=4242&ns_st_dupc=4242&ns_st_iupc=4242&ns_st_upa=4242&ns_st_dupa=4242&ns_st_iupa=4242&ns_st_lpc=4242&ns_st_dlpc=4242&ns_st_lpa=4242&ns_st_dlpa=4242&ns_st_pa=12199&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999400768&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=1&ns_st_dpc=1&ns_st_pp=2&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_an=1&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=va12&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&ns_st_amg=92830326&ns_st_ami=214161967&ns_st_amp=92845689&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=&cs_ad_tu=https%3A%2F%2Fsb.scorecardresearch.com%2Fp%3Fc1%3D3%26c2%3D28881558%26c3%3D92830326%26c4%3D214161967%26c5%3D92845689%26c12%3D%26ns_ad_vevent%3Dv_start%26ns_ad_pcd%3D4%26ns__t%3D1801516585%26ns__p%3D1801516585%26ns_st_pr%3DNBCS%3A%20CSNPhilly%3A%20Baseball%3A%20Live%3A%20MLB%3A%20Men%26ns_st_ge%3D%26ns_st_pu%3DNBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Web%3A%20Live%20Event%26ns_st_ep%3DNBCS%3A%20Live%3A%20CSNPhilly%3A%20Baseball%3A%20MLB%3A%20Non-Broadcast%3A%20Philadelphia%20Phillies%3A%20Washington%20Nationals%3A%20nbc_dtc_12014222%5E%26ns_st_ct%3DNBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Computer%3A%20Web%3A%20Live%20Event%26cs_vp_sv%3D1%26rn%3D1801516585%26ccr%3D1%26ccrsdk%3D1%26c6%3Dmidroll%26ns_ap_device%3D%26ns_ap_pn%3D,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55192,sb.scorecardresearch.com - 99.84.215.5:443,16:23:20.782,16:23:20.903,121,16:23:20.933,16:23:20.933,0,151,16:23:20.933,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3075,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=33816576&r=169843&adid=92845690&reid=843522403&arid=0&auid=&cn=complete&et=i&_cc=&tpos=0&async=0&init=1&iw=&uxnw=169843&uxss=vg2400781&uxct=4&metr=1031,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55179,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:20.774,16:23:20.774,0,16:23:20.812,16:23:20.812,0,38,16:23:20.812,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3074,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:55188,map.mp.nbc.com - 151.101.130.49:443,16:23:20.018,16:23:20.076,58,16:23:20.106,16:23:20.107,0,89,16:23:20.107,4930,42,0,62,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3073,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=33816576&r=169843&adid=92845690&reid=843522403&arid=0&auid=&cn=thirdQuartile&et=i&_cc=&tpos=0&async=0&init=1&iw=&uxnw=169843&uxss=vg2400781&uxct=4&metr=1031,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55179,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:19.704,16:23:19.704,0,16:23:19.745,16:23:19.745,0,40,16:23:19.745,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3071,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=33816576&r=169843&adid=92845690&reid=843522403&arid=0&auid=&cn=midPoint&et=i&_cc=&tpos=0&async=0&init=1&iw=&uxnw=169843&uxss=vg2400781&uxct=4&metr=1031,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55179,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:18.643,16:23:18.643,0,16:23:18.692,16:23:18.692,0,49,16:23:18.692,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3070,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=33816576&r=169843&adid=92845690&reid=843522403&arid=0&auid=&cn=firstQuartile&et=i&_cc=&tpos=0&async=0&init=1&iw=&uxnw=169843&uxss=vg2400781&uxct=4&metr=1031,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55179,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:17.586,16:23:17.679,93,16:23:17.790,16:23:17.790,0,205,16:23:17.790,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3058,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=5&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=4&ns_st_sp=1&ns_st_sc=1&ns_st_psq=2&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=1&ns_st_spc=1&ns_st_cn=2&ns_st_ev=play&ns_st_po=0&ns_st_cl=4238&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=1&ns_st_ad=1&ns_st_ci=12014223&ns_st_pt=0&ns_st_dpt=0&ns_st_ipt=0&ns_st_ap=0&ns_st_dap=0&ns_st_et=0&ns_st_det=0&ns_st_upc=0&ns_st_dupc=0&ns_st_iupc=0&ns_st_upa=0&ns_st_dupa=0&ns_st_iupa=0&ns_st_lpc=0&ns_st_dlpc=0&ns_st_lpa=0&ns_st_dlpa=0&ns_st_pa=7957&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999396526&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=0&ns_st_dpc=0&ns_st_pp=1&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_an=1&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=va12&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&ns_st_amg=92830326&ns_st_ami=214161967&ns_st_amp=92845689&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=&cs_ad_tu=https%3A%2F%2Fsb.scorecardresearch.com%2Fp%3Fc1%3D3%26c2%3D28881558%26c3%3D92830326%26c4%3D214161967%26c5%3D92845689%26c12%3D%26ns_ad_vevent%3Dv_start%26ns_ad_pcd%3D4%26ns__t%3D1801516585%26ns__p%3D1801516585%26ns_st_pr%3DNBCS%3A%20CSNPhilly%3A%20Baseball%3A%20Live%3A%20MLB%3A%20Men%26ns_st_ge%3D%26ns_st_pu%3DNBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Web%3A%20Live%20Event%26ns_st_ep%3DNBCS%3A%20Live%3A%20CSNPhilly%3A%20Baseball%3A%20MLB%3A%20Non-Broadcast%3A%20Philadelphia%20Phillies%3A%20Washington%20Nationals%3A%20nbc_dtc_12014222%5E%26ns_st_ct%3DNBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Computer%3A%20Web%3A%20Live%20Event%26cs_vp_sv%3D1%26rn%3D1801516585%26ccr%3D1%26ccrsdk%3D1%26c6%3Dmidroll%26ns_ap_device%3D%26ns_ap_pn%3D,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55148,sb.scorecardresearch.com - 99.84.215.5:443,16:23:16.595,16:23:16.765,170,16:23:16.791,16:23:16.791,0,196,16:23:16.791,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3057,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=4&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=3&ns_st_sp=1&ns_st_sc=1&ns_st_psq=1&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=1&ns_st_spc=1&ns_st_cn=1&ns_st_ev=end&ns_st_po=7957&ns_st_cl=0&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=0&ns_st_li=1&ns_st_ci=12014223&ns_st_pt=7957&ns_st_dpt=0&ns_st_ipt=0&ns_st_ap=7957&ns_st_dap=0&ns_st_et=8499&ns_st_det=542&ns_st_upc=7957&ns_st_dupc=0&ns_st_iupc=0&ns_st_upa=7957&ns_st_dupa=0&ns_st_iupa=0&ns_st_lpc=7957&ns_st_dlpc=0&ns_st_lpa=7957&ns_st_dlpa=0&ns_st_pa=7957&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999396526&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=1&ns_st_dpc=0&ns_st_pp=1&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=vc13&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55074,sb.scorecardresearch.com - 99.84.215.5:443,16:23:16.574,16:23:16.574,0,16:23:16.601,16:23:16.601,0,27,16:23:16.601,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3055,https://nbcume.sc.omtrdc.net/b/ss/nbcutve/1/JS-2.24.0/s15683288284250,nbcume.sc.omtrdc.net,/b/ss/nbcutve/1/JS-2.24.0/s15683288284250,Completed,200,POST,image/gif;charset=utf-8,Google Chrome,127.0.0.1:55138,nbcume.sc.omtrdc.net - 63.140.37.201:443,16:23:16.542,16:23:16.709,167,16:23:16.757,16:23:16.757,0,215,16:23:16.757,2172,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.201:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
3055,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55083,map.mp.nbc.com - 151.101.130.49:443,16:23:16.541,16:23:16.541,0,16:23:16.570,16:23:16.570,0,29,16:23:16.570,2214,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3054,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55079,map.mp.nbc.com - 151.101.130.49:443,16:23:16.539,16:23:16.539,0,16:23:16.568,16:23:16.568,0,28,16:23:16.568,2192,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3040,https://sb.scorecardresearch.com/p?c1=3&c2=28881558&c3=92830326&c4=214161967&c5=92845689&c12=&ns_ad_vevent=v_start&ns_ad_pcd=4&ns__t=1801516585&ns__p=1801516585&ns_st_pr=NBCS%3A%20CSNPhilly%3A%20Baseball%3A%20Live%3A%20MLB%3A%20Men&ns_st_ge=&ns_st_pu=NBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Web%3A%20Live%20Event&ns_st_ep=NBCS%3A%20Live%3A%20CSNPhilly%3A%20Baseball%3A%20MLB%3A%20Non-Broadcast%3A%20Philadelphia%20Phillies%3A%20Washington%20Nationals%3A%20nbc_dtc_12014222%5E&ns_st_ct=NBCU%3A%20One%20App%3A%20On%20Domain%3A%20Desktop%3A%20Computer%3A%20Web%3A%20Live%20Event&cs_vp_sv=1&rn=1801516585&ccr=1&ccrsdk=1&c6=midroll&ns_ap_device=&ns_ap_pn=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55074,sb.scorecardresearch.com - 99.84.215.5:443,16:23:16.530,16:23:16.530,0,16:23:16.564,16:23:16.564,0,34,16:23:16.564,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3039,"https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=33816576&r=169843&adid=92845690&reid=843522403&arid=0&auid=&cn=defaultImpression&et=i&_cc=92845690,843522403,,,1774999391,1&tpos=0&async=0&iw=&uxnw=169843&uxss=vg2400781&uxct=4&metr=1031&init=1&vcid2=874355218828044267&pingids=2018,3831",29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55124,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:16.530,16:23:16.530,0,16:23:16.574,16:23:16.574,0,44,16:23:16.574,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3037,https://29773.v.fwmrm.net/ad/l/1?s=w8c03&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999391225685334&f=262144&cn=slotImpression&et=i&tpos=0&async=0&init=1&slid=0,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55124,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,16:23:15.995,16:23:16.095,100,16:23:16.180,16:23:16.180,0,184,16:23:16.180,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd07:8e37:a89c:e407:72f:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3037,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55083,map.mp.nbc.com - 151.101.130.49:443,16:23:15.991,16:23:15.992,0,16:23:16.028,16:23:16.028,0,36,16:23:16.028,1184,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3036,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55079,map.mp.nbc.com - 151.101.130.49:443,16:23:15.991,16:23:15.991,0,16:23:16.025,16:23:16.025,0,34,16:23:16.025,1236,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3034,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=3&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=2&ns_st_sp=1&ns_st_sc=1&ns_st_psq=1&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=1&ns_st_spc=1&ns_st_cn=1&ns_st_ev=pause&ns_st_po=7957&ns_st_cl=0&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=0&ns_st_li=1&ns_st_ci=12014223&ns_st_pt=7957&ns_st_dpt=7957&ns_st_ipt=7957&ns_st_ap=7957&ns_st_dap=7957&ns_st_et=7957&ns_st_det=7957&ns_st_upc=7957&ns_st_dupc=7957&ns_st_iupc=7957&ns_st_upa=7957&ns_st_dupa=7957&ns_st_iupa=7957&ns_st_lpc=7957&ns_st_dlpc=7957&ns_st_lpa=7957&ns_st_dlpa=7957&ns_st_pa=7957&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999395984&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=1&ns_st_dpc=1&ns_st_pp=1&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=vc13&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55074,sb.scorecardresearch.com - 99.84.215.5:443,16:23:15.989,16:23:15.989,0,16:23:16.024,16:23:16.024,0,35,16:23:16.024,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3032,https://69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com,,Completed,200,CONNECT,,Google Chrome,127.0.0.1:55111,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,16:23:10.931,16:23:10.975,44,16:23:10.975,16:23:10.975,0,44,16:23:10.975,0,0,0,0,,69af625f11604bac9af691cd670a8c8d.mediatailor.us-west-2.amazonaws.com - 184.33.133.133:443,,,
|
||||
3031,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:55079,map.mp.nbc.com - 151.101.130.49:443,16:23:09.975,16:23:09.975,0,16:23:10.006,16:23:10.006,0,31,16:23:10.006,6346,42,0,62,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3028,https://www.nbc.com/generetic/images/player/back5.svg,www.nbc.com,/generetic/images/player/back5.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55076,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.345,16:23:08.345,0,16:23:08.555,16:23:08.558,3,213,16:23:08.558,0,1446,0,677,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3027,https://www.nbc.com/generetic/images/player/ahead5.svg,www.nbc.com,/generetic/images/player/ahead5.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55075,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.215,16:23:08.215,0,16:23:08.368,16:23:08.368,0,153,16:23:08.368,0,1491,0,690,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3026,https://www.nbc.com/generetic/images/player/ahead10.svg,www.nbc.com,/generetic/images/player/ahead10.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55077,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.214,16:23:08.214,0,16:23:08.344,16:23:08.344,0,130,16:23:08.344,0,1697,0,710,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3024,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55083,map.mp.nbc.com - 151.101.130.49:443,16:23:08.053,16:23:08.166,113,16:23:08.193,16:23:08.193,0,139,16:23:08.193,1270,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3023,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55085,map.mp.nbc.com - 151.101.130.49:443,16:23:08.053,16:23:08.152,99,16:23:08.185,16:23:08.186,0,132,16:23:08.186,1322,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3022,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55084,map.mp.nbc.com - 151.101.130.49:443,16:23:08.053,16:23:08.138,85,16:23:08.169,16:23:08.169,0,116,16:23:08.169,1407,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3021,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55082,map.mp.nbc.com - 151.101.130.49:443,16:23:08.053,16:23:08.152,100,16:23:08.180,16:23:08.180,0,127,16:23:08.180,1578,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3020,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55081,map.mp.nbc.com - 151.101.130.49:443,16:23:08.052,16:23:08.146,94,16:23:08.176,16:23:08.176,0,124,16:23:08.176,1814,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3024,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:55079,map.mp.nbc.com - 151.101.130.49:443,16:23:08.051,16:23:08.165,114,16:23:08.195,16:23:08.195,0,144,16:23:08.195,1587,0,0,0,,map.mp.nbc.com - 151.101.130.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
3020,https://www.nbc.com/generetic/images/player/fullscreen.svg,www.nbc.com,/generetic/images/player/fullscreen.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55078,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.048,16:23:08.112,64,16:23:08.839,16:23:08.840,0,792,16:23:08.840,0,579,0,323,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3018,https://www.nbc.com/generetic/images/player/keyboard.svg,www.nbc.com,/generetic/images/player/keyboard.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55077,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.047,16:23:08.115,68,16:23:08.214,16:23:08.214,0,166,16:23:08.214,0,1014,0,438,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3016,https://www.nbc.com/generetic/images/player/cc.svg,www.nbc.com,/generetic/images/player/cc.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55076,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.047,16:23:08.110,63,16:23:08.344,16:23:08.344,0,298,16:23:08.344,0,801,0,444,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3015,https://www.nbc.com/generetic/images/player/volume.svg,www.nbc.com,/generetic/images/player/volume.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55075,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.046,16:23:08.118,72,16:23:08.215,16:23:08.215,0,169,16:23:08.215,0,508,0,270,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3014,https://sb.scorecardresearch.com/p?c1=2&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=foreground&ns_ap_ec=2&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=hidden&ns_radio=unknown&ns_nc=1&ns_st_sv=6.3.4.190424&ns_st_smv=5.10&ns_st_it=r&ns_st_id=1774999385980&ns_st_ec=1&ns_st_sp=1&ns_st_sc=1&ns_st_psq=1&ns_st_asq=1&ns_st_sq=1&ns_st_ppc=1&ns_st_apc=1&ns_st_spc=1&ns_st_cn=1&ns_st_ev=play&ns_st_po=0&ns_st_cl=0&ns_st_pb=1&ns_st_mp=js_api&ns_st_mv=6.3.4.190424&ns_st_pn=1&ns_st_tp=0&ns_st_li=1&ns_st_ci=12014223&ns_st_pt=0&ns_st_dpt=0&ns_st_ipt=0&ns_st_ap=0&ns_st_dap=0&ns_st_et=0&ns_st_det=0&ns_st_upc=0&ns_st_dupc=0&ns_st_iupc=0&ns_st_upa=0&ns_st_dupa=0&ns_st_iupa=0&ns_st_lpc=0&ns_st_dlpc=0&ns_st_lpa=0&ns_st_dlpa=0&ns_st_pa=0&ns_st_ldw=0&ns_st_ldo=0&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_cs=1&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999388027&ns_st_bc=0&ns_st_dbc=0&ns_st_bt=0&ns_st_dbt=0&ns_st_bp=0&ns_st_lt=2046&ns_st_skc=0&ns_st_dskc=0&ns_st_ska=0&ns_st_dska=0&ns_st_skd=0&ns_st_skt=0&ns_st_dskt=0&ns_st_pc=0&ns_st_dpc=0&ns_st_pp=0&ns_st_br=0&ns_st_rt=100&ns_st_ub=0&ns_st_ki=1200000&ns_st_pr=MLB&ns_st_sn=None&ns_st_en=None&ns_st_ep=Nationals%20vs.%20Phillies&ns_st_ct=vc13&ns_st_ge=Sports%2C%20Baseball&ns_st_st=NBC%20Sports%20Philadelphia&ns_st_ce=1&ns_st_ia=0&ns_st_ddt=2026-03-31&ns_st_tdt=2026-03-31&ns_st_pu=NBC%20Sports%20Philadelphia&ns_st_ft=None&c3=NBC%20Sports%20Philadelphia&c4=Browser&c6=NBC%20Sports%20Philadelphia_MLB_Sports%2C%20Baseball&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=&cs_ucfr=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:55074,sb.scorecardresearch.com - 99.84.215.5:443,16:23:08.046,16:23:08.107,61,16:23:08.140,16:23:08.140,0,94,16:23:08.140,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
3013,https://www.nbc.com/generetic/images/player/back10.svg,www.nbc.com,/generetic/images/player/back10.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:55017,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.041,16:23:08.041,0,16:23:09.363,16:23:09.363,0,1322,16:23:09.363,0,727,0,394,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3012,https://www.nbc.com/generetic/images/player/pause.svg,www.nbc.com,/generetic/images/player/pause.svg,Completed,200,GET,image/svg+xml,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:08.041,16:23:08.041,0,16:23:08.527,16:23:08.528,0,487,16:23:08.528,0,644,0,329,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
3009,https://drmproxy.digitalsvc.apps.nbcuni.com/drm-proxy/license/widevine?time=1774999386044&hash=bf4c81b2ca82d25dde43e12aaa3dbcddae29f0b2cec16d3018c44097d784e3de&device=web,drmproxy.digitalsvc.apps.nbcuni.com,/drm-proxy/license/widevine,Completed,200,POST,application/octet-stream,Google Chrome,127.0.0.1:55065,drmproxy.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,16:23:07.709,16:23:07.709,0,16:23:07.878,16:23:07.878,0,169,16:23:07.878,3942,620,0,0,,drmproxy.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,www.seeso.com,www.seeso.com; *.apps.nbcuni.com; *.bxjyb2jvda.net; *.digitalsvc.apps.nbcuni.com; *.e-corp-usa.com; *.eonline.com; *.evil-corp-usa.com; *.mvpd-admin.nbcuni.com; *.roku.usa.nbcuni.com; *.seeso.com; *.serverfarm.evil-corp-usa.com; *.swe.apps.nbcuni.com; *.syfy.com; *.tvecms.bravo.nbcuni.com; *.tvecms.chiller.nbcuni.com; *.tvecms.cnbc.nbcuni.com; *.tvecms.eonline.nbcuni.com; *.tvecms.esquire.nbcuni.com; *.tvecms.msnbc.nbcuni.com; *.tvecms.nbcuniverso.nbcuni.com; *.tvecms.oxygen.nbcuni.com; *.tvecms.sprout.nbcuni.com; *.tvecms.syfy.nbcuni.com; *.tvecms.telemundo.nbcuni.com; betadev.idxapi.nbcuni.com; betaidentity.apps.nbcuni.com; betaqa.idxapi.nbcuni.com; betastage.idxapi.nbcuni.com; citywalkhollywood.com; conficturaindustries.com; dev.tvecms.usanetwork.nbcuni.com; e-corp-online.com; e-corp-usa.com; eonline.com; evil-corp-usa.com; fsoc.sh; iammrrobot.com; m.citywalkhollywood.com; racksure.com; realtimetranslation.net; seeso.com; stage.syfywire.com; staticfiles.blastr.com; syfywire.com; universalstudios.com; whereismrrobot.com; whoismrrobot.com; www.citywalkhollywood.com; www.conficturaindustries.com; www.e-corp-online.com; www.e-corp-usa.com; www.fastandfurious-hobbsshaw.it; www.fsoc.sh; www.goodboys.ch; www.hobbs-shaw.at; www.hobbsandshaw-lefilm.be; www.hobbsandshaw-lefilm.ch; www.hobbsandshaw-movie.be; www.hobbsandshaw.ch; www.hobbsandshaw.nl; www.hobbsandshawmovie.ph; www.hobbseshaw-ofilme.pt; www.iammrrobot.com; www.racksure.com; www.rapidosyfuriosos-latam.com; www.realtimetranslation.net; www.syfywire.com; www.tudobonsmeninos.pt; www.universalbranddevelopment.com; www.universalstudios.com; www.upi-digital.com; www.whereismrrobot.com; www.whoismrrobot.com; drmproxy.digitalsvc.apps.nbcuni.com,
|
||||
3008,https://drmproxy.digitalsvc.apps.nbcuni.com/drm-proxy/license/widevine?time=1774999386044&hash=bf4c81b2ca82d25dde43e12aaa3dbcddae29f0b2cec16d3018c44097d784e3de&device=web,drmproxy.digitalsvc.apps.nbcuni.com,/drm-proxy/license/widevine,Completed,200,POST,application/octet-stream,Google Chrome,127.0.0.1:55065,drmproxy.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,16:23:07.591,16:23:07.591,0,16:23:07.683,16:23:07.683,0,93,16:23:07.683,2,711,0,0,,drmproxy.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,www.seeso.com,www.seeso.com; *.apps.nbcuni.com; *.bxjyb2jvda.net; *.digitalsvc.apps.nbcuni.com; *.e-corp-usa.com; *.eonline.com; *.evil-corp-usa.com; *.mvpd-admin.nbcuni.com; *.roku.usa.nbcuni.com; *.seeso.com; *.serverfarm.evil-corp-usa.com; *.swe.apps.nbcuni.com; *.syfy.com; *.tvecms.bravo.nbcuni.com; *.tvecms.chiller.nbcuni.com; *.tvecms.cnbc.nbcuni.com; *.tvecms.eonline.nbcuni.com; *.tvecms.esquire.nbcuni.com; *.tvecms.msnbc.nbcuni.com; *.tvecms.nbcuniverso.nbcuni.com; *.tvecms.oxygen.nbcuni.com; *.tvecms.sprout.nbcuni.com; *.tvecms.syfy.nbcuni.com; *.tvecms.telemundo.nbcuni.com; betadev.idxapi.nbcuni.com; betaidentity.apps.nbcuni.com; betaqa.idxapi.nbcuni.com; betastage.idxapi.nbcuni.com; citywalkhollywood.com; conficturaindustries.com; dev.tvecms.usanetwork.nbcuni.com; e-corp-online.com; e-corp-usa.com; eonline.com; evil-corp-usa.com; fsoc.sh; iammrrobot.com; m.citywalkhollywood.com; racksure.com; realtimetranslation.net; seeso.com; stage.syfywire.com; staticfiles.blastr.com; syfywire.com; universalstudios.com; whereismrrobot.com; whoismrrobot.com; www.citywalkhollywood.com; www.conficturaindustries.com; www.e-corp-online.com; www.e-corp-usa.com; www.fastandfurious-hobbsshaw.it; www.fsoc.sh; www.goodboys.ch; www.hobbs-shaw.at; www.hobbsandshaw-lefilm.be; www.hobbsandshaw-lefilm.ch; www.hobbsandshaw-movie.be; www.hobbsandshaw.ch; www.hobbsandshaw.nl; www.hobbsandshawmovie.ph; www.hobbseshaw-ofilme.pt; www.iammrrobot.com; www.racksure.com; www.rapidosyfuriosos-latam.com; www.realtimetranslation.net; www.syfywire.com; www.tudobonsmeninos.pt; www.universalbranddevelopment.com; www.universalstudios.com; www.upi-digital.com; www.whereismrrobot.com; www.whoismrrobot.com; drmproxy.digitalsvc.apps.nbcuni.com,
|
||||
3007,https://drmproxy.digitalsvc.apps.nbcuni.com/drm-proxy/license/widevine?time=1774999386044&hash=bf4c81b2ca82d25dde43e12aaa3dbcddae29f0b2cec16d3018c44097d784e3de&device=web,drmproxy.digitalsvc.apps.nbcuni.com,/drm-proxy/license/widevine,Completed,200,OPTIONS,application/json,Google Chrome,127.0.0.1:55065,drmproxy.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,16:23:07.428,16:23:07.476,48,16:23:07.590,16:23:07.590,0,162,16:23:07.590,0,0,0,0,,drmproxy.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,www.seeso.com,www.seeso.com; *.apps.nbcuni.com; *.bxjyb2jvda.net; *.digitalsvc.apps.nbcuni.com; *.e-corp-usa.com; *.eonline.com; *.evil-corp-usa.com; *.mvpd-admin.nbcuni.com; *.roku.usa.nbcuni.com; *.seeso.com; *.serverfarm.evil-corp-usa.com; *.swe.apps.nbcuni.com; *.syfy.com; *.tvecms.bravo.nbcuni.com; *.tvecms.chiller.nbcuni.com; *.tvecms.cnbc.nbcuni.com; *.tvecms.eonline.nbcuni.com; *.tvecms.esquire.nbcuni.com; *.tvecms.msnbc.nbcuni.com; *.tvecms.nbcuniverso.nbcuni.com; *.tvecms.oxygen.nbcuni.com; *.tvecms.sprout.nbcuni.com; *.tvecms.syfy.nbcuni.com; *.tvecms.telemundo.nbcuni.com; betadev.idxapi.nbcuni.com; betaidentity.apps.nbcuni.com; betaqa.idxapi.nbcuni.com; betastage.idxapi.nbcuni.com; citywalkhollywood.com; conficturaindustries.com; dev.tvecms.usanetwork.nbcuni.com; e-corp-online.com; e-corp-usa.com; eonline.com; evil-corp-usa.com; fsoc.sh; iammrrobot.com; m.citywalkhollywood.com; racksure.com; realtimetranslation.net; seeso.com; stage.syfywire.com; staticfiles.blastr.com; syfywire.com; universalstudios.com; whereismrrobot.com; whoismrrobot.com; www.citywalkhollywood.com; www.conficturaindustries.com; www.e-corp-online.com; www.e-corp-usa.com; www.fastandfurious-hobbsshaw.it; www.fsoc.sh; www.goodboys.ch; www.hobbs-shaw.at; www.hobbsandshaw-lefilm.be; www.hobbsandshaw-lefilm.ch; www.hobbsandshaw-movie.be; www.hobbsandshaw.ch; www.hobbsandshaw.nl; www.hobbsandshawmovie.ph; www.hobbseshaw-ofilme.pt; www.iammrrobot.com; www.racksure.com; www.rapidosyfuriosos-latam.com; www.realtimetranslation.net; www.syfywire.com; www.tudobonsmeninos.pt; www.universalbranddevelopment.com; www.universalstudios.com; www.upi-digital.com; www.whereismrrobot.com; www.whoismrrobot.com; drmproxy.digitalsvc.apps.nbcuni.com,
|
||||
3003,"https://29773.v.fwmrm.net/ad/l/1?s=wad20&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999386141630193&f=262144&cn=slotEnd&et=i&tpos=0&async=0&init=1&slid=0,1",29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55046,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,16:23:07.111,16:23:07.270,159,16:23:07.313,16:23:07.313,0,203,16:23:07.313,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3002,"https://29773.v.fwmrm.net/ad/l/1?s=wad20&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999386141630193&f=262144&cn=slotImpression&et=i&tpos=0&async=0&init=1&slid=0,1",29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55048,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,16:23:07.098,16:23:07.192,94,16:23:07.305,16:23:07.305,0,206,16:23:07.305,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
3001,https://29773.v.fwmrm.net/ad/l/1?s=wad20&n=169843%3B169843%3B147530%3B190200%3B372496%3B376521%3B378678%3B378901%3B379619%3B380903%3B381963%3B382114%3B382926%3B384777%3B386329%3B391823%3B393638%3B499607%3B505334%3B510702%3B510839%3B511345%3B512029%3B515123%3B515154%3B516448%3B529773&t=1774999386141630193&f=262144&cn=videoView&et=i&uxnw=169843&uxss=vg2400781&uxct=4&init=1&vcid2=874355218828044267,29773.v.fwmrm.net,/ad/l/1,Completed,200,GET,text/html,Google Chrome,127.0.0.1:55047,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,16:23:07.086,16:23:07.185,100,16:23:07.269,16:23:07.269,0,183,16:23:07.269,0,0,0,0,,29773.v.fwmrm.net - 2600:1f14:c96:cd06:af96:f83e:4305:7629:443,*.v.fwmrm.net,*.v.fwmrm.net; 29773.v.fwmrm.net,
|
||||
2999,https://nbcume.sc.omtrdc.net/b/ss/nbcutve/1/JS-2.24.0/s13469709661995?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A23%3A6%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=UTF-8&g=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c.&videoapp=NBC%20Network%20App&appVersion=1.249.0&playerVersion=v3.3.10-v102.4&videoplatform=Web&videoguid=nbc_dtc_12014222&videosubcat2=Sports%2C%20Baseball&videosubcat1=Sports&videonetwork=NBC%20Sports%20Philadelphia&videoprogram=MLB&videoseason=None&videoepnumber=None&videotitle=Nationals%20vs.%20Phillies&a.&media.&friendlyName=Nationals%20vs.%20Phillies&length=86400&asset=POPUP%3A12014223&streamType=video&name=nbc_dtc_12014222&playerName=CVSDK%20Javascript%20Player%20-%20Shaka&channel=On-Domain&view=true&vsid=1774999386913920330261&.media&contentType=live&.a&videoinitiate=Auto-play&videodate=03-31-26&videoday=Tuesday&videohour=16%3A00&videominute=23&videoairdate=03-31-26&videostatus=Entitled&videoplayerurl=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&videodomain=https%3A%2F%2Fwww.nbc.com&videocallsign=None&videomvpd=Verizon&videotmsid=None&videocliptype=unknown&videoscreen=Normal&videosport=Baseball&videoleague=MLB&videobroadcast=Digital&videodaypart=None&videoplayertech=DASH&videocastsource=N%2FA&videocrossdevice=F&videolanguage=N%2FA&videopassguid=5525ff59adcaac313923ab89d0a618c5&videorequestorid=nbcentertainment&videoresearchtitle=260331%20-%20rsn-philadelphia%20-%20Nationals%20vs.%20Phillies&videosponsor=N%2FA&.c&aamb=RKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y&pe=ms_s&pev3=video&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&AQE=1,nbcume.sc.omtrdc.net,/b/ss/nbcutve/1/JS-2.24.0/s13469709661995,Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:55051,nbcume.sc.omtrdc.net - 63.140.37.201:443,16:23:06.922,16:23:07.026,104,16:23:07.077,16:23:07.077,0,155,16:23:07.077,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.201:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2996,https://mt.ssai-oneapp.nbcuni.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?mt.config=oneapp-atp-dash-sle-4s-generic&audio=all&subtitle=all&forcedNarrative=true,mt.ssai-oneapp.nbcuni.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,200,POST,application/json,Google Chrome,127.0.0.1:55042,mt.ssai-oneapp.nbcuni.com - 2600:9000:211d:ce00:a:768a:2640:93a1:443,16:23:06.743,16:23:06.743,0,16:23:06.898,16:23:06.898,0,156,16:23:06.898,2148,636,0,0,,mt.ssai-oneapp.nbcuni.com - 2600:9000:211d:ce00:a:768a:2640:93a1:443,mt.ssai-oneapp.nbcuni.com,mt.ssai-oneapp.nbcuni.com,
|
||||
2995,https://mt.ssai-oneapp.nbcuni.com/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd?mt.config=oneapp-atp-dash-sle-4s-generic&audio=all&subtitle=all&forcedNarrative=true,mt.ssai-oneapp.nbcuni.com,/Content/CMAF_OS1-CTR-4s-v2/Live/channel(12014223-12014222-444afee22018e)/master_2hr.mpd,Completed,204,OPTIONS,application/json,Google Chrome,127.0.0.1:55042,mt.ssai-oneapp.nbcuni.com - 2600:9000:211d:ce00:a:768a:2640:93a1:443,16:23:06.574,16:23:06.660,85,16:23:06.741,16:23:06.741,0,167,16:23:06.741,0,0,0,0,,mt.ssai-oneapp.nbcuni.com - 2600:9000:211d:ce00:a:768a:2640:93a1:443,mt.ssai-oneapp.nbcuni.com,mt.ssai-oneapp.nbcuni.com,
|
||||
2991,https://www.nbc.com/generetic/scripts/omweb-v1.js,www.nbc.com,/generetic/scripts/omweb-v1.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:06.092,16:23:06.092,0,16:23:06.141,16:23:06.141,0,48,16:23:06.141,0,69498,0,14906,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2987,https://sb.scorecardresearch.com/p2?c1=19&c2=6035083&ns_ap_an=NBC%20Network%20App&ns_ap_pn=Mac%20OS&ns_ap_pv=5&c12=d6cb60632d35f07ce7f1951dcd3dd8b2-cs72&name=start&ns_ap_ec=1&ns_ap_ev=start&ns_ap_device=MacIntel&ns_ap_id=1774999385979&ns_ap_csf=1&ns_ap_bi=&ns_ap_pfm=html&ns_ap_pfv=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&ns_ap_ver=1.249.0&ns_ap_sv=6.3.4.190424&ns_type=view&ns_radio=unknown&ns_nc=1&ns_ap_gs=1774999385979&ns_ap_jb=unknown&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_install=1774999385979&ns_ap_lastrun=0&ns_ap_cs=1&ns_ap_runs=1&ns_ap_usage=0&ns_ap_fg=1&ns_ap_ft=0&ns_ap_dft=0&ns_ap_bt=0&ns_ap_dbt=0&ns_ap_dit=0&ns_ap_as=1&ns_ap_das=0&ns_ap_it=0&ns_ap_ut=60000&ns_ap_lang=en-US&ns_ap_ar=unknown&ns_ts=1774999385979&cs_ucfr=,sb.scorecardresearch.com,/p2,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:54909,sb.scorecardresearch.com - 99.84.215.5:443,16:23:05.993,16:23:05.993,0,16:23:06.024,16:23:06.024,0,31,16:23:06.024,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2986,https://nbcume.sc.omtrdc.net/id?d_visid_ver=5.5.0&d_fieldgroup=A&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&mid=48149800880344474781461921042733640603&ts=1774999385978,nbcume.sc.omtrdc.net,/id,Completed,200,GET,application/x-javascript;charset=utf-8,Google Chrome,127.0.0.1:54988,nbcume.sc.omtrdc.net - 63.140.37.201:443,16:23:05.979,16:23:05.979,0,16:23:06.027,16:23:06.027,0,48,16:23:06.027,0,2,0,0,,nbcume.sc.omtrdc.net - 63.140.37.201:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2985,https://www.nbc.com/watch/mlb/nationals-vs-phillies/omweb-v1.js,www.nbc.com,/watch/mlb/nationals-vs-phillies/omweb-v1.js,Redirect,301,GET,text/plain; charset=utf-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:05.976,16:23:05.976,0,16:23:06.092,16:23:06.092,0,116,16:23:06.092,0,64,0,0,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2984,https://www.nbc.com/generetic/generated/chunks/8452.e13c296f6759707ba8b0.js,www.nbc.com,/generetic/generated/chunks/8452.e13c296f6759707ba8b0.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:05.616,16:23:05.616,0,16:23:05.693,16:23:05.697,3,81,16:23:05.697,0,361538,0,117246,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2983,https://www.nbc.com/generetic/generated/chunks/7738.8e268cc07398c51c8aec.js,www.nbc.com,/generetic/generated/chunks/7738.8e268cc07398c51c8aec.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55017,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:04.347,16:23:04.347,0,16:23:05.478,16:23:05.479,0,1131,16:23:05.479,0,213,0,111,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2982,https://www.nbc.com/generetic/generated/chunks/617.39fd88239a001e5f4fd4.js,www.nbc.com,/generetic/generated/chunks/617.39fd88239a001e5f4fd4.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:04.289,16:23:04.290,0,16:23:04.371,16:23:04.391,20,101,16:23:04.391,0,1079237,0,157916,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2981,https://www.nbc.com/generetic/generated/chunks/2612.639ec8413ef63217203f.js,www.nbc.com,/generetic/generated/chunks/2612.639ec8413ef63217203f.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55017,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:04.289,16:23:04.289,0,16:23:04.346,16:23:04.346,0,57,16:23:04.346,0,32981,0,5848,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2980,https://www.nbc.com/generetic/generated/chunks/6852.8e8d78ca9e5ee5b2d393.js,www.nbc.com,/generetic/generated/chunks/6852.8e8d78ca9e5ee5b2d393.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55016,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:04.289,16:23:04.289,0,16:23:05.247,16:23:05.248,0,958,16:23:05.248,0,87328,0,13357,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2979,https://www.nbc.com/generetic/generated/chunks/1754.21ae655dbc3875b3a9b2.js,www.nbc.com,/generetic/generated/chunks/1754.21ae655dbc3875b3a9b2.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:04.289,16:23:04.289,0,16:23:05.607,16:23:05.607,0,1318,16:23:05.607,0,36320,0,6488,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2978,https://www.nbc.com/generetic/generated/chunks/9858.8170d73af8c99769dedb.js,www.nbc.com,/generetic/generated/chunks/9858.8170d73af8c99769dedb.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:04.289,16:23:04.289,0,16:23:04.399,16:23:04.399,0,110,16:23:04.399,0,58024,0,8577,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2977,https://www.nbc.com/generetic/generated/chunks/3589.00a9e4a448d3d02686c5.js,www.nbc.com,/generetic/generated/chunks/3589.00a9e4a448d3d02686c5.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:04.289,16:23:04.289,0,16:23:04.441,16:23:04.442,1,154,16:23:04.442,0,198083,0,29560,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2976,https://www.nbc.com/generetic/generated/chunks/865.7fa93d2fedca26256bf0.js,www.nbc.com,/generetic/generated/chunks/865.7fa93d2fedca26256bf0.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:04.166,16:23:04.166,0,16:23:04.274,16:23:04.274,0,108,16:23:04.274,0,7906,0,1948,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2975,https://www.nbc.com/generetic/generated/chunks/1447.b265bc15ed7d3ea313bc.js,www.nbc.com,/generetic/generated/chunks/1447.b265bc15ed7d3ea313bc.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:04.166,16:23:04.166,0,16:23:04.284,16:23:04.284,0,118,16:23:04.284,0,77794,0,12273,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2974,https://www.nbc.com/generetic/generated/chunks/1920.14e324e388b3e79b5e58.js,www.nbc.com,/generetic/generated/chunks/1920.14e324e388b3e79b5e58.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55016,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:04.166,16:23:04.166,0,16:23:04.210,16:23:04.210,0,44,16:23:04.210,0,132209,0,19191,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2973,https://www.nbc.com/generetic/generated/chunks/5161.d63a41f9ec36bd3bfa9e.js,www.nbc.com,/generetic/generated/chunks/5161.d63a41f9ec36bd3bfa9e.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:04.165,16:23:04.165,0,16:23:04.230,16:23:04.230,0,65,16:23:04.230,0,19003,0,5038,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2972,https://www.nbc.com/generetic/generated/chunks/4912.32c763cf22f4de975f0e.js,www.nbc.com,/generetic/generated/chunks/4912.32c763cf22f4de975f0e.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55016,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.840,16:23:02.840,0,16:23:03.504,16:23:03.505,1,665,16:23:03.505,0,100082,0,13919,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2971,https://www.nbc.com/generetic/generated/chunks/2881.ff0e42b4075313d52d3a.js,www.nbc.com,/generetic/generated/chunks/2881.ff0e42b4075313d52d3a.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.453,16:23:02.453,0,16:23:02.550,16:23:02.550,0,97,16:23:02.550,0,86480,0,13096,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2970,https://www.nbc.com/generetic/generated/chunks/7464.cf6f9a934559a3ce6550.js,www.nbc.com,/generetic/generated/chunks/7464.cf6f9a934559a3ce6550.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.360,16:23:02.360,0,16:23:02.427,16:23:02.427,0,67,16:23:02.427,0,5498,0,1547,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2969,https://www.nbc.com/generetic/generated/chunks/6741.dedf6c0bfe69723b1308.js,www.nbc.com,/generetic/generated/chunks/6741.dedf6c0bfe69723b1308.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.322,16:23:02.322,0,16:23:02.587,16:23:02.588,0,266,16:23:02.588,0,36470,0,6255,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2968,https://www.nbc.com/generetic/generated/chunks/5275.73a6acb807d0c7218823.js,www.nbc.com,/generetic/generated/chunks/5275.73a6acb807d0c7218823.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.314,16:23:02.314,0,16:23:04.160,16:23:04.160,0,1847,16:23:04.160,0,23660,0,4737,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2966,https://www.nbc.com/generetic/generated/chunks/9855.d5d6f20b438e882d7d18.js,www.nbc.com,/generetic/generated/chunks/9855.d5d6f20b438e882d7d18.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55017,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.277,16:23:02.337,60,16:23:02.547,16:23:02.548,0,271,16:23:02.548,0,63013,0,9907,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2967,https://www.nbc.com/generetic/generated/chunks/6028.51e0b3aacc415d474601.js,www.nbc.com,/generetic/generated/chunks/6028.51e0b3aacc415d474601.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:55016,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.277,16:23:02.334,57,16:23:02.831,16:23:02.833,2,556,16:23:02.833,0,186497,0,35036,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2965,https://www.nbc.com/generetic/generated/chunks/9558.74c80de93e916d34668a.js,www.nbc.com,/generetic/generated/chunks/9558.74c80de93e916d34668a.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.270,16:23:02.270,0,16:23:02.450,16:23:02.451,0,180,16:23:02.451,0,59968,0,7714,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2964,https://www.nbc.com/generetic/generated/chunks/7498.30e5f63ed16c5dc8728e.js,www.nbc.com,/generetic/generated/chunks/7498.30e5f63ed16c5dc8728e.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.270,16:23:02.270,0,16:23:02.320,16:23:02.320,0,50,16:23:02.320,0,60011,0,9182,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2963,https://www.nbc.com/generetic/generated/chunks/6064.489101561449c5ec8ace.js,www.nbc.com,/generetic/generated/chunks/6064.489101561449c5ec8ace.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.270,16:23:02.270,0,16:23:02.357,16:23:02.360,3,90,16:23:02.360,0,124563,0,30894,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2962,https://www.nbc.com/generetic/generated/chunks/3932.bbf4be740487f4e4256c.js,www.nbc.com,/generetic/generated/chunks/3932.bbf4be740487f4e4256c.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.270,16:23:02.270,0,16:23:02.313,16:23:02.314,1,44,16:23:02.314,0,91159,0,16918,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2959,https://www.nbc.com/generetic/generated/chunks/2859.f0865b626c12bc9ee1fe.js,www.nbc.com,/generetic/generated/chunks/2859.f0865b626c12bc9ee1fe.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.073,16:23:02.074,0,16:23:02.222,16:23:02.222,0,149,16:23:02.222,0,43750,0,7285,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2958,https://www.nbc.com/generetic/generated/chunks/4160.dbd7af47207bdf03c69b.js,www.nbc.com,/generetic/generated/chunks/4160.dbd7af47207bdf03c69b.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.073,16:23:02.074,0,16:23:02.158,16:23:02.160,2,87,16:23:02.160,0,173470,0,30990,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2957,https://www.nbc.com/generetic/generated/chunks/7988.78bc6efd346c51993e4f.js,www.nbc.com,/generetic/generated/chunks/7988.78bc6efd346c51993e4f.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.073,16:23:02.074,0,16:23:02.237,16:23:02.268,31,194,16:23:02.268,0,1101411,0,178913,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2956,https://www.nbc.com/generetic/generated/chunks/4706.4d1a2ce67f3ce19f0732.js,www.nbc.com,/generetic/generated/chunks/4706.4d1a2ce67f3ce19f0732.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:23:02.073,16:23:02.074,0,16:23:02.224,16:23:02.225,0,151,16:23:02.225,0,78006,0,8581,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2955,https://www.nbc.com/generetic/generated/chunks/5695.b062340e3944f1931722.js,www.nbc.com,/generetic/generated/chunks/5695.b062340e3944f1931722.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:23:02.010,16:23:02.010,0,16:23:02.070,16:23:02.070,0,60,16:23:02.070,0,2637,0,975,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2951,https://tokenverifier.digitalsvc.apps.nbcuni.com/tokenverifier/entitlement/verifier,tokenverifier.digitalsvc.apps.nbcuni.com,/tokenverifier/entitlement/verifier,Completed,200,POST,application/media.entitlement-v1+json,Google Chrome,127.0.0.1:54999,tokenverifier.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,16:23:01.500,16:23:01.500,0,16:23:01.586,16:23:01.586,0,86,16:23:01.586,1024,452,0,0,,tokenverifier.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,www.seeso.com,www.seeso.com; *.apps.nbcuni.com; *.bxjyb2jvda.net; *.digitalsvc.apps.nbcuni.com; *.e-corp-usa.com; *.eonline.com; *.evil-corp-usa.com; *.mvpd-admin.nbcuni.com; *.roku.usa.nbcuni.com; *.seeso.com; *.serverfarm.evil-corp-usa.com; *.swe.apps.nbcuni.com; *.syfy.com; *.tvecms.bravo.nbcuni.com; *.tvecms.chiller.nbcuni.com; *.tvecms.cnbc.nbcuni.com; *.tvecms.eonline.nbcuni.com; *.tvecms.esquire.nbcuni.com; *.tvecms.msnbc.nbcuni.com; *.tvecms.nbcuniverso.nbcuni.com; *.tvecms.oxygen.nbcuni.com; *.tvecms.sprout.nbcuni.com; *.tvecms.syfy.nbcuni.com; *.tvecms.telemundo.nbcuni.com; betadev.idxapi.nbcuni.com; betaidentity.apps.nbcuni.com; betaqa.idxapi.nbcuni.com; betastage.idxapi.nbcuni.com; citywalkhollywood.com; conficturaindustries.com; dev.tvecms.usanetwork.nbcuni.com; e-corp-online.com; e-corp-usa.com; eonline.com; evil-corp-usa.com; fsoc.sh; iammrrobot.com; m.citywalkhollywood.com; racksure.com; realtimetranslation.net; seeso.com; stage.syfywire.com; staticfiles.blastr.com; syfywire.com; universalstudios.com; whereismrrobot.com; whoismrrobot.com; www.citywalkhollywood.com; www.conficturaindustries.com; www.e-corp-online.com; www.e-corp-usa.com; www.fastandfurious-hobbsshaw.it; www.fsoc.sh; www.goodboys.ch; www.hobbs-shaw.at; www.hobbsandshaw-lefilm.be; www.hobbsandshaw-lefilm.ch; www.hobbsandshaw-movie.be; www.hobbsandshaw.ch; www.hobbsandshaw.nl; www.hobbsandshawmovie.ph; www.hobbseshaw-ofilme.pt; www.iammrrobot.com; www.racksure.com; www.rapidosyfuriosos-latam.com; www.realtimetranslation.net; www.syfywire.com; www.tudobonsmeninos.pt; www.universalbranddevelopment.com; www.universalstudios.com; www.upi-digital.com; www.whereismrrobot.com; www.whoismrrobot.com; tokenverifier.digitalsvc.apps.nbcuni.com,
|
||||
2949,https://tokenverifier.digitalsvc.apps.nbcuni.com/tokenverifier/entitlement/verifier,tokenverifier.digitalsvc.apps.nbcuni.com,/tokenverifier/entitlement/verifier,Completed,200,OPTIONS,application/json,Google Chrome,127.0.0.1:54999,tokenverifier.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,16:23:01.355,16:23:01.402,47,16:23:01.499,16:23:01.499,0,144,16:23:01.499,0,0,0,0,,tokenverifier.digitalsvc.apps.nbcuni.com - 2600:1406:4c00:199::1297:443,www.seeso.com,www.seeso.com; *.apps.nbcuni.com; *.bxjyb2jvda.net; *.digitalsvc.apps.nbcuni.com; *.e-corp-usa.com; *.eonline.com; *.evil-corp-usa.com; *.mvpd-admin.nbcuni.com; *.roku.usa.nbcuni.com; *.seeso.com; *.serverfarm.evil-corp-usa.com; *.swe.apps.nbcuni.com; *.syfy.com; *.tvecms.bravo.nbcuni.com; *.tvecms.chiller.nbcuni.com; *.tvecms.cnbc.nbcuni.com; *.tvecms.eonline.nbcuni.com; *.tvecms.esquire.nbcuni.com; *.tvecms.msnbc.nbcuni.com; *.tvecms.nbcuniverso.nbcuni.com; *.tvecms.oxygen.nbcuni.com; *.tvecms.sprout.nbcuni.com; *.tvecms.syfy.nbcuni.com; *.tvecms.telemundo.nbcuni.com; betadev.idxapi.nbcuni.com; betaidentity.apps.nbcuni.com; betaqa.idxapi.nbcuni.com; betastage.idxapi.nbcuni.com; citywalkhollywood.com; conficturaindustries.com; dev.tvecms.usanetwork.nbcuni.com; e-corp-online.com; e-corp-usa.com; eonline.com; evil-corp-usa.com; fsoc.sh; iammrrobot.com; m.citywalkhollywood.com; racksure.com; realtimetranslation.net; seeso.com; stage.syfywire.com; staticfiles.blastr.com; syfywire.com; universalstudios.com; whereismrrobot.com; whoismrrobot.com; www.citywalkhollywood.com; www.conficturaindustries.com; www.e-corp-online.com; www.e-corp-usa.com; www.fastandfurious-hobbsshaw.it; www.fsoc.sh; www.goodboys.ch; www.hobbs-shaw.at; www.hobbsandshaw-lefilm.be; www.hobbsandshaw-lefilm.ch; www.hobbsandshaw-movie.be; www.hobbsandshaw.ch; www.hobbsandshaw.nl; www.hobbsandshawmovie.ph; www.hobbseshaw-ofilme.pt; www.iammrrobot.com; www.racksure.com; www.rapidosyfuriosos-latam.com; www.realtimetranslation.net; www.syfywire.com; www.tudobonsmeninos.pt; www.universalbranddevelopment.com; www.universalstudios.com; www.upi-digital.com; www.whereismrrobot.com; www.whoismrrobot.com; tokenverifier.digitalsvc.apps.nbcuni.com,
|
||||
2949,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s15228206811507?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A23%3A1%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=nbcentertainment%3APC%3AWatch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&passmvpd=Verizon&passguid=1d547c24870d5152823ebb0c5ca5f837&contenthub=Adobe%20Pass&passnetwork=nbcentertainment&passauthorizesuccess=true&passauthorize=Authorized&title=nbcentertainment%3APC%3AWatch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A23&.tve&.c&cc=USD&pe=lnk_o&pev2=Adobe%20Pass%3AAuthorize%3ASuccess&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&lrt=234&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s15228206811507",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54988,nbcume.sc.omtrdc.net - 63.140.37.201:443,16:23:01.192,16:23:01.192,0,16:23:01.240,16:23:01.240,0,48,16:23:01.240,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.201:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2947,https://sp.auth.adobe.com/adobe-services/shortAuthorize,sp.auth.adobe.com,/adobe-services/shortAuthorize,Completed,200,POST,text/xml;charset=UTF-8,Google Chrome,127.0.0.1:54995,sp.auth.adobe.com - 35.161.249.167:443,16:23:01.131,16:23:01.131,0,16:23:01.182,16:23:01.182,0,51,16:23:01.182,1950,742,0,532,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2946,https://sp.auth.adobe.com/adobe-services/shortAuthorize,sp.auth.adobe.com,/adobe-services/shortAuthorize,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54991,sp.auth.adobe.com - 35.161.249.167:443,16:23:01.090,16:23:01.090,0,16:23:01.130,16:23:01.130,0,40,16:23:01.130,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2945,https://sp.auth.adobe.com/adobe-services/authorize,sp.auth.adobe.com,/adobe-services/authorize,Completed,200,POST,text/xml;charset=UTF-8,Google Chrome,127.0.0.1:54995,sp.auth.adobe.com - 35.161.249.167:443,16:23:00.374,16:23:00.545,171,16:23:01.086,16:23:01.086,0,712,16:23:01.086,2256,1864,0,967,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2944,https://img.nbc.com/files/12012834_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12012834_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54993,img.nbc.com - 2600:1406:5800::173c:a819:443,16:23:00.273,16:23:00.346,73,16:23:00.659,16:23:00.664,5,390,16:23:00.664,0,22883,0,0,,img.nbc.com - 2600:1406:5800::173c:a819:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2943,https://sp.auth.adobe.com/adobe-services/authorize,sp.auth.adobe.com,/adobe-services/authorize,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54991,sp.auth.adobe.com - 35.161.249.167:443,16:23:00.205,16:23:00.332,127,16:23:00.370,16:23:00.370,0,165,16:23:00.370,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2942,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s14602483678589?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A59%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=null%3Avideo%3Aundefined&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&contenthub=Adobe%20Pass&network=NBC%20Entertainment&title=null%3Avideo%3Aundefined&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A22&.tve&nbcu.&contentType=Video&showSiteFeature=Video%20Details%20-%20Short%20Form&.nbcu&.c&cc=USD&server=www.nbc.com&aamb=RKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y&c.&a.&activitymap.&page=global%3Ahome&link=LIVE%20MLB%20Nationals%20vs.%20Phillies%20Started%20at%203%3A00%20PM®ion=main&pageIDType=1&.activitymap&.a&.c&pid=global%3Ahome&pidt=1&oid=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&ot=A&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&lrt=194&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s14602483678589",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54988,nbcume.sc.omtrdc.net - 63.140.37.201:443,16:22:59.856,16:23:00.032,176,16:23:00.082,16:23:00.082,0,226,16:23:00.082,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.201:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2941,https://mps.nbcuni.com/request/page/json/params/?CALLBACK=mpsCallback&site=nbc-web&type=video-detail&cat=undefined%7Cvideo&pubdate=1774999379&path=%2Ffile%2Fb5cf7d89f6e364db101094be374449528d8138bc&content_id=fileb5cf7d89f6e364db101094be374449528d8138bc&is_content=1&LOADMODE=more&ASYNC=1&_=4&NOLOAD=mpstools&USE_OVERLAY=0&IRSOURCE=false&cag%5Bcontent-type%5D=Single%20Live%20Event&cag%5Bgenre%5D=Sports,mps.nbcuni.com,/request/page/json/params/,Completed,200,GET,application/json; charset=utf-8,Google Chrome,127.0.0.1:54987,mps.nbcuni.com - 23.192.167.152:443,16:22:59.856,16:22:59.980,125,16:23:00.100,16:23:00.100,0,245,16:23:00.100,0,34774,0,9825,,mps.nbcuni.com - 23.192.167.152:443,*.nbcuni.com,*.nbcuni.com; nbcuni.com; mps.nbcuni.com,
|
||||
2942,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54953,map.mp.nbc.com - 151.101.2.49:443,16:22:59.853,16:22:59.853,0,16:22:59.958,16:22:59.958,0,105,16:22:59.958,7635,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2941,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54955,map.mp.nbc.com - 151.101.2.49:443,16:22:59.853,16:22:59.853,0,16:22:59.958,16:22:59.958,0,105,16:22:59.958,1341,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2940,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54956,map.mp.nbc.com - 151.101.2.49:443,16:22:59.853,16:22:59.853,0,16:22:59.953,16:22:59.953,0,100,16:22:59.953,1057,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2939,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54954,map.mp.nbc.com - 151.101.2.49:443,16:22:59.852,16:22:59.852,0,16:22:59.958,16:22:59.958,0,105,16:22:59.958,1324,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2937,https://sb.scorecardresearch.com/b?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999379849&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fwatch%2Fmlb%2Fnationals-vs-phillies%2F12014223&c8=Watch%20Live%3A%20Nationals%20vs.%20Phillies%20-%20NBC.com&c9=,sb.scorecardresearch.com,/b,Completed,204,GET,,Google Chrome,127.0.0.1:54909,sb.scorecardresearch.com - 99.84.215.5:443,16:22:59.851,16:22:59.851,0,16:22:59.925,16:22:59.925,0,75,16:22:59.925,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2935,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Atrue%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22watch%2Fmlb%2Fnationals-vs-phillies%2F12014223%22%2C%22type%22%3A%22STREAM%22%2C%22subType%22%3A%22home%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22appVersion%22%3A1249000%2C%22queryName%22%3A%22componentsForPlaceholders_cached%22%2C%22componentConfigs%22%3A%5B%22eyJmaWx0ZXJWYWx1ZSI6ImFsbCIsImRlZXBMaW5rSGFuZGxlIjoic3BvcnRzLWxpdmVfZ3VpZGVfbWVudXMtLWFsbCIsInR5cGUiOiJTdGFja0dyb3VwIiwiaW1wbGVtZW50YXRpb24iOiJsaXZlR3VpZGVTcG9ydHMiLCJudWxsU3RhdGVNZXNzYWdlIjoiTm8gZXZlbnRzIHNjaGVkdWxlZCB0b2RheS4gQnJvd3NlIGZvciBvdGhlciBMaXZlIFNwb3J0cy4iLCJuYW1lIjoibGl2ZUd1aWRlU3BvcnRzIiwiYXBwIjoibmJjIiwiZmlsdGVyQnkiOiJzcG9ydCJ9%22%5D%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%223b7ab0262b412e1a69aa09333228ebcf55cf4f40a56cff0769607c6f0743a36a%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54984,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:59.848,16:22:59.848,0,16:23:00.238,16:23:00.241,3,393,16:23:00.241,0,441973,0,26722,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2934,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Atrue%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22watch%2Fmlb%2Fnationals-vs-phillies%2F12014223%22%2C%22type%22%3A%22STREAM%22%2C%22subType%22%3A%22home%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22appVersion%22%3A1249000%2C%22queryName%22%3A%22page%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22ac2e08429df2a1b856252b7cc6b38c31975be9b2ad83352e5c74128fb9b8d0ac%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54984,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:59.447,16:22:59.532,85,16:22:59.837,16:22:59.837,0,390,16:22:59.837,0,23644,0,3776,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2933,https://www.nbc.com/generetic/generated/chunks/9451.c7b32c40d56ce5f0648a.js,www.nbc.com,/generetic/generated/chunks/9451.c7b32c40d56ce5f0648a.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54976,www.nbc.com - 2600:1406:5800::173c:a813:443,16:22:55.772,16:22:55.901,130,16:22:59.424,16:22:59.433,9,3661,16:22:59.433,0,505956,0,85327,,www.nbc.com - 2600:1406:5800::173c:a813:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2933,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54954,map.mp.nbc.com - 151.101.2.49:443,16:22:55.767,16:22:55.767,0,16:22:55.793,16:22:55.793,0,26,16:22:55.793,1470,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2932,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54956,map.mp.nbc.com - 151.101.2.49:443,16:22:55.767,16:22:55.767,0,16:22:55.793,16:22:55.793,0,26,16:22:55.793,174,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2930,https://www.nbc.com/generetic/generated/chunks/5521.e476de954d0bf8b88355.js,www.nbc.com,/generetic/generated/chunks/5521.e476de954d0bf8b88355.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:55.766,16:22:55.766,0,16:22:55.847,16:22:55.877,31,111,16:22:55.877,0,579576,0,89388,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2929,https://www.nbc.com/generetic/generated/chunks/4347.3ace46faec5171929283.js,www.nbc.com,/generetic/generated/chunks/4347.3ace46faec5171929283.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:55.766,16:22:55.766,0,16:22:56.054,16:22:56.055,1,289,16:22:56.055,0,56309,0,12171,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2928,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54955,map.mp.nbc.com - 151.101.2.49:443,16:22:55.766,16:22:55.766,0,16:22:55.793,16:22:55.793,0,27,16:22:55.793,1372,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2927,https://www.nbc.com/generetic/generated/chunks/379.ab56f6b122f60be849d9.js,www.nbc.com,/generetic/generated/chunks/379.ab56f6b122f60be849d9.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:55.766,16:22:55.766,0,16:22:55.849,16:22:55.867,18,101,16:22:55.867,0,2896717,0,416820,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2925,https://img.nbc.com/files/12014353_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014353_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54952,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.577,16:22:54.577,0,16:22:54.686,16:22:54.686,0,109,16:22:54.686,0,16933,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2924,https://img.nbc.com/files/12014311_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014311_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54961,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.456,16:22:54.547,91,16:22:54.609,16:22:54.609,0,153,16:22:54.609,0,15204,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2923,https://img.nbc.com/files/12014268_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014268_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54960,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.456,16:22:54.545,89,16:22:54.588,16:22:54.591,3,135,16:22:54.591,0,16513,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2922,https://img.nbc.com/files/12014268_1920x1080.jpg?impolicy=nbc_com&imwidth=1920&imdensity=1,img.nbc.com,/files/12014268_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54959,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.456,16:22:54.543,87,16:22:54.686,16:22:54.703,18,248,16:22:54.703,0,81691,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2924,https://img.nbc.com/files/12014222_1920x1080.jpg?impolicy=nbc_com&imwidth=1920&imdensity=1,img.nbc.com,/files/12014222_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54957,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.453,16:22:54.549,96,16:22:54.686,16:22:54.703,18,250,16:22:54.703,0,90358,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2923,https://img.nbc.com/files/12014273_1920x1080.jpg?impolicy=nbc_com&imwidth=1920&imdensity=1,img.nbc.com,/files/12014273_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54958,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.453,16:22:54.544,91,16:22:54.609,16:22:54.698,89,245,16:22:54.698,0,78235,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2919,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54955,map.mp.nbc.com - 151.101.2.49:443,16:22:54.449,16:22:54.547,98,16:22:54.578,16:22:54.578,0,129,16:22:54.578,7888,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2918,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54954,map.mp.nbc.com - 151.101.2.49:443,16:22:54.448,16:22:54.541,93,16:22:54.574,16:22:54.574,0,126,16:22:54.574,2105,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2917,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54956,map.mp.nbc.com - 151.101.2.49:443,16:22:54.448,16:22:54.547,100,16:22:54.576,16:22:54.576,0,128,16:22:54.576,2151,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2916,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54953,map.mp.nbc.com - 151.101.2.49:443,16:22:54.448,16:22:54.541,94,16:22:54.569,16:22:54.569,0,121,16:22:54.569,1297,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2916,https://img.nbc.com/files/2025-05/mlb_league.png,img.nbc.com,/files/2025-05/mlb_league.png,Completed,200,GET,image/avif,Google Chrome,127.0.0.1:54952,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:54.447,16:22:54.541,94,16:22:54.576,16:22:54.576,0,129,16:22:54.576,0,2693,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2915,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54911,map.mp.nbc.com - 151.101.2.49:443,16:22:54.441,16:22:54.441,0,16:22:54.470,16:22:54.470,0,29,16:22:54.470,1054,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2914,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54915,map.mp.nbc.com - 151.101.2.49:443,16:22:54.441,16:22:54.441,0,16:22:54.468,16:22:54.468,0,27,16:22:54.468,1294,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2912,https://sb.scorecardresearch.com/b?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999374439&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fsports%2Fleague%2Fmlb&c8=Sport%20Landing%20Page%20-%20MLB&c9=,sb.scorecardresearch.com,/b,Completed,204,GET,,Google Chrome,127.0.0.1:54909,sb.scorecardresearch.com - 99.84.215.5:443,16:22:54.439,16:22:54.440,0,16:22:54.492,16:22:54.492,0,53,16:22:54.492,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2911,https://www.nbc.com/generetic/images/icons/down-arrow-icon.png,www.nbc.com,/generetic/images/icons/down-arrow-icon.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:54.439,16:22:54.439,0,16:22:54.481,16:22:54.481,0,41,16:22:54.481,0,228,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2910,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Atrue%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22sports%2Fleague%2Fmlb%22%2C%22type%22%3A%22LANDING_PAGE%22%2C%22subType%22%3A%22sport%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22appVersion%22%3A1249000%2C%22queryName%22%3A%22page%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22a6e21da7a9882cfe8498724f9f91fda9b8ab477666209d594dffb1742f77ad1e%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54948,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:53.939,16:22:54.023,84,16:22:54.421,16:22:54.422,0,483,16:22:54.422,0,85718,0,9414,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2909,https://friendship.nbc.com/v3/graphql?variables=%7B%22appVersion%22%3A1249000%2C%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Atrue%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22sports%2Fleague%2Fmlb%22%2C%22type%22%3A%22LANDING_PAGE%22%2C%22subType%22%3A%22sport%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22queryName%22%3A%22featuredSection%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22a76f2966d3fbe90881952db8e3b69f211d84c9c3d2d1d7cd9368378ece8e930e%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54947,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:53.939,16:22:54.024,85,16:22:54.078,16:22:54.078,0,139,16:22:54.078,0,32485,0,3676,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2908,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54911,map.mp.nbc.com - 151.101.2.49:443,16:22:53.648,16:22:53.648,0,16:22:53.679,16:22:53.679,0,31,16:22:53.679,173,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2907,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54915,map.mp.nbc.com - 151.101.2.49:443,16:22:53.648,16:22:53.648,0,16:22:53.692,16:22:53.692,0,44,16:22:53.692,971,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2906,https://www.nbc.com/generetic/generated/chunks/2050.ca8996e868f9976b39e4.js,www.nbc.com,/generetic/generated/chunks/2050.ca8996e868f9976b39e4.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:53.648,16:22:53.648,0,16:22:53.897,16:22:53.897,0,250,16:22:53.897,0,15824,0,3291,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2905,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54914,map.mp.nbc.com - 151.101.2.49:443,16:22:53.648,16:22:53.648,0,16:22:53.678,16:22:53.678,0,31,16:22:53.678,927,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2904,https://www.nbc.com/generetic/generated/chunks/7832.99917d345c3f54e967c4.js,www.nbc.com,/generetic/generated/chunks/7832.99917d345c3f54e967c4.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:53.648,16:22:53.648,0,16:22:53.920,16:22:53.920,0,273,16:22:53.920,0,61689,0,11339,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2903,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54914,map.mp.nbc.com - 151.101.2.49:443,16:22:52.974,16:22:52.974,0,16:22:53.026,16:22:53.026,0,52,16:22:53.026,1404,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2902,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54911,map.mp.nbc.com - 151.101.2.49:443,16:22:52.973,16:22:52.973,0,16:22:53.025,16:22:53.025,0,52,16:22:53.025,1267,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2900,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54911,map.mp.nbc.com - 151.101.2.49:443,16:22:49.890,16:22:49.890,0,16:22:49.968,16:22:49.968,0,79,16:22:49.968,4879,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2895,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54911,map.mp.nbc.com - 151.101.2.49:443,16:22:47.443,16:22:47.443,0,16:22:47.472,16:22:47.472,0,30,16:22:47.472,965,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2894,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54914,map.mp.nbc.com - 151.101.2.49:443,16:22:47.442,16:22:47.443,0,16:22:47.472,16:22:47.472,0,30,16:22:47.472,1275,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2893,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54915,map.mp.nbc.com - 151.101.2.49:443,16:22:47.440,16:22:47.441,0,16:22:47.469,16:22:47.469,0,28,16:22:47.469,1269,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2892,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54913,map.mp.nbc.com - 151.101.2.49:443,16:22:47.317,16:22:47.417,100,16:22:47.451,16:22:47.451,0,134,16:22:47.451,11067,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2891,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54915,map.mp.nbc.com - 151.101.2.49:443,16:22:47.317,16:22:47.409,92,16:22:47.440,16:22:47.440,0,123,16:22:47.440,6836,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2890,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54912,map.mp.nbc.com - 151.101.2.49:443,16:22:47.317,16:22:47.412,95,16:22:47.453,16:22:47.453,0,136,16:22:47.453,6882,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2889,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54914,map.mp.nbc.com - 151.101.2.49:443,16:22:47.317,16:22:47.412,95,16:22:47.442,16:22:47.442,0,125,16:22:47.442,1280,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2888,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54911,map.mp.nbc.com - 151.101.2.49:443,16:22:47.316,16:22:47.412,96,16:22:47.442,16:22:47.442,0,126,16:22:47.442,1048,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2892,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54910,map.mp.nbc.com - 151.101.2.49:443,16:22:47.313,16:22:47.414,101,16:22:47.446,16:22:47.446,0,133,16:22:47.446,1278,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2891,https://sb.scorecardresearch.com/b?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999367305&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fsports&c8=NBC%20Sports%20Video%2C%20News%2C%20and%20Highlights%20%E2%80%93%20nbc.com&c9=,sb.scorecardresearch.com,/b,Completed,204,GET,,Google Chrome,127.0.0.1:54909,sb.scorecardresearch.com - 99.84.215.5:443,16:22:47.312,16:22:47.390,78,16:22:47.422,16:22:47.422,0,110,16:22:47.422,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2890,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s15175960719290?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A47%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=global%3Ahome&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&contenthub=Adobe%20Pass&network=NBC%20Entertainment&title=global%3Ahome&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A22&.tve&nbcu.&contentGroup=Online&contentType=Home&showSite=Global&.nbcu&pageTitle=Home&.c&cc=USD&server=www.nbc.com&aamb=RKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y&c.&a.&activitymap.&page=nbcentertainment%3APC%3AMVPD%20Picker&link=SKIP®ion=main&pageIDType=1&.activitymap&.a&.c&pid=nbcentertainment%3APC%3AMVPD%20Picker&pidt=1&oid=functionjf%28%29%7B%7D&oidt=2&ot=BUTTON&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&lrt=157&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s15175960719290",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54908,nbcume.sc.omtrdc.net - 63.140.37.201:443,16:22:47.312,16:22:47.446,135,16:22:47.499,16:22:47.499,0,187,16:22:47.499,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.201:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2876,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54864,sp.auth.adobe.com - 44.249.253.27:443,16:22:46.035,16:22:46.035,0,16:22:46.087,16:22:46.087,0,52,16:22:46.087,2270,515,0,435,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2874,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54881,map.mp.nbc.com - 151.101.2.49:443,16:22:45.960,16:22:46.038,78,16:22:46.074,16:22:46.074,0,114,16:22:46.074,4143,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2873,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54880,map.mp.nbc.com - 151.101.2.49:443,16:22:45.960,16:22:46.036,77,16:22:46.066,16:22:46.066,0,106,16:22:46.066,1300,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2873,https://sb.scorecardresearch.com/b?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999365951&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fprovider-linked&c8=TV%20Provider%20Linked&c9=,sb.scorecardresearch.com,/b,Completed,204,GET,,Google Chrome,127.0.0.1:54877,sb.scorecardresearch.com - 99.84.215.5:443,16:22:45.959,16:22:46.016,57,16:22:46.045,16:22:46.045,0,86,16:22:46.045,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2872,https://www.nbc.com/generetic/images/authhero/identity_default_hero_bg.png?impolicy=nbc_com&imwidth=1920&imdensity=1,www.nbc.com,/generetic/images/authhero/identity_default_hero_bg.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54876,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:45.959,16:22:46.028,70,16:22:46.156,16:22:46.298,142,340,16:22:46.298,0,1190346,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2873,https://www.nbc.com/generetic/images/authhero/nbc-logo-white.png?impolicy=nbc_com&imwidth=340&imdensity=1,www.nbc.com,/generetic/images/authhero/nbc-logo-white.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54875,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:45.955,16:22:46.024,69,16:22:46.141,16:22:46.141,0,186,16:22:46.141,0,8031,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2870,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54848,map.mp.nbc.com - 151.101.2.49:443,16:22:45.954,16:22:45.954,0,16:22:45.987,16:22:45.987,0,33,16:22:45.987,1059,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2867,https://mps.nbcuni.com/request/page/json/params/?CALLBACK=mpsCallback&site=nbc-web&path=%2Fprovider-linked&LOADMODE=more&ASYNC=1&_=2&NOLOAD=mpstools&USE_OVERLAY=0&IRSOURCE=false,mps.nbcuni.com,/request/page/json/params/,Completed,200,GET,application/json; charset=utf-8,Google Chrome,127.0.0.1:54874,mps.nbcuni.com - 23.192.167.152:443,16:22:45.953,16:22:46.021,67,16:22:46.096,16:22:46.098,2,145,16:22:46.098,0,37199,0,9976,,mps.nbcuni.com - 23.192.167.152:443,*.nbcuni.com,*.nbcuni.com; nbcuni.com; mps.nbcuni.com,
|
||||
2866,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54846,map.mp.nbc.com - 151.101.2.49:443,16:22:45.953,16:22:45.953,0,16:22:45.987,16:22:45.987,0,33,16:22:45.987,1298,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2865,https://www.nbc.com/generetic/images/authhero/brand-logos-1-liner@2x.png,www.nbc.com,/generetic/images/authhero/brand-logos-1-liner@2x.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:45.952,16:22:45.952,0,16:22:46.000,16:22:46.000,0,48,16:22:46.000,0,10444,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2863,https://sp.auth.adobe.com/adobe-services/preauthorize,sp.auth.adobe.com,/adobe-services/preauthorize,Completed,200,POST,application/xml;charset=UTF-8,Google Chrome,127.0.0.1:54867,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.881,16:22:45.983,102,16:22:46.606,16:22:46.606,0,725,16:22:46.606,2157,1372,0,244,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2863,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54865,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.880,16:22:45.985,106,16:22:46.045,16:22:46.045,0,165,16:22:46.045,2270,515,0,435,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2862,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54866,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.880,16:22:45.984,104,16:22:46.038,16:22:46.038,0,159,16:22:46.038,2270,515,0,434,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2862,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54864,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.877,16:22:45.977,100,16:22:46.034,16:22:46.034,0,157,16:22:46.034,2270,515,0,434,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2861,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54863,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.877,16:22:45.983,106,16:22:46.038,16:22:46.038,0,162,16:22:46.038,2270,515,0,431,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2860,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54862,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.874,16:22:45.979,105,16:22:46.034,16:22:46.034,0,160,16:22:46.034,2270,515,0,434,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2857,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54843,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.871,16:22:45.871,0,16:22:45.909,16:22:45.909,0,38,16:22:45.909,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2856,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54840,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.666,16:22:45.833,166,16:22:45.874,16:22:45.874,0,207,16:22:45.874,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2855,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54843,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.666,16:22:45.830,163,16:22:45.869,16:22:45.869,0,203,16:22:45.869,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2854,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54841,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.666,16:22:45.831,165,16:22:45.871,16:22:45.871,0,205,16:22:45.871,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2853,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54848,map.mp.nbc.com - 151.101.2.49:443,16:22:45.666,16:22:45.821,155,16:22:45.850,16:22:45.850,0,184,16:22:45.850,991,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2852,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54845,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.666,16:22:45.830,165,16:22:45.871,16:22:45.871,0,206,16:22:45.871,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2851,https://ss.nbc.co/conveyor/search?mpid=-8189650932773340651,ss.nbc.co,/conveyor/search,Error,404,GET,application/json,Google Chrome,127.0.0.1:54849,ss.nbc.co - 2600:1406:4e00:19::1738:6d4c:443,16:22:45.666,16:22:45.807,141,16:22:46.030,16:22:46.030,0,364,16:22:46.030,0,132,0,0,,ss.nbc.co - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.co,*.nbc.co; nbc.co; ss.nbc.co,
|
||||
2850,https://sp.auth.adobe.com/adobe-services/usermetadata,sp.auth.adobe.com,/adobe-services/usermetadata,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54844,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.666,16:22:45.832,166,16:22:45.874,16:22:45.874,0,208,16:22:45.874,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2849,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54846,map.mp.nbc.com - 151.101.2.49:443,16:22:45.666,16:22:45.826,160,16:22:45.855,16:22:45.855,0,189,16:22:45.855,961,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2848,https://sp.auth.adobe.com/adobe-services/preauthorize,sp.auth.adobe.com,/adobe-services/preauthorize,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54842,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.665,16:22:45.833,168,16:22:45.877,16:22:45.877,0,212,16:22:45.877,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2856,https://www.nbc.com/generetic/generated/chunks/7388.82469fe00df132b4f19b.js,www.nbc.com,/generetic/generated/chunks/7388.82469fe00df132b4f19b.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54838,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:45.656,16:22:45.776,119,16:22:45.941,16:22:45.941,0,285,16:22:45.941,0,17306,0,3798,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2844,https://api.nbc.com/localized-mvpd/entitlements?mvpd=Verizon&platform=iOS2&brand=nbcentertainment&instance=prod,api.nbc.com,/localized-mvpd/entitlements,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54833,api.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:45.488,16:22:45.558,70,16:22:45.640,16:22:45.640,0,152,16:22:45.640,0,24201,0,2960,,api.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; api.nbc.com,
|
||||
2843,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s17566515395113?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A45%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=nbcentertainment%3APC%3AMVPD%20Picker&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&passmvpd=Verizon&passguid=1d547c24870d5152823ebb0c5ca5f837&contenthub=Adobe%20Pass&passnetwork=nbcentertainment&passauthensuccess=true&passauthen=Authenticated&title=nbcentertainment%3APC%3AMVPD%20Picker&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A22&.tve&.c&cc=USD&pe=lnk_o&pev2=Adobe%20Pass%3AAuthenticate%3ASuccess&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&lrt=172&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s17566515395113",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54832,nbcume.sc.omtrdc.net - 63.140.37.172:443,16:22:45.483,16:22:45.583,101,16:22:45.635,16:22:45.635,0,152,16:22:45.635,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.172:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2842,https://sp.auth.adobe.com/adobe-services/session,sp.auth.adobe.com,/adobe-services/session,Completed,200,POST,application/xml;charset=ISO-8859-1,Google Chrome,127.0.0.1:54830,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.255,16:22:45.353,98,16:22:45.471,16:22:45.471,0,216,16:22:45.471,58,2160,0,1212,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2841,https://sp.auth.adobe.com/adobe-services/session,sp.auth.adobe.com,/adobe-services/session,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54826,sp.auth.adobe.com - 44.249.253.27:443,16:22:45.059,16:22:45.162,102,16:22:45.246,16:22:45.246,0,187,16:22:45.246,0,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2839,https://entitlement.auth.adobe.com/entitlement/v4/completeBackgroundLogin.html,entitlement.auth.adobe.com,/entitlement/v4/completeBackgroundLogin.html,Completed,200,GET,text/html,Google Chrome,127.0.0.1:54824,entitlement.auth.adobe.com - 23.42.82.205:443,16:22:44.942,16:22:45.013,71,16:22:45.029,16:22:45.029,0,87,16:22:45.029,0,238,0,168,,entitlement.auth.adobe.com - 23.42.82.205:443,ssl.adobe.com,ssl.adobe.com; signcsa.experienceleague.stage.adobe.com; wwwimages.stage2.adobe.com; www.macromedia.com; www.adobeidealab.com; cdn-stg-ffc.oobesaas.adobe.com; solutionpartners.stage2.adobe.com; www-stage01.acrobat.adobe.com; updates.adobeleanprint.com; download.stage.adobeprerelease.com; store2.stage2.adobe.com; live.adobeprimetime.com; wwwimages2.stage2.adobe.com; adobeprerelease.com; exchange.adobe.com; stage.status.adobe.com; experiencecloud.adobeexchange.com; www.stage.macromedia.com; experiencecloudstg2.adobeexchange.com; www.stage.adobeprerelease.com; www.qa.adobeprerelease.com; fpdownload.macromedia.com; stage.adobeprerelease.com; plan.adobe.com; dev.status.adobe.com; static.stage.adobeprimetime.com; preprod.status.adobe.com; live.stage.adobeprimetime.com; www.adobe.io; qe-ffc-static-cdn.oobesaas.adobe.com; download.adobeprerelease.com; trainingpartners.stage2.adobe.com; www.adobe-students.com; store1.stage2.adobe.com; auth.adobefpl.com; partners.stage2.adobe.com; media.stage.adobeprimetime.com; stage.adobe.io; cdn-qe-ffc.oobesaas.adobe.com; geo2.adobe.com; media.adobeprimetime.com; ffc-static-cdn.oobesaas.adobe.com; data.status.adobe.com; updates.stage.adobeleanprint.com; static.adobeprimetime.com; www.adobeprerelease.com; signcsa.experienceleague.adobe.com; hendrix360.qe.adobe.com; download.macromedia.com; csa.experienceleague.stage.adobe.com; app-cdn.stage2.adobe.com; stage.plan.adobe.com; www.stage2.adobe.com; stage.exchange.adobe.com; csa.experienceleague.adobe.com; entitlement.auth.adobe.com; download.qa.adobeprerelease.com; channelpartners.stage2.adobe.com; cdn-ffc.oobesaas.adobe.com; stg-ffc-static-cdn.oobesaas.adobe.com; technologypartners.stage2.adobe.com; www.stage.adobe-students.com,
|
||||
2838,https://sp.auth.adobe.com/sp/saml/SAMLAssertionConsumer,sp.auth.adobe.com,/sp/saml/SAMLAssertionConsumer,Redirect,302,POST,,Google Chrome,127.0.0.1:54822,sp.auth.adobe.com - 44.249.253.27:443,16:22:44.589,16:22:44.804,215,16:22:44.934,16:22:44.934,0,345,16:22:44.934,8228,0,0,0,,sp.auth.adobe.com - 44.249.253.27:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2836,https://sanalytics.verizon.com/ee/or2/v1/collect?configId=60e576ec-f42a-48ad-9fe5-0b9a866a6413&requestId=d14227dc-a932-46bc-b8b4-cfd51fe41efe,sanalytics.verizon.com,/ee/or2/v1/collect,Completed,204,POST,,Google Chrome,127.0.0.1:54815,sanalytics.verizon.com - 63.140.36.154:443,16:22:44.041,16:22:44.247,206,16:22:44.325,16:22:44.325,0,284,16:22:44.325,4947,0,0,0,,sanalytics.verizon.com - 63.140.36.154:443,sanalytics.verizon.com,sanalytics.verizon.com,
|
||||
2835,https://passwordsleakcheck-pa.googleapis.com/v1/leaks:lookupSingle,passwordsleakcheck-pa.googleapis.com,/v1/leaks:lookupSingle,Completed,200,POST,application/x-protobuf,Google Chrome,127.0.0.1:54811,passwordsleakcheck-pa.googleapis.com - 2607:f8b0:4007:80b::200a:443,16:22:43.892,16:22:43.962,70,16:22:44.039,16:22:44.040,0,148,16:22:44.040,45,3523,0,3449,,passwordsleakcheck-pa.googleapis.com - 2607:f8b0:4007:80b::200a:443,upload.video.google.com,upload.video.google.com; *.clients.google.com; *.docs.google.com; *.drive.google.com; *.gdata.youtube.com; *.googleapis.com; *.photos.google.com; *.youtube-3rd-party.com; upload.google.com; *.upload.google.com; upload.youtube.com; *.upload.youtube.com; uploads.stage.gdata.youtube.com; bg-call-donation.goog; bg-call-donation-alpha.goog; bg-call-donation-canary.goog; bg-call-donation-dev.goog; passwordsleakcheck-pa.googleapis.com,
|
||||
2830,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCbee5e1d24157460e8d62373d628e49fe-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCbee5e1d24157460e8d62373d628e49fe-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54784,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:43.582,16:22:43.582,0,16:22:43.674,16:22:43.675,0,93,16:22:43.675,0,554,0,336,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2828,https://sanalytics.verizon.com/ee/or2/v1/interact?configId=028f67d9-9bd3-4306-b399-0c5025453572&requestId=f7ca6a4f-908f-4dc5-802d-ef6b9882f85b,sanalytics.verizon.com,/ee/or2/v1/interact,Completed,200,POST,application/json;charset=utf-8,Google Chrome,127.0.0.1:54783,sanalytics.verizon.com - 63.140.36.154:443,16:22:43.512,16:22:43.512,0,16:22:43.579,16:22:43.579,0,67,16:22:43.579,3367,442,0,283,,sanalytics.verizon.com - 63.140.36.154:443,sanalytics.verizon.com,sanalytics.verizon.com,
|
||||
2827,https://ssoauth.verizon.com/sso/TVPFR7HandlerServlet?loginType=vzRedirect&partner=nbcentertainment&partnerlogo=null&RelayState=34d6b7dc-f921-4ac5-964c-291f96ed842a&cancelURL=https%3A%2F%2Fsp.auth.adobe.com%2Fadobe-services%2F1.0%2Fsession%3Fcancelled%3D1%26_method%3DPOST%26mso_id%3DVerizon&TARGET=https%3A%2F%2Fsp.auth.adobe.com%2Fsp%2Fsaml%2FSAMLAssertionConsumer&hl=no,ssoauth.verizon.com,/sso/TVPFR7HandlerServlet,Completed,200,POST,,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:43.485,16:22:43.485,0,16:22:44.571,16:22:44.571,0,1085,16:22:44.571,0,8119,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2825,https://ssoauth.verizon.com/sso/choice/tvpFR7Handler.jsp?loginType=vzRedirect&partner=nbcentertainment&partnerlogo=null&RelayState=34d6b7dc-f921-4ac5-964c-291f96ed842a&cancelURL=https%3A%2F%2Fsp.auth.adobe.com%2Fadobe-services%2F1.0%2Fsession%3Fcancelled%3D1%26_method%3DPOST%26mso_id%3DVerizon&TARGET=https%3A%2F%2Fsp.auth.adobe.com%2Fsp%2Fsaml%2FSAMLAssertionConsumer&hl=no,ssoauth.verizon.com,/sso/choice/tvpFR7Handler.jsp,Completed,200,GET,text/html; charset=ISO-8859-1,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:43.152,16:22:43.152,0,16:22:43.461,16:22:43.461,0,309,16:22:43.461,0,8816,0,2526,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2823,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:42.892,16:22:42.892,0,16:22:43.151,16:22:43.151,0,258,16:22:43.151,5860,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2820,https://ssoauth.verizon.com/sso/TVPFR7HandlerServlet?goto=https://ssoauth.verizon.com/sso/choice/tvpFR7Handler.jsp?loginType%3DvzRedirect%26partner%3Dnbcentertainment%26partnerlogo%3Dnull%26RelayState%3D34d6b7dc-f921-4ac5-964c-291f96ed842a%26cancelURL%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fadobe-services%252F1.0%252Fsession%253Fcancelled%253D1%2526_method%253DPOST%2526mso_id%253DVerizon%26TARGET%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fsp%252Fsaml%252FSAMLAssertionConsumer%26hl%3Dno&clientId=TvLogin&partner=nbcentertainment&errorURL=https://ssoauth.verizon.com/sso/VOLPortalLogin?src%3DSAM%26loginType%3DvzRedirect%26partner%3Dnbcentertainment%26partnerlogo%3Dnull%26RelayState%3D34d6b7dc-f921-4ac5-964c-291f96ed842a%26cancelURL%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fadobe-services%252F1.0%252Fsession%253Fcancelled%253D1%2526_method%253DPOST%2526mso_id%253DVerizon%26TARGET%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fsp%252Fsaml%252FSAMLAssertionConsumer%26hl%3Dno,ssoauth.verizon.com,/sso/TVPFR7HandlerServlet,Internalerror,999,POST,,Google Chrome,127.0.0.1:54771,ssoauth.verizon.com - 23.217.118.25:443,16:22:41.952,16:22:41.952,0,16:22:43.149,16:22:43.150,1,1198,16:22:43.150,1336,0,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2819,https://sanalytics.verizon.com/ee/or2/v1/collect?configId=60e576ec-f42a-48ad-9fe5-0b9a866a6413&requestId=06859c18-94e0-4402-8295-f9b62236bd23,sanalytics.verizon.com,/ee/or2/v1/collect,Completed,204,POST,,Google Chrome,127.0.0.1:54783,sanalytics.verizon.com - 63.140.36.154:443,16:22:41.946,16:22:41.947,0,16:22:42.017,16:22:42.017,0,71,16:22:42.017,8330,0,0,0,,sanalytics.verizon.com - 63.140.36.154:443,sanalytics.verizon.com,sanalytics.verizon.com,
|
||||
2817,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:41.879,16:22:41.879,0,16:22:42.242,16:22:42.242,0,364,16:22:42.242,3871,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2816,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:41.148,16:22:41.148,0,16:22:41.436,16:22:41.436,0,289,16:22:41.436,6902,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2815,https://sanalytics.verizon.com/ee/or2/v1/collect?configId=60e576ec-f42a-48ad-9fe5-0b9a866a6413&requestId=f7a6f962-ee96-48d0-8242-f5dd2b9f6396,sanalytics.verizon.com,/ee/or2/v1/collect,Completed,204,POST,,Google Chrome,127.0.0.1:54783,sanalytics.verizon.com - 63.140.36.154:443,16:22:41.062,16:22:41.172,109,16:22:41.282,16:22:41.282,0,219,16:22:41.282,8330,0,0,0,,sanalytics.verizon.com - 63.140.36.154:443,sanalytics.verizon.com,sanalytics.verizon.com,
|
||||
2814,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCace0285ad6cb44ca9e86b2a1a315fa7c-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCace0285ad6cb44ca9e86b2a1a315fa7c-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54784,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:41.062,16:22:41.111,49,16:22:41.136,16:22:41.136,0,74,16:22:41.136,0,1277,0,410,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2813,https://ssoauth.verizon.com/sso/TVPFR7HandlerServlet?goto=https://ssoauth.verizon.com/sso/choice/tvpFR7Handler.jsp?loginType%3DvzRedirect%26partner%3Dnbcentertainment%26partnerlogo%3Dnull%26RelayState%3D34d6b7dc-f921-4ac5-964c-291f96ed842a%26cancelURL%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fadobe-services%252F1.0%252Fsession%253Fcancelled%253D1%2526_method%253DPOST%2526mso_id%253DVerizon%26TARGET%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fsp%252Fsaml%252FSAMLAssertionConsumer%26hl%3Dno&clientId=TvLogin&partner=nbcentertainment&errorURL=https://ssoauth.verizon.com/sso/VOLPortalLogin?src%3DSAM%26loginType%3DvzRedirect%26partner%3Dnbcentertainment%26partnerlogo%3Dnull%26RelayState%3D34d6b7dc-f921-4ac5-964c-291f96ed842a%26cancelURL%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fadobe-services%252F1.0%252Fsession%253Fcancelled%253D1%2526_method%253DPOST%2526mso_id%253DVerizon%26TARGET%3Dhttps%253A%252F%252Fsp.auth.adobe.com%252Fsp%252Fsaml%252FSAMLAssertionConsumer%26hl%3Dno,ssoauth.verizon.com,/sso/TVPFR7HandlerServlet,Internalerror,999,POST,,Google Chrome,127.0.0.1:54765,ssoauth.verizon.com - 23.217.118.25:443,16:22:41.056,16:22:41.057,0,16:22:41.942,16:22:41.942,0,885,16:22:41.942,1336,0,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2810,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCe6c8547fbdd44141a0acb30d0af789b5-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCe6c8547fbdd44141a0acb30d0af789b5-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54777,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:40.906,16:22:40.965,59,16:22:41.054,16:22:41.054,0,149,16:22:41.054,0,743,0,375,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2809,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54774,ssoauth.verizon.com - 23.217.118.25:443,16:22:40.897,16:22:40.952,54,16:22:41.158,16:22:41.158,0,261,16:22:41.158,7037,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2808,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54773,ssoauth.verizon.com - 23.217.118.25:443,16:22:40.897,16:22:40.966,69,16:22:41.249,16:22:41.249,0,352,16:22:41.249,6938,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2807,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54771,ssoauth.verizon.com - 23.217.118.25:443,16:22:40.883,16:22:40.951,68,16:22:41.394,16:22:41.394,0,511,16:22:41.394,5860,17,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2806,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:40.876,16:22:40.876,0,16:22:41.147,16:22:41.147,0,271,16:22:41.147,7086,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2805,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54765,ssoauth.verizon.com - 23.217.118.25:443,16:22:40.872,16:22:40.872,0,16:22:41.056,16:22:41.056,0,184,16:22:41.056,6998,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2804,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:40.868,16:22:40.869,0,16:22:41.269,16:22:41.269,0,401,16:22:41.269,7116,18,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2803,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:40.224,16:22:40.224,0,16:22:40.428,16:22:40.428,0,204,16:22:40.428,6639,18,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2802,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54767,map.mp.nbc.com - 151.101.2.49:443,16:22:39.787,16:22:39.851,64,16:22:39.883,16:22:39.883,0,96,16:22:39.883,4353,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2801,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:39.728,16:22:39.728,0,16:22:40.006,16:22:40.007,0,278,16:22:40.007,6453,18,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2800,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54765,ssoauth.verizon.com - 23.217.118.25:443,16:22:39.682,16:22:39.743,61,16:22:40.004,16:22:40.004,0,323,16:22:40.004,6541,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2799,https://sanalytics.verizon.com/ee/or2/v1/collect?configId=60e576ec-f42a-48ad-9fe5-0b9a866a6413&requestId=f6a691ea-240d-4ee9-be4a-9e29371748ef,sanalytics.verizon.com,/ee/or2/v1/collect,Completed,204,POST,,Google Chrome,127.0.0.1:54722,sanalytics.verizon.com - 63.140.36.154:443,16:22:39.498,16:22:39.499,0,16:22:39.561,16:22:39.561,0,63,16:22:39.561,8492,0,0,0,,sanalytics.verizon.com - 63.140.36.154:443,sanalytics.verizon.com,sanalytics.verizon.com,
|
||||
2798,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RC97da83d389df405fb9a8e93f6ad74e96-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RC97da83d389df405fb9a8e93f6ad74e96-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54756,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:39.498,16:22:39.499,0,16:22:39.519,16:22:39.519,0,20,16:22:39.519,0,1584,0,455,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2797,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:39.476,16:22:39.476,0,16:22:39.701,16:22:39.701,0,225,16:22:39.701,6330,18,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2796,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RC568d08de522a4f72a90dd358b252e3d3-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RC568d08de522a4f72a90dd358b252e3d3-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54756,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:39.466,16:22:39.466,0,16:22:39.492,16:22:39.492,0,26,16:22:39.492,0,596,0,320,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2795,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54762,ssoauth.verizon.com - 23.217.118.25:443,16:22:39.167,16:22:39.231,64,16:22:39.697,16:22:39.697,0,530,16:22:39.697,6313,18,0,0,,ssoauth.verizon.com - 23.217.118.25:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2794,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:39.128,16:22:39.128,0,16:22:39.324,16:22:39.324,0,196,16:22:39.324,6135,18,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2792,https://www.verizon.com/business/geoloc.json,www.verizon.com,/business/geoloc.json,Completed,200,GET,text/javascript; charset=utf-8,Google Chrome,127.0.0.1:54758,www.verizon.com - 23.217.118.33:443,16:22:38.794,16:22:38.855,61,16:22:38.954,16:22:38.954,0,161,16:22:38.954,0,148,0,0,,www.verizon.com - 23.217.118.33:443,verizon.com,verizon.com; ws01.static-verizon.com; ws02.static-verizon.com; ws03.static-verizon.com; ws04.static-verizon.com; www.verizon.com,
|
||||
2791,https://ssoauth.verizon.com/favicon.ico,ssoauth.verizon.com,/favicon.ico,Completed,200,GET,image/x-icon,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:38.779,16:22:38.780,0,16:22:38.954,16:22:38.954,0,175,16:22:38.954,0,822,0,94,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2790,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RC7484a41e5cdc40acb9076fc220290294-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RC7484a41e5cdc40acb9076fc220290294-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54756,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:38.730,16:22:38.730,0,16:22:38.748,16:22:38.748,0,18,16:22:38.748,0,524,0,303,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2789,https://www.verizon.com/vendor/transcend/vz/xdi.js,www.verizon.com,/vendor/transcend/vz/xdi.js,Completed,200,GET,application/x-javascript; charset=utf-8,Google Chrome,127.0.0.1:54742,www.verizon.com - 23.217.118.33:443,16:22:38.713,16:22:38.713,0,16:22:38.765,16:22:38.765,0,52,16:22:38.765,0,26230,0,11782,,www.verizon.com - 23.217.118.33:443,verizon.com,verizon.com; ws01.static-verizon.com; ws02.static-verizon.com; ws03.static-verizon.com; ws04.static-verizon.com; www.verizon.com,
|
||||
2788,https://assets.adobedtm.com/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCd269e4a00d46491bbf41a20741d40442-source.min.js,assets.adobedtm.com,/2ea7ee22c8c2/fee1b09a7b1e/f5f782cfa86b/RCd269e4a00d46491bbf41a20741d40442-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54756,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:38.649,16:22:38.709,60,16:22:38.726,16:22:38.726,0,77,16:22:38.726,0,840,0,445,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2782,https://www.verizon.com/vendor/transcend/vz/xdi.html,www.verizon.com,/vendor/transcend/vz/xdi.html,Completed,200,GET,text/html; charset=utf-8,Google Chrome,127.0.0.1:54742,www.verizon.com - 23.217.118.33:443,16:22:38.513,16:22:38.610,96,16:22:38.708,16:22:38.708,0,195,16:22:38.708,0,2876,0,1224,,www.verizon.com - 23.217.118.33:443,verizon.com,verizon.com; ws01.static-verizon.com; ws02.static-verizon.com; ws03.static-verizon.com; ws04.static-verizon.com; www.verizon.com,
|
||||
2778,https://sanalytics.verizon.com/ee/or2/v1/interact?configId=028f67d9-9bd3-4306-b399-0c5025453572&requestId=1d66aa47-6418-4d41-9ad3-4fdd80aacdf7,sanalytics.verizon.com,/ee/or2/v1/interact,Completed,200,POST,application/json;charset=utf-8,Google Chrome,127.0.0.1:54722,sanalytics.verizon.com - 63.140.36.154:443,16:22:38.349,16:22:38.568,219,16:22:38.642,16:22:38.642,0,293,16:22:38.642,4542,442,0,280,,sanalytics.verizon.com - 63.140.36.154:443,sanalytics.verizon.com,sanalytics.verizon.com,
|
||||
2764,https://secure.verizon.com/vendor/transcend/vcg/xdi.html,secure.verizon.com,/vendor/transcend/vcg/xdi.html,Completed,200,GET,text/html; charset=utf-8,Google Chrome,127.0.0.1:54705,secure.verizon.com - 23.217.118.25:443,16:22:38.205,16:22:38.288,83,16:22:38.433,16:22:38.433,0,228,16:22:38.433,0,2803,0,1208,,secure.verizon.com - 23.217.118.25:443,login.verizonwireless.com,login.verizonwireless.com; tlogin.verizonwireless.com; securepp.verizon.com; secure.verizon.com,
|
||||
2762,https://adobedc.demdex.net/ee/v1/identity/acquire?configId=60e576ec-f42a-48ad-9fe5-0b9a866a6413&requestId=1f2588bb-3c94-458e-a81f-cc26b165db2b,adobedc.demdex.net,/ee/v1/identity/acquire,Completed,200,POST,application/json;charset=utf-8,Google Chrome,127.0.0.1:54699,adobedc.demdex.net - 63.140.37.142:443,16:22:38.124,16:22:38.258,134,16:22:38.314,16:22:38.315,1,191,16:22:38.315,140,2523,0,1078,,adobedc.demdex.net - 63.140.37.142:443,adobedc.demdex.net,adobedc.demdex.net,
|
||||
2758,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,201,POST,application/json,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:38.110,16:22:38.111,0,16:22:38.309,16:22:38.309,0,198,16:22:38.309,4074,18,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2756,https://assets.adobedtm.com/10d5272d092923c410feae744225087686012423/satelliteLib-8df7d93db820b272138ecb04dbe4ed7f5023b893.js,assets.adobedtm.com,/10d5272d092923c410feae744225087686012423/satelliteLib-8df7d93db820b272138ecb04dbe4ed7f5023b893.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54692,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:37.954,16:22:38.003,48,16:22:38.026,16:22:38.081,55,127,16:22:38.081,0,950253,0,230440,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2755,https://ssoauth.verizon.com/sso/VOLPortalLogin?hl=no&SAMLRequest=fVJdc9owEPwrGr37A7mFoMFkaJhMmaGNGzt56NvZHESMLbk6mX78%2BgpDXDrJ8Gaf9nZv9252%2B6up2QEtKaNTPgpjzlBXZqP0LuVPxX1ww2%2FnM4KmFq1cdO5FP%2BKPDskx36hJnl5S3lktDZAiqaFBkq6S%2BeLLWoowlq01zlSm5mxBhNZ5qTujqWvQ5mgPqsKnx3XKX5xrSUYRtSF4oRA2psSwMo2vREed6Mj4hoKzpR9HaXC9hYGFTM%2Fivak%2FRp94yETPD%2BvMWAf12uyU5uze2Ap7YynfQk3I2WqZctjDeAs4KZVS04%2Bl2IyEELtykuz3H%2BKJx1AGROqA%2F7qIOlxpcqBdykUsxkGcBMmoEIkUQibjcHoz%2Fc5Zdk7jk9KnlK9FV55AJD8XRRZkD3nB2fPrtjyAn3cje3V7uZTrxPAaI58Pifmu8E34s%2BhSYDiFr55xtcxMrarfbFHX5uedRXA%2BDmc77GNtwF2f4VhRm2DbQ6WzoEmhdpzl2ZH%2BWwe12iq0F0t9d0QeDWPllWl9XsP%2F%2BVjRrpZzXVae3bsGpRv%2FdXb2P2goDkzRO8c%2F%2Fws%3D&RelayState=34d6b7dc-f921-4ac5-964c-291f96ed842a&TARGET=https%3A%2F%2Fsp.auth.adobe.com%2Fsp%2Fsaml%2FSAMLAssertionConsumer&TOP=yes&cancelURL=https%3A%2F%2Fsp.auth.adobe.com%2Fadobe-services%2F1.0%2Fsession%3Fcancelled%3D1%26_method%3DPOST%26mso_id%3DVerizon&cancelURLDevice=https%3A%2F%2Fsp.auth.adobe.com%2Fadobe-services%2F1.0%2FsessionDevice%3Fcancelled%3D1%26_method%3DPOST%26mso_id%3DVerizon&loginType=vzRedirect&partner=nbcentertainment&scb=yes&ver=2&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=HClc34LA%2FAvIgyDTBP1AX9OlW1WshudAkANCyBS%2Blz6SVICCnqu0luqnxYPqyvCZxgxxhGv1l8EMaYXX1Qedrox4lwJbTO6fG4wCzFRQftxz22eDyhnd2zrTXuWF9Gjcf9uCOzsFXscjDgXVy%2FskC3AE3iACJGCqbMudFQ65jR3%2FUxG1ODLZ7C4VGBWM4zm%2B94J%2BObjPdxE3rS9VntK8HEWnU9ilc%2FQD3qgF8qhF5GUzqZik5Vv1AnvS5h6thpMdlF69Qbjwv2OG7HIN0Qfb4c%2BxvO2%2FlWgBgyeCPA9DVPAdqrAD8JY1ZZpjGDwfbSaQIokaS74txNzU7Pa6V6zOfQ%3D%3D,ssoauth.verizon.com,/sso/VOLPortalLogin,Completed,200,GET,text/html; charset=UTF-8,Google Chrome,127.0.0.1:54690,ssoauth.verizon.com - 23.217.118.26:443,16:22:37.693,16:22:37.759,66,16:22:37.932,16:22:37.932,0,238,16:22:37.932,0,25139,0,4739,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2754,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Internalerror,999,POST,,Google Chrome,127.0.0.1:54678,ssoauth.verizon.com - 23.217.118.26:443,16:22:37.645,16:22:37.645,0,16:22:37.934,16:22:37.934,0,289,16:22:37.934,3965,0,0,0,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2749,https://ssoauth.verizon.com/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,ssoauth.verizon.com,/B-vhP6NcScBpVMYp-mcRBOJFHos/hQ1NpSOEi5E9Xc5r/LGsGQUshZAg/chFK/SlEqbWsB,Completed,200,GET,application/javascript,Google Chrome,127.0.0.1:54678,ssoauth.verizon.com - 23.217.118.26:443,16:22:37.318,16:22:37.318,0,16:22:37.450,16:22:37.478,28,161,16:22:37.478,0,533056,0,172655,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2748,https://ssoauth.verizon.com/sso/VOLPortalLogin?SAMLRequest=fVJdc9owEPwrGr37A7mFoMFkaJhMmaGNGzt56NvZHESMLbk6mX78%2BgpDXDrJ8Gaf9nZv9252%2B6up2QEtKaNTPgpjzlBXZqP0LuVPxX1ww2%2FnM4KmFq1cdO5FP%2BKPDskx36hJnl5S3lktDZAiqaFBkq6S%2BeLLWoowlq01zlSm5mxBhNZ5qTujqWvQ5mgPqsKnx3XKX5xrSUYRtSF4oRA2psSwMo2vREed6Mj4hoKzpR9HaXC9hYGFTM%2Fivak%2FRp94yETPD%2BvMWAf12uyU5uze2Ap7YynfQk3I2WqZctjDeAs4KZVS04%2Bl2IyEELtykuz3H%2BKJx1AGROqA%2F7qIOlxpcqBdykUsxkGcBMmoEIkUQibjcHoz%2Fc5Zdk7jk9KnlK9FV55AJD8XRRZkD3nB2fPrtjyAn3cje3V7uZTrxPAaI58Pifmu8E34s%2BhSYDiFr55xtcxMrarfbFHX5uedRXA%2BDmc77GNtwF2f4VhRm2DbQ6WzoEmhdpzl2ZH%2BWwe12iq0F0t9d0QeDWPllWl9XsP%2F%2BVjRrpZzXVae3bsGpRv%2FdXb2P2goDkzRO8c%2F%2Fws%3D&RelayState=34d6b7dc-f921-4ac5-964c-291f96ed842a&TARGET=https%3A%2F%2Fsp.auth.adobe.com%2Fsp%2Fsaml%2FSAMLAssertionConsumer&TOP=yes&cancelURL=https%3A%2F%2Fsp.auth.adobe.com%2Fadobe-services%2F1.0%2Fsession%3Fcancelled%3D1%26_method%3DPOST%26mso_id%3DVerizon&cancelURLDevice=https%3A%2F%2Fsp.auth.adobe.com%2Fadobe-services%2F1.0%2FsessionDevice%3Fcancelled%3D1%26_method%3DPOST%26mso_id%3DVerizon&loginType=vzRedirect&partner=nbcentertainment&scb=yes&ver=2&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=HClc34LA%2FAvIgyDTBP1AX9OlW1WshudAkANCyBS%2Blz6SVICCnqu0luqnxYPqyvCZxgxxhGv1l8EMaYXX1Qedrox4lwJbTO6fG4wCzFRQftxz22eDyhnd2zrTXuWF9Gjcf9uCOzsFXscjDgXVy%2FskC3AE3iACJGCqbMudFQ65jR3%2FUxG1ODLZ7C4VGBWM4zm%2B94J%2BObjPdxE3rS9VntK8HEWnU9ilc%2FQD3qgF8qhF5GUzqZik5Vv1AnvS5h6thpMdlF69Qbjwv2OG7HIN0Qfb4c%2BxvO2%2FlWgBgyeCPA9DVPAdqrAD8JY1ZZpjGDwfbSaQIokaS74txNzU7Pa6V6zOfQ%3D%3D,ssoauth.verizon.com,/sso/VOLPortalLogin,Completed,200,GET,text/html; charset=UTF-8,Google Chrome,127.0.0.1:54678,ssoauth.verizon.com - 23.217.118.26:443,16:22:37.016,16:22:37.105,89,16:22:37.302,16:22:37.303,0,287,16:22:37.303,0,4288,0,2133,,ssoauth.verizon.com - 23.217.118.26:443,ssoauth.verizon.com,ssoauth.verizon.com; pprod-ssoauth.verizon.com; pprd-ssoauth.verizon.com,
|
||||
2747,https://sp.auth.adobe.com/adobe-services/authenticate/saml?reg_code=JX47BH7&mso_id=Verizon&requestor_id=nbcentertainment&no_iframe=true&domain_name=adobe.com&redirect_url=https%3A%2F%2Fentitlement.auth.adobe.com%2Fentitlement%2Fv4%2FcompleteBackgroundLogin.html,sp.auth.adobe.com,/adobe-services/authenticate/saml,Redirect,302,GET,,Google Chrome,127.0.0.1:54676,sp.auth.adobe.com - 35.161.249.167:443,16:22:36.791,16:22:36.951,160,16:22:37.006,16:22:37.006,0,215,16:22:37.006,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2746,https://sb.scorecardresearch.com/p?c1=19&c2=6035083&ns_ap_an=unknown&ns_ap_pn=js&ns_ap_pv=5&c12=_&name=foreground&ns_ap_ec=2&ns_ap_ev=hidden&ns_ap_device=MacIntel&ns_ap_id=1774999355781&ns_ap_bi=unknown&ns_ap_pfm=webbrowser&ns_ap_pfv=Chrome%20146.0.0.0&ns_ap_ver=unknown&ns_ap_sv=7.6.0.210114&ns_ap_bv=7.6.0.210114&ns_ap_smv=6.4&ns_type=hidden&ns_ts=1774999350619&ns_ap_env=0-0-2&ns_ap_ut=60000&ns_ap_ar=unknown&ns_ap_cs=1&ns_ap_fg=0&ns_ap_dft=0&ns_ap_dbt=0&ns_ap_dit=0&ns_ap_as=0&ns_ap_das=0&ns_ap_usage=0&ns_radio=unknown&cs_ucfr=&ns_ap_ft=0&ns_ap_bt=0&ns_ap_it=0&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_po=0x0&ns_ap_lang=en-US&ns_c=UTF-8&c7=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&c8=MVPD%20Picker&c9=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:54670,sb.scorecardresearch.com - 99.84.215.5:443,16:22:35.790,16:22:35.860,70,16:22:35.892,16:22:35.892,0,101,16:22:35.892,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2745,https://sb.scorecardresearch.com/p?c1=19&c2=6035083&ns_ap_an=unknown&ns_ap_pn=js&ns_ap_pv=5&c12=_&name=foreground&ns_ap_ec=1&ns_ap_ev=start&ns_ap_device=MacIntel&ns_ap_id=1774999355781&ns_ap_csf=1&ns_ap_bi=unknown&ns_ap_pfm=webbrowser&ns_ap_pfv=Chrome%20146.0.0.0&ns_ap_ver=unknown&ns_ap_sv=7.6.0.210114&ns_ap_bv=7.6.0.210114&ns_ap_smv=6.4&ns_type=view&ns_ap_gs=1774999350619&ns_ts=1774999350619&ns_ap_cfg=1110101-110-3C-7D0-A-1F-1E-1E-12C-A&ns_ap_env=0-0-2&ns_ap_ut=60000&ns_ap_ar=unknown&ns_ap_cs=1&ns_ap_fg=1&ns_ap_dft=0&ns_ap_dbt=0&ns_ap_dit=0&ns_ap_as=1&ns_ap_das=0&ns_ap_usage=0&ns_radio=unknown&cs_ucfr=&ns_ap_install=1774999350619&ns_ap_ft=0&ns_ap_bt=0&ns_ap_it=0&ns_ap_res=3024x1724&ns_ap_sd=3600x2338&ns_ap_po=0x0&ns_ap_lang=en-US&ns_ap_jb=unknown&ns_c=UTF-8&c7=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&c8=MVPD%20Picker&c9=,sb.scorecardresearch.com,/p,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:54671,sb.scorecardresearch.com - 99.84.215.5:443,16:22:35.790,16:22:35.862,72,16:22:35.892,16:22:35.892,0,102,16:22:35.892,0,43,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2744,https://nbcu.track.securedvisit.com/citecapture/?cc_event=viewpage&cc_context=Custom%20View%20Page&sv_cid=5998_04670&sv_onetag_id=3977&sv_session=b57cedfd2f0e710b9203ebdc75fdb14e&sv_ver=2.0.2&sv_dt=2026-03-31T23%3A22%3A35.778Z&sv_referrer=&sv_url=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&sv_title=MVPD%20Picker&sv_keywords=&cc_data=%7B%22gK_KykkH%22%3A%22yWKie1WiK%22%2C%22gK_1D2_3l1GDgH_gzz01i_J%22%3A%22JTnn%22%7D,nbcu.track.securedvisit.com,/citecapture/,Completed,200,GET,application/javascript; charset=utf-8,Google Chrome,127.0.0.1:54641,nbcu.track.securedvisit.com - 52.9.185.121:443,16:22:35.781,16:22:35.781,0,16:22:35.836,16:22:35.836,0,55,16:22:35.836,0,0,0,0,,nbcu.track.securedvisit.com - 52.9.185.121:443,securedvisit.com,securedvisit.com; sv.rkdms.com; *.sv.rkdms.com; *.securedvisit.com; *.track.securedvisit.com; nbcu.track.securedvisit.com,
|
||||
2743,https://sp.auth.adobe.com/reggie/v1/nbcentertainment/regcode,sp.auth.adobe.com,/reggie/v1/nbcentertainment/regcode,Completed,201,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54666,sp.auth.adobe.com - 35.161.249.167:443,16:22:35.332,16:22:35.332,0,16:22:35.384,16:22:35.384,0,52,16:22:35.384,80,1977,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2742,https://sp.auth.adobe.com/reggie/v1/nbcentertainment/regcode,sp.auth.adobe.com,/reggie/v1/nbcentertainment/regcode,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54664,sp.auth.adobe.com - 35.161.249.167:443,16:22:35.288,16:22:35.288,0,16:22:35.331,16:22:35.331,0,43,16:22:35.331,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2741,https://sp.auth.adobe.com/indiv/devices,sp.auth.adobe.com,/indiv/devices,Completed,200,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54666,sp.auth.adobe.com - 35.161.249.167:443,16:22:35.074,16:22:35.178,104,16:22:35.285,16:22:35.285,0,211,16:22:35.285,50,47,0,67,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2740,https://sp.auth.adobe.com/indiv/devices,sp.auth.adobe.com,/indiv/devices,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54664,sp.auth.adobe.com - 35.161.249.167:443,16:22:34.916,16:22:35.022,106,16:22:35.065,16:22:35.065,0,149,16:22:35.065,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2739,https://nbcu.sv.rkdms.com/?sv_dt=2026-03-31T23%3A22%3A34.379Z&sv_cid=5998_04670&sv_uid=-8189650932773340651&sv_title=MVPD%20Picker&sv_referrer=&sv_url=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&sv_keywords=&sv_tzOffset=-0700&sv_inframe=false&sv_ver=2.0.2&sv_session=b57cedfd2f0e710b9203ebdc75fdb14e&sv_first=true&sv_px_domain_data=%22iHjobdQ1L1QHmw5yAfQvQWn3bhslbd01L1jIKqsID8t1mfjID89ebksJ2kt1LkbY2CnqmfjID89eKkhvQwz1KuYSgohD8ZtJ2xjlZJhT9hZjtPKQnWqyDrQvQWn3bhsGgC9GKdt1LkbY2Cnqet%22,nbcu.sv.rkdms.com,/,Completed,200,GET,image/gif,Google Chrome,127.0.0.1:54662,nbcu.sv.rkdms.com - 50.18.213.95:443,16:22:34.485,16:22:34.578,93,16:22:34.634,16:22:34.634,0,148,16:22:34.634,0,43,0,0,,nbcu.sv.rkdms.com - 50.18.213.95:443,securedvisit.com,securedvisit.com; sv.rkdms.com; *.sv.rkdms.com; *.securedvisit.com; *.track.securedvisit.com; nbcu.sv.rkdms.com,
|
||||
2737,https://entitlement.auth.adobe.com/entitlement/v4/4.js,entitlement.auth.adobe.com,/entitlement/v4/4.js,Completed,200,GET,application/javascript,Google Chrome,127.0.0.1:54646,entitlement.auth.adobe.com - 23.42.82.205:443,16:22:34.383,16:22:34.463,80,16:22:34.482,16:22:34.483,0,100,16:22:34.483,0,608,0,359,,entitlement.auth.adobe.com - 23.42.82.205:443,ssl.adobe.com,ssl.adobe.com; signcsa.experienceleague.stage.adobe.com; wwwimages.stage2.adobe.com; www.macromedia.com; www.adobeidealab.com; cdn-stg-ffc.oobesaas.adobe.com; solutionpartners.stage2.adobe.com; www-stage01.acrobat.adobe.com; updates.adobeleanprint.com; download.stage.adobeprerelease.com; store2.stage2.adobe.com; live.adobeprimetime.com; wwwimages2.stage2.adobe.com; adobeprerelease.com; exchange.adobe.com; stage.status.adobe.com; experiencecloud.adobeexchange.com; www.stage.macromedia.com; experiencecloudstg2.adobeexchange.com; www.stage.adobeprerelease.com; www.qa.adobeprerelease.com; fpdownload.macromedia.com; stage.adobeprerelease.com; plan.adobe.com; dev.status.adobe.com; static.stage.adobeprimetime.com; preprod.status.adobe.com; live.stage.adobeprimetime.com; www.adobe.io; qe-ffc-static-cdn.oobesaas.adobe.com; download.adobeprerelease.com; trainingpartners.stage2.adobe.com; www.adobe-students.com; store1.stage2.adobe.com; auth.adobefpl.com; partners.stage2.adobe.com; media.stage.adobeprimetime.com; stage.adobe.io; cdn-qe-ffc.oobesaas.adobe.com; geo2.adobe.com; media.adobeprimetime.com; ffc-static-cdn.oobesaas.adobe.com; data.status.adobe.com; updates.stage.adobeleanprint.com; static.adobeprimetime.com; www.adobeprerelease.com; signcsa.experienceleague.adobe.com; hendrix360.qe.adobe.com; download.macromedia.com; csa.experienceleague.stage.adobe.com; app-cdn.stage2.adobe.com; stage.plan.adobe.com; www.stage2.adobe.com; stage.exchange.adobe.com; csa.experienceleague.adobe.com; entitlement.auth.adobe.com; download.qa.adobeprerelease.com; channelpartners.stage2.adobe.com; cdn-ffc.oobesaas.adobe.com; stg-ffc-static-cdn.oobesaas.adobe.com; technologypartners.stage2.adobe.com; www.stage.adobe-students.com,
|
||||
2736,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54650,map.mp.nbc.com - 151.101.2.49:443,16:22:34.383,16:22:34.446,63,16:22:34.482,16:22:34.482,0,100,16:22:34.482,958,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2735,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54651,map.mp.nbc.com - 151.101.2.49:443,16:22:34.383,16:22:34.452,69,16:22:34.482,16:22:34.482,0,100,16:22:34.482,173,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2734,https://entitlement.auth.adobe.com/entitlement/v4/3.js,entitlement.auth.adobe.com,/entitlement/v4/3.js,Completed,200,GET,application/javascript,Google Chrome,127.0.0.1:54648,entitlement.auth.adobe.com - 23.42.82.205:443,16:22:34.383,16:22:34.461,78,16:22:34.479,16:22:34.479,0,97,16:22:34.479,0,34612,0,11660,,entitlement.auth.adobe.com - 23.42.82.205:443,ssl.adobe.com,ssl.adobe.com; signcsa.experienceleague.stage.adobe.com; wwwimages.stage2.adobe.com; www.macromedia.com; www.adobeidealab.com; cdn-stg-ffc.oobesaas.adobe.com; solutionpartners.stage2.adobe.com; www-stage01.acrobat.adobe.com; updates.adobeleanprint.com; download.stage.adobeprerelease.com; store2.stage2.adobe.com; live.adobeprimetime.com; wwwimages2.stage2.adobe.com; adobeprerelease.com; exchange.adobe.com; stage.status.adobe.com; experiencecloud.adobeexchange.com; www.stage.macromedia.com; experiencecloudstg2.adobeexchange.com; www.stage.adobeprerelease.com; www.qa.adobeprerelease.com; fpdownload.macromedia.com; stage.adobeprerelease.com; plan.adobe.com; dev.status.adobe.com; static.stage.adobeprimetime.com; preprod.status.adobe.com; live.stage.adobeprimetime.com; www.adobe.io; qe-ffc-static-cdn.oobesaas.adobe.com; download.adobeprerelease.com; trainingpartners.stage2.adobe.com; www.adobe-students.com; store1.stage2.adobe.com; auth.adobefpl.com; partners.stage2.adobe.com; media.stage.adobeprimetime.com; stage.adobe.io; cdn-qe-ffc.oobesaas.adobe.com; geo2.adobe.com; media.adobeprimetime.com; ffc-static-cdn.oobesaas.adobe.com; data.status.adobe.com; updates.stage.adobeleanprint.com; static.adobeprimetime.com; www.adobeprerelease.com; signcsa.experienceleague.adobe.com; hendrix360.qe.adobe.com; download.macromedia.com; csa.experienceleague.stage.adobe.com; app-cdn.stage2.adobe.com; stage.plan.adobe.com; www.stage2.adobe.com; stage.exchange.adobe.com; csa.experienceleague.adobe.com; entitlement.auth.adobe.com; download.qa.adobeprerelease.com; channelpartners.stage2.adobe.com; cdn-ffc.oobesaas.adobe.com; stg-ffc-static-cdn.oobesaas.adobe.com; technologypartners.stage2.adobe.com; www.stage.adobe-students.com,
|
||||
2733,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54647,map.mp.nbc.com - 151.101.2.49:443,16:22:34.383,16:22:34.447,65,16:22:34.485,16:22:34.485,0,102,16:22:34.485,880,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2732,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s18028242192904?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A34%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=nbcentertainment%3APC%3AMVPD%20Picker&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&network=NBC%20Entertainment&passnetwork=NBC&passmvpd=Verizon&passselected=true&title=nbcentertainment%3APC%3AMVPD%20Picker&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A22&.tve&.c&cc=USD&pe=lnk_o&pev2=Adobe%20Pass%3AVerizon%20Selected&c.&a.&activitymap.&page=nbcentertainment%3APC%3ANBC%20Sports%20Video%2C%20News%2C%20and%20Highlights%20%E2%80%93%20nbc.com&link=Verizon®ion=main&pageIDType=1&.activitymap&.a&.c&pid=nbcentertainment%3APC%3ANBC%20Sports%20Video%2C%20News%2C%20and%20Highlights%20%E2%80%93%20nbc.com&pidt=1&oid=functionjf%28%29%7B%7D&oidt=2&ot=BUTTON&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&lrt=157&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s18028242192904",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54649,nbcume.sc.omtrdc.net - 63.140.37.172:443,16:22:34.382,16:22:34.489,107,16:22:34.540,16:22:34.540,0,158,16:22:34.540,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.172:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2736,https://entitlement.auth.adobe.com/,entitlement.auth.adobe.com,/,Completed,200,GET,text/html,Google Chrome,127.0.0.1:54644,entitlement.auth.adobe.com - 23.42.82.205:443,16:22:34.381,16:22:34.447,66,16:22:34.469,16:22:34.469,0,88,16:22:34.469,0,26,0,46,,entitlement.auth.adobe.com - 23.42.82.205:443,ssl.adobe.com,ssl.adobe.com; signcsa.experienceleague.stage.adobe.com; wwwimages.stage2.adobe.com; www.macromedia.com; www.adobeidealab.com; cdn-stg-ffc.oobesaas.adobe.com; solutionpartners.stage2.adobe.com; www-stage01.acrobat.adobe.com; updates.adobeleanprint.com; download.stage.adobeprerelease.com; store2.stage2.adobe.com; live.adobeprimetime.com; wwwimages2.stage2.adobe.com; adobeprerelease.com; exchange.adobe.com; stage.status.adobe.com; experiencecloud.adobeexchange.com; www.stage.macromedia.com; experiencecloudstg2.adobeexchange.com; www.stage.adobeprerelease.com; www.qa.adobeprerelease.com; fpdownload.macromedia.com; stage.adobeprerelease.com; plan.adobe.com; dev.status.adobe.com; static.stage.adobeprimetime.com; preprod.status.adobe.com; live.stage.adobeprimetime.com; www.adobe.io; qe-ffc-static-cdn.oobesaas.adobe.com; download.adobeprerelease.com; trainingpartners.stage2.adobe.com; www.adobe-students.com; store1.stage2.adobe.com; auth.adobefpl.com; partners.stage2.adobe.com; media.stage.adobeprimetime.com; stage.adobe.io; cdn-qe-ffc.oobesaas.adobe.com; geo2.adobe.com; media.adobeprimetime.com; ffc-static-cdn.oobesaas.adobe.com; data.status.adobe.com; updates.stage.adobeleanprint.com; static.adobeprimetime.com; www.adobeprerelease.com; signcsa.experienceleague.adobe.com; hendrix360.qe.adobe.com; download.macromedia.com; csa.experienceleague.stage.adobe.com; app-cdn.stage2.adobe.com; stage.plan.adobe.com; www.stage2.adobe.com; stage.exchange.adobe.com; csa.experienceleague.adobe.com; entitlement.auth.adobe.com; download.qa.adobeprerelease.com; channelpartners.stage2.adobe.com; cdn-ffc.oobesaas.adobe.com; stg-ffc-static-cdn.oobesaas.adobe.com; technologypartners.stage2.adobe.com; www.stage.adobe-students.com,
|
||||
2730,https://nbcu.track.securedvisit.com/js/sv.js?sv_cid=5998_04670&sv_origin=nbc.com,nbcu.track.securedvisit.com,/js/sv.js,Completed,200,GET,application/javascript; charset=utf-8,Google Chrome,127.0.0.1:54641,nbcu.track.securedvisit.com - 52.9.185.121:443,16:22:34.203,16:22:34.320,118,16:22:34.372,16:22:34.375,3,172,16:22:34.375,0,65065,0,26619,,nbcu.track.securedvisit.com - 52.9.185.121:443,securedvisit.com,securedvisit.com; sv.rkdms.com; *.sv.rkdms.com; *.securedvisit.com; *.track.securedvisit.com; nbcu.track.securedvisit.com,
|
||||
2723,https://sb.scorecardresearch.com/b?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999353200&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&c8=MVPD%20Picker&c9=,sb.scorecardresearch.com,/b,Completed,204,GET,,Google Chrome,127.0.0.1:54617,sb.scorecardresearch.com - 99.84.215.5:443,16:22:33.201,16:22:33.201,0,16:22:33.233,16:22:33.233,0,31,16:22:33.233,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2720,https://sb.scorecardresearch.com/b2?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999353054&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&c8=MVPD%20Picker&c9=,sb.scorecardresearch.com,/b2,Completed,204,GET,,Google Chrome,127.0.0.1:54617,sb.scorecardresearch.com - 99.84.215.5:443,16:22:33.086,16:22:33.086,0,16:22:33.187,16:22:33.187,0,101,16:22:33.187,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2719,https://api.nbc.com/localized-mvpd/entitlements?platform=iOS2&brand=nbcentertainment&instance=prod&zip=90404,api.nbc.com,/localized-mvpd/entitlements,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54616,api.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:33.067,16:22:33.191,124,16:22:33.255,16:22:33.265,10,198,16:22:33.265,0,554282,0,30727,,api.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; api.nbc.com,
|
||||
2718,https://sb.scorecardresearch.com/b?c1=2&c2=6035083&cs_it=b1&cv=4.13.1%2B2508250908&ns__t=1774999353054&ns_c=UTF-8&cs_cfg=1001110&cs_ucc=1&cs_cmp_id=28&cs_cmp_rt=0&cs_cmp_av=1.1&gpp_sid=7&gpp_smv=1.1&cs_cmp_ie=13&c7=https%3A%2F%2Fwww.nbc.com%2Fmvpd-picker&c8=MVPD%20Picker&c9=,sb.scorecardresearch.com,/b,Redirect,302,GET,,Google Chrome,127.0.0.1:54617,sb.scorecardresearch.com - 99.84.215.5:443,16:22:33.056,16:22:33.056,0,16:22:33.084,16:22:33.084,0,29,16:22:33.084,0,0,0,0,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2717,https://mps.nbcuni.com/request/page/json/params/?CALLBACK=mpsCallback&site=nbc-web&path=%2Fmvpd-picker&LOADMODE=more&ASYNC=1&_=1&NOLOAD=mpstools&USE_OVERLAY=0&IRSOURCE=false,mps.nbcuni.com,/request/page/json/params/,Completed,200,GET,application/json; charset=utf-8,Google Chrome,127.0.0.1:54618,mps.nbcuni.com - 23.192.167.152:443,16:22:32.955,16:22:33.024,69,16:22:33.187,16:22:33.189,2,234,16:22:33.189,0,37023,0,9968,,mps.nbcuni.com - 23.192.167.152:443,*.nbcuni.com,*.nbcuni.com; nbcuni.com; mps.nbcuni.com,
|
||||
2716,https://sb.scorecardresearch.com/beacon.js,sb.scorecardresearch.com,/beacon.js,Completed,200,GET,text/javascript,Google Chrome,127.0.0.1:54617,sb.scorecardresearch.com - 99.84.215.5:443,16:22:32.955,16:22:33.033,78,16:22:33.051,16:22:33.051,0,96,16:22:33.051,0,21373,0,6468,,sb.scorecardresearch.com - 99.84.215.5:443,*.scorecardresearch.com,*.scorecardresearch.com; sb.scorecardresearch.com,
|
||||
2715,https://www.nbc.com/geoIp/insights,www.nbc.com,/geoIp/insights,Error,403,POST,text/html,Google Chrome,127.0.0.1:54611,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:32.947,16:22:32.948,0,16:22:33.004,16:22:33.004,0,57,16:22:33.004,47,383,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2714,https://www.nbc.com/generetic/generated/chunks/1637.9cbb36fc2327638c3691.js,www.nbc.com,/generetic/generated/chunks/1637.9cbb36fc2327638c3691.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54611,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:32.778,16:22:32.837,59,16:22:32.937,16:22:32.937,0,159,16:22:32.937,0,7896,0,2383,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2713,https://www.nbc.com/generetic/generated/chunks/4471.c44dd574d1063b58e85b.js,www.nbc.com,/generetic/generated/chunks/4471.c44dd574d1063b58e85b.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54610,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:32.778,16:22:32.842,64,16:22:32.923,16:22:32.923,0,145,16:22:32.923,0,53343,0,10452,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2714,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54555,map.mp.nbc.com - 151.101.2.49:443,16:22:32.775,16:22:32.775,0,16:22:32.813,16:22:32.813,0,38,16:22:32.813,973,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2713,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54553,map.mp.nbc.com - 151.101.2.49:443,16:22:32.775,16:22:32.775,0,16:22:32.808,16:22:32.808,0,33,16:22:32.808,173,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2711,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54556,map.mp.nbc.com - 151.101.2.49:443,16:22:32.774,16:22:32.775,0,16:22:32.803,16:22:32.803,0,29,16:22:32.803,886,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2707,https://www.nbc.com/generetic/favicon.ico,www.nbc.com,/generetic/favicon.ico,Completed,200,GET,image/x-icon,Google Chrome,127.0.0.1:54599,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:32.736,16:22:32.798,62,16:22:32.848,16:22:32.848,0,112,16:22:32.848,0,9662,0,1561,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2702,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54556,map.mp.nbc.com - 151.101.2.49:443,16:22:31.986,16:22:31.987,0,16:22:32.016,16:22:32.016,0,29,16:22:32.016,872,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2697,https://img.nbc.com/files/2024-11/nbcsportsnow_white_logo-425x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-11/nbcsportsnow_white_logo-425x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54585,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:31.337,16:22:31.395,58,16:22:31.439,16:22:31.439,0,102,16:22:31.439,0,7600,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2698,https://img.nbc.com/files/2024-01/nbc-news-now_white_stacked.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-01/nbc-news-now_white_stacked.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54584,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:31.337,16:22:31.404,67,16:22:31.456,16:22:31.456,0,119,16:22:31.456,0,15256,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2696,https://img.nbc.com/files/images/2019/3/11/Bravo-Logo-All-platform-AssetsBravo-Logo-White-905x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/images/2019/3/11/Bravo-Logo-All-platform-AssetsBravo-Logo-White-905x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54583,img.nbc.com - 2600:1406:4e00:19::1738:6d50:443,16:22:31.337,16:22:31.401,64,16:22:31.469,16:22:31.469,0,131,16:22:31.469,0,4766,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d50:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2695,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Afalse%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22sport%22%2C%22type%22%3A%22LANDING_PAGE%22%2C%22subType%22%3A%22sportHome%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22appVersion%22%3A1249000%2C%22componentConfigs%22%3A%5B%22eyJ0aXRsZSI6IkZhdm9yaXRlcyIsInR5cGUiOiJTaGVsZiIsImltcGxlbWVudGF0aW9uIjoiZmF2b3JpdGVzU2hlbGYiLCJuYW1lIjoiZmF2b3JpdGVzU2hlbGYiLCJhcHAiOiJuYmMifQ%3D%3D%22%2C%22eyJ0eXBlIjoiU2hlbGYiLCJpbXBsZW1lbnRhdGlvbiI6ImF1dG9tYXRlZExpbmVhclNoZWxmIiwibmFtZSI6ImF1dG9tYXRlZExpbmVhclNoZWxmIiwiYXBwIjoibmJjIn0%3D%22%5D%2C%22queryName%22%3A%22componentsForPlaceholders%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%223b7ab0262b412e1a69aa09333228ebcf55cf4f40a56cff0769607c6f0743a36a%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54567,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:31.253,16:22:31.253,0,16:22:31.323,16:22:31.323,0,70,16:22:31.323,0,10255,0,2421,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2691,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54556,map.mp.nbc.com - 151.101.2.49:443,16:22:30.799,16:22:30.799,0,16:22:30.832,16:22:30.832,0,33,16:22:30.832,17094,42,0,62,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2688,https://map.mp.nbc.com/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,map.mp.nbc.com,/webevents/v3/JS/69dedba1e9714049b35bde9e2f9bf059/events,Completed,202,POST,application/json,Google Chrome,127.0.0.1:54556,map.mp.nbc.com - 151.101.2.49:443,16:22:30.734,16:22:30.734,0,16:22:30.798,16:22:30.798,0,64,16:22:30.798,10567,40,0,60,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2687,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54553,map.mp.nbc.com - 151.101.2.49:443,16:22:30.734,16:22:30.734,0,16:22:30.765,16:22:30.765,0,31,16:22:30.765,1227,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2686,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54555,map.mp.nbc.com - 151.101.2.49:443,16:22:30.729,16:22:30.729,0,16:22:30.763,16:22:30.763,0,34,16:22:30.763,1221,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2684,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54552,map.mp.nbc.com - 151.101.2.49:443,16:22:30.716,16:22:30.716,0,16:22:30.750,16:22:30.750,0,34,16:22:30.750,914,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2683,https://friendship.nbc.com/v3/graphql?variables=%7B%22appVersion%22%3A1249000%2C%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Afalse%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22sport%22%2C%22type%22%3A%22LANDING_PAGE%22%2C%22subType%22%3A%22sportHome%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22queryName%22%3A%22featuredSection%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22a76f2966d3fbe90881952db8e3b69f211d84c9c3d2d1d7cd9368378ece8e930e%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54568,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.703,16:22:30.776,73,16:22:30.914,16:22:30.914,0,211,16:22:30.914,0,23822,0,3761,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2682,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22874355218828044267%22%2C%22device%22%3A%22web%22%2C%22platform%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22authorized%22%3Afalse%2C%22isDayZero%22%3Atrue%2C%22name%22%3A%22sport%22%2C%22type%22%3A%22LANDING_PAGE%22%2C%22subType%22%3A%22sportHome%22%2C%22timeZone%22%3A%22America%2FLos_Angeles%22%2C%22nbcAffiliateName%22%3A%22knbc%22%2C%22telemundoAffiliateName%22%3A%22kvea%22%2C%22nationalBroadcastType%22%3A%22westCoast%22%2C%22app%22%3A%22nbc%22%2C%22appVersion%22%3A1249000%2C%22queryName%22%3A%22page%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22661bcb56e11fbe0692168bc41f864967ba8bbaa160985099c3092877e9b8dddc%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54567,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.703,16:22:30.772,69,16:22:31.229,16:22:31.229,1,527,16:22:31.229,0,381036,0,45214,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2681,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54559,map.mp.nbc.com - 151.101.2.49:443,16:22:30.614,16:22:30.706,92,16:22:30.737,16:22:30.737,0,123,16:22:30.737,917,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2681,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54557,map.mp.nbc.com - 151.101.2.49:443,16:22:30.613,16:22:30.704,92,16:22:30.735,16:22:30.735,0,122,16:22:30.735,6670,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2680,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54555,map.mp.nbc.com - 151.101.2.49:443,16:22:30.613,16:22:30.692,79,16:22:30.728,16:22:30.728,0,116,16:22:30.728,6716,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2679,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54556,map.mp.nbc.com - 151.101.2.49:443,16:22:30.612,16:22:30.698,85,16:22:30.734,16:22:30.734,0,121,16:22:30.734,1278,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2678,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54553,map.mp.nbc.com - 151.101.2.49:443,16:22:30.612,16:22:30.698,85,16:22:30.734,16:22:30.734,0,121,16:22:30.734,1046,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2680,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54552,map.mp.nbc.com - 151.101.2.49:443,16:22:30.608,16:22:30.685,77,16:22:30.716,16:22:30.716,0,108,16:22:30.716,1276,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2672,https://www.nbc.com/firebase-messaging-sw.js,www.nbc.com,/firebase-messaging-sw.js,Notmodified,304,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54544,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.479,16:22:30.479,0,16:22:30.568,16:22:30.568,0,89,16:22:30.568,0,0,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2671,https://www.nbc.com/generetic/scripts/comScore-7.6.0.min.js,www.nbc.com,/generetic/scripts/comScore-7.6.0.min.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54532,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.466,16:22:30.466,0,16:22:30.568,16:22:30.569,1,103,16:22:30.569,0,170726,0,47385,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2670,https://www.nbc.com/firebase-messaging-sw.js,www.nbc.com,/firebase-messaging-sw.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54544,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.363,16:22:30.427,65,16:22:30.472,16:22:30.474,2,112,16:22:30.474,0,163333,0,33994,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2669,https://img.nbc.com/files/images/2021/6/29/elements-search-icon-active.png,img.nbc.com,/files/images/2021/6/29/elements-search-icon-active.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54535,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:30.353,16:22:30.429,76,16:22:30.472,16:22:30.472,0,120,16:22:30.472,0,764,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2668,https://img.nbc.com/files/images/2021/6/30/elements-search-rollover-hover.png,img.nbc.com,/files/images/2021/6/30/elements-search-rollover-hover.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54536,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:30.352,16:22:30.430,78,16:22:30.470,16:22:30.470,0,117,16:22:30.470,0,694,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2667,https://img.nbc.com/files/images/2021/6/29/elements-search-icon.png,img.nbc.com,/files/images/2021/6/29/elements-search-icon.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54533,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:30.352,16:22:30.426,74,16:22:30.461,16:22:30.461,0,109,16:22:30.461,0,746,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2666,https://img.nbc.com/files/images/2021/6/29/peacock_preview.png,img.nbc.com,/files/images/2021/6/29/peacock_preview.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54534,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:30.352,16:22:30.420,67,16:22:30.461,16:22:30.461,0,109,16:22:30.461,0,5043,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2665,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22-8189650932773340651%22%2C%22platform%22%3A%22web%22%2C%22app%22%3A%22nbc%22%2C%22queryName%22%3A%22userInteractions%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%222fc3608e4ca15d6964cf319391ce4d24ba29f37e484e0a3afe63e788b0c7e594%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54537,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.352,16:22:30.431,79,16:22:30.568,16:22:30.568,0,216,16:22:30.568,0,67,0,0,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2664,https://www.nbc.com/generetic/generated/images/4fac8d8aaa7d8798a512dac0322d8d14.png,www.nbc.com,/generetic/generated/images/4fac8d8aaa7d8798a512dac0322d8d14.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54532,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:30.346,16:22:30.408,63,16:22:30.454,16:22:30.454,0,108,16:22:30.454,0,7219,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2656,https://sp.auth.adobe.com/adobe-services/config/nbcentertainment,sp.auth.adobe.com,/adobe-services/config/nbcentertainment,Completed,200,GET,application/xml;charset=UTF-8,Google Chrome,127.0.0.1:54494,sp.auth.adobe.com - 35.161.249.167:443,16:22:30.151,16:22:30.151,0,16:22:30.208,16:22:30.208,0,57,16:22:30.208,0,192247,0,12463,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2655,https://sp.auth.adobe.com/adobe-services/config/nbcentertainment,sp.auth.adobe.com,/adobe-services/config/nbcentertainment,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54461,sp.auth.adobe.com - 35.161.249.167:443,16:22:30.104,16:22:30.104,0,16:22:30.150,16:22:30.150,0,46,16:22:30.150,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2654,https://mps.nbcuni.com/images/MPS-STATISTIC-REPORTING.png?X=hasab&AB=0&S=nbc-web&H=www.nbc.com&P=%2Fsports&D=desktop&U=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F146.0.0.0%20Safari%2F537.36&_=daeqwtokpvfx,mps.nbcuni.com,/images/MPS-STATISTIC-REPORTING.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54517,mps.nbcuni.com - 23.192.167.152:443,16:22:30.088,16:22:30.138,50,16:22:30.251,16:22:30.251,0,163,16:22:30.251,0,84,0,0,,mps.nbcuni.com - 23.192.167.152:443,*.nbcuni.com,*.nbcuni.com; nbcuni.com; mps.nbcuni.com,
|
||||
2652,https://sp.auth.adobe.com/o/client/token,sp.auth.adobe.com,/o/client/token,Completed,201,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54494,sp.auth.adobe.com - 35.161.249.167:443,16:22:30.042,16:22:30.042,0,16:22:30.102,16:22:30.102,0,60,16:22:30.102,127,767,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2651,https://sp.auth.adobe.com/o/client/token,sp.auth.adobe.com,/o/client/token,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54461,sp.auth.adobe.com - 35.161.249.167:443,16:22:29.959,16:22:29.959,0,16:22:30.041,16:22:30.041,0,82,16:22:30.041,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2642,https://map.mp.nbc.com/identity/v1/identify,map.mp.nbc.com,/identity/v1/identify,Completed,200,POST,application/json; charset=utf-8,Google Chrome,127.0.0.1:54468,map.mp.nbc.com - 151.101.2.49:443,16:22:29.799,16:22:29.799,0,16:22:29.887,16:22:29.887,0,87,16:22:29.887,384,161,0,153,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2641,https://sp.auth.adobe.com/o/client/register,sp.auth.adobe.com,/o/client/register,Completed,201,POST,application/json;charset=UTF-8,Google Chrome,127.0.0.1:54494,sp.auth.adobe.com - 35.161.249.167:443,16:22:29.792,16:22:29.909,117,16:22:29.958,16:22:29.958,0,166,16:22:29.958,527,310,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2641,https://ss.nbc.co/conveyor/search?mpid=874355218828044267,ss.nbc.co,/conveyor/search,Error,404,GET,application/json,Google Chrome,127.0.0.1:54493,ss.nbc.co - 2600:1406:4e00:19::1738:6d4c:443,16:22:29.790,16:22:29.891,102,16:22:30.195,16:22:30.195,0,406,16:22:30.195,0,132,0,0,,ss.nbc.co - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.co,*.nbc.co; nbc.co; ss.nbc.co,
|
||||
2635,https://friendship.nbc.com/v3/graphql?variables=%7B%22userId%22%3A%22874355218828044267%22%2C%22platform%22%3A%22web%22%2C%22device%22%3A%22web%22%2C%22language%22%3A%22en%22%2C%22app%22%3A%22nbc%22%2C%22isDayZero%22%3Atrue%2C%22queryName%22%3A%22globalNavigation%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2233e6c80b28860c5c634c6c4f10b2cb7de6e5dd54fbcec05138055ad9125cee63%22%7D%7D,friendship.nbc.com,/v3/graphql,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54483,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:29.743,16:22:29.836,93,16:22:29.931,16:22:29.931,0,188,16:22:29.931,0,2223,0,467,,friendship.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; friendship.nbc.com,
|
||||
2634,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s11966585529175?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A29%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=nbcentertainment%3APC%3ANBC%20Sports%20Video%2C%20News%2C%20and%20Highlights%20%E2%80%93%20nbc.com&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&network=NBC%20Entertainment&identityguid=874355218828044267&identityauthen=Unauthenticated&identitymethod=None&title=nbcentertainment%3APC%3ANBC%20Sports%20Video%2C%20News%2C%20and%20Highlights%20%E2%80%93%20nbc.com&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A22&.tve&.c&cc=USD&pe=lnk_o&pev2=Identity%20Sign-In%20Check&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&lrt=191&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s11966585529175",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54482,nbcume.sc.omtrdc.net - 63.140.37.172:443,16:22:29.742,16:22:29.844,102,16:22:29.894,16:22:29.894,0,152,16:22:29.894,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.172:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2632,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54470,map.mp.nbc.com - 151.101.2.49:443,16:22:29.701,16:22:29.767,67,16:22:29.808,16:22:29.808,0,107,16:22:29.808,155,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2631,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54468,map.mp.nbc.com - 151.101.2.49:443,16:22:29.701,16:22:29.765,65,16:22:29.799,16:22:29.799,0,98,16:22:29.799,155,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2630,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54467,map.mp.nbc.com - 151.101.2.49:443,16:22:29.700,16:22:29.765,65,16:22:29.794,16:22:29.794,0,94,16:22:29.794,155,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2629,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54469,map.mp.nbc.com - 151.101.2.49:443,16:22:29.700,16:22:29.763,63,16:22:29.791,16:22:29.791,0,90,16:22:29.791,155,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2625,https://map.mp.nbc.com/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,map.mp.nbc.com,/webevents/v1/JS/69dedba1e9714049b35bde9e2f9bf059/Forwarding,Completed,202,POST,,Google Chrome,127.0.0.1:54441,map.mp.nbc.com - 151.101.2.49:443,16:22:29.695,16:22:29.695,0,16:22:29.724,16:22:29.724,0,29,16:22:29.724,155,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2622,https://sp.auth.adobe.com/o/client/register,sp.auth.adobe.com,/o/client/register,Completed,200,OPTIONS,,Google Chrome,127.0.0.1:54461,sp.auth.adobe.com - 35.161.249.167:443,16:22:29.623,16:22:29.747,124,16:22:29.788,16:22:29.788,0,166,16:22:29.788,0,0,0,0,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2621,https://img.nbc.com/files/12012833_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12012833_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.604,16:22:29.604,0,16:22:29.653,16:22:29.656,2,52,16:22:29.656,0,22144,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2619,https://map.mp.nbc.com/identity/v1/identify,map.mp.nbc.com,/identity/v1/identify,Completed,200,POST,application/json; charset=utf-8,Google Chrome,127.0.0.1:54441,map.mp.nbc.com - 151.101.2.49:443,16:22:29.589,16:22:29.589,0,16:22:29.692,16:22:29.692,0,103,16:22:29.692,318,175,0,163,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2618,https://img.nbc.com/files/12014237_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014237_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.568,16:22:29.568,0,16:22:29.617,16:22:29.617,0,49,16:22:29.617,0,15197,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2617,https://sp.auth.adobe.com/entitlement/v4/AccessEnablerProxy.js,sp.auth.adobe.com,/entitlement/v4/AccessEnablerProxy.js,Completed,200,GET,application/javascript,Google Chrome,127.0.0.1:54429,sp.auth.adobe.com - 35.161.249.167:443,16:22:29.559,16:22:29.559,0,16:22:29.612,16:22:29.612,0,53,16:22:29.612,0,6012,0,2450,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2615,https://img.nbc.com/files/12014273_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014273_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.557,16:22:29.557,0,16:22:29.603,16:22:29.603,0,47,16:22:29.603,0,15146,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2614,https://img.nbc.com/files/12014303_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014303_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.520,16:22:29.520,0,16:22:29.567,16:22:29.568,1,47,16:22:29.568,0,33588,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2613,https://img.nbc.com/files/12014222_1920x1080.jpg?impolicy=nbc_com&imwidth=480&imdensity=1,img.nbc.com,/files/12014222_1920x1080.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.445,16:22:29.445,0,16:22:29.555,16:22:29.555,0,110,16:22:29.555,0,16992,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2611,https://mps.nbcuni.com/request/page/json/params/?CALLBACK=mpsCallback&cat=sportspage&site=nbc-web&type=sportspage&path=%2F&title=NBC%20Sports%20Video%20News%20and%20Highlights%20%20nbc.com&cag%5Bsubdomains%5D=www&cag%5Btopdomain%5D=nbc.com&field%5Bpageurl%5D=www.nbc.com%2Fsports&NOLOAD=mpstools&USE_OVERLAY=0&IRSOURCE=false&ASYNC=1,mps.nbcuni.com,/request/page/json/params/,Completed,200,GET,application/json; charset=utf-8,Google Chrome,127.0.0.1:54448,mps.nbcuni.com - 23.192.167.152:443,16:22:29.441,16:22:29.558,117,16:22:29.653,16:22:29.654,0,212,16:22:29.654,0,40289,0,9930,,mps.nbcuni.com - 23.192.167.152:443,*.nbcuni.com,*.nbcuni.com; nbcuni.com; mps.nbcuni.com,
|
||||
2609,"https://nbcume.sc.omtrdc.net/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s15552565633701?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A29%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=ISO-8859-1&pageName=global%3Ahome&g=https%3A%2F%2Fwww.nbc.com%2Fsports&c.&tve.&contenthub=Adobe%20Pass&network=NBC%20Entertainment&title=global%3Ahome&domain=www.nbc.com&platform=PC&did=demdex%20cookie%20not%20set&date=03%2F31%2F2026&day=Tuesday&hour=16%3A00&minute=16%3A22&.tve&nbcu.&contentGroup=Online&contentType=Home&showSite=Global&.nbcu&pageTitle=Home&.c&cc=USD&server=www.nbc.com&aamb=RKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&AQE=1",nbcume.sc.omtrdc.net,"/b/ss/nbcutve,nbcunetworkbu/1/JS-2.24.0-LEWM/s15552565633701",Completed,200,GET,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54442,nbcume.sc.omtrdc.net - 63.140.37.172:443,16:22:29.432,16:22:29.575,144,16:22:29.618,16:22:29.618,0,187,16:22:29.618,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.172:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
2608,https://map.mp.nbc.com/identity/v1/identify,map.mp.nbc.com,/identity/v1/identify,Completed,204,OPTIONS,,Google Chrome,127.0.0.1:54441,map.mp.nbc.com - 151.101.2.49:443,16:22:29.428,16:22:29.567,138,16:22:29.588,16:22:29.588,0,160,16:22:29.588,0,0,0,0,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2608,https://img.nbc.com/files/images/2019/4/16/Telemundo-Logo-White-360x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/images/2019/4/16/Telemundo-Logo-White-360x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.422,16:22:29.422,0,16:22:29.519,16:22:29.520,0,97,16:22:29.520,0,4704,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2602,https://img.nbc.com/files/2024-08/nbcsports-boston-logo-white-375x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-08/nbcsports-boston-logo-white-375x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.395,16:22:29.395,0,16:22:29.444,16:22:29.444,0,49,16:22:29.444,0,15022,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2601,https://img.nbc.com/files/2024-08/nbcsports-bay-area-logo-white-382x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-08/nbcsports-bay-area-logo-white-382x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.380,16:22:29.380,0,16:22:29.421,16:22:29.421,0,41,16:22:29.421,0,15232,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2600,https://img.nbc.com/files/2024-08/nbcsports-california-logo-white-378x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-08/nbcsports-california-logo-white-378x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.345,16:22:29.345,0,16:22:29.379,16:22:29.379,0,34,16:22:29.379,0,15814,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2599,https://img.nbc.com/files/images/2019/4/25/Universo-logos-templateUniverso-Logo-White-450x228.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/images/2019/4/25/Universo-logos-templateUniverso-Logo-White-450x228.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.315,16:22:29.315,0,16:22:29.395,16:22:29.395,0,80,16:22:29.395,0,7468,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2598,https://mps.nbcuni.com/fetch/ext/load-nbc-web.js?nowrite=2,mps.nbcuni.com,/fetch/ext/load-nbc-web.js,Completed,200,GET,application/javascript; charset=utf-8,Google Chrome,127.0.0.1:54430,mps.nbcuni.com - 23.192.167.152:443,16:22:29.312,16:22:29.387,75,16:22:29.412,16:22:29.436,24,124,16:22:29.436,0,279843,0,64317,,mps.nbcuni.com - 23.192.167.152:443,*.nbcuni.com,*.nbcuni.com; nbcuni.com; mps.nbcuni.com,
|
||||
2596,https://sp.auth.adobe.com/entitlement/v4/AccessEnablerProxy.html?9f61ce3349740cda7528,sp.auth.adobe.com,/entitlement/v4/AccessEnablerProxy.html,Completed,200,GET,text/html,Google Chrome,127.0.0.1:54429,sp.auth.adobe.com - 35.161.249.167:443,16:22:29.283,16:22:29.445,161,16:22:29.555,16:22:29.555,0,272,16:22:29.555,0,235,0,181,,sp.auth.adobe.com - 35.161.249.167:443,*.auth.adobe.com,*.auth.adobe.com; api-gateway-ap-prd-va6-02-b.infra.adobe.io; adobepass-release-production-cg.ethos551-prod-va6.ethos.adobe.net; sp-us-west-2-az1.adobepass.com; sp-gw-us-west-2.adobepass.com; sp-us-east-1.adobepass.com; api-gateway-ap-prd-va6-01.infra.adobe.io; api-gateway-ap-prd-va6-01-a.infra.adobe.io; sp-gw-prod2-us-east-1.adobepass.com; sp-us-east-1-az2.adobepass.com; adobepass-release-production-cg.ethos501-prod-or2.ethos.adobe.net; api-gateway-ap-prd-or2-a.infra.adobe.io; adobepass-release-production-cg.ethos501-prod-va6.ethos.adobe.net; sp-dc.adobepass.com; sp-gw-us-east-1.adobepass.com; api-gateway-ap-prd-or2.infra.adobe.io; api-gateway-ap-prd-or2-b.infra.adobe.io; api-gateway-ap-prd-va6-02.infra.adobe.io; api-gateway-ap-prd-va6-02-a.infra.adobe.io; api-gateway-ap-prd-va6-01-b.infra.adobe.io; sp.auth.adobe.com,
|
||||
2595,https://map.mp.nbc.com/tags/JS/v2/69dedba1e9714049b35bde9e2f9bf059/config?env=0&plan_id=nbcu_data_plan,map.mp.nbc.com,/tags/JS/v2/69dedba1e9714049b35bde9e2f9bf059/config,Completed,200,GET,text/plain; charset=utf-8,Google Chrome,127.0.0.1:54428,map.mp.nbc.com - 151.101.2.49:443,16:22:29.280,16:22:29.375,95,16:22:29.390,16:22:29.419,29,139,16:22:29.419,0,472348,0,190151,,map.mp.nbc.com - 151.101.2.49:443,map.mp.nbc.com,map.mp.nbc.com,
|
||||
2593,https://img.nbc.com/files/2024-08/nbcsports-philadelphia-logo-white-378x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-08/nbcsports-philadelphia-logo-white-378x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.253,16:22:29.253,0,16:22:29.314,16:22:29.314,0,61,16:22:29.314,0,16244,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2592,https://img.nbc.com/files/2025-11/smallball_s1-logo-white-602x428.png?impolicy=nbc_com&imwidth=1260&imdensity=1,img.nbc.com,/files/2025-11/smallball_s1-logo-white-602x428.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.187,16:22:29.187,0,16:22:29.250,16:22:29.252,2,66,16:22:29.252,0,17790,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2591,"https://img.nbc.com/files/2026-03/1920x1080-westdreamsb-2026-web-dynamiclead-desktop.jpg?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/1920x1080-westdreamsb-2026-web-dynamiclead-desktop.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.168,16:22:29.168,0,16:22:29.338,16:22:29.345,7,177,16:22:29.345,0,113461,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2588,https://img.nbc.com/files/2026-03/menscollegebasketball-logo-color-311x428.png?impolicy=nbc_com&imwidth=1260&imdensity=1,img.nbc.com,/files/2026-03/menscollegebasketball-logo-color-311x428.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.141,16:22:29.141,0,16:22:29.186,16:22:29.186,0,46,16:22:29.186,0,15214,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2587,https://www.nbc.com/generetic/generated/chunks/7380.b12c50e3d1ef34d42b66.js,www.nbc.com,/generetic/generated/chunks/7380.b12c50e3d1ef34d42b66.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54378,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:29.078,16:22:29.078,0,16:22:29.271,16:22:29.271,0,193,16:22:29.271,0,12002,0,2832,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2586,https://www.nbc.com/generetic/generated/chunks/1944.f06c416df55bf341a86b.js,www.nbc.com,/generetic/generated/chunks/1944.f06c416df55bf341a86b.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54376,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:29.078,16:22:29.078,0,16:22:29.138,16:22:29.141,3,63,16:22:29.141,0,162955,0,27016,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2585,https://www.nbc.com/generetic/generated/chunks/8817.10b93d60c41cad6f391d.js,www.nbc.com,/generetic/generated/chunks/8817.10b93d60c41cad6f391d.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54377,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:29.078,16:22:29.078,0,16:22:29.126,16:22:29.126,0,48,16:22:29.126,0,44216,0,6322,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2584,https://www.nbc.com/generetic/generated/chunks/4577.5cff97b09ba86735c8b6.js,www.nbc.com,/generetic/generated/chunks/4577.5cff97b09ba86735c8b6.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54373,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:29.078,16:22:29.078,0,16:22:29.232,16:22:29.232,0,154,16:22:29.232,0,92084,0,15867,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2583,"https://img.nbc.com/files/2026-03/michigan_033026_1920.jpg?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/michigan_033026_1920.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.073,16:22:29.073,0,16:22:29.160,16:22:29.167,7,94,16:22:29.167,0,91758,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2582,"https://img.nbc.com/files/2026-03/warren_033026_1920.jpg?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/warren_033026_1920.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.029,16:22:29.029,0,16:22:29.131,16:22:29.140,9,112,16:22:29.140,0,78758,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2581,https://img.nbc.com/files/2025-10/nbaonnbc-logo-color-508x428.png?impolicy=nbc_com&imwidth=1260&imdensity=1,img.nbc.com,/files/2025-10/nbaonnbc-logo-color-508x428.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:29.018,16:22:29.018,0,16:22:29.071,16:22:29.073,2,55,16:22:29.073,0,25080,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2579,"https://img.nbc.com/files/2026-03/1920x1080-detokchl0330-2026-web-dynamiclead-desktop.jpg?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/1920x1080-detokchl0330-2026-web-dynamiclead-desktop.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.871,16:22:28.871,0,16:22:28.996,16:22:29.017,21,146,16:22:29.017,0,133203,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2578,https://img.nbc.com/files/2025-10/ncaa_mens_college_basketball.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-10/ncaa_mens_college_basketball.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.847,16:22:28.847,0,16:22:29.028,16:22:29.028,0,181,16:22:29.028,0,2236,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2577,https://img.nbc.com/files/2025-05/nba_league.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/nba_league.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.839,16:22:28.839,0,16:22:28.870,16:22:28.870,0,31,16:22:28.870,0,1720,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2576,https://img.nbc.com/files/2025-05/premier_league.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/premier_league.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.798,16:22:28.798,0,16:22:28.838,16:22:28.838,0,40,16:22:28.838,0,1520,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2575,https://assets.adobedtm.com/a2ef59fba8e9/9c7a708dbcb2/d3d66595eddb/RCde3a0129d7334bb0bf2895038d0c6952-source.min.js,assets.adobedtm.com,/a2ef59fba8e9/9c7a708dbcb2/d3d66595eddb/RCde3a0129d7334bb0bf2895038d0c6952-source.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54415,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:28.794,16:22:28.865,72,16:22:28.879,16:22:28.880,0,86,16:22:28.880,0,532,0,314,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2574,https://assets.adobedtm.com/extensions/EPe51f9b26f7c243dfa8d1d3ea2bf16f5f/AppMeasurement_Module_ActivityMap.min.js,assets.adobedtm.com,/extensions/EPe51f9b26f7c243dfa8d1d3ea2bf16f5f/AppMeasurement_Module_ActivityMap.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54414,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:28.794,16:22:28.859,65,16:22:28.876,16:22:28.876,0,83,16:22:28.876,0,3284,0,1597,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2573,https://img.nbc.com/files/2025-05/nhl.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/nhl.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.785,16:22:28.785,0,16:22:28.846,16:22:28.846,0,61,16:22:28.846,0,2842,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2571,https://assets.adobedtm.com/extensions/EPe51f9b26f7c243dfa8d1d3ea2bf16f5f/AppMeasurement.min.js,assets.adobedtm.com,/extensions/EPe51f9b26f7c243dfa8d1d3ea2bf16f5f/AppMeasurement.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54399,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:28.784,16:22:28.784,0,16:22:28.799,16:22:28.799,0,15,16:22:28.799,0,34560,0,12463,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2569,https://img.nbc.com/files/2026-03/snbb_reverse_rgb.png?impolicy=nbc_com&imwidth=1260&imdensity=1,img.nbc.com,/files/2026-03/snbb_reverse_rgb.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.755,16:22:28.755,0,16:22:28.794,16:22:28.798,4,43,16:22:28.798,0,27656,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2568,https://img.nbc.com/files/2025-05/nascar.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/nascar.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.721,16:22:28.721,0,16:22:28.755,16:22:28.755,0,33,16:22:28.755,0,2007,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2567,https://img.nbc.com/files/2025-05/golf-icon.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/golf-icon.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.704,16:22:28.704,0,16:22:28.785,16:22:28.785,0,81,16:22:28.785,0,972,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2566,https://img.nbc.com/files/2025-08/ncaa_mens_college_football.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-08/ncaa_mens_college_football.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.656,16:22:28.656,0,16:22:28.704,16:22:28.704,0,48,16:22:28.704,0,2302,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2565,"https://img.nbc.com/files/2026-03/emerson-2023-web-dynamiclead-desktop-1920x1080.png?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/emerson-2023-web-dynamiclead-desktop-1920x1080.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.654,16:22:28.654,0,16:22:28.694,16:22:28.721,27,67,16:22:28.721,0,59692,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2564,https://img.nbc.com/files/2025-06/all_sports_text_53x20_4x.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-06/all_sports_text_53x20_4x.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.614,16:22:28.614,0,16:22:28.655,16:22:28.655,0,42,16:22:28.655,0,928,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2563,https://img.nbc.com/files/2025-05/big_ten_conference.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/big_ten_conference.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.613,16:22:28.613,0,16:22:28.653,16:22:28.653,0,40,16:22:28.653,0,1316,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2562,https://img.nbc.com/files/2025-05/mlb_league.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/mlb_league.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.582,16:22:28.582,0,16:22:28.613,16:22:28.613,0,31,16:22:28.613,0,1362,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2561,https://img.nbc.com/files/2025-05/nfl_league.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/nfl_league.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.576,16:22:28.576,0,16:22:28.612,16:22:28.612,0,37,16:22:28.612,0,2244,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2560,https://www.nbc.com/generetic/generated/images/88e243732529f35b45f990fcb2125014.png,www.nbc.com,/generetic/generated/images/88e243732529f35b45f990fcb2125014.png,Completed,200,GET,image/png,Google Chrome,127.0.0.1:54378,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.540,16:22:28.540,0,16:22:28.577,16:22:28.577,0,38,16:22:28.577,0,5439,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2559,https://www.nbc.com/generetic/scripts/ads.js,www.nbc.com,/generetic/scripts/ads.js,Completed,200,GET,application/javascript,Google Chrome,127.0.0.1:54375,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.535,16:22:28.535,0,16:22:28.573,16:22:28.573,0,38,16:22:28.573,0,22,0,42,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2558,https://img.nbc.com/files/2025-05/wnba-league.png?impolicy=nbc_com&imwidth=45&imdensity=1,img.nbc.com,/files/2025-05/wnba-league.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.525,16:22:28.525,0,16:22:28.575,16:22:28.575,0,50,16:22:28.575,0,1554,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2557,https://www.nbc.com/generetic/generated/fonts/MontserratBold.woff2,www.nbc.com,/generetic/generated/fonts/MontserratBold.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54365,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.521,16:22:28.521,0,16:22:28.571,16:22:28.571,0,50,16:22:28.571,0,16064,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2556,https://www.nbc.com/generetic/generated/fonts/MontserratRegular.woff2,www.nbc.com,/generetic/generated/fonts/MontserratRegular.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54375,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.473,16:22:28.473,0,16:22:28.534,16:22:28.535,0,62,16:22:28.535,0,16136,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2555,https://www.nbc.com/generetic/generated/fonts/AcuminProSemiCondensed_700.woff2,www.nbc.com,/generetic/generated/fonts/AcuminProSemiCondensed_700.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54365,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.472,16:22:28.473,0,16:22:28.520,16:22:28.520,0,48,16:22:28.520,0,47016,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2554,https://www.nbc.com/generetic/generated/fonts/nbciconfont.woff2,www.nbc.com,/generetic/generated/fonts/nbciconfont.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54365,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.372,16:22:28.372,0,16:22:28.470,16:22:28.471,0,98,16:22:28.471,0,15956,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2553,https://assets.adobedtm.com/a2ef59fba8e9/9c7a708dbcb2/launch-678397df18e0.min.js,assets.adobedtm.com,/a2ef59fba8e9/9c7a708dbcb2/launch-678397df18e0.min.js,Completed,200,GET,application/x-javascript,Google Chrome,127.0.0.1:54399,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,16:22:28.282,16:22:28.718,436,16:22:28.735,16:22:28.777,43,495,16:22:28.777,0,330196,0,80730,,assets.adobedtm.com - 2600:1406:5400:3a6::1e80:443,assets.adobedtm.com,assets.adobedtm.com; cdn1-staging.adoberesources.net; assets.adoberesources.net; assets-staging.adoberesources.net; cdn1.adoberesources.net; commerce.adobedtm.com; magento-recs-sdk.adobe.net,
|
||||
2552,https://img.nbc.com/files/2025-09/pft-logo-color-900x302.png?impolicy=nbc_com&imwidth=1260&imdensity=1,img.nbc.com,/files/2025-09/pft-logo-color-900x302.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54398,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.282,16:22:32.672,4390,16:22:32.721,16:22:32.726,4,4444,16:22:32.726,0,36824,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2551,https://entitlement.auth.adobe.com/entitlement/v4/AccessEnabler.js,entitlement.auth.adobe.com,/entitlement/v4/AccessEnabler.js,Completed,200,GET,application/javascript,Google Chrome,127.0.0.1:54396,entitlement.auth.adobe.com - 23.42.82.205:443,16:22:28.282,16:22:28.732,451,16:22:28.748,16:22:28.779,31,498,16:22:28.779,0,131981,0,39090,,entitlement.auth.adobe.com - 23.42.82.205:443,ssl.adobe.com,ssl.adobe.com; signcsa.experienceleague.stage.adobe.com; wwwimages.stage2.adobe.com; www.macromedia.com; www.adobeidealab.com; cdn-stg-ffc.oobesaas.adobe.com; solutionpartners.stage2.adobe.com; www-stage01.acrobat.adobe.com; updates.adobeleanprint.com; download.stage.adobeprerelease.com; store2.stage2.adobe.com; live.adobeprimetime.com; wwwimages2.stage2.adobe.com; adobeprerelease.com; exchange.adobe.com; stage.status.adobe.com; experiencecloud.adobeexchange.com; www.stage.macromedia.com; experiencecloudstg2.adobeexchange.com; www.stage.adobeprerelease.com; www.qa.adobeprerelease.com; fpdownload.macromedia.com; stage.adobeprerelease.com; plan.adobe.com; dev.status.adobe.com; static.stage.adobeprimetime.com; preprod.status.adobe.com; live.stage.adobeprimetime.com; www.adobe.io; qe-ffc-static-cdn.oobesaas.adobe.com; download.adobeprerelease.com; trainingpartners.stage2.adobe.com; www.adobe-students.com; store1.stage2.adobe.com; auth.adobefpl.com; partners.stage2.adobe.com; media.stage.adobeprimetime.com; stage.adobe.io; cdn-qe-ffc.oobesaas.adobe.com; geo2.adobe.com; media.adobeprimetime.com; ffc-static-cdn.oobesaas.adobe.com; data.status.adobe.com; updates.stage.adobeleanprint.com; static.adobeprimetime.com; www.adobeprerelease.com; signcsa.experienceleague.adobe.com; hendrix360.qe.adobe.com; download.macromedia.com; csa.experienceleague.stage.adobe.com; app-cdn.stage2.adobe.com; stage.plan.adobe.com; www.stage2.adobe.com; stage.exchange.adobe.com; csa.experienceleague.adobe.com; entitlement.auth.adobe.com; download.qa.adobeprerelease.com; channelpartners.stage2.adobe.com; cdn-ffc.oobesaas.adobe.com; stg-ffc-static-cdn.oobesaas.adobe.com; technologypartners.stage2.adobe.com; www.stage.adobe-students.com,
|
||||
2550,https://www.nbc.com/generetic/manifest.json,www.nbc.com,/generetic/manifest.json,Completed,200,GET,application/json,Google Chrome,127.0.0.1:54397,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.281,16:22:28.894,612,16:22:28.996,16:22:28.996,0,715,16:22:28.996,0,205,0,170,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2553,https://img.nbc.com/files/2024-08/nbcsports_white_logo-425x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/2024-08/nbcsports_white_logo-425x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54395,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.279,16:22:32.670,4391,16:22:32.711,16:22:32.711,0,4432,16:22:32.711,0,12136,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2552,"https://img.nbc.com/files/2026-03/pickens-2026-web-dynamiclead-desktop-1920x1080.png?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/pickens-2026-web-dynamiclead-desktop-1920x1080.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54394,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.279,16:22:29.572,1293,16:22:29.632,16:22:29.681,49,1402,16:22:29.681,0,110928,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2551,https://img.nbc.com/files/2025-11/nba-coast-2-coast-logo.png?impolicy=nbc_com&imwidth=1260&imdensity=1,img.nbc.com,/files/2025-11/nba-coast-2-coast-logo.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54393,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.278,16:22:29.576,1298,16:22:29.664,16:22:29.666,2,1388,16:22:29.666,0,15998,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2544,https://www.nbc.com/generetic/generated/fonts/AcuminPro_600.woff2,www.nbc.com,/generetic/generated/fonts/AcuminPro_600.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54365,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.267,16:22:28.267,0,16:22:28.369,16:22:28.371,2,104,16:22:28.371,0,47300,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2543,https://www.nbc.com/generetic/generated/fonts/AcuminPro_400.woff2,www.nbc.com,/generetic/generated/fonts/AcuminPro_400.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54376,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.102,16:22:28.889,787,16:22:28.996,16:22:28.996,0,894,16:22:28.996,0,46872,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2542,https://www.nbc.com/generetic/generated/fonts/AcuminPro_200.woff2,www.nbc.com,/generetic/generated/fonts/AcuminPro_200.woff2,Completed,200,GET,font/woff2,Google Chrome,127.0.0.1:54375,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.102,16:22:28.345,242,16:22:28.471,16:22:28.471,0,368,16:22:28.471,0,46112,0,0,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2541,https://www.nbc.com/generetic/generated/generetic.47e9e8df145539694627.js,www.nbc.com,/generetic/generated/generetic.47e9e8df145539694627.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54378,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.102,16:22:28.349,247,16:22:28.470,16:22:28.540,69,437,16:22:28.540,0,1691217,0,281277,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2540,https://img.nbc.com/files/images/2023/1/05/NBC-Logo-Stacked-White-318x300.png?impolicy=nbc_com&imwidth=340&imdensity=1,img.nbc.com,/files/images/2023/1/05/NBC-Logo-Stacked-White-318x300.png,Completed,200,GET,image/webp,Google Chrome,127.0.0.1:54381,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.102,16:22:28.481,379,16:22:28.525,16:22:28.525,0,423,16:22:28.525,0,7104,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2539,"https://img.nbc.com/files/2026-03/nba25-26_wk24_tue_nyk-hou_2048x1152-sm-dynamic-lead-mobile-web.jpg?impolicy=nbc_com&im=Resize,width=1050;Crop,width=1050,height=472,gravity=NorthEast",img.nbc.com,/files/2026-03/nba25-26_wk24_tue_nyk-hou_2048x1152-sm-dynamic-lead-mobile-web.jpg,Completed,200,GET,image/jpeg,Google Chrome,127.0.0.1:54380,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,16:22:28.102,16:22:28.473,371,16:22:28.549,16:22:28.582,33,480,16:22:28.582,0,90154,0,0,,img.nbc.com - 2600:1406:4e00:19::1738:6d49:443,*.nbc.com,*.nbc.com; nbc.com; img.nbc.com,
|
||||
2538,https://www.nbc.com/generetic/generated/generetic.4de6b8d2e256b882e8f3.js,www.nbc.com,/generetic/generated/generetic.4de6b8d2e256b882e8f3.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54377,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.102,16:22:28.880,779,16:22:28.921,16:22:29.063,142,962,16:22:29.063,0,2877978,0,636470,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2543,https://www.nbc.com/generetic/generated/generetic.795899cb87676149668c.js,www.nbc.com,/generetic/generated/generetic.795899cb87676149668c.js,Completed,200,GET,application/javascript; charset=UTF-8,Google Chrome,127.0.0.1:54373,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.094,16:22:28.881,787,16:22:29.001,16:22:29.002,0,907,16:22:29.002,0,3227,0,1215,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2537,https://www.nbc.com/generetic/generated/generetic.aee12a093cbf23fc187f.css,www.nbc.com,/generetic/generated/generetic.aee12a093cbf23fc187f.css,Completed,200,GET,text/css; charset=UTF-8,Google Chrome,127.0.0.1:54365,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:28.089,16:22:28.089,0,16:22:28.254,16:22:28.267,13,177,16:22:28.267,0,841561,0,133459,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2532,https://www.nbc.com/sports,www.nbc.com,/sports,Completed,200,GET,text/html; charset=utf-8,Google Chrome,127.0.0.1:54365,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,16:22:26.024,16:22:26.124,99,16:22:28.071,16:22:28.089,18,2065,16:22:28.089,0,665910,0,71508,,www.nbc.com - 2600:1406:4e00:19::1738:6d4c:443,*.nbc.com,*.nbc.com; nbc.com; www.nbc.com,
|
||||
2531,https://nbcsports.app.link/stream_Phillies_NBCSPhilly,nbcsports.app.link,/stream_Phillies_NBCSPhilly,Redirect,307,GET,,Google Chrome,127.0.0.1:54357,nbcsports.app.link - 2600:9000:24da:5000:19:9934:6a80:93a1:443,16:22:25.951,16:22:25.951,0,16:22:26.012,16:22:26.012,0,61,16:22:26.012,0,0,0,0,,nbcsports.app.link - 2600:9000:24da:5000:19:9934:6a80:93a1:443,appipv4.link,appipv4.link; app.link; *.app.link; *.appipv4.link; nbcsports.app.link,
|
||||
2530,https://nbcsports.app.link/stream_Phillies_NBCSPhilly,nbcsports.app.link,/stream_Phillies_NBCSPhilly,Redirect,307,GET,text/html,Google Chrome,127.0.0.1:54357,nbcsports.app.link - 2600:9000:24da:5000:19:9934:6a80:93a1:443,16:22:25.737,16:22:25.901,163,16:22:25.948,16:22:25.948,0,211,16:22:25.948,0,54,0,0,,nbcsports.app.link - 2600:9000:24da:5000:19:9934:6a80:93a1:443,appipv4.link,appipv4.link; app.link; *.app.link; *.appipv4.link; nbcsports.app.link,
|
||||
2531,https://nbcu.demdex.net/event?d_dil_ver=9.5&_ts=1774999345725,nbcu.demdex.net,/event,Completed,200,POST,application/json;charset=utf-8,Google Chrome,127.0.0.1:54353,nbcu.demdex.net - 54.71.255.99:443,16:22:25.734,16:22:25.971,238,16:22:26.036,16:22:26.036,0,302,16:22:26.036,2340,2901,0,982,,nbcu.demdex.net - 54.71.255.99:443,*.demdex.com,*.demdex.com; tableau.aam.adobe.com; audiencemanager.adobe.com; *.demdex.net; demdex.net; *.audiencemanager.adobe.com; *.aam.adobe.com; tableau.demdex.com; demdex.com; audience-manager.adobe.com; aam.adobe.com; *.audience-manager.adobe.com; nbcu.demdex.net,
|
||||
2530,https://nbcume.sc.omtrdc.net/b/ss/nbcursndivisiontotal/1/JS-2.22.3-LEWM/s19040561078154?AQB=1&ndh=1&pf=1&t=31%2F2%2F2026%2016%3A22%3A25%202%20420&mid=48149800880344474781461921042733640603&aamlh=9&ce=UTF-8&pageName=streaming-faqs-phillies%3Astreaming-faqs-phillies%20landing%3Asection%20landing%20page&g=https%3A%2F%2Fwww.nbcsportsphiladelphia.com%2Fstreaming-faqs-phillies%2F&cc=USD&ch=streaming-faqs-phillies&server=nbcsportsphiladelphia&events=event1&c1=streaming-faqs-phillies&v1=D%3Dc1&c2=form%20landing&v3=D%3Dc3&v4=D%3Dc4&v5=streaming-faqs-phillies%3Astreaming-faqs-phillies%20landing%3Asection%20landing%20page&c6=https%3A%2F%2Fwww.nbcsportsphiladelphia.com%2Fstreaming-faqs-phillies%2F&v6=D%3Dc6&c8=rsn&v8=D%3Dc8&c9=RSN-PHI&v9=D%3Dc9&c10=NSPH&v10=D%3Dc10&v12=https%3A%2F%2Fwww.nbcsportsphiladelphia.com%2Fstreaming-faqs-phillies%2F&v13=D%3Dc13&v15=D%3Dc15&v16=D%3Dc16&v17=D%3Dc17&c19=false&c20=nbcursndivisiontotal&v21=D%3Dc21&v22=D%3Dc22&c23=form&v23=D%3Dc23&c25=d%3Dc18&v25=D%3Dc25&v28=D%3Dc28&v29=D%3Dc29&c32=no%20keyword&v32=D%3Dc32&c48=desktop&c49=Phillies%20Streaming%20%E2%80%93%20NBC%20Sports%20Philadelphia&v49=D%3Dc49&v54=nbcsportsphiladelphia&v55=streaming-faqs-phillies&v57=D%3Dc57&c59=feb%2006%2C%202026%2022%3A10%3A10%20pm%20est&v59=D%3Dc59&v68=D%3Dc68&c70=may%2010%2C%202023%2016%3A05%3A16%20pm%20edt&v70=D%3Dc70&c74=page%20not%20sponsored&v74=D%3Dc74&v75=D%3Dc75&v76=page%20not%20sponsored&v80=D%3Dc2&pe=lnk_e&pev1=https%3A%2F%2Fnbcsports.app.link%2Fstream_Phillies_NBCSPhilly&c.&a.&activitymap.&page=streaming-faqs-phillies%3Astreaming-faqs-phillies%20landing%3Asection%20landing%20page&link=right%20here®ion=post-219153&pageIDType=1&.activitymap&.a&.c&pid=streaming-faqs-phillies%3Astreaming-faqs-phillies%20landing%3Asection%20landing%20page&pidt=1&oid=https%3A%2F%2Fnbcsports.app.link%2Fstream_Phillies_NBCSPhilly&ot=A&s=1800x1169&c=30&j=1.6&v=N&k=Y&bw=1512&bh=862&mcorgid=A8AB776A5245B4220A490D44%40AdobeOrg&AQE=1,nbcume.sc.omtrdc.net,/b/ss/nbcursndivisiontotal/1/JS-2.22.3-LEWM/s19040561078154,Completed,200,POST,image/gif;charset=utf-8,Google Chrome,127.0.0.1:54352,nbcume.sc.omtrdc.net - 63.140.37.172:443,16:22:25.734,16:22:25.947,213,16:22:25.999,16:22:25.999,0,265,16:22:25.999,0,43,0,0,,nbcume.sc.omtrdc.net - 63.140.37.172:443,*.sc.omtrdc.net,*.sc.omtrdc.net; nbcume.sc.omtrdc.net,
|
||||
|
File diff suppressed because one or more lines are too long
6
justfile
6
justfile
|
|
@ -12,15 +12,9 @@ git-hosting:
|
|||
git-mirrors:
|
||||
./scripts/git-push-mirrors.sh
|
||||
|
||||
deploy-ecp-forge:
|
||||
./scripts/deploy-ecp-forge.sh
|
||||
|
||||
netboot-stage:
|
||||
./scripts/netboot-stage.sh
|
||||
|
||||
netboot-build-ipxe:
|
||||
./scripts/netboot-build-ipxe.sh
|
||||
|
||||
netboot-serve:
|
||||
./scripts/netboot-serve.sh
|
||||
|
||||
|
|
|
|||
|
|
@ -1,485 +0,0 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.every-channel.ethereum;
|
||||
|
||||
mkNetworkSubmodule =
|
||||
name: defaults:
|
||||
{ ... }:
|
||||
{
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to run the ${name} Ethereum execution and consensus pair.";
|
||||
};
|
||||
|
||||
rootDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.rootDir}/${name}";
|
||||
description = "Persistent root directory for the ${name} node state.";
|
||||
};
|
||||
|
||||
reth = {
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.rethHttpPort;
|
||||
description = "Local HTTP JSON-RPC port for the ${name} Reth node.";
|
||||
};
|
||||
|
||||
wsPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.rethWsPort;
|
||||
description = "Local WebSocket JSON-RPC port for the ${name} Reth node.";
|
||||
};
|
||||
|
||||
authPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.rethAuthPort;
|
||||
description = "Local Engine API port for the ${name} Reth node.";
|
||||
};
|
||||
|
||||
p2pPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.rethP2pPort;
|
||||
description = "RLPx/P2P TCP port for the ${name} Reth node.";
|
||||
};
|
||||
|
||||
discoveryPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.rethDiscoveryPort;
|
||||
description = "Discovery UDP port for the ${name} Reth node.";
|
||||
};
|
||||
|
||||
metricsPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.rethMetricsPort;
|
||||
description = "Prometheus port for the ${name} Reth node.";
|
||||
};
|
||||
};
|
||||
|
||||
lighthouse = {
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.lighthouseHttpPort;
|
||||
description = "Local Beacon API port for the ${name} Lighthouse node.";
|
||||
};
|
||||
|
||||
p2pPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.lighthouseP2pPort;
|
||||
description = "TCP libp2p port for the ${name} Lighthouse node.";
|
||||
};
|
||||
|
||||
discoveryPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.lighthouseDiscoveryPort;
|
||||
description = "UDP discovery port for the ${name} Lighthouse node.";
|
||||
};
|
||||
|
||||
quicPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.lighthouseQuicPort;
|
||||
description = "UDP QUIC port for the ${name} Lighthouse node.";
|
||||
};
|
||||
|
||||
metricsPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = defaults.lighthouseMetricsPort;
|
||||
description = "Prometheus port for the ${name} Lighthouse node.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networks = {
|
||||
mainnet = cfg.mainnet;
|
||||
sepolia = cfg.sepolia;
|
||||
};
|
||||
|
||||
enabledNetworks = lib.filterAttrs (_: networkCfg: networkCfg.enable) networks;
|
||||
|
||||
rethContainerName = network: "every-channel-ethereum-${network}-reth";
|
||||
lighthouseContainerName = network: "every-channel-ethereum-${network}-lighthouse";
|
||||
|
||||
networkDatasetLines = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList
|
||||
(network: networkCfg: ''
|
||||
ensure_dataset ${lib.escapeShellArg "${cfg.poolName}/${network}"}
|
||||
ensure_dataset ${lib.escapeShellArg "${cfg.poolName}/${network}/reth"}
|
||||
ensure_dataset ${lib.escapeShellArg "${cfg.poolName}/${network}/lighthouse"}
|
||||
ensure_jwt ${lib.escapeShellArg "${networkCfg.rootDir}/jwt.hex"}
|
||||
'')
|
||||
enabledNetworks
|
||||
);
|
||||
|
||||
mkNatArgs = lib.optionals (cfg.publicIp != null) [ "--nat" "extip:${cfg.publicIp}" ];
|
||||
mkEnrArgs = lib.optionals (cfg.publicIp != null) [ "--enr-address" cfg.publicIp ];
|
||||
|
||||
mkRethContainer =
|
||||
network: networkCfg: {
|
||||
image = cfg.images.reth;
|
||||
autoStart = true;
|
||||
extraOptions = [ "--network=host" ];
|
||||
volumes = [ "${networkCfg.rootDir}:/state" ];
|
||||
cmd =
|
||||
[
|
||||
"node"
|
||||
"--chain"
|
||||
network
|
||||
"--datadir"
|
||||
"/state/reth"
|
||||
"--full"
|
||||
"--http"
|
||||
"--http.addr"
|
||||
"127.0.0.1"
|
||||
"--http.port"
|
||||
(toString networkCfg.reth.httpPort)
|
||||
"--http.api"
|
||||
"eth,net,web3,rpc"
|
||||
"--ws"
|
||||
"--ws.addr"
|
||||
"127.0.0.1"
|
||||
"--ws.port"
|
||||
(toString networkCfg.reth.wsPort)
|
||||
"--ws.api"
|
||||
"eth,net,web3,rpc"
|
||||
"--authrpc.addr"
|
||||
"127.0.0.1"
|
||||
"--authrpc.port"
|
||||
(toString networkCfg.reth.authPort)
|
||||
"--authrpc.jwtsecret"
|
||||
"/state/jwt.hex"
|
||||
"--port"
|
||||
(toString networkCfg.reth.p2pPort)
|
||||
"--discovery.port"
|
||||
(toString networkCfg.reth.discoveryPort)
|
||||
"--metrics"
|
||||
"127.0.0.1:${toString networkCfg.reth.metricsPort}"
|
||||
"--log.stdout.format"
|
||||
"json"
|
||||
]
|
||||
++ mkNatArgs;
|
||||
};
|
||||
|
||||
mkLighthouseContainer =
|
||||
network: networkCfg: {
|
||||
image = cfg.images.lighthouse;
|
||||
autoStart = true;
|
||||
extraOptions = [ "--network=host" ];
|
||||
volumes = [ "${networkCfg.rootDir}:/state" ];
|
||||
entrypoint = "/usr/local/bin/lighthouse";
|
||||
cmd =
|
||||
[
|
||||
"beacon_node"
|
||||
"--network"
|
||||
network
|
||||
"--datadir"
|
||||
"/state/lighthouse"
|
||||
"--http"
|
||||
"--http-address"
|
||||
"127.0.0.1"
|
||||
"--http-port"
|
||||
(toString networkCfg.lighthouse.httpPort)
|
||||
"--execution-endpoint"
|
||||
"http://127.0.0.1:${toString networkCfg.reth.authPort}"
|
||||
"--execution-jwt"
|
||||
"/state/jwt.hex"
|
||||
"--allow-insecure-genesis-sync"
|
||||
"--port"
|
||||
(toString networkCfg.lighthouse.p2pPort)
|
||||
"--discovery-port"
|
||||
(toString networkCfg.lighthouse.discoveryPort)
|
||||
"--quic-port"
|
||||
(toString networkCfg.lighthouse.quicPort)
|
||||
"--metrics"
|
||||
"--metrics-address"
|
||||
"127.0.0.1"
|
||||
"--metrics-port"
|
||||
(toString networkCfg.lighthouse.metricsPort)
|
||||
]
|
||||
++ mkEnrArgs;
|
||||
};
|
||||
|
||||
caddyRootBody = ''
|
||||
every.channel ethereum nodes
|
||||
mainnet sync: /mainnet/sync
|
||||
mainnet finality: /mainnet/finality
|
||||
sepolia sync: /sepolia/sync
|
||||
sepolia finality: /sepolia/finality
|
||||
raw execution and beacon RPC remain local-only on ecp-forge for now.
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.services.every-channel.ethereum = {
|
||||
enable = lib.mkEnableOption "every.channel dual-network Ethereum full nodes";
|
||||
|
||||
poolName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "eth";
|
||||
description = "Dedicated ZFS pool name used for Ethereum node state.";
|
||||
};
|
||||
|
||||
poolDevice = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Block device used to create the dedicated Ethereum ZFS pool if it does not already exist.";
|
||||
};
|
||||
|
||||
rootDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/eth";
|
||||
description = "Mountpoint for the dedicated Ethereum ZFS pool.";
|
||||
};
|
||||
|
||||
publicIp = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Public IP to advertise in Ethereum P2P metadata.";
|
||||
};
|
||||
|
||||
publicHost = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional HTTPS host that publishes node sync and finality surfaces.";
|
||||
};
|
||||
|
||||
images = {
|
||||
reth = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ghcr.io/paradigmxyz/reth:v1.9.3";
|
||||
description = "Pinned Reth OCI image.";
|
||||
};
|
||||
|
||||
lighthouse = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "docker.io/sigp/lighthouse:v8.1.1";
|
||||
description = "Pinned Lighthouse OCI image.";
|
||||
};
|
||||
};
|
||||
|
||||
mainnet = lib.mkOption {
|
||||
type = lib.types.submodule (mkNetworkSubmodule "mainnet" {
|
||||
rethHttpPort = 8545;
|
||||
rethWsPort = 8546;
|
||||
rethAuthPort = 8551;
|
||||
rethP2pPort = 30303;
|
||||
rethDiscoveryPort = 30303;
|
||||
rethMetricsPort = 19001;
|
||||
lighthouseHttpPort = 5052;
|
||||
lighthouseP2pPort = 9000;
|
||||
lighthouseDiscoveryPort = 9000;
|
||||
lighthouseQuicPort = 9001;
|
||||
lighthouseMetricsPort = 5054;
|
||||
});
|
||||
default = { };
|
||||
description = "Mainnet Ethereum node configuration.";
|
||||
};
|
||||
|
||||
sepolia = lib.mkOption {
|
||||
type = lib.types.submodule (mkNetworkSubmodule "sepolia" {
|
||||
rethHttpPort = 18545;
|
||||
rethWsPort = 18546;
|
||||
rethAuthPort = 18551;
|
||||
rethP2pPort = 31303;
|
||||
rethDiscoveryPort = 31303;
|
||||
rethMetricsPort = 29001;
|
||||
lighthouseHttpPort = 15052;
|
||||
lighthouseP2pPort = 19000;
|
||||
lighthouseDiscoveryPort = 19000;
|
||||
lighthouseQuicPort = 19001;
|
||||
lighthouseMetricsPort = 15054;
|
||||
});
|
||||
default = { };
|
||||
description = "Sepolia Ethereum node configuration.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.poolDevice != null;
|
||||
message = "services.every-channel.ethereum.poolDevice must be set when the Ethereum node is enabled";
|
||||
}
|
||||
{
|
||||
assertion = enabledNetworks != { };
|
||||
message = "At least one Ethereum network must be enabled";
|
||||
}
|
||||
];
|
||||
|
||||
boot.zfs.extraPools = [ cfg.poolName ];
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts =
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList
|
||||
(_: networkCfg: [
|
||||
networkCfg.reth.p2pPort
|
||||
networkCfg.lighthouse.p2pPort
|
||||
])
|
||||
enabledNetworks
|
||||
);
|
||||
allowedUDPPorts =
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList
|
||||
(_: networkCfg: [
|
||||
networkCfg.reth.discoveryPort
|
||||
networkCfg.lighthouse.discoveryPort
|
||||
networkCfg.lighthouse.quicPort
|
||||
])
|
||||
enabledNetworks
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers =
|
||||
(lib.mapAttrs'
|
||||
(network: networkCfg:
|
||||
lib.nameValuePair (rethContainerName network) (mkRethContainer network networkCfg))
|
||||
enabledNetworks)
|
||||
// (lib.mapAttrs'
|
||||
(network: networkCfg:
|
||||
lib.nameValuePair (lighthouseContainerName network) (mkLighthouseContainer network networkCfg))
|
||||
enabledNetworks);
|
||||
|
||||
systemd.services =
|
||||
{
|
||||
every-channel-ethereum-storage = {
|
||||
description = "every.channel Ethereum NVMe ZFS pool and dataset bootstrap";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "local-fs.target" "zfs.target" ];
|
||||
wants = [ "zfs.target" ];
|
||||
before =
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList
|
||||
(network: _: [
|
||||
"podman-${rethContainerName network}.service"
|
||||
"podman-${lighthouseContainerName network}.service"
|
||||
])
|
||||
enabledNetworks
|
||||
);
|
||||
path = with pkgs; [
|
||||
coreutils
|
||||
openssl
|
||||
util-linux
|
||||
zfs
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
pool=${lib.escapeShellArg cfg.poolName}
|
||||
root_dir=${lib.escapeShellArg cfg.rootDir}
|
||||
device=${lib.escapeShellArg cfg.poolDevice}
|
||||
|
||||
ensure_dataset() {
|
||||
local dataset="$1"
|
||||
if ! zfs list -H "$dataset" >/dev/null 2>&1; then
|
||||
zfs create -p "$dataset"
|
||||
fi
|
||||
zfs set atime=off compression=lz4 xattr=sa "$dataset" >/dev/null
|
||||
}
|
||||
|
||||
ensure_jwt() {
|
||||
local path="$1"
|
||||
if [[ ! -s "$path" ]]; then
|
||||
umask 077
|
||||
openssl rand -hex 32 | tr -d '\n' > "$path"
|
||||
printf '\n' >> "$path"
|
||||
fi
|
||||
chmod 0400 "$path"
|
||||
}
|
||||
|
||||
if ! zpool list -H "$pool" >/dev/null 2>&1; then
|
||||
if [[ -z "$device" ]]; then
|
||||
echo "every-channel-ethereum-storage: missing poolDevice for pool $pool" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -b "$device" ]]; then
|
||||
echo "every-channel-ethereum-storage: device $device not present" >&2
|
||||
exit 1
|
||||
fi
|
||||
if blkid "$device" >/dev/null 2>&1; then
|
||||
echo "every-channel-ethereum-storage: device $device already has signatures; refusing to overwrite automatically" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zpool create -f \
|
||||
-o ashift=12 \
|
||||
-O mountpoint="$root_dir" \
|
||||
-O atime=off \
|
||||
-O compression=lz4 \
|
||||
-O xattr=sa \
|
||||
"$pool" "$device"
|
||||
else
|
||||
zfs set mountpoint="$root_dir" "$pool" >/dev/null
|
||||
fi
|
||||
|
||||
${networkDatasetLines}
|
||||
'';
|
||||
};
|
||||
}
|
||||
// (lib.mapAttrs'
|
||||
(network: networkCfg:
|
||||
lib.nameValuePair "podman-${rethContainerName network}" {
|
||||
after = [ "network-online.target" "every-channel-ethereum-storage.service" ];
|
||||
wants = [ "network-online.target" "every-channel-ethereum-storage.service" ];
|
||||
requires = [ "every-channel-ethereum-storage.service" ];
|
||||
unitConfig.RequiresMountsFor = [ networkCfg.rootDir ];
|
||||
})
|
||||
enabledNetworks)
|
||||
// (lib.mapAttrs'
|
||||
(network: networkCfg:
|
||||
lib.nameValuePair "podman-${lighthouseContainerName network}" {
|
||||
after = [
|
||||
"network-online.target"
|
||||
"every-channel-ethereum-storage.service"
|
||||
"podman-${rethContainerName network}.service"
|
||||
];
|
||||
wants = [
|
||||
"network-online.target"
|
||||
"every-channel-ethereum-storage.service"
|
||||
"podman-${rethContainerName network}.service"
|
||||
];
|
||||
requires = [
|
||||
"every-channel-ethereum-storage.service"
|
||||
"podman-${rethContainerName network}.service"
|
||||
];
|
||||
unitConfig.RequiresMountsFor = [ networkCfg.rootDir ];
|
||||
})
|
||||
enabledNetworks);
|
||||
|
||||
services.caddy.virtualHosts = lib.mkIf (cfg.publicHost != null) {
|
||||
"${cfg.publicHost}".extraConfig = ''
|
||||
encode zstd gzip
|
||||
|
||||
handle /mainnet/sync {
|
||||
uri replace /mainnet/sync /eth/v1/node/syncing
|
||||
reverse_proxy http://127.0.0.1:${toString cfg.mainnet.lighthouse.httpPort}
|
||||
}
|
||||
|
||||
handle /mainnet/finality {
|
||||
uri replace /mainnet/finality /eth/v1/beacon/states/head/finality_checkpoints
|
||||
reverse_proxy http://127.0.0.1:${toString cfg.mainnet.lighthouse.httpPort}
|
||||
}
|
||||
|
||||
handle /sepolia/sync {
|
||||
uri replace /sepolia/sync /eth/v1/node/syncing
|
||||
reverse_proxy http://127.0.0.1:${toString cfg.sepolia.lighthouse.httpPort}
|
||||
}
|
||||
|
||||
handle /sepolia/finality {
|
||||
uri replace /sepolia/finality /eth/v1/beacon/states/head/finality_checkpoints
|
||||
reverse_proxy http://127.0.0.1:${toString cfg.sepolia.lighthouse.httpPort}
|
||||
}
|
||||
|
||||
handle {
|
||||
header Content-Type text/plain
|
||||
respond "${caddyRootBody}" 200
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,377 +0,0 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.every-channel.ipxe-qemu;
|
||||
scriptsRoot = ../..;
|
||||
|
||||
firstDns = lib.head cfg.boot.userNet.dnsServers;
|
||||
qemuBin = "${cfg.package}/bin/qemu-system-x86_64";
|
||||
qemuImgBin = "${cfg.package}/bin/qemu-img";
|
||||
bootNetdevArg = lib.escapeShellArg "user,id=boot0,dns=${firstDns},hostname=${cfg.boot.userNet.hostname},tftp=${cfg.boot.userNet.tftpDir},bootfile=${cfg.boot.userNet.bootFilename}";
|
||||
bootDeviceArg = lib.escapeShellArg "${cfg.boot.userNet.model},netdev=boot0";
|
||||
lanNetdevArg = lib.escapeShellArg "tap,id=lan0,fd=3";
|
||||
lanDeviceArg = lib.escapeShellArg "${cfg.lan.model},netdev=lan0";
|
||||
driveArg = lib.escapeShellArg "if=virtio,format=qcow2,file=${cfg.disk.path}";
|
||||
effectiveChainUrl =
|
||||
if cfg.boot.chainUrl != null
|
||||
then cfg.boot.chainUrl
|
||||
else "http://10.0.2.2:${toString cfg.boot.http.port}/netboot.ipxe";
|
||||
|
||||
bootScript = pkgs.writeText "every-channel-qemu.ipxe" ''
|
||||
#!ipxe
|
||||
dhcp
|
||||
set dns ${firstDns}
|
||||
chain ${effectiveChainUrl} || shell
|
||||
'';
|
||||
bootAsset = pkgs.ipxe.override {
|
||||
embedScript = bootScript;
|
||||
additionalTargets = {
|
||||
"bin/undionly.kpxe" = null;
|
||||
};
|
||||
firmwareBinary = "undionly.kpxe";
|
||||
};
|
||||
|
||||
bootExtraArgs = lib.concatMapStringsSep "\n" (arg: " cmd+=(${lib.escapeShellArg arg})") cfg.extraArgs;
|
||||
|
||||
runner = pkgs.writeShellApplication {
|
||||
name = "every-channel-ipxe-qemu";
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.iproute2
|
||||
cfg.package
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
|
||||
state_dir=${lib.escapeShellArg cfg.stateDir}
|
||||
tftp_dir=${lib.escapeShellArg cfg.boot.userNet.tftpDir}
|
||||
boot_file=${lib.escapeShellArg cfg.boot.userNet.bootFilename}
|
||||
disk_path=${lib.escapeShellArg cfg.disk.path}
|
||||
disk_size=${lib.escapeShellArg cfg.disk.size}
|
||||
lan_ifname=${lib.escapeShellArg cfg.lan.macvtap.name}
|
||||
enable_kvm=${lib.escapeShellArg (lib.boolToString cfg.enableKvm)}
|
||||
enable_lan=${lib.escapeShellArg (lib.boolToString cfg.lan.enable)}
|
||||
serve_http=${lib.escapeShellArg (lib.boolToString cfg.boot.http.enable)}
|
||||
http_bind_ip=${lib.escapeShellArg cfg.boot.http.bindIp}
|
||||
http_port=${toString cfg.boot.http.port}
|
||||
http_root=${lib.escapeShellArg cfg.boot.http.root}
|
||||
|
||||
cleanup() {
|
||||
if [[ "$enable_lan" == true ]]; then
|
||||
ip link del "$lan_ifname" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
terminate() {
|
||||
if [[ -n "''${http_pid:-}" ]]; then
|
||||
kill "$http_pid" 2>/dev/null || true
|
||||
wait "$http_pid" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -n "''${qemu_pid:-}" ]]; then
|
||||
kill "$qemu_pid" 2>/dev/null || true
|
||||
wait "$qemu_pid" 2>/dev/null || true
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
trap terminate TERM INT
|
||||
|
||||
install -d -m 0755 "$state_dir" "$tftp_dir" "$(dirname "$disk_path")"
|
||||
install -m 0644 ${bootAsset}/undionly.kpxe "$tftp_dir/$boot_file"
|
||||
|
||||
if [[ "$serve_http" == true ]]; then
|
||||
if [[ ! -d "$http_root" ]]; then
|
||||
echo "error: boot HTTP root not found: $http_root" >&2
|
||||
exit 1
|
||||
fi
|
||||
${pkgs.python3}/bin/python3 ${scriptsRoot}/scripts/netboot-http-server.py \
|
||||
--bind-ip "$http_bind_ip" \
|
||||
--port "$http_port" \
|
||||
--root "$http_root" &
|
||||
http_pid=$!
|
||||
fi
|
||||
|
||||
if [[ ! -f "$disk_path" ]]; then
|
||||
${qemuImgBin} create -f qcow2 "$disk_path" "$disk_size" >/dev/null
|
||||
fi
|
||||
|
||||
cmd=(
|
||||
${lib.escapeShellArg qemuBin}
|
||||
-name ${lib.escapeShellArg cfg.name}
|
||||
-machine ${lib.escapeShellArg cfg.machine}
|
||||
-cpu ${lib.escapeShellArg cfg.cpu}
|
||||
-smp ${toString cfg.vcpus}
|
||||
-m ${toString cfg.memoryMiB}
|
||||
-nographic
|
||||
-boot ${lib.escapeShellArg cfg.boot.order}
|
||||
-serial ${lib.escapeShellArg cfg.serial}
|
||||
-device virtio-rng-pci
|
||||
)
|
||||
|
||||
if [[ "$enable_kvm" == true ]]; then
|
||||
cmd+=(-enable-kvm)
|
||||
fi
|
||||
|
||||
cmd+=(
|
||||
-netdev ${bootNetdevArg}
|
||||
-device ${bootDeviceArg}
|
||||
)
|
||||
|
||||
if [[ "$enable_lan" == true ]]; then
|
||||
ip link del "$lan_ifname" 2>/dev/null || true
|
||||
ip link add link ${lib.escapeShellArg cfg.lan.macvtap.interface} name "$lan_ifname" type macvtap mode ${lib.escapeShellArg cfg.lan.macvtap.mode}
|
||||
ip link set "$lan_ifname" up
|
||||
tap_index="$(< /sys/class/net/$lan_ifname/ifindex)"
|
||||
exec 3<>"/dev/tap''${tap_index}"
|
||||
cmd+=(
|
||||
-netdev ${lanNetdevArg}
|
||||
-device ${lanDeviceArg}
|
||||
)
|
||||
fi
|
||||
|
||||
cmd+=(
|
||||
-drive ${driveArg}
|
||||
)
|
||||
${bootExtraArgs}
|
||||
|
||||
"''${cmd[@]}" &
|
||||
qemu_pid=$!
|
||||
wait "$qemu_pid"
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.every-channel.ipxe-qemu = {
|
||||
enable = lib.mkEnableOption "every.channel iPXE/QEMU boot VM";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.qemu_kvm;
|
||||
description = "QEMU package providing qemu-system-x86_64 and qemu-img.";
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "every-channel-ipxe";
|
||||
description = "QEMU guest name.";
|
||||
};
|
||||
|
||||
machine = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "q35,accel=kvm";
|
||||
description = "QEMU machine string.";
|
||||
};
|
||||
|
||||
cpu = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "host";
|
||||
description = "QEMU CPU model.";
|
||||
};
|
||||
|
||||
enableKvm = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Use KVM acceleration when available.";
|
||||
};
|
||||
|
||||
vcpus = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 2;
|
||||
description = "Virtual CPU count.";
|
||||
};
|
||||
|
||||
memoryMiB = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 4096;
|
||||
description = "Guest memory in MiB.";
|
||||
};
|
||||
|
||||
serial = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "mon:stdio";
|
||||
description = "Serial/monitor wiring passed to QEMU.";
|
||||
};
|
||||
|
||||
stateDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/ipxe-qemu";
|
||||
description = "Persistent state directory for qcow2 disk and boot assets.";
|
||||
};
|
||||
|
||||
disk = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.stateDir}/disk.qcow2";
|
||||
description = "Persistent qcow2 disk path.";
|
||||
};
|
||||
|
||||
size = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "16G";
|
||||
description = "Size used when initializing the qcow2 disk.";
|
||||
};
|
||||
};
|
||||
|
||||
boot = {
|
||||
order = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "order=n";
|
||||
description = "QEMU boot order, defaulting to network first.";
|
||||
};
|
||||
|
||||
chainUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional explicit iPXE chain target fetched by the guest; defaults to the local boot.http server.";
|
||||
};
|
||||
|
||||
http = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Serve a host-local HTTP root for the guest netboot flow.";
|
||||
};
|
||||
|
||||
bindIp = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Bind address used for the host-local boot HTTP server.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = "Port used for the host-local boot HTTP server.";
|
||||
};
|
||||
|
||||
root = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Directory containing kernel/initrd/netboot.ipxe served to the guest.";
|
||||
};
|
||||
};
|
||||
|
||||
userNet = {
|
||||
hostname = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "every-channel-ipxe";
|
||||
description = "Hostname advertised on the user-mode boot network.";
|
||||
};
|
||||
|
||||
dnsServers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "1.1.1.1" ];
|
||||
description = "DNS servers used by the user-mode boot NIC; the first entry is applied.";
|
||||
};
|
||||
|
||||
tftpDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.stateDir}/tftp";
|
||||
description = "TFTP directory used by the user-mode boot NIC.";
|
||||
};
|
||||
|
||||
bootFilename = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "undionly.kpxe";
|
||||
description = "Boot asset filename exposed on the user-mode boot NIC.";
|
||||
};
|
||||
|
||||
model = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "e1000";
|
||||
description = "NIC model used for the boot network.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
lan = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Attach a second NIC to a real LAN using macvtap.";
|
||||
};
|
||||
|
||||
model = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "virtio-net-pci";
|
||||
description = "NIC model used for the LAN attachment.";
|
||||
};
|
||||
|
||||
macvtap = {
|
||||
interface = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Host interface used as the lower device for macvtap.";
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ecqemu0";
|
||||
description = "macvtap link name created while the guest runs.";
|
||||
};
|
||||
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"private"
|
||||
"vepa"
|
||||
"bridge"
|
||||
"passthru"
|
||||
"source"
|
||||
];
|
||||
default = "bridge";
|
||||
description = "macvtap mode used for the LAN attachment.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional arguments appended to the QEMU command.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.boot.userNet.dnsServers != [ ];
|
||||
message = "services.every-channel.ipxe-qemu.boot.userNet.dnsServers must not be empty";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.lan.enable) || (cfg.lan.macvtap.interface != null);
|
||||
message = "services.every-channel.ipxe-qemu.lan.macvtap.interface must be set when lan.enable is true";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.boot.http.enable) || (cfg.boot.http.root != null);
|
||||
message = "services.every-channel.ipxe-qemu.boot.http.root must be set when boot.http.enable is true";
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [ runner ];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.stateDir} 0755 root root -"
|
||||
"d ${cfg.boot.userNet.tftpDir} 0755 root root -"
|
||||
];
|
||||
|
||||
systemd.services.every-channel-ipxe-qemu = {
|
||||
description = "every.channel iPXE QEMU VM";
|
||||
after = [ "local-fs.target" "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [
|
||||
pkgs.coreutils
|
||||
pkgs.iproute2
|
||||
cfg.package
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${runner}/bin/every-channel-ipxe-qemu";
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,338 +0,0 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.every-channel.netboot;
|
||||
scriptsRoot = ../..;
|
||||
|
||||
boolString = v: if v then "true" else "false";
|
||||
|
||||
mkExport = name: value: "export ${name}=${lib.escapeShellArg value}";
|
||||
|
||||
optionalExport = name: value:
|
||||
if value == null then "" else mkExport name value;
|
||||
|
||||
optionalFileExport = name: path:
|
||||
if path == null then ""
|
||||
else ''
|
||||
if [[ ! -r ${lib.escapeShellArg path} ]]; then
|
||||
echo "error: required file is not readable: ${path}" >&2
|
||||
exit 2
|
||||
fi
|
||||
export ${name}="$(tr -d '\\r\\n' < ${lib.escapeShellArg path})"
|
||||
'';
|
||||
|
||||
stageToolchain = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
curl
|
||||
gawk
|
||||
gnugrep
|
||||
gnused
|
||||
gnutar
|
||||
gzip
|
||||
python3
|
||||
];
|
||||
|
||||
ipxeToolchain = with pkgs; [
|
||||
git
|
||||
gnumake
|
||||
gcc
|
||||
binutils
|
||||
perl
|
||||
mtools
|
||||
docker-client
|
||||
];
|
||||
in
|
||||
{
|
||||
options.services.every-channel.netboot = {
|
||||
enable = lib.mkEnableOption "every.channel persistent netboot stage/serve services";
|
||||
|
||||
listenIP = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "10.20.30.2";
|
||||
description = "IP address bound for TFTP/HTTP on the provisioning VLAN.";
|
||||
};
|
||||
|
||||
interface = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "enp3s0";
|
||||
description = "Network interface name on the provisioning VLAN.";
|
||||
};
|
||||
|
||||
hostname = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "boot.every.channel";
|
||||
description = "Boot server hostname advertised to DHCP/iPXE clients.";
|
||||
};
|
||||
|
||||
rootDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel-netboot";
|
||||
description = "Persistent root directory containing staged HTTP and TFTP artifacts.";
|
||||
};
|
||||
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = "HTTP port used for kernel/initrd/netboot script serving.";
|
||||
};
|
||||
|
||||
tftpBootFilename = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ec-ipxe.efi";
|
||||
description = "Filename served via TFTP (DHCP option 67 in UniFi-only mode).";
|
||||
};
|
||||
|
||||
chainTokenFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/run/agenix/every-channel-netboot-chain-token";
|
||||
description = "Optional file containing netboot chain token passed to stage/serve scripts.";
|
||||
};
|
||||
|
||||
httpAllowedCIDRs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "10.20.30.0/24" ];
|
||||
description = "Optional CIDR allowlist for HTTP artifact serving.";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Open firewall ports for TFTP/HTTP (and ProxyDHCP ports when enabled).";
|
||||
};
|
||||
|
||||
stageOnBoot = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Start the stage oneshot service during boot before serving.";
|
||||
};
|
||||
|
||||
release = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://git.every.channel";
|
||||
description = "Forge host used to fetch netboot release assets.";
|
||||
};
|
||||
|
||||
repo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "every-channel/every.channel";
|
||||
description = "Forge repository containing netboot release assets.";
|
||||
};
|
||||
|
||||
tag = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional release tag to stage; defaults to latest release.";
|
||||
};
|
||||
|
||||
localTarball = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional local netboot tarball path; bypasses release API download when set.";
|
||||
};
|
||||
|
||||
tokenFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional file containing Forge API token for private release access.";
|
||||
};
|
||||
|
||||
verifyChecksums = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Verify staged release tarball using SHA256SUMS.txt when available.";
|
||||
};
|
||||
};
|
||||
|
||||
ipxe = {
|
||||
buildEmbedded = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Build embedded iPXE (ec-ipxe.efi) before staging artifacts.";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional prebuilt iPXE EFI binary path; used when buildEmbedded is false.";
|
||||
};
|
||||
|
||||
allowRemoteDownload = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Allow remote iPXE URL download during staging. Disabled by default.";
|
||||
};
|
||||
|
||||
sha256 = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional SHA256 digest expected for selected iPXE EFI binary.";
|
||||
};
|
||||
|
||||
repo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://github.com/ipxe/ipxe.git";
|
||||
description = "iPXE source repository used for embedded EFI builds.";
|
||||
};
|
||||
|
||||
ref = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional git ref/commit for iPXE build reproducibility.";
|
||||
};
|
||||
|
||||
useDocker = lib.mkOption {
|
||||
type = lib.types.enum [ "auto" "true" "false" ];
|
||||
default = "auto";
|
||||
description = "Container fallback mode for iPXE builds (auto/true/false).";
|
||||
};
|
||||
|
||||
dockerImage = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ubuntu:24.04";
|
||||
description = "Docker image used when iPXE build runs in container mode.";
|
||||
};
|
||||
|
||||
dockerPlatform = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "linux/amd64";
|
||||
description = "Docker platform used for containerized iPXE build.";
|
||||
};
|
||||
};
|
||||
|
||||
proxyDhcp = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable dnsmasq ProxyDHCP chainloading mode.";
|
||||
};
|
||||
|
||||
subnet = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.20.30.0/24";
|
||||
description = "ProxyDHCP subnet (required when proxyDhcp.enable is true).";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.proxyDhcp.enable -> cfg.proxyDhcp.subnet != null;
|
||||
message = "services.every-channel.netboot.proxyDhcp.subnet must be set when proxyDhcp.enable is true";
|
||||
}
|
||||
{
|
||||
assertion = cfg.ipxe.buildEmbedded || cfg.ipxe.path != null || cfg.ipxe.allowRemoteDownload;
|
||||
message = "Set ipxe.buildEmbedded=true, or provide ipxe.path, or allow ipxe.allowRemoteDownload=true";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.rootDir} 0750 root root -"
|
||||
"d ${cfg.rootDir}/http 0750 root root -"
|
||||
"d ${cfg.rootDir}/tftp 0750 root root -"
|
||||
];
|
||||
|
||||
systemd.services.every-channel-netboot-ipxe = lib.mkIf cfg.ipxe.buildEmbedded {
|
||||
description = "every.channel netboot embedded iPXE build";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = lib.mkIf cfg.stageOnBoot [ "multi-user.target" ];
|
||||
path = stageToolchain ++ ipxeToolchain;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ROOT" cfg.rootDir}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HOSTNAME" cfg.hostname}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_PORT" (toString cfg.httpPort)}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_FILENAME" cfg.tftpBootFilename}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_REPO" cfg.ipxe.repo}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_IPXE_REF" cfg.ipxe.ref}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_USE_DOCKER" cfg.ipxe.useDocker}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_DOCKER_IMAGE" cfg.ipxe.dockerImage}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_DOCKER_PLATFORM" cfg.ipxe.dockerPlatform}
|
||||
${optionalFileExport "EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN" cfg.chainTokenFile}
|
||||
|
||||
${scriptsRoot}/scripts/netboot-build-ipxe.sh
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.every-channel-netboot-stage = {
|
||||
description = "every.channel netboot artifact stage";
|
||||
requires = lib.optionals cfg.ipxe.buildEmbedded [ "every-channel-netboot-ipxe.service" ];
|
||||
after = [ "network-online.target" ] ++ lib.optionals cfg.ipxe.buildEmbedded [ "every-channel-netboot-ipxe.service" ];
|
||||
wants = [ "network-online.target" ] ++ lib.optionals cfg.ipxe.buildEmbedded [ "every-channel-netboot-ipxe.service" ];
|
||||
wantedBy = lib.mkIf cfg.stageOnBoot [ "multi-user.target" ];
|
||||
path = stageToolchain;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ROOT" cfg.rootDir}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HOSTNAME" cfg.hostname}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_PORT" (toString cfg.httpPort)}
|
||||
${mkExport "EVERY_CHANNEL_FORGE_HOST" cfg.release.host}
|
||||
${mkExport "EVERY_CHANNEL_FORGE_REPO" cfg.release.repo}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_RELEASE_TAG" cfg.release.tag}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_TARBALL" cfg.release.localTarball}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_VERIFY_RELEASE_CHECKSUMS" (boolString cfg.release.verifyChecksums)}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ALLOW_REMOTE_IPXE" (boolString cfg.ipxe.allowRemoteDownload)}
|
||||
${mkExport "EVERY_CHANNEL_IPXE_EFI_FILENAME" cfg.tftpBootFilename}
|
||||
${optionalExport "EVERY_CHANNEL_IPXE_EFI_SHA256" cfg.ipxe.sha256}
|
||||
${optionalFileExport "EVERY_CHANNEL_FORGE_TOKEN" cfg.release.tokenFile}
|
||||
${optionalFileExport "EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN" cfg.chainTokenFile}
|
||||
|
||||
${lib.optionalString cfg.ipxe.buildEmbedded (mkExport "EVERY_CHANNEL_IPXE_EFI_PATH" "${cfg.rootDir}/tftp/${cfg.tftpBootFilename}")}
|
||||
${lib.optionalString (!cfg.ipxe.buildEmbedded && cfg.ipxe.path != null) (mkExport "EVERY_CHANNEL_IPXE_EFI_PATH" cfg.ipxe.path)}
|
||||
|
||||
${scriptsRoot}/scripts/netboot-stage.sh
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.every-channel-netboot = {
|
||||
description = "every.channel netboot HTTP + TFTP service";
|
||||
requires = [ "every-channel-netboot-stage.service" ];
|
||||
after = [ "network-online.target" "every-channel-netboot-stage.service" ];
|
||||
wants = [ "network-online.target" "every-channel-netboot-stage.service" ];
|
||||
wantedBy = lib.mkIf cfg.stageOnBoot [ "multi-user.target" ];
|
||||
path = stageToolchain ++ [ pkgs.dnsmasq ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ROOT" cfg.rootDir}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_LISTEN_IP" cfg.listenIP}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_INTERFACE" cfg.interface}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HOSTNAME" cfg.hostname}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_PORT" (toString cfg.httpPort)}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_PROXY_DHCP" (boolString cfg.proxyDhcp.enable)}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_PROXY_SUBNET" cfg.proxyDhcp.subnet}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_TFTP_BOOT_FILENAME" cfg.tftpBootFilename}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS" (lib.concatStringsSep "," cfg.httpAllowedCIDRs)}
|
||||
${optionalFileExport "EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN" cfg.chainTokenFile}
|
||||
|
||||
${scriptsRoot}/scripts/netboot-serve.sh
|
||||
'';
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.httpPort ];
|
||||
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall (
|
||||
[ 69 ] ++ lib.optionals cfg.proxyDhcp.enable [ 67 4011 ]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
@ -266,124 +266,6 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
nbc = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable Linux Chrome + virtual-display support for NBC browser-backed broadcasts.";
|
||||
};
|
||||
|
||||
chromeBinary = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/run/current-system/sw/bin/google-chrome-stable";
|
||||
description = "Chrome binary used by `ec-node nbc-bootstrap` and `ec-node nbc-wt-publish`.";
|
||||
};
|
||||
|
||||
profileDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/nbc-profile";
|
||||
description = "Persistent Chrome profile directory used for NBC / Adobe auth sessions.";
|
||||
};
|
||||
|
||||
authScreenshotDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/nbc-auth";
|
||||
description = "Directory for operator-facing screenshots when bootstrap hits an interactive auth page.";
|
||||
};
|
||||
|
||||
display = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = ":99";
|
||||
description = "DISPLAY used by the NBC virtual display session.";
|
||||
};
|
||||
|
||||
screen = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "1920x1080x24";
|
||||
description = "Xvfb screen geometry for the NBC virtual display.";
|
||||
};
|
||||
|
||||
noSandbox = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Pass `EVERY_CHANNEL_NBC_NO_SANDBOX=1` for Chrome worker sessions.";
|
||||
};
|
||||
|
||||
mvpdProvider = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "Verizon Fios";
|
||||
description = "MVPD provider name used when the NBC worker must choose a TV provider.";
|
||||
};
|
||||
|
||||
mvpdUsernameFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "Optional root-managed file containing the MVPD username for unattended NBC login.";
|
||||
};
|
||||
|
||||
mvpdPasswordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "Optional root-managed file containing the MVPD password for unattended NBC login.";
|
||||
};
|
||||
|
||||
isolateWithUserNetns = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Launch NBC browser-backed workers inside a rootless user+network namespace backed by
|
||||
slirp4netns. This keeps the Chrome / ec-node process tree in its own network context
|
||||
while still using the host's active upstream route.
|
||||
'';
|
||||
};
|
||||
|
||||
requireMullvad = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Refuse to start NBC browser-backed workers until `mullvad status` reports a connected
|
||||
tunnel. This assumes the host Mullvad daemon is already logged in and connected.
|
||||
'';
|
||||
};
|
||||
|
||||
mullvadWaitSeconds = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 90;
|
||||
description = "Maximum time to wait for Mullvad connectivity before failing an NBC worker start.";
|
||||
};
|
||||
|
||||
mullvadLocation = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "USA";
|
||||
description = ''
|
||||
Optional case-insensitive substring that must appear in `mullvad status` before an NBC
|
||||
worker starts. Use this to pin workers to a country or city family without committing the
|
||||
operational login material itself.
|
||||
'';
|
||||
};
|
||||
|
||||
vnc = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Expose the NBC virtual display over VNC so auth can be completed remotely when needed.";
|
||||
};
|
||||
|
||||
listen = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "x11vnc listen address for the NBC virtual display.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5900;
|
||||
description = "x11vnc TCP port for the NBC virtual display.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
broadcasts = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
|
|
@ -401,15 +283,10 @@ in
|
|||
default = null;
|
||||
description = "Optional explicit ffmpeg input URL/file. When set, HDHomeRun settings are ignored for this broadcast.";
|
||||
};
|
||||
nbcUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional NBC watch/live URL for a browser-backed relay publish worker.";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
description = "List of broadcasts (HDHomeRun, explicit input, or NBC browser-backed URL) to publish.";
|
||||
description = "List of broadcasts (name + channel, or explicit input) to publish.";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -422,7 +299,7 @@ in
|
|||
{
|
||||
assertion =
|
||||
let
|
||||
needsHdhr = builtins.any (b: b.input == null && b.nbcUrl == null) cfg.broadcasts;
|
||||
needsHdhr = builtins.any (b: b.input == null) cfg.broadcasts;
|
||||
in
|
||||
(!needsHdhr) || (cfg.hdhomerun.host != null) || (cfg.hdhomerun.deviceId != null);
|
||||
message = "Set services.every-channel.ec-node.hdhomerun.host or .deviceId (required when any broadcast omits `input`)";
|
||||
|
|
@ -432,20 +309,8 @@ in
|
|||
message = "hdhomerun.autoDiscover only applies when hdhomerun.host is unset";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
builtins.all
|
||||
(b:
|
||||
(lib.length (lib.filter (value: value != null) [ b.input b.channel b.nbcUrl ])) == 1)
|
||||
cfg.broadcasts;
|
||||
message = "Each broadcast must set exactly one of `input`, `channel`, or `nbcUrl`";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
let
|
||||
hasNbcBroadcast = builtins.any (b: b.nbcUrl != null) cfg.broadcasts;
|
||||
in
|
||||
(!hasNbcBroadcast) || cfg.nbc.enable;
|
||||
message = "Set services.every-channel.ec-node.nbc.enable = true before configuring `broadcasts.*.nbcUrl`";
|
||||
assertion = builtins.all (b: (b.input != null) || (b.channel != null)) cfg.broadcasts;
|
||||
message = "Each broadcast must set either `input` or `channel`";
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -453,30 +318,16 @@ in
|
|||
[
|
||||
"d /run/every-channel 1777 root root - -"
|
||||
]
|
||||
++ lib.optionals cfg.nbc.enable [
|
||||
"d /var/lib/every-channel 0750 every-channel every-channel - -"
|
||||
"d ${cfg.nbc.profileDir} 0750 every-channel every-channel - -"
|
||||
"d ${cfg.nbc.authScreenshotDir} 0750 every-channel every-channel - -"
|
||||
]
|
||||
++ lib.optionals cfg.archive.enable [
|
||||
"d ${cfg.archive.outputDir} 0750 root root - -"
|
||||
"d ${cfg.archive.manifestDir} 0750 root root - -"
|
||||
];
|
||||
|
||||
users.groups.every-channel = lib.mkIf cfg.nbc.enable { };
|
||||
users.users.every-channel = lib.mkIf cfg.nbc.enable {
|
||||
isSystemUser = true;
|
||||
group = "every-channel";
|
||||
home = "/var/lib/every-channel";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
systemd.services =
|
||||
lib.listToAttrs (map
|
||||
(b:
|
||||
let
|
||||
unit = "every-channel-wt-publish-${sanitizeUnitName b.name}";
|
||||
isNbc = b.nbcUrl != null;
|
||||
runner = pkgs.writeShellApplication {
|
||||
name = unit;
|
||||
runtimeInputs =
|
||||
|
|
@ -489,11 +340,6 @@ in
|
|||
pkgs.iproute2
|
||||
cfg.package
|
||||
]
|
||||
++ lib.optionals (isNbc && cfg.nbc.requireMullvad) [ pkgs.mullvad-vpn ]
|
||||
++ lib.optionals (isNbc && cfg.nbc.isolateWithUserNetns) [
|
||||
pkgs.slirp4netns
|
||||
pkgs.util-linux
|
||||
]
|
||||
++ lib.optionals cfg.hdhomerun.autoDiscover [ pkgs.jq cfg.discoveryPackage ];
|
||||
text =
|
||||
let
|
||||
|
|
@ -506,90 +352,22 @@ in
|
|||
"cmd+=(${lib.concatStringsSep " " (map lib.escapeShellArg cfg.extraArgs)})";
|
||||
explicitInputStr = if b.input == null then "" else b.input;
|
||||
channelStr = if b.channel == null then "" else b.channel;
|
||||
nbcUrlStr = if b.nbcUrl == null then "" else b.nbcUrl;
|
||||
controlEndpointOutPath = "/run/every-channel/control-peer-${sanitizeUnitName b.name}.json";
|
||||
controlDiscoveryStr = if cfg.control.discovery == null then "" else cfg.control.discovery;
|
||||
controlIrohSecretStr = if cfg.control.irohSecret == null then "" else cfg.control.irohSecret;
|
||||
controlGossipPeerLines = lib.concatMapStrings (peer: "cmd+=(--gossip-peer ${lib.escapeShellArg peer})\n") cfg.control.gossipPeers;
|
||||
nbcMullvadLocationStr = if cfg.nbc.mullvadLocation == null then "" else cfg.nbc.mullvadLocation;
|
||||
in
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
wait_for_mullvad() {
|
||||
local wait_seconds status expected
|
||||
wait_seconds=${toString cfg.nbc.mullvadWaitSeconds}
|
||||
expected=${lib.escapeShellArg nbcMullvadLocationStr}
|
||||
for _ in $(seq 1 "$wait_seconds"); do
|
||||
status="$(mullvad status 2>/dev/null || true)"
|
||||
if [[ "$status" == Connected* ]]; then
|
||||
if [[ -z "$expected" ]] || printf '%s\n' "$status" | grep -Fqi -- "$expected"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "ec-node: Mullvad was not connected${lib.optionalString (cfg.nbc.mullvadLocation != null) " to the expected location"} within ${toString cfg.nbc.mullvadWaitSeconds}s" >&2
|
||||
mullvad status >&2 || true
|
||||
return 1
|
||||
}
|
||||
|
||||
run_in_user_netns() {
|
||||
local tmpdir pid_file ready_fifo ns_pid slirp_pid status
|
||||
tmpdir="$(mktemp -d /tmp/${unit}.usernet.XXXXXX)"
|
||||
pid_file="$tmpdir/pid"
|
||||
ready_fifo="$tmpdir/ready"
|
||||
mkfifo "$ready_fifo"
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
unshare --user --map-root-user --net ${pkgs.bash}/bin/bash -lc '
|
||||
set -euo pipefail
|
||||
ip link set lo up
|
||||
echo $$ > "$1"
|
||||
read -r _ < "$2"
|
||||
shift 2
|
||||
exec "$@"
|
||||
' bash "$pid_file" "$ready_fifo" "''${cmd[@]}" &
|
||||
ns_pid=$!
|
||||
|
||||
for _ in $(seq 1 50); do
|
||||
[[ -s "$pid_file" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
if [[ ! -s "$pid_file" ]]; then
|
||||
echo "ec-node: timed out waiting for NBC user-netns PID" >&2
|
||||
kill "$ns_pid" 2>/dev/null || true
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
slirp4netns --configure --mtu=1500 "$(cat "$pid_file")" tap0 >"$tmpdir/slirp.log" 2>&1 &
|
||||
slirp_pid=$!
|
||||
sleep 1
|
||||
printf 'go\n' > "$ready_fifo"
|
||||
|
||||
set +e
|
||||
wait "$ns_pid"
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
kill "$slirp_pid" 2>/dev/null || true
|
||||
wait "$slirp_pid" 2>/dev/null || true
|
||||
rm -rf "$tmpdir"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
nbc_url=${lib.escapeShellArg nbcUrlStr}
|
||||
input=""
|
||||
if [[ -z "$nbc_url" ]]; then
|
||||
explicit_input=${lib.escapeShellArg explicitInputStr}
|
||||
if [[ -n "$explicit_input" ]]; then
|
||||
input="$explicit_input"
|
||||
else
|
||||
ch=${lib.escapeShellArg channelStr}
|
||||
if [[ -z "$ch" ]]; then
|
||||
echo "ec-node: broadcast missing input, channel, and nbcUrl" >&2
|
||||
echo "ec-node: broadcast missing both input and channel" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
|
@ -623,8 +401,10 @@ in
|
|||
}
|
||||
|
||||
if ${lib.boolToString cfg.hdhomerun.autoDiscover}; then
|
||||
# Primary: UDP broadcast discover.
|
||||
base="$(${cfg.discoveryPackage}/bin/ec-cli discover | jq -r --arg id "$dev_id" '.[] | select(.id == $id) | .base_url // empty' | head -n1 || true)"
|
||||
|
||||
# Fallback: probe known neighbors for /discover.json (fast; avoids full /24 scan).
|
||||
if [[ -z "$base" ]]; then
|
||||
while read -r ip; do
|
||||
found="$(try_ip "$ip" || true)"
|
||||
|
|
@ -635,6 +415,7 @@ in
|
|||
done < <(ip neigh | awk '{print $1}' | sort -u)
|
||||
fi
|
||||
|
||||
# Fallback: scan local /24 subnets for /discover.json (slow; worst-case ~50s).
|
||||
if [[ -z "$base" ]]; then
|
||||
while read -r cidr; do
|
||||
ip_addr="''${cidr%/*}"
|
||||
|
|
@ -661,6 +442,7 @@ in
|
|||
exit 2
|
||||
fi
|
||||
else
|
||||
# Best-effort mDNS convention.
|
||||
base="http://$dev_id.local"
|
||||
fi
|
||||
fi
|
||||
|
|
@ -670,23 +452,14 @@ in
|
|||
base="http://$base"
|
||||
fi
|
||||
|
||||
# HDHomeRun streaming is on port 5004, regardless of the discover BaseURL.
|
||||
hostport="''${base#http://}"
|
||||
hostport="''${hostport#https://}"
|
||||
hostport="''${hostport%%/*}"
|
||||
host="''${hostport%%:*}"
|
||||
input="http://$host:5004/auto/v$ch"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$nbc_url" ]]; then
|
||||
cmd=(
|
||||
${lib.escapeShellArg "${cfg.package}/bin/ec-node"}
|
||||
nbc-wt-publish
|
||||
--url ${lib.escapeShellArg cfg.relayUrl}
|
||||
--name ${lib.escapeShellArg b.name}
|
||||
--source-url "$nbc_url"
|
||||
)
|
||||
else
|
||||
cmd=(
|
||||
${lib.escapeShellArg "${cfg.package}/bin/ec-node"}
|
||||
wt-publish
|
||||
|
|
@ -695,7 +468,6 @@ in
|
|||
--input "$input"
|
||||
)
|
||||
${lib.optionalString (!cfg.transcode) "cmd+=(--transcode=false)"}
|
||||
fi
|
||||
${lib.optionalString (!cfg.passthrough) "cmd+=(--passthrough=false)"}
|
||||
${lib.optionalString cfg.tlsDisableVerify "cmd+=(--tls-disable-verify)"}
|
||||
${lib.optionalString cfg.control.enable ''
|
||||
|
|
@ -720,16 +492,7 @@ in
|
|||
# quickly during activation.
|
||||
trap 'exit 0' INT TERM
|
||||
while true; do
|
||||
${lib.optionalString (isNbc && cfg.nbc.requireMullvad) ''
|
||||
if ! wait_for_mullvad; then
|
||||
sleep 2
|
||||
continue
|
||||
fi
|
||||
''}
|
||||
${lib.optionalString (isNbc && cfg.nbc.isolateWithUserNetns) "run_in_user_netns || true"}
|
||||
${lib.optionalString (!isNbc || !cfg.nbc.isolateWithUserNetns) ''
|
||||
"''${cmd[@]}" || true
|
||||
''}
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
|
@ -740,14 +503,8 @@ in
|
|||
value = {
|
||||
description = "every.channel WebTransport publish (${b.name} -> ${cfg.relayUrl})";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after =
|
||||
[ "network-online.target" ]
|
||||
++ lib.optionals isNbc [ "every-channel-nbc-display.service" ]
|
||||
++ lib.optionals (isNbc && cfg.nbc.requireMullvad) [ "mullvad-daemon.service" ];
|
||||
wants =
|
||||
[ "network-online.target" ]
|
||||
++ lib.optionals isNbc [ "every-channel-nbc-display.service" ]
|
||||
++ lib.optionals (isNbc && cfg.nbc.requireMullvad) [ "mullvad-daemon.service" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
# Keep the unit from entering "failed" due to rapid restarts (deploy-flake treats
|
||||
# failed units during `switch-to-configuration test` as a deployment failure).
|
||||
|
|
@ -764,45 +521,23 @@ in
|
|||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
|
||||
DynamicUser = !isNbc;
|
||||
User = lib.mkIf isNbc "every-channel";
|
||||
Group = lib.mkIf isNbc "every-channel";
|
||||
DynamicUser = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = !isNbc;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = !isNbc;
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = !isNbc;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
ReadWritePaths =
|
||||
lib.optionals cfg.control.enable [ "/run/every-channel" ]
|
||||
++ lib.optionals isNbc [ "/tmp" ]
|
||||
++ lib.optionals isNbc [ cfg.nbc.profileDir cfg.nbc.authScreenshotDir ];
|
||||
ReadWritePaths = lib.optionals cfg.control.enable [ "/run/every-channel" ];
|
||||
};
|
||||
|
||||
environment =
|
||||
cfg.environment
|
||||
// lib.optionalAttrs isNbc (
|
||||
{
|
||||
DISPLAY = cfg.nbc.display;
|
||||
EVERY_CHANNEL_NBC_CHROME_PATH = cfg.nbc.chromeBinary;
|
||||
EVERY_CHANNEL_NBC_MVPD_PROVIDER = cfg.nbc.mvpdProvider;
|
||||
EVERY_CHANNEL_NBC_PROFILE_DIR = cfg.nbc.profileDir;
|
||||
EVERY_CHANNEL_NBC_NO_SANDBOX = if cfg.nbc.noSandbox then "1" else "0";
|
||||
HOME = "/var/lib/every-channel";
|
||||
}
|
||||
// lib.optionalAttrs (cfg.nbc.mvpdUsernameFile != null) {
|
||||
EVERY_CHANNEL_NBC_MVPD_USERNAME_FILE = toString cfg.nbc.mvpdUsernameFile;
|
||||
}
|
||||
// lib.optionalAttrs (cfg.nbc.mvpdPasswordFile != null) {
|
||||
EVERY_CHANNEL_NBC_MVPD_PASSWORD_FILE = toString cfg.nbc.mvpdPasswordFile;
|
||||
}
|
||||
);
|
||||
environment = cfg.environment;
|
||||
};
|
||||
})
|
||||
cfg.broadcasts)
|
||||
|
|
@ -1117,112 +852,6 @@ in
|
|||
|
||||
environment = cfg.environment;
|
||||
};
|
||||
})
|
||||
// lib.optionalAttrs cfg.nbc.enable
|
||||
(let
|
||||
displayUnit = "every-channel-nbc-display";
|
||||
displayNumber = lib.strings.removePrefix ":" cfg.nbc.display;
|
||||
displayRunner = pkgs.writeShellApplication {
|
||||
name = displayUnit;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.xorg.xorgserver
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
exec ${pkgs.xorg.xorgserver}/bin/Xvfb ${lib.escapeShellArg cfg.nbc.display} \
|
||||
-screen 0 ${lib.escapeShellArg cfg.nbc.screen} \
|
||||
-nolisten tcp \
|
||||
-ac \
|
||||
+extension RANDR
|
||||
'';
|
||||
};
|
||||
vncUnit = "every-channel-nbc-vnc";
|
||||
vncRunner = pkgs.writeShellApplication {
|
||||
name = vncUnit;
|
||||
runtimeInputs = [
|
||||
pkgs.x11vnc
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
exec ${pkgs.x11vnc}/bin/x11vnc \
|
||||
-display ${lib.escapeShellArg cfg.nbc.display} \
|
||||
-forever \
|
||||
-shared \
|
||||
-nopw \
|
||||
-listen ${lib.escapeShellArg cfg.nbc.vnc.listen} \
|
||||
-rfbport ${toString cfg.nbc.vnc.port}
|
||||
'';
|
||||
};
|
||||
in
|
||||
({
|
||||
"${displayUnit}" = {
|
||||
description = "every.channel NBC virtual display";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${displayRunner}/bin/${displayUnit}";
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
User = "every-channel";
|
||||
Group = "every-channel";
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = false;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = false;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = false;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
ReadWritePaths = [ "/tmp" "/var/lib/every-channel" ];
|
||||
};
|
||||
|
||||
environment = cfg.environment // {
|
||||
HOME = "/var/lib/every-channel";
|
||||
};
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs cfg.nbc.vnc.enable {
|
||||
"${vncUnit}" = {
|
||||
description = "every.channel NBC virtual display VNC bridge";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" "${displayUnit}.service" ];
|
||||
wants = [ "network-online.target" "${displayUnit}.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${vncRunner}/bin/${vncUnit}";
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
User = "every-channel";
|
||||
Group = "every-channel";
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = false;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = false;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = false;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
ReadWritePaths = [ "/tmp" "/var/lib/every-channel" ];
|
||||
};
|
||||
|
||||
environment = cfg.environment // {
|
||||
DISPLAY = cfg.nbc.display;
|
||||
HOME = "/var/lib/every-channel";
|
||||
};
|
||||
};
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,432 +0,0 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.every-channel.op-stack;
|
||||
scriptRoot = ../..;
|
||||
bootstrapScript = "${scriptRoot}/scripts/op-stack/setup-rollup.sh";
|
||||
downloadScript = "${scriptRoot}/scripts/op-stack/download-op-deployer.sh";
|
||||
|
||||
containerName = name: "every-channel-op-${name}";
|
||||
in
|
||||
{
|
||||
options.services.every-channel.op-stack = {
|
||||
enable = lib.mkEnableOption "every.channel OP Stack Sepolia testnet services";
|
||||
|
||||
rootDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/op-stack";
|
||||
description = "Persistent root directory for OP Stack bootstrap outputs and container state.";
|
||||
};
|
||||
|
||||
privateKeyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "File containing the Sepolia private key used for op-deployer and operator services.";
|
||||
};
|
||||
|
||||
l1RpcUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://ethereum-sepolia-rpc.publicnode.com";
|
||||
description = "Sepolia L1 RPC URL.";
|
||||
};
|
||||
|
||||
l1BeaconUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://ethereum-sepolia-beacon-api.publicnode.com";
|
||||
description = "Sepolia L1 beacon API URL.";
|
||||
};
|
||||
|
||||
chainId = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 245245;
|
||||
description = "L2 chain ID for the every.channel OP Stack testnet.";
|
||||
};
|
||||
|
||||
p2pAdvertiseIp = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Public IP advertised by op-node for P2P.";
|
||||
};
|
||||
|
||||
p2pListenPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 9222;
|
||||
description = "P2P listen port for op-node.";
|
||||
};
|
||||
|
||||
ports = {
|
||||
l2Http = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28545;
|
||||
description = "Local op-geth HTTP JSON-RPC port.";
|
||||
};
|
||||
|
||||
l2Ws = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28546;
|
||||
description = "Local op-geth WebSocket JSON-RPC port.";
|
||||
};
|
||||
|
||||
l2Auth = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28551;
|
||||
description = "Local op-geth Engine API port.";
|
||||
};
|
||||
|
||||
l2P2p = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28549;
|
||||
description = "Local op-geth P2P port, kept away from the host Ethereum node's 30303.";
|
||||
};
|
||||
|
||||
rollupRpc = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28547;
|
||||
description = "Local op-node rollup RPC port.";
|
||||
};
|
||||
|
||||
batcherRpc = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28548;
|
||||
description = "Local op-batcher admin RPC port.";
|
||||
};
|
||||
|
||||
proposerRpc = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28560;
|
||||
description = "Local op-proposer admin RPC port.";
|
||||
};
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Open the op-node P2P TCP/UDP port.";
|
||||
};
|
||||
|
||||
disputeMonEnable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Run op-dispute-mon alongside the core OP Stack services.";
|
||||
};
|
||||
|
||||
challengerEnable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Run op-challenger for the rollup.";
|
||||
};
|
||||
|
||||
challengerPrestateFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Path to the Cannon absolute prestate .bin.gz file used by op-challenger.";
|
||||
};
|
||||
|
||||
opDeployerTag = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "op-deployer/v0.6.0-rc.3";
|
||||
description = "Pinned op-deployer release tag used for bootstrap.";
|
||||
};
|
||||
|
||||
images = {
|
||||
opNode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.16.6";
|
||||
description = "Container image for op-node.";
|
||||
};
|
||||
opGeth = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101702.0-rc.1";
|
||||
description = "Container image for op-geth.";
|
||||
};
|
||||
batcher = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.14.0";
|
||||
description = "Container image for op-batcher.";
|
||||
};
|
||||
proposer = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.10.0";
|
||||
description = "Container image for op-proposer.";
|
||||
};
|
||||
challenger = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.1";
|
||||
description = "Container image for op-challenger.";
|
||||
};
|
||||
disputeMon = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-dispute-mon:v1.4.2-rc.1";
|
||||
description = "Container image for op-dispute-mon.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.privateKeyFile != null;
|
||||
message = "services.every-channel.op-stack.privateKeyFile must be set when the OP Stack is enabled";
|
||||
}
|
||||
{
|
||||
assertion = builtins.pathExists bootstrapScript;
|
||||
message = "missing bootstrap script at scripts/op-stack/setup-rollup.sh";
|
||||
}
|
||||
{
|
||||
assertion = builtins.pathExists downloadScript;
|
||||
message = "missing download helper at scripts/op-stack/download-op-deployer.sh";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.challengerEnable) || cfg.challengerPrestateFile != null;
|
||||
message = "services.every-channel.op-stack.challengerPrestateFile must be set when challengerEnable = true";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.disputeMonEnable) || cfg.challengerEnable;
|
||||
message = "services.every-channel.op-stack.disputeMonEnable requires challengerEnable = true";
|
||||
}
|
||||
];
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.p2pListenPort ];
|
||||
allowedUDPPorts = [ cfg.p2pListenPort ];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.rootDir} 0750 root root - -"
|
||||
"d ${cfg.rootDir}/bin 0750 root root - -"
|
||||
"d ${cfg.rootDir}/deployer 0750 root root - -"
|
||||
"d ${cfg.rootDir}/sequencer 0750 root root - -"
|
||||
"d ${cfg.rootDir}/batcher 0750 root root - -"
|
||||
"d ${cfg.rootDir}/proposer 0750 root root - -"
|
||||
"d ${cfg.rootDir}/challenger 0750 root root - -"
|
||||
"d ${cfg.rootDir}/challenger/data 0750 root root - -"
|
||||
"d ${cfg.rootDir}/dispute-mon 0750 root root - -"
|
||||
"d ${cfg.rootDir}/op-geth-data 0750 root root - -"
|
||||
];
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
"${containerName "geth"}" = {
|
||||
image = cfg.images.opGeth;
|
||||
autoStart = true;
|
||||
volumes = [
|
||||
"${cfg.rootDir}/sequencer:/workspace"
|
||||
"${cfg.rootDir}/op-geth-data:/workspace/op-geth-data"
|
||||
];
|
||||
extraOptions = [ "--network=host" ];
|
||||
entrypoint = "/bin/sh";
|
||||
cmd = [
|
||||
"-lc"
|
||||
''
|
||||
set -e
|
||||
if [ ! -d /workspace/op-geth-data/geth/chaindata ]; then
|
||||
geth init --datadir=/workspace/op-geth-data --state.scheme=hash /workspace/genesis.json
|
||||
fi
|
||||
exec geth --datadir=/workspace/op-geth-data --http --http.addr=127.0.0.1 --http.port=${toString cfg.ports.l2Http} --ws --ws.addr=127.0.0.1 --ws.port=${toString cfg.ports.l2Ws} --authrpc.addr=127.0.0.1 --authrpc.port=${toString cfg.ports.l2Auth} --authrpc.jwtsecret=/workspace/jwt.txt --port=${toString cfg.ports.l2P2p} --syncmode=full --gcmode=archive --rollup.disabletxpoolgossip=true --http.vhosts=* --http.corsdomain=* --http.api=eth,net,web3,debug,txpool,admin --ws.origins=* --ws.api=eth,net,web3,debug,txpool,admin --authrpc.vhosts=*
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
"${containerName "node"}" = {
|
||||
image = cfg.images.opNode;
|
||||
autoStart = true;
|
||||
volumes = [
|
||||
"${cfg.rootDir}/sequencer:/workspace"
|
||||
"${cfg.rootDir}/op-geth-data:/workspace/op-geth-data"
|
||||
];
|
||||
environmentFiles = [ "${cfg.rootDir}/sequencer/.env" ];
|
||||
extraOptions = [ "--network=host" ];
|
||||
entrypoint = "/bin/sh";
|
||||
cmd = [
|
||||
"-lc"
|
||||
''
|
||||
exec op-node \
|
||||
--l1="$L1_RPC_URL" \
|
||||
--l1.beacon="$L1_BEACON_URL" \
|
||||
--l2=http://127.0.0.1:${toString cfg.ports.l2Auth} \
|
||||
--l2.jwt-secret=/workspace/jwt.txt \
|
||||
--rollup.config=/workspace/rollup.json \
|
||||
--sequencer.enabled=true \
|
||||
--sequencer.stopped=false \
|
||||
--sequencer.max-safe-lag=3600 \
|
||||
--verifier.l1-confs=4 \
|
||||
--p2p.listen.ip=0.0.0.0 \
|
||||
--p2p.listen.tcp=${toString cfg.p2pListenPort} \
|
||||
--p2p.listen.udp=${toString cfg.p2pListenPort} \
|
||||
--p2p.advertise.ip="$P2P_ADVERTISE_IP" \
|
||||
--p2p.advertise.tcp=${toString cfg.p2pListenPort} \
|
||||
--p2p.advertise.udp=${toString cfg.p2pListenPort} \
|
||||
--p2p.sequencer.key="$PRIVATE_KEY" \
|
||||
--rpc.addr=127.0.0.1 \
|
||||
--rpc.port=${toString cfg.ports.rollupRpc} \
|
||||
--rpc.enable-admin \
|
||||
--log.level=info \
|
||||
--log.format=json
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
"${containerName "batcher"}" = {
|
||||
image = cfg.images.batcher;
|
||||
autoStart = true;
|
||||
volumes = [ "${cfg.rootDir}/batcher:/workspace" ];
|
||||
environmentFiles = [ "${cfg.rootDir}/batcher/.env" ];
|
||||
extraOptions = [ "--network=host" ];
|
||||
entrypoint = "/bin/sh";
|
||||
cmd = [
|
||||
"-lc"
|
||||
''
|
||||
exec op-batcher \
|
||||
--l1-eth-rpc="$L1_RPC_URL" \
|
||||
--l2-eth-rpc="$L2_RPC_URL" \
|
||||
--rollup-rpc="$ROLLUP_RPC_URL" \
|
||||
--private-key="$PRIVATE_KEY" \
|
||||
--rpc.addr=127.0.0.1 \
|
||||
--rpc.port=${toString cfg.ports.batcherRpc} \
|
||||
--rpc.enable-admin \
|
||||
--max-channel-duration=1 \
|
||||
--data-availability-type=calldata \
|
||||
--resubmission-timeout=30s \
|
||||
--log.level=info \
|
||||
--log.format=json
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
"${containerName "proposer"}" = {
|
||||
image = cfg.images.proposer;
|
||||
autoStart = true;
|
||||
volumes = [ "${cfg.rootDir}/proposer:/workspace" ];
|
||||
environmentFiles = [ "${cfg.rootDir}/proposer/.env" ];
|
||||
extraOptions = [ "--network=host" ];
|
||||
entrypoint = "/bin/sh";
|
||||
cmd = [
|
||||
"-lc"
|
||||
''
|
||||
exec op-proposer \
|
||||
--rpc.port=${toString cfg.ports.proposerRpc} \
|
||||
--rollup-rpc="$ROLLUP_RPC_URL" \
|
||||
--l1-eth-rpc="$L1_RPC_URL" \
|
||||
--private-key="$PRIVATE_KEY" \
|
||||
--game-factory-address="$GAME_FACTORY_ADDRESS" \
|
||||
--proposal-interval="$PROPOSAL_INTERVAL" \
|
||||
--allow-non-finalized=true \
|
||||
--wait-node-sync=true \
|
||||
--log.level=info \
|
||||
--log.format=json
|
||||
''
|
||||
];
|
||||
};
|
||||
} // lib.optionalAttrs cfg.challengerEnable {
|
||||
"${containerName "challenger"}" = {
|
||||
image = cfg.images.challenger;
|
||||
autoStart = true;
|
||||
volumes = [ "${cfg.rootDir}/challenger:/workspace" ];
|
||||
environmentFiles = [ "${cfg.rootDir}/challenger/.env" ];
|
||||
extraOptions = [ "--network=host" ];
|
||||
entrypoint = "/bin/sh";
|
||||
cmd = [
|
||||
"-lc"
|
||||
''
|
||||
exec op-challenger run-trace \
|
||||
--trace-type=cannon \
|
||||
--l1-eth-rpc="$L1_RPC_URL" \
|
||||
--l1-beacon="$L1_BEACON_URL" \
|
||||
--private-key="$PRIVATE_KEY" \
|
||||
--game-factory-address="$GAME_FACTORY_ADDRESS" \
|
||||
--cannon-l2-genesis=/workspace/genesis.json \
|
||||
--cannon-rollup-config=/workspace/rollup.json \
|
||||
--cannon-prestate="$CANNON_PRESTATE" \
|
||||
--l2-eth-rpc="$L2_RPC_URL" \
|
||||
--rollup-rpc="$ROLLUP_RPC_URL" \
|
||||
--datadir=/workspace/data \
|
||||
--log.level=info \
|
||||
--log.format=json
|
||||
''
|
||||
];
|
||||
};
|
||||
} // lib.optionalAttrs cfg.disputeMonEnable {
|
||||
"${containerName "dispute-mon"}" = {
|
||||
image = cfg.images.disputeMon;
|
||||
autoStart = true;
|
||||
volumes = [ "${cfg.rootDir}/dispute-mon:/workspace" ];
|
||||
environmentFiles = [ "${cfg.rootDir}/dispute-mon/.env" ];
|
||||
extraOptions = [ "--network=host" ];
|
||||
entrypoint = "/bin/sh";
|
||||
cmd = [
|
||||
"-lc"
|
||||
''
|
||||
exec op-dispute-mon
|
||||
''
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
every-channel-op-stack-bootstrap = {
|
||||
description = "every.channel OP Stack bootstrap";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
curl
|
||||
gnutar
|
||||
gzip
|
||||
jq
|
||||
openssl
|
||||
foundry
|
||||
python3
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
export EVERY_CHANNEL_OP_STACK_ROOT=${lib.escapeShellArg cfg.rootDir}
|
||||
export EVERY_CHANNEL_OP_STACK_PRIVATE_KEY_FILE=${lib.escapeShellArg cfg.privateKeyFile}
|
||||
export EVERY_CHANNEL_OP_STACK_L1_RPC_URL=${lib.escapeShellArg cfg.l1RpcUrl}
|
||||
export EVERY_CHANNEL_OP_STACK_L1_BEACON_URL=${lib.escapeShellArg cfg.l1BeaconUrl}
|
||||
export EVERY_CHANNEL_OP_STACK_CHAIN_ID=${toString cfg.chainId}
|
||||
export EVERY_CHANNEL_OP_STACK_P2P_ADVERTISE_IP=${lib.escapeShellArg cfg.p2pAdvertiseIp}
|
||||
export EVERY_CHANNEL_OP_STACK_L2_RPC_URL=http://127.0.0.1:${toString cfg.ports.l2Http}
|
||||
export EVERY_CHANNEL_OP_STACK_ROLLUP_RPC_URL=http://127.0.0.1:${toString cfg.ports.rollupRpc}
|
||||
export EVERY_CHANNEL_OP_DEPLOYER_BIN=${lib.escapeShellArg "${cfg.rootDir}/bin/op-deployer"}
|
||||
export EVERY_CHANNEL_OP_DEPLOYER_TAG=${lib.escapeShellArg cfg.opDeployerTag}
|
||||
export EVERY_CHANNEL_OP_DEPLOYER_DOWNLOAD_SCRIPT=${lib.escapeShellArg downloadScript}
|
||||
export EVERY_CHANNEL_OP_STACK_CHALLENGER_PRESTATE_FILE=${lib.escapeShellArg (if cfg.challengerPrestateFile == null then "" else cfg.challengerPrestateFile)}
|
||||
${lib.escapeShellArg bootstrapScript}
|
||||
'';
|
||||
};
|
||||
"podman-${containerName "geth"}" = {
|
||||
after = [ "every-channel-op-stack-bootstrap.service" ];
|
||||
wants = [ "every-channel-op-stack-bootstrap.service" ];
|
||||
requires = [ "every-channel-op-stack-bootstrap.service" ];
|
||||
};
|
||||
"podman-${containerName "node"}" = {
|
||||
after = [ "every-channel-op-stack-bootstrap.service" "podman-${containerName "geth"}.service" ];
|
||||
wants = [ "every-channel-op-stack-bootstrap.service" "podman-${containerName "geth"}.service" ];
|
||||
requires = [ "every-channel-op-stack-bootstrap.service" ];
|
||||
};
|
||||
"podman-${containerName "batcher"}" = {
|
||||
after = [ "podman-${containerName "node"}.service" ];
|
||||
wants = [ "podman-${containerName "node"}.service" ];
|
||||
};
|
||||
"podman-${containerName "proposer"}" = {
|
||||
after = [ "podman-${containerName "node"}.service" ];
|
||||
wants = [ "podman-${containerName "node"}.service" ];
|
||||
};
|
||||
} // lib.optionalAttrs cfg.challengerEnable {
|
||||
"podman-${containerName "challenger"}" = {
|
||||
after = [ "podman-${containerName "node"}.service" ];
|
||||
wants = [ "podman-${containerName "node"}.service" ];
|
||||
};
|
||||
} // lib.optionalAttrs cfg.disputeMonEnable {
|
||||
"podman-${containerName "dispute-mon"}" = {
|
||||
after = [ "podman-${containerName "node"}.service" ];
|
||||
wants = [ "podman-${containerName "node"}.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
networking.hostName = lib.mkForce "ec-publisher";
|
||||
|
||||
services.every-channel.ec-node = {
|
||||
relayUrl = lib.mkDefault "https://cdn.moq.dev/anon";
|
||||
passthrough = lib.mkDefault false;
|
||||
|
||||
hdhomerun.autoDiscover = lib.mkDefault true;
|
||||
|
||||
control = {
|
||||
enable = lib.mkDefault true;
|
||||
discovery = lib.mkDefault "dht,mdns,dns";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
curl
|
||||
ffmpeg
|
||||
jq
|
||||
];
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
boot.initrd.availableKernelModules = [
|
||||
"ahci"
|
||||
"xhci_pci"
|
||||
"nvme"
|
||||
"sd_mod"
|
||||
"sr_mod"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-label/nixos";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-label/boot";
|
||||
fsType = "vfat";
|
||||
options = [ "fmask=0077" "dmask=0077" ];
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
}
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
hasForgejoApiToken = builtins.pathExists ../../secrets/forgejo-api-token.age;
|
||||
hasNetbootChainToken = builtins.pathExists ../../secrets/netboot-chain-token.age;
|
||||
hasOpStackSepoliaKey = builtins.pathExists ../../secrets/op-stack-sepolia-private-key.age;
|
||||
hasOpStackChallengerPrestate = builtins.pathExists ../../secrets/op-stack-challenger-prestate.bin.gz.age;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./ecp-forge-hardware.nix
|
||||
];
|
||||
|
||||
nixpkgs.config.allowUnfreePredicate = pkg:
|
||||
builtins.elem (lib.getName pkg) [
|
||||
"google-chrome"
|
||||
"google-chrome-stable"
|
||||
];
|
||||
|
||||
networking = {
|
||||
hostName = "ecp-forge";
|
||||
hostId = "007f0200";
|
||||
useDHCP = lib.mkForce true;
|
||||
networkmanager.enable = lib.mkForce false;
|
||||
nameservers = [ "1.1.1.1" "8.8.8.8" ];
|
||||
firewall = {
|
||||
trustedInterfaces = [ "tailscale0" ];
|
||||
allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
2222
|
||||
69
|
||||
];
|
||||
allowedUDPPorts = [
|
||||
67
|
||||
69
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
startWhenNeeded = true;
|
||||
openFirewall = true;
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
PermitRootLogin = lib.mkForce "prohibit-password";
|
||||
KbdInteractiveAuthentication = false;
|
||||
};
|
||||
};
|
||||
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBueQxNbP2246pxr/m7au4zNVm+ShC96xuOcfEcpIjWZ"
|
||||
];
|
||||
|
||||
security.sudo = {
|
||||
execWheelOnly = true;
|
||||
wheelNeedsPassword = false;
|
||||
};
|
||||
|
||||
users = {
|
||||
mutableUsers = false;
|
||||
defaultUserShell = pkgs.bash;
|
||||
users.conradev = {
|
||||
uid = 1000;
|
||||
isNormalUser = true;
|
||||
password = "password";
|
||||
group = "conradev";
|
||||
extraGroups = [ "wheel" "docker" "libvirtd" "kvm" ];
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBueQxNbP2246pxr/m7au4zNVm+ShC96xuOcfEcpIjWZ"
|
||||
];
|
||||
};
|
||||
groups.conradev = { };
|
||||
};
|
||||
|
||||
boot.loader = {
|
||||
grub = {
|
||||
enable = true;
|
||||
device = "/dev/disk/by-id/nvme-KIOXIA_KCD81RUG7T68_25R0A0KZTTEJ";
|
||||
};
|
||||
efi.canTouchEfiVariables = false;
|
||||
};
|
||||
|
||||
boot.supportedFilesystems = [ "zfs" ];
|
||||
boot.zfs.extraPools = [ "tank" ];
|
||||
boot.kernel.sysctl = {
|
||||
"fs.inotify.max_user_watches" = "204800";
|
||||
};
|
||||
|
||||
hardware.nvidia-container-toolkit.enable = false;
|
||||
|
||||
virtualisation = {
|
||||
containers.enable = true;
|
||||
oci-containers.backend = "podman";
|
||||
podman = {
|
||||
enable = true;
|
||||
dockerCompat = true;
|
||||
autoPrune.enable = true;
|
||||
defaultNetwork.settings.dns_enabled = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.tailscale = {
|
||||
enable = true;
|
||||
extraUpFlags = "--accept-routes";
|
||||
};
|
||||
|
||||
age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
age.secrets = lib.mkMerge [
|
||||
(lib.mkIf hasForgejoApiToken {
|
||||
"forgejo-api-token" = {
|
||||
file = ../../secrets/forgejo-api-token.age;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
})
|
||||
(lib.mkIf hasNetbootChainToken {
|
||||
"every-channel-netboot-chain-token" = {
|
||||
file = ../../secrets/netboot-chain-token.age;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
})
|
||||
(lib.mkIf hasOpStackSepoliaKey {
|
||||
"every-channel-op-stack-sepolia-private-key" = {
|
||||
file = ../../secrets/op-stack-sepolia-private-key.age;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
})
|
||||
(lib.mkIf hasOpStackChallengerPrestate {
|
||||
"every-channel-op-stack-challenger-prestate" = {
|
||||
file = ../../secrets/op-stack-challenger-prestate.bin.gz.age;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
services.zfs.autoScrub = {
|
||||
enable = true;
|
||||
pools = [ "tank" ];
|
||||
interval = "weekly";
|
||||
};
|
||||
|
||||
services.nfs.server = {
|
||||
enable = true;
|
||||
# Keep NFS on trusted/private paths only; public Hetzner exposure triggers
|
||||
# rpcbind/portmapper enumeration and is not needed for forge access.
|
||||
exports = ''
|
||||
/tank 10.0.0.0/8(rw,fsid=0,crossmnt,no_subtree_check,sync) 100.64.0.0/10(rw,fsid=0,crossmnt,no_subtree_check,sync) 192.168.0.0/16(rw,fsid=0,crossmnt,no_subtree_check,sync)
|
||||
'';
|
||||
};
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 30d";
|
||||
};
|
||||
|
||||
nix.settings = {
|
||||
experimental-features = [ "nix-command" "flakes" ];
|
||||
substituters = lib.mkForce [ "https://cache.nixos.org" ];
|
||||
trusted-substituters = lib.mkForce [ "https://cache.nixos.org" ];
|
||||
extra-substituters = lib.mkForce [ ];
|
||||
trusted-public-keys = lib.mkForce [
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||
];
|
||||
extra-trusted-public-keys = lib.mkForce [ ];
|
||||
};
|
||||
|
||||
time.timeZone = "America/New_York";
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
xdg.mime.enable = true;
|
||||
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
database.type = "sqlite3";
|
||||
lfs.enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
DOMAIN = "git.every.channel";
|
||||
ROOT_URL = "https://git.every.channel/";
|
||||
HTTP_ADDR = "127.0.0.1";
|
||||
HTTP_PORT = 3000;
|
||||
SSH_DOMAIN = "git.every.channel";
|
||||
SSH_PORT = 2222;
|
||||
SSH_LISTEN_PORT = 2222;
|
||||
START_SSH_SERVER = true;
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = true;
|
||||
REQUIRE_SIGNIN_VIEW = false;
|
||||
};
|
||||
repository = {
|
||||
DEFAULT_BRANCH = "main";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
email = "infra@every.channel";
|
||||
acmeCA = "https://acme-v02.api.letsencrypt.org/directory";
|
||||
virtualHosts = {
|
||||
"git.every.channel".extraConfig = ''
|
||||
reverse_proxy http://127.0.0.1:3000
|
||||
'';
|
||||
"forge.every.channel".extraConfig = ''
|
||||
redir https://git.every.channel{uri} permanent
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
services.every-channel.netboot = {
|
||||
enable = true;
|
||||
listenIP = "95.216.114.54";
|
||||
interface = "enp5s0f3u2u2c2";
|
||||
hostname = "boot.every.channel";
|
||||
httpPort = 8080;
|
||||
tftpBootFilename = "ec-ipxe.efi";
|
||||
httpAllowedCIDRs = [
|
||||
"10.0.0.0/8"
|
||||
"172.16.0.0/12"
|
||||
"192.168.0.0/16"
|
||||
"100.64.0.0/10"
|
||||
];
|
||||
chainTokenFile =
|
||||
if hasNetbootChainToken
|
||||
then config.age.secrets."every-channel-netboot-chain-token".path
|
||||
else null;
|
||||
proxyDhcp.enable = false;
|
||||
release.host = "https://git.every.channel";
|
||||
release.repo = "every-channel/every.channel";
|
||||
release.localTarball = "/var/lib/every-channel-netboot/sources/ec-runner-x86_64-netboot-local.tar.gz";
|
||||
release.tokenFile =
|
||||
if hasForgejoApiToken
|
||||
then config.age.secrets."forgejo-api-token".path
|
||||
else null;
|
||||
};
|
||||
|
||||
services.every-channel.ipxe-qemu = {
|
||||
enable = true;
|
||||
name = "ecp-forge-ipxe";
|
||||
boot.userNet.hostname = "ecp-forge-ipxe";
|
||||
boot.http.root = "${config.services.every-channel.netboot.rootDir}/http";
|
||||
};
|
||||
|
||||
systemd.services.every-channel-ipxe-qemu = {
|
||||
after = [ "every-channel-netboot-stage.service" ];
|
||||
wants = [ "every-channel-netboot-stage.service" ];
|
||||
};
|
||||
|
||||
services.every-channel.ec-node = {
|
||||
enable = true;
|
||||
nbc = {
|
||||
enable = true;
|
||||
chromeBinary = "${pkgs.google-chrome}/bin/google-chrome-stable";
|
||||
display = ":120";
|
||||
screen = "1920x1080x24";
|
||||
isolateWithUserNetns = true;
|
||||
requireMullvad = false;
|
||||
mullvadLocation = null;
|
||||
noSandbox = true;
|
||||
vnc = {
|
||||
enable = true;
|
||||
listen = "127.0.0.1";
|
||||
port = 5900;
|
||||
};
|
||||
};
|
||||
broadcasts = [
|
||||
{
|
||||
name = "forge-nbc-sports-philly";
|
||||
nbcUrl = "https://www.nbc.com/live?brand=nbc-sports-philadelphia";
|
||||
}
|
||||
];
|
||||
archive = {
|
||||
enable = true;
|
||||
outputDir = "/tank/every-channel/archive";
|
||||
manifestDir = "/var/lib/every-channel/manifests";
|
||||
# Keep forge archival as an ingest worker only; replay serving is separate.
|
||||
serve.enable = false;
|
||||
};
|
||||
};
|
||||
|
||||
services.every-channel.ethereum = {
|
||||
enable = true;
|
||||
poolName = "eth";
|
||||
poolDevice = "/dev/disk/by-id/nvme-eui.01000000000000008ce38ee307de5c01";
|
||||
rootDir = "/eth";
|
||||
publicIp = "95.216.114.54";
|
||||
publicHost = "eth.every.channel";
|
||||
};
|
||||
|
||||
services.mullvad-vpn = {
|
||||
enable = false;
|
||||
enableExcludeWrapper = false;
|
||||
};
|
||||
|
||||
services.every-channel.op-stack = {
|
||||
enable = hasOpStackSepoliaKey;
|
||||
challengerEnable = hasOpStackChallengerPrestate;
|
||||
disputeMonEnable = hasOpStackChallengerPrestate;
|
||||
privateKeyFile =
|
||||
if hasOpStackSepoliaKey
|
||||
then config.age.secrets."every-channel-op-stack-sepolia-private-key".path
|
||||
else null;
|
||||
challengerPrestateFile =
|
||||
if hasOpStackChallengerPrestate
|
||||
then config.age.secrets."every-channel-op-stack-challenger-prestate".path
|
||||
else null;
|
||||
p2pAdvertiseIp = "95.216.114.54";
|
||||
};
|
||||
|
||||
environment.systemPackages =
|
||||
(with pkgs; [
|
||||
git
|
||||
google-chrome
|
||||
htop
|
||||
jq
|
||||
mullvad-vpn
|
||||
tmux
|
||||
x11vnc
|
||||
zfs
|
||||
])
|
||||
++ [
|
||||
config.services.every-channel.ec-node.package
|
||||
];
|
||||
|
||||
system.stateVersion = "22.11";
|
||||
}
|
||||
|
|
@ -12,17 +12,7 @@ let
|
|||
let
|
||||
base = baseNameOf path;
|
||||
in
|
||||
!(base == "target"
|
||||
|| base == ".git"
|
||||
|| base == ".direnv"
|
||||
|| base == "tmp"
|
||||
|| base == "node_modules"
|
||||
|| base == "out"
|
||||
|| base == "test-results"
|
||||
|| base == "deploy"
|
||||
|| base == "intake"
|
||||
|| base == "cache"
|
||||
|| base == ".tower-minimal");
|
||||
!(base == "target" || base == ".git" || base == ".direnv" || base == "tmp" || base == "node_modules");
|
||||
};
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{ lib
|
||||
, rustPlatform
|
||||
, rustfmt
|
||||
, stdenv
|
||||
, pkg-config
|
||||
, openssl
|
||||
|
|
@ -15,17 +14,7 @@ let
|
|||
base = baseNameOf path;
|
||||
in
|
||||
# Skip typical build outputs and large scratch dirs.
|
||||
!(base == "target"
|
||||
|| base == ".git"
|
||||
|| base == ".direnv"
|
||||
|| base == "tmp"
|
||||
|| base == "node_modules"
|
||||
|| base == "out"
|
||||
|| base == "test-results"
|
||||
|| base == "deploy"
|
||||
|| base == "intake"
|
||||
|| base == "cache"
|
||||
|| base == ".tower-minimal");
|
||||
!(base == "target" || base == ".git" || base == ".direnv" || base == "tmp" || base == "node_modules");
|
||||
};
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
|
|
@ -41,7 +30,6 @@ rustPlatform.buildRustPackage {
|
|||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
rustfmt
|
||||
];
|
||||
|
||||
buildInputs =
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||
cd "${root}"
|
||||
|
||||
in_file="${1:-secrets/token.txt}"
|
||||
out_file="${2:-secrets/forgejo-api-token.age}"
|
||||
out_file="${2:-secrets/forge-token.age}"
|
||||
|
||||
rules_file="${EVERY_CHANNEL_AGE_RULES_FILE:-${root}/secrets.nix}"
|
||||
identity_file="${EVERY_CHANNEL_AGE_IDENTITY_FILE:-$HOME/.config/every.channel/keys/founder_ed25519}"
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
target="${EVERY_CHANNEL_FORGE_TARGET_HOST:-root@git.every.channel}"
|
||||
build_host="${EVERY_CHANNEL_FORGE_BUILD_HOST:-${target}}"
|
||||
identity_file="${EVERY_CHANNEL_FORGE_SSH_IDENTITY:-$HOME/.ssh/id_ed25519}"
|
||||
|
||||
if [[ ! -f "${identity_file}" ]]; then
|
||||
echo "error: SSH identity not found: ${identity_file}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
export NIX_SSHOPTS="-o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes -i ${identity_file}"
|
||||
|
||||
exec nix run nixpkgs#nixos-rebuild -- \
|
||||
--flake "${root}#ecp-forge" \
|
||||
--target-host "${target}" \
|
||||
--build-host "${build_host}" \
|
||||
--use-remote-sudo \
|
||||
switch
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
host="${EVERY_CHANNEL_E2E_HDHR_HOST:-}"
|
||||
channel="${EVERY_CHANNEL_E2E_HDHR_CHANNEL:-}"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
usage:
|
||||
scripts/e2e-hdhr-blockchain.sh --host <HDHR_HOST> --channel <CHANNEL>
|
||||
|
||||
notes:
|
||||
- starts a local Anvil chain
|
||||
- deploys the observation registry and ledger with quorum=1
|
||||
- runs ec-node against the HDHomeRun source
|
||||
- verifies that the published manifest observation finalizes on-chain
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--host)
|
||||
host="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--channel)
|
||||
channel="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "error: unknown arg: $1" >&2
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${host}" || -z "${channel}" ]]; then
|
||||
echo "error: --host and --channel are required" >&2
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
export EVERY_CHANNEL_E2E_HDHR_HOST="${host}"
|
||||
export EVERY_CHANNEL_E2E_HDHR_CHANNEL="${channel}"
|
||||
|
||||
nix develop --accept-flake-config -c \
|
||||
bash -lc 'cargo test -p ec-node --test e2e_hdhr_blockchain -- --ignored --nocapture'
|
||||
|
|
@ -8,13 +8,12 @@ cd "${root}"
|
|||
#
|
||||
# Auth token source order:
|
||||
# 1) EVERY_CHANNEL_FORGE_TOKEN / FORGE_TOKEN / CODEBERG_TOKEN env var
|
||||
# 2) `agenix -d secrets/forgejo-api-token.age` (preferred) / `secrets/forge-token.age` / `secrets/codeberg-token.age` (optional)
|
||||
# 3) `age -d -i <identity> secrets/forgejo-api-token.age` / `secrets/forge-token.age` / `secrets/codeberg-token.age` (optional)
|
||||
# 2) `agenix -d secrets/forge-token.age` or `secrets/codeberg-token.age` (optional)
|
||||
# 3) `age -d -i <identity> secrets/forge-token.age` or `secrets/codeberg-token.age` (optional)
|
||||
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://git.every.channel}"
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://forge.every.channel}"
|
||||
account="${EVERY_CHANNEL_FORGE_ACCOUNT:-every-channel}"
|
||||
token_file_primary="${EVERY_CHANNEL_FORGE_TOKEN_FILE:-secrets/forgejo-api-token.age}"
|
||||
token_file_secondary="${EVERY_CHANNEL_LEGACY_FORGE_TOKEN_FILE:-secrets/forge-token.age}"
|
||||
token_file_primary="${EVERY_CHANNEL_FORGE_TOKEN_FILE:-secrets/forge-token.age}"
|
||||
token_file_compat="${EVERY_CHANNEL_CODEBERG_TOKEN_FILE:-secrets/codeberg-token.age}"
|
||||
|
||||
rules_file="${EVERY_CHANNEL_AGE_RULES_FILE:-./secrets.nix}"
|
||||
|
|
@ -39,9 +38,6 @@ load_token_from_file() {
|
|||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_primary}" || true)"
|
||||
fi
|
||||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_secondary}" || true)"
|
||||
fi
|
||||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_compat}" || true)"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ set -euo pipefail
|
|||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://git.every.channel}"
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://forge.every.channel}"
|
||||
repo="${EVERY_CHANNEL_FORGE_REPO:-every-channel/every.channel}"
|
||||
branch="${EVERY_CHANNEL_PROTECTED_BRANCH:-main}"
|
||||
required_checks_csv="${EVERY_CHANNEL_REQUIRED_CHECKS:-ci-gates / checks}"
|
||||
|
|
@ -13,8 +13,7 @@ require_signed_commits_raw="${EVERY_CHANNEL_REQUIRE_SIGNED_COMMITS:-true}"
|
|||
|
||||
rules_file="${EVERY_CHANNEL_AGE_RULES_FILE:-./secrets.nix}"
|
||||
identity_file="${EVERY_CHANNEL_AGE_IDENTITY_FILE:-$HOME/.config/every.channel/keys/founder_ed25519}"
|
||||
token_file_primary="${EVERY_CHANNEL_FORGE_TOKEN_FILE:-secrets/forgejo-api-token.age}"
|
||||
token_file_secondary="${EVERY_CHANNEL_LEGACY_FORGE_TOKEN_FILE:-secrets/forge-token.age}"
|
||||
token_file_primary="${EVERY_CHANNEL_FORGE_TOKEN_FILE:-secrets/forge-token.age}"
|
||||
token_file_compat="${EVERY_CHANNEL_CODEBERG_TOKEN_FILE:-secrets/codeberg-token.age}"
|
||||
|
||||
token="${EVERY_CHANNEL_FORGE_TOKEN:-${FORGE_TOKEN:-${CODEBERG_TOKEN:-}}}"
|
||||
|
|
@ -36,9 +35,6 @@ load_token_from_file() {
|
|||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_primary}" || true)"
|
||||
fi
|
||||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_secondary}" || true)"
|
||||
fi
|
||||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_compat}" || true)"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ set -euo pipefail
|
|||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://git.every.channel}"
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://forge.every.channel}"
|
||||
repo="${EVERY_CHANNEL_FORGE_REPO:-every-channel/every.channel}"
|
||||
secret_name="${EVERY_CHANNEL_FORGE_AGE_SECRET_NAME:-AGE_FORGE_SSH_KEY}"
|
||||
key_path="${1:-$HOME/.config/every.channel/keys/founder_ed25519}"
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@ set -euo pipefail
|
|||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://git.every.channel}"
|
||||
host="${EVERY_CHANNEL_FORGE_HOST:-https://forge.every.channel}"
|
||||
repo="${EVERY_CHANNEL_FORGE_REPO:-every-channel/every.channel}"
|
||||
enabled_raw="${EVERY_CHANNEL_FORGE_ACTIONS_ENABLED:-false}"
|
||||
|
||||
rules_file="${EVERY_CHANNEL_AGE_RULES_FILE:-./secrets.nix}"
|
||||
identity_file="${EVERY_CHANNEL_AGE_IDENTITY_FILE:-$HOME/.config/every.channel/keys/founder_ed25519}"
|
||||
token_file_primary="${EVERY_CHANNEL_FORGE_TOKEN_FILE:-secrets/forgejo-api-token.age}"
|
||||
token_file_secondary="${EVERY_CHANNEL_LEGACY_FORGE_TOKEN_FILE:-secrets/forge-token.age}"
|
||||
token_file_primary="${EVERY_CHANNEL_FORGE_TOKEN_FILE:-secrets/forge-token.age}"
|
||||
token_file_compat="${EVERY_CHANNEL_CODEBERG_TOKEN_FILE:-secrets/codeberg-token.age}"
|
||||
|
||||
token="${EVERY_CHANNEL_FORGE_TOKEN:-${FORGE_TOKEN:-${CODEBERG_TOKEN:-}}}"
|
||||
|
|
@ -33,9 +32,6 @@ load_token_from_file() {
|
|||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_primary}" || true)"
|
||||
fi
|
||||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_secondary}" || true)"
|
||||
fi
|
||||
if [[ -z "${token}" ]]; then
|
||||
token="$(load_token_from_file "${token_file_compat}" || true)"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||
cd "${root}"
|
||||
|
||||
primary_remote="${EVERY_CHANNEL_PRIMARY_REMOTE:-origin}"
|
||||
primary_url="${EVERY_CHANNEL_PRIMARY_GIT_URL:-ssh://forgejo@git.every.channel:2222/every-channel/every.channel.git}"
|
||||
primary_url="${EVERY_CHANNEL_PRIMARY_GIT_URL:-git@forge.every.channel:every-channel/every.channel.git}"
|
||||
|
||||
codeberg_remote="${EVERY_CHANNEL_CODEBERG_REMOTE:-mirror-codeberg}"
|
||||
codeberg_url="${EVERY_CHANNEL_CODEBERG_GIT_URL:-git@codeberg.org:every-channel/every.channel.git}"
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
robot_base="${EVERY_CHANNEL_ROBOT_API_BASE:-https://robot-ws.your-server.de}"
|
||||
server="${EVERY_CHANNEL_ROBOT_SERVER:-2800441}"
|
||||
host_ip="${EVERY_CHANNEL_FORGE_IP:-95.216.114.54}"
|
||||
host_name="${EVERY_CHANNEL_FORGE_HOSTNAME:-git.every.channel}"
|
||||
op_item="${EVERY_CHANNEL_ROBOT_OP_ITEM:-Hetzner Robot}"
|
||||
op_vault="${EVERY_CHANNEL_ROBOT_OP_VAULT:-}"
|
||||
ssh_identity="${EVERY_CHANNEL_FORGE_SSH_IDENTITY:-$HOME/.ssh/id_ed25519}"
|
||||
ssh_pub="${EVERY_CHANNEL_FORGE_SSH_PUBLIC_KEY:-${ssh_identity}.pub}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <command>
|
||||
|
||||
Commands:
|
||||
probe Check public HTTPS and SSH reachability for ${host_name}.
|
||||
server Query Robot server metadata.
|
||||
status Query Robot reset and rescue status.
|
||||
rescue-status Query Robot rescue status.
|
||||
activate-rescue Activate Linux rescue mode for ${server}.
|
||||
reset [type] Execute a Robot reset, default: hw.
|
||||
recover [type] Activate rescue mode, then execute a reset.
|
||||
wait-ssh Wait until TCP/22 answers on ${host_ip}.
|
||||
|
||||
Credentials:
|
||||
Set EVERY_CHANNEL_ROBOT_USER and EVERY_CHANNEL_ROBOT_PASSWORD, or sign in with
|
||||
1Password CLI and keep the existing item named "${op_item}" available.
|
||||
|
||||
Optional:
|
||||
EVERY_CHANNEL_ROBOT_OP_ITEM default: Hetzner Robot
|
||||
EVERY_CHANNEL_ROBOT_OP_VAULT optional 1Password vault scope
|
||||
EVERY_CHANNEL_ROBOT_AUTHORIZED_KEY_FINGERPRINT
|
||||
EVERY_CHANNEL_ROBOT_PRINT_SENSITIVE=1 print Robot-generated rescue password
|
||||
EVERY_CHANNEL_ROBOT_RESCUE_OS=linux
|
||||
EVERY_CHANNEL_ROBOT_RESCUE_KEYBOARD=us
|
||||
EVERY_CHANNEL_ROBOT_RESET_TYPE=hw
|
||||
EOF
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "error: required command not found: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
op_field() {
|
||||
local field="$1"
|
||||
if [[ -n "${op_vault}" ]]; then
|
||||
op item get "${op_item}" --vault "${op_vault}" --fields "label=${field}" --reveal
|
||||
else
|
||||
op item get "${op_item}" --fields "label=${field}" --reveal
|
||||
fi
|
||||
}
|
||||
|
||||
load_robot_auth() {
|
||||
robot_user="${EVERY_CHANNEL_ROBOT_USER:-${HETZNER_ROBOT_USER:-}}"
|
||||
robot_password="${EVERY_CHANNEL_ROBOT_PASSWORD:-${HETZNER_ROBOT_PASSWORD:-}}"
|
||||
|
||||
if [[ -z "${robot_user}" || -z "${robot_password}" ]]; then
|
||||
require_cmd op
|
||||
robot_user="${robot_user:-$(op_field username)}"
|
||||
robot_password="${robot_password:-$(op_field password)}"
|
||||
fi
|
||||
|
||||
if [[ -z "${robot_user}" || -z "${robot_password}" ]]; then
|
||||
echo "error: Robot credentials are not available" >&2
|
||||
echo "hint: run 'op signin' or export EVERY_CHANNEL_ROBOT_USER and EVERY_CHANNEL_ROBOT_PASSWORD" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
robot_curl() {
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
shift 2
|
||||
|
||||
local config
|
||||
config="$(mktemp "${TMPDIR:-/tmp}/ec-robot-curl.XXXXXX")"
|
||||
chmod 600 "${config}"
|
||||
cleanup_config() {
|
||||
rm -f "${config}"
|
||||
}
|
||||
trap cleanup_config RETURN
|
||||
|
||||
{
|
||||
printf 'url = "%s%s"\n' "${robot_base}" "${path}"
|
||||
printf 'request = "%s"\n' "${method}"
|
||||
printf 'user = "%s:%s"\n' "${robot_user}" "${robot_password}"
|
||||
printf 'silent\nshow-error\nfail\n'
|
||||
} >"${config}"
|
||||
|
||||
curl --config "${config}" "$@"
|
||||
}
|
||||
|
||||
mask_sensitive_json() {
|
||||
if [[ "${EVERY_CHANNEL_ROBOT_PRINT_SENSITIVE:-0}" == "1" ]]; then
|
||||
cat
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq 'walk(if type == "object" and has("password") then .password = "<redacted>" else . end)'
|
||||
else
|
||||
sed -E 's/"password"[[:space:]]*:[[:space:]]*"[^"]*"/"password":"<redacted>"/g'
|
||||
fi
|
||||
}
|
||||
|
||||
authorized_key_fingerprint() {
|
||||
if [[ -n "${EVERY_CHANNEL_ROBOT_AUTHORIZED_KEY_FINGERPRINT:-}" ]]; then
|
||||
printf '%s\n' "${EVERY_CHANNEL_ROBOT_AUTHORIZED_KEY_FINGERPRINT}"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -f "${ssh_pub}" ]] && command -v ssh-keygen >/dev/null 2>&1; then
|
||||
ssh-keygen -E md5 -lf "${ssh_pub}" | awk '{print $2}' | sed 's/^MD5://'
|
||||
fi
|
||||
}
|
||||
|
||||
probe() {
|
||||
echo "== HTTPS via DNS =="
|
||||
curl --max-time 12 -I -sS "https://${host_name}/" || true
|
||||
echo
|
||||
echo "== HTTPS via pinned IP =="
|
||||
curl --max-time 12 -I -sS --resolve "${host_name}:443:${host_ip}" "https://${host_name}/" || true
|
||||
echo
|
||||
echo "== SSH =="
|
||||
ssh -o ConnectTimeout=12 -o BatchMode=yes -o IdentityAgent=none -o IdentitiesOnly=yes \
|
||||
-i "${ssh_identity}" "root@${host_ip}" echo ssh-ok || true
|
||||
}
|
||||
|
||||
server_metadata() {
|
||||
load_robot_auth
|
||||
robot_curl GET "/server/${server}" | mask_sensitive_json
|
||||
}
|
||||
|
||||
reset_status() {
|
||||
robot_curl GET "/reset/${server}" | mask_sensitive_json
|
||||
}
|
||||
|
||||
rescue_status() {
|
||||
load_robot_auth
|
||||
robot_curl GET "/boot/${server}/rescue" | mask_sensitive_json
|
||||
}
|
||||
|
||||
status() {
|
||||
load_robot_auth
|
||||
echo "== reset =="
|
||||
reset_status
|
||||
echo
|
||||
echo "== rescue =="
|
||||
robot_curl GET "/boot/${server}/rescue" | mask_sensitive_json
|
||||
}
|
||||
|
||||
activate_rescue() {
|
||||
load_robot_auth
|
||||
local rescue_os="${EVERY_CHANNEL_ROBOT_RESCUE_OS:-linux}"
|
||||
local keyboard="${EVERY_CHANNEL_ROBOT_RESCUE_KEYBOARD:-us}"
|
||||
local key_fingerprint
|
||||
key_fingerprint="$(authorized_key_fingerprint || true)"
|
||||
|
||||
local args=(--data-urlencode "os=${rescue_os}" --data-urlencode "keyboard=${keyboard}")
|
||||
if [[ -n "${key_fingerprint}" ]]; then
|
||||
args+=(--data-urlencode "authorized_key[]=${key_fingerprint}")
|
||||
fi
|
||||
|
||||
robot_curl POST "/boot/${server}/rescue" "${args[@]}" | mask_sensitive_json
|
||||
}
|
||||
|
||||
reset_server() {
|
||||
load_robot_auth
|
||||
local reset_type="${1:-${EVERY_CHANNEL_ROBOT_RESET_TYPE:-hw}}"
|
||||
robot_curl POST "/reset/${server}" --data-urlencode "type=${reset_type}" | mask_sensitive_json
|
||||
}
|
||||
|
||||
recover() {
|
||||
local reset_type="${1:-${EVERY_CHANNEL_ROBOT_RESET_TYPE:-hw}}"
|
||||
echo "== activate rescue =="
|
||||
activate_rescue
|
||||
echo
|
||||
echo "== reset ${reset_type} =="
|
||||
reset_server "${reset_type}"
|
||||
echo
|
||||
echo "Rescue boot requested. Run '$0 wait-ssh' to watch for SSH on ${host_ip}."
|
||||
}
|
||||
|
||||
wait_ssh() {
|
||||
local deadline="${EVERY_CHANNEL_FORGE_WAIT_SECONDS:-300}"
|
||||
local started
|
||||
started="$(date +%s)"
|
||||
|
||||
while true; do
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
if nc -z -w 5 "${host_ip}" 22 >/dev/null 2>&1; then
|
||||
echo "ssh port is reachable on ${host_ip}:22"
|
||||
return 0
|
||||
fi
|
||||
elif ssh -o ConnectTimeout=5 -o BatchMode=yes -o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null "root@${host_ip}" true >/dev/null 2>&1; then
|
||||
echo "ssh is reachable on ${host_ip}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( "$(date +%s)" - started >= deadline )); then
|
||||
echo "error: timed out waiting for SSH on ${host_ip}:22" >&2
|
||||
return 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
case "${cmd}" in
|
||||
""|-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
probe)
|
||||
probe
|
||||
;;
|
||||
server)
|
||||
require_cmd curl
|
||||
server_metadata
|
||||
;;
|
||||
status)
|
||||
require_cmd curl
|
||||
status
|
||||
;;
|
||||
rescue-status)
|
||||
require_cmd curl
|
||||
rescue_status
|
||||
;;
|
||||
activate-rescue)
|
||||
require_cmd curl
|
||||
activate_rescue
|
||||
;;
|
||||
reset)
|
||||
require_cmd curl
|
||||
reset_server "${2:-}"
|
||||
;;
|
||||
recover)
|
||||
require_cmd curl
|
||||
recover "${2:-}"
|
||||
;;
|
||||
wait-ssh)
|
||||
wait_ssh
|
||||
;;
|
||||
*)
|
||||
echo "error: unknown command: ${cmd}" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
netboot_root="${EVERY_CHANNEL_NETBOOT_ROOT:-tmp/netboot}"
|
||||
tftp_dir="${netboot_root}/tftp"
|
||||
netboot_hostname="${EVERY_CHANNEL_NETBOOT_HOSTNAME:-boot.every.channel}"
|
||||
http_port="${EVERY_CHANNEL_NETBOOT_HTTP_PORT:-8080}"
|
||||
ipxe_repo="${EVERY_CHANNEL_NETBOOT_IPXE_REPO:-https://github.com/ipxe/ipxe.git}"
|
||||
ipxe_ref="${EVERY_CHANNEL_NETBOOT_IPXE_REF:-}"
|
||||
output_name="${EVERY_CHANNEL_NETBOOT_IPXE_FILENAME:-ec-ipxe.efi}"
|
||||
use_docker="${EVERY_CHANNEL_NETBOOT_IPXE_USE_DOCKER:-auto}"
|
||||
docker_image="${EVERY_CHANNEL_NETBOOT_IPXE_DOCKER_IMAGE:-ubuntu:24.04}"
|
||||
docker_platform="${EVERY_CHANNEL_NETBOOT_IPXE_DOCKER_PLATFORM:-linux/amd64}"
|
||||
chain_token="${EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN:-}"
|
||||
|
||||
need_cmd() {
|
||||
local name="$1"
|
||||
if ! command -v "${name}" >/dev/null 2>&1; then
|
||||
echo "error: required command not found: ${name}" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
validate_chain_token() {
|
||||
local value="$1"
|
||||
if [[ -z "${value}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ! "${value}" =~ ^[A-Za-z0-9._~-]{16,128}$ ]]; then
|
||||
echo "error: EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN must match [A-Za-z0-9._~-]{16,128}" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
validate_chain_token "${chain_token}"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "${tmp_dir}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
repo_dir="${tmp_dir}/ipxe"
|
||||
embed_script="${tmp_dir}/embed.ipxe"
|
||||
build_output="${tmp_dir}/ipxe.efi"
|
||||
|
||||
chain_url="http://${netboot_hostname}:${http_port}/netboot.ipxe"
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
chain_url="${chain_url}?token=${chain_token}"
|
||||
fi
|
||||
|
||||
cat > "${embed_script}" <<EOF
|
||||
#!ipxe
|
||||
dhcp
|
||||
chain ${chain_url} || shell
|
||||
EOF
|
||||
|
||||
bool_norm() {
|
||||
local raw
|
||||
raw="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
||||
case "${raw}" in
|
||||
''|auto) echo "auto" ;;
|
||||
true|1|yes|y|on) echo "true" ;;
|
||||
false|0|no|n|off) echo "false" ;;
|
||||
*)
|
||||
echo "error: invalid boolean value '${1}'" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
build_native() {
|
||||
need_cmd git
|
||||
need_cmd make
|
||||
if ! command -v cc >/dev/null 2>&1 && ! command -v gcc >/dev/null 2>&1; then
|
||||
echo "error: required C compiler not found (need cc or gcc)" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! command -v objcopy >/dev/null 2>&1; then
|
||||
echo "error: required tool not found: objcopy" >&2
|
||||
return 1
|
||||
fi
|
||||
git clone --depth 1 "${ipxe_repo}" "${repo_dir}" >/dev/null
|
||||
if [[ -n "${ipxe_ref}" ]]; then
|
||||
git -C "${repo_dir}" fetch --depth 1 origin "${ipxe_ref}" >/dev/null
|
||||
git -C "${repo_dir}" checkout -q FETCH_HEAD
|
||||
fi
|
||||
make -C "${repo_dir}/src" -s "bin-x86_64-efi/ipxe.efi" "EMBED=${embed_script}"
|
||||
cp -f "${repo_dir}/src/bin-x86_64-efi/ipxe.efi" "${build_output}"
|
||||
}
|
||||
|
||||
build_docker() {
|
||||
need_cmd docker
|
||||
docker run --rm \
|
||||
--platform "${docker_platform}" \
|
||||
-v "${tmp_dir}:/work" \
|
||||
-e IPXE_REPO="${ipxe_repo}" \
|
||||
-e IPXE_REF="${ipxe_ref}" \
|
||||
-w /work \
|
||||
"${docker_image}" \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
apt-get update >/dev/null
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates git make gcc binutils perl mtools >/dev/null
|
||||
git clone --depth 1 "${IPXE_REPO}" ipxe >/dev/null
|
||||
if [ -n "${IPXE_REF}" ]; then
|
||||
git -C ipxe fetch --depth 1 origin "${IPXE_REF}" >/dev/null
|
||||
git -C ipxe checkout -q FETCH_HEAD
|
||||
fi
|
||||
make -C ipxe/src -s "bin-x86_64-efi/ipxe.efi" "EMBED=/work/embed.ipxe"
|
||||
'
|
||||
cp -f "${tmp_dir}/ipxe/src/bin-x86_64-efi/ipxe.efi" "${build_output}"
|
||||
}
|
||||
|
||||
use_docker="$(bool_norm "${use_docker}")"
|
||||
build_ok="false"
|
||||
if [[ "${use_docker}" != "true" ]]; then
|
||||
if build_native; then
|
||||
build_ok="true"
|
||||
fi
|
||||
fi
|
||||
if [[ "${build_ok}" != "true" ]]; then
|
||||
if [[ "${use_docker}" == "false" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
build_docker
|
||||
fi
|
||||
|
||||
mkdir -p "${tftp_dir}"
|
||||
cp -f "${build_output}" "${tftp_dir}/${output_name}"
|
||||
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "${tftp_dir}/${output_name}" > "${tftp_dir}/${output_name}.sha256"
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "${tftp_dir}/${output_name}" > "${tftp_dir}/${output_name}.sha256"
|
||||
fi
|
||||
|
||||
echo "ok: built embedded iPXE binary: ${tftp_dir}/${output_name}"
|
||||
echo "ok: chains to ${chain_url}"
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
echo "ok: chain token enabled"
|
||||
fi
|
||||
echo "hint: in UniFi set DHCP boot filename to ${output_name}"
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Minimal HTTP server for netboot artifacts with optional CIDR/token controls."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ipaddress
|
||||
import pathlib
|
||||
import urllib.parse
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from typing import Union
|
||||
|
||||
|
||||
ALLOWED_FILES = {
|
||||
"kernel": "application/octet-stream",
|
||||
"bzImage": "application/octet-stream",
|
||||
"initrd": "application/octet-stream",
|
||||
"netboot.ipxe": "text/plain; charset=utf-8",
|
||||
}
|
||||
|
||||
Network = Union[ipaddress.IPv4Network, ipaddress.IPv6Network]
|
||||
|
||||
|
||||
class NetbootHTTPServer(ThreadingHTTPServer):
|
||||
def __init__(
|
||||
self,
|
||||
server_address: tuple[str, int],
|
||||
root: pathlib.Path,
|
||||
allowed_networks: list[Network],
|
||||
netboot_token: str | None,
|
||||
) -> None:
|
||||
super().__init__(server_address, NetbootRequestHandler)
|
||||
self.root = root
|
||||
self.allowed_networks = allowed_networks
|
||||
self.netboot_token = netboot_token
|
||||
|
||||
|
||||
class NetbootRequestHandler(BaseHTTPRequestHandler):
|
||||
server_version = "ec-netboot/1.0"
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def do_GET(self) -> None:
|
||||
self._serve_file(include_body=True)
|
||||
|
||||
def do_HEAD(self) -> None:
|
||||
self._serve_file(include_body=False)
|
||||
|
||||
def _serve_file(self, include_body: bool) -> None:
|
||||
if not self._client_allowed():
|
||||
self._send_error(HTTPStatus.FORBIDDEN, "client not allowed")
|
||||
return
|
||||
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
path = parsed.path.lstrip("/")
|
||||
if path not in ALLOWED_FILES:
|
||||
self._send_error(HTTPStatus.NOT_FOUND, "file not found")
|
||||
return
|
||||
|
||||
if path == "netboot.ipxe" and self.server.netboot_token:
|
||||
query = urllib.parse.parse_qs(parsed.query, keep_blank_values=True)
|
||||
token = query.get("token", [""])[0]
|
||||
if token != self.server.netboot_token:
|
||||
self._send_error(HTTPStatus.FORBIDDEN, "missing or invalid token")
|
||||
return
|
||||
|
||||
if path == "bzImage":
|
||||
file_path = self.server.root / "kernel"
|
||||
else:
|
||||
file_path = self.server.root / path
|
||||
if not file_path.is_file():
|
||||
self._send_error(HTTPStatus.NOT_FOUND, "file not found")
|
||||
return
|
||||
|
||||
stat = file_path.stat()
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", ALLOWED_FILES[path])
|
||||
self.send_header("Content-Length", str(stat.st_size))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.end_headers()
|
||||
|
||||
if not include_body:
|
||||
return
|
||||
|
||||
with file_path.open("rb") as handle:
|
||||
while True:
|
||||
chunk = handle.read(64 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
self.wfile.write(chunk)
|
||||
|
||||
def _client_allowed(self) -> bool:
|
||||
networks = self.server.allowed_networks
|
||||
if not networks:
|
||||
return True
|
||||
|
||||
try:
|
||||
client_ip = ipaddress.ip_address(self.client_address[0])
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
return any(client_ip in network for network in networks)
|
||||
|
||||
def _send_error(self, code: HTTPStatus, message: str) -> None:
|
||||
payload = (message + "\n").encode("utf-8")
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--bind-ip", required=True)
|
||||
parser.add_argument("--port", type=int, required=True)
|
||||
parser.add_argument("--root", required=True)
|
||||
parser.add_argument("--allow-cidr", action="append", default=[])
|
||||
parser.add_argument("--netboot-token", default="")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
root = pathlib.Path(args.root).resolve()
|
||||
if not root.is_dir():
|
||||
raise SystemExit(f"error: root directory does not exist: {root}")
|
||||
|
||||
allowed_networks = []
|
||||
for cidr in args.allow_cidr:
|
||||
try:
|
||||
allowed_networks.append(ipaddress.ip_network(cidr, strict=False))
|
||||
except ValueError as exc:
|
||||
raise SystemExit(f"error: invalid CIDR '{cidr}': {exc}") from exc
|
||||
|
||||
token = args.netboot_token or None
|
||||
server = NetbootHTTPServer((args.bind_ip, args.port), root, allowed_networks, token)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -14,10 +14,6 @@ proxy_subnet="${EVERY_CHANNEL_NETBOOT_PROXY_SUBNET:-}"
|
|||
netboot_hostname="${EVERY_CHANNEL_NETBOOT_HOSTNAME:-}"
|
||||
http_port="${EVERY_CHANNEL_NETBOOT_HTTP_PORT:-8080}"
|
||||
dnsmasq_port="${EVERY_CHANNEL_NETBOOT_DNS_PORT:-0}"
|
||||
proxy_dhcp="${EVERY_CHANNEL_NETBOOT_PROXY_DHCP:-true}"
|
||||
tftp_boot_filename="${EVERY_CHANNEL_NETBOOT_TFTP_BOOT_FILENAME:-ipxe.efi}"
|
||||
http_allowed_cidrs="${EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS:-}"
|
||||
chain_token="${EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN:-}"
|
||||
|
||||
need_cmd() {
|
||||
local name="$1"
|
||||
|
|
@ -30,43 +26,10 @@ need_cmd() {
|
|||
need_cmd dnsmasq
|
||||
need_cmd python3
|
||||
|
||||
bool_norm() {
|
||||
local raw
|
||||
raw="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
||||
case "${raw}" in
|
||||
''|true|1|yes|y|on) echo "true" ;;
|
||||
false|0|no|n|off) echo "false" ;;
|
||||
*)
|
||||
echo "error: invalid boolean value '${1}'" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
trim_ws() {
|
||||
local value="$1"
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
printf '%s' "${value}"
|
||||
}
|
||||
|
||||
validate_chain_token() {
|
||||
local value="$1"
|
||||
if [[ -z "${value}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ! "${value}" =~ ^[A-Za-z0-9._~-]{16,128}$ ]]; then
|
||||
echo "error: EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN must match [A-Za-z0-9._~-]{16,128}" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "error: netboot-serve requires root (TFTP + ProxyDHCP ports)." >&2
|
||||
echo "hint: run with sudo and pass env vars. Example (UniFi-only):" >&2
|
||||
echo " sudo EVERY_CHANNEL_NETBOOT_LISTEN_IP=10.20.30.2 EVERY_CHANNEL_NETBOOT_INTERFACE=eth0 EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel EVERY_CHANNEL_NETBOOT_PROXY_DHCP=false EVERY_CHANNEL_NETBOOT_TFTP_BOOT_FILENAME=ec-ipxe.efi ./scripts/netboot-serve.sh" >&2
|
||||
echo "hint: Example (ProxyDHCP):" >&2
|
||||
echo " sudo EVERY_CHANNEL_NETBOOT_LISTEN_IP=10.20.30.2 EVERY_CHANNEL_NETBOOT_INTERFACE=eth0 EVERY_CHANNEL_NETBOOT_PROXY_SUBNET=10.20.30.0/24 EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel EVERY_CHANNEL_NETBOOT_PROXY_DHCP=true ./scripts/netboot-serve.sh" >&2
|
||||
echo "hint: run with sudo and pass env vars, for example:" >&2
|
||||
echo " sudo EVERY_CHANNEL_NETBOOT_LISTEN_IP=10.20.30.2 EVERY_CHANNEL_NETBOOT_INTERFACE=eth0 EVERY_CHANNEL_NETBOOT_PROXY_SUBNET=10.20.30.0/24 EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel ./scripts/netboot-serve.sh" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
|
@ -78,25 +41,15 @@ if [[ -z "${interface_name}" ]]; then
|
|||
echo "error: set EVERY_CHANNEL_NETBOOT_INTERFACE (interface on NUC VLAN)" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "${proxy_subnet}" ]]; then
|
||||
echo "error: set EVERY_CHANNEL_NETBOOT_PROXY_SUBNET (for example 10.20.30.0/24)" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "${netboot_hostname}" ]]; then
|
||||
netboot_hostname="${listen_ip}"
|
||||
fi
|
||||
proxy_dhcp="$(bool_norm "${proxy_dhcp}")"
|
||||
validate_chain_token "${chain_token}"
|
||||
if [[ "${proxy_dhcp}" == "true" && -z "${proxy_subnet}" ]]; then
|
||||
echo "error: set EVERY_CHANNEL_NETBOOT_PROXY_SUBNET (for example 10.20.30.0/24) when proxy mode is enabled" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "${http_allowed_cidrs}" && "${proxy_dhcp}" == "true" ]]; then
|
||||
http_allowed_cidrs="${proxy_subnet}"
|
||||
fi
|
||||
|
||||
netboot_chain_url="http://${netboot_hostname}:${http_port}/netboot.ipxe"
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
netboot_chain_url="${netboot_chain_url}?token=${chain_token}"
|
||||
fi
|
||||
|
||||
for required in "${http_dir}/kernel" "${http_dir}/initrd" "${http_dir}/netboot.ipxe" "${tftp_dir}/${tftp_boot_filename}"; do
|
||||
for required in "${http_dir}/kernel" "${http_dir}/initrd" "${http_dir}/netboot.ipxe" "${tftp_dir}/ipxe.efi"; do
|
||||
if [[ ! -f "${required}" ]]; then
|
||||
echo "error: missing required staged file: ${required}" >&2
|
||||
echo "hint: run ./scripts/netboot-stage.sh first" >&2
|
||||
|
|
@ -122,73 +75,25 @@ listen-address=${listen_ip}
|
|||
log-dhcp
|
||||
enable-tftp
|
||||
tftp-root=${tftp_dir}
|
||||
EOF
|
||||
|
||||
if [[ "${proxy_dhcp}" == "true" ]]; then
|
||||
cat >> "${run_dir}/dnsmasq.conf" <<EOF
|
||||
dhcp-range=${proxy_subnet},proxy
|
||||
dhcp-userclass=set:ipxe,iPXE
|
||||
dhcp-match=set:efi64,option:client-arch,7
|
||||
dhcp-match=set:efi64,option:client-arch,9
|
||||
dhcp-option=66,${netboot_hostname}
|
||||
dhcp-boot=tag:!ipxe,tag:efi64,${tftp_boot_filename}
|
||||
dhcp-boot=tag:ipxe,tag:efi64,${netboot_chain_url}
|
||||
dhcp-boot=tag:!ipxe,${tftp_boot_filename}
|
||||
dhcp-boot=tag:ipxe,${netboot_chain_url}
|
||||
dhcp-boot=tag:!ipxe,tag:efi64,ipxe.efi
|
||||
dhcp-boot=tag:ipxe,tag:efi64,http://${netboot_hostname}:${http_port}/netboot.ipxe
|
||||
dhcp-boot=tag:!ipxe,ipxe.efi
|
||||
dhcp-boot=tag:ipxe,http://${netboot_hostname}:${http_port}/netboot.ipxe
|
||||
EOF
|
||||
fi
|
||||
|
||||
http_server_script="${root}/scripts/netboot-http-server.py"
|
||||
if [[ ! -f "${http_server_script}" ]]; then
|
||||
echo "error: missing HTTP server helper: ${http_server_script}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
http_args=(python3 "${http_server_script}" --bind-ip "${listen_ip}" --port "${http_port}" --root "${http_dir}")
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
http_args+=(--netboot-token "${chain_token}")
|
||||
fi
|
||||
|
||||
if [[ -n "${http_allowed_cidrs}" ]]; then
|
||||
IFS=',' read -r -a cidr_raw <<< "${http_allowed_cidrs}"
|
||||
for raw in "${cidr_raw[@]}"; do
|
||||
cidr="$(trim_ws "${raw}")"
|
||||
if [[ -n "${cidr}" ]]; then
|
||||
http_args+=(--allow-cidr "${cidr}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
http_log="${run_dir}/http.log"
|
||||
"${http_args[@]}" >"${http_log}" 2>&1 &
|
||||
python3 -m http.server "${http_port}" --bind "${listen_ip}" --directory "${http_dir}" >/tmp/every-channel-netboot-http.log 2>&1 &
|
||||
http_pid="$!"
|
||||
sleep 0.2
|
||||
if ! kill -0 "${http_pid}" >/dev/null 2>&1; then
|
||||
echo "error: HTTP server failed to start; see ${http_log}" >&2
|
||||
cat "${http_log}" >&2 || true
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "ok: HTTP serving ${http_dir} on http://${listen_ip}:${http_port}/"
|
||||
echo "ok: advertised netboot host: ${netboot_hostname}"
|
||||
echo "ok: TFTP serving ${tftp_dir} on ${listen_ip}:69"
|
||||
echo "ok: TFTP boot filename: ${tftp_boot_filename}"
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
echo "ok: chain token enabled"
|
||||
fi
|
||||
if [[ -n "${http_allowed_cidrs}" ]]; then
|
||||
echo "ok: HTTP allowed CIDRs: ${http_allowed_cidrs}"
|
||||
else
|
||||
echo "warning: HTTP CIDR allowlist is disabled; set EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS to lock this down"
|
||||
fi
|
||||
if [[ "${proxy_dhcp}" == "true" ]]; then
|
||||
echo "ok: ProxyDHCP active for ${proxy_subnet} on interface ${interface_name}"
|
||||
echo "ok: Use normal Unifi DHCP for IP assignment; do not configure Unifi DHCP bootfile while proxy mode is active."
|
||||
else
|
||||
echo "ok: ProxyDHCP disabled."
|
||||
echo "ok: Configure UniFi DHCP option 66=${netboot_hostname}, option 67=${tftp_boot_filename}"
|
||||
fi
|
||||
echo "ok: chain URL: ${netboot_chain_url}"
|
||||
echo "ok: ProxyDHCP active for ${proxy_subnet} on interface ${interface_name}"
|
||||
echo "ok: Use normal Unifi DHCP for IP assignment; do not configure Unifi DHCP bootfile while proxy mode is active."
|
||||
echo
|
||||
echo "Press Ctrl+C to stop."
|
||||
dnsmasq --no-daemon --conf-file="${run_dir}/dnsmasq.conf"
|
||||
|
|
|
|||
|
|
@ -4,20 +4,14 @@ set -euo pipefail
|
|||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${root}"
|
||||
|
||||
forge_host="${EVERY_CHANNEL_FORGE_HOST:-https://git.every.channel}"
|
||||
forge_host="${EVERY_CHANNEL_FORGE_HOST:-https://forge.every.channel}"
|
||||
forge_repo="${EVERY_CHANNEL_FORGE_REPO:-every-channel/every.channel}"
|
||||
release_tag="${EVERY_CHANNEL_NETBOOT_RELEASE_TAG:-}"
|
||||
local_tarball="${EVERY_CHANNEL_NETBOOT_TARBALL:-}"
|
||||
out_root="${EVERY_CHANNEL_NETBOOT_ROOT:-tmp/netboot}"
|
||||
ipxe_efi_url="${EVERY_CHANNEL_IPXE_EFI_URL:-https://boot.ipxe.org/snponly.efi}"
|
||||
ipxe_efi_path="${EVERY_CHANNEL_IPXE_EFI_PATH:-}"
|
||||
ipxe_efi_filename="${EVERY_CHANNEL_IPXE_EFI_FILENAME:-ipxe.efi}"
|
||||
netboot_hostname="${EVERY_CHANNEL_NETBOOT_HOSTNAME:-boot.every.channel}"
|
||||
http_port="${EVERY_CHANNEL_NETBOOT_HTTP_PORT:-8080}"
|
||||
verify_release_checksums="${EVERY_CHANNEL_NETBOOT_VERIFY_RELEASE_CHECKSUMS:-true}"
|
||||
allow_remote_ipxe="${EVERY_CHANNEL_NETBOOT_ALLOW_REMOTE_IPXE:-false}"
|
||||
ipxe_efi_sha256="${EVERY_CHANNEL_IPXE_EFI_SHA256:-}"
|
||||
chain_token="${EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN:-}"
|
||||
token="${EVERY_CHANNEL_FORGE_TOKEN:-${FORGE_TOKEN:-${CODEBERG_TOKEN:-}}}"
|
||||
|
||||
need_cmd() {
|
||||
|
|
@ -32,54 +26,6 @@ need_cmd curl
|
|||
need_cmd tar
|
||||
need_cmd python3
|
||||
|
||||
bool_norm() {
|
||||
local raw
|
||||
raw="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
||||
case "${raw}" in
|
||||
''|true|1|yes|y|on) echo "true" ;;
|
||||
false|0|no|n|off) echo "false" ;;
|
||||
*)
|
||||
echo "error: invalid boolean value '${1}'" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_sha256() {
|
||||
local file_path="$1"
|
||||
local expected="$2"
|
||||
local actual
|
||||
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
actual="$(sha256sum "${file_path}" | awk '{print $1}')"
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
actual="$(shasum -a 256 "${file_path}" | awk '{print $1}')"
|
||||
else
|
||||
echo "error: checksum verification requested but no sha256 tool is available" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ "${actual}" != "${expected}" ]]; then
|
||||
echo "error: checksum mismatch for ${file_path}" >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "$(basename "${file_path}"): OK"
|
||||
}
|
||||
|
||||
validate_chain_token() {
|
||||
local value="$1"
|
||||
if [[ -z "${value}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ! "${value}" =~ ^[A-Za-z0-9._~-]{16,128}$ ]]; then
|
||||
echo "error: EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN must match [A-Za-z0-9._~-]{16,128}" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
allow_remote_ipxe="$(bool_norm "${allow_remote_ipxe}")"
|
||||
validate_chain_token "${chain_token}"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "${tmp_dir}"
|
||||
|
|
@ -88,8 +34,6 @@ trap cleanup EXIT
|
|||
|
||||
archive_path="${tmp_dir}/netboot.tar.gz"
|
||||
release_asset_url=""
|
||||
release_asset_name=""
|
||||
checksum_asset_url=""
|
||||
|
||||
if [[ -n "${local_tarball}" ]]; then
|
||||
if [[ ! -f "${local_tarball}" ]]; then
|
||||
|
|
@ -112,7 +56,7 @@ else
|
|||
release_json="${tmp_dir}/release.json"
|
||||
curl -fsSL "${auth_args[@]}" "${release_endpoint}" -o "${release_json}"
|
||||
|
||||
parsed_assets="$(
|
||||
release_asset_url="$(
|
||||
python3 - "${release_json}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
|
@ -133,20 +77,9 @@ if not candidates:
|
|||
|
||||
# Pick newest by release ordering if API already sorted; otherwise prefer largest id.
|
||||
chosen = sorted(candidates, key=lambda x: x.get("id", 0))[-1]
|
||||
checksum_asset = None
|
||||
for asset in assets:
|
||||
if asset.get("name") == "SHA256SUMS.txt":
|
||||
checksum_asset = asset
|
||||
break
|
||||
|
||||
print(chosen.get("name", ""))
|
||||
print(chosen.get("browser_download_url", ""))
|
||||
print("" if checksum_asset is None else checksum_asset.get("browser_download_url", ""))
|
||||
PY
|
||||
)"
|
||||
release_asset_name="$(printf '%s\n' "${parsed_assets}" | sed -n '1p')"
|
||||
release_asset_url="$(printf '%s\n' "${parsed_assets}" | sed -n '2p')"
|
||||
checksum_asset_url="$(printf '%s\n' "${parsed_assets}" | sed -n '3p')"
|
||||
|
||||
if [[ -z "${release_asset_url}" ]]; then
|
||||
echo "error: unable to find x86_64 netboot asset in release" >&2
|
||||
|
|
@ -154,28 +87,6 @@ PY
|
|||
fi
|
||||
|
||||
curl -fsSL "${auth_args[@]}" -o "${archive_path}" "${release_asset_url}"
|
||||
|
||||
verify_release_checksums_lc="$(printf '%s' "${verify_release_checksums}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "${verify_release_checksums_lc}" != "false" && -n "${checksum_asset_url}" ]]; then
|
||||
checksum_file="${tmp_dir}/SHA256SUMS.txt"
|
||||
curl -fsSL "${auth_args[@]}" -o "${checksum_file}" "${checksum_asset_url}"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
(
|
||||
cd "${tmp_dir}"
|
||||
grep -F " ${release_asset_name}" "${checksum_file}" | sha256sum -c -
|
||||
)
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
expected="$(grep -F " ${release_asset_name}" "${checksum_file}" | awk '{print $1}')"
|
||||
actual="$(shasum -a 256 "${archive_path}" | awk '{print $1}')"
|
||||
if [[ -z "${expected}" || "${expected}" != "${actual}" ]]; then
|
||||
echo "error: checksum mismatch for ${release_asset_name}" >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "${release_asset_name}: OK"
|
||||
else
|
||||
echo "warning: no sha256 tool available; skipping checksum verification"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
http_dir="${out_root}/http"
|
||||
|
|
@ -192,47 +103,23 @@ for required in kernel initrd netboot.ipxe; do
|
|||
fi
|
||||
done
|
||||
|
||||
if [[ -n "${ipxe_efi_path}" ]]; then
|
||||
if [[ ! -f "${ipxe_efi_path}" ]]; then
|
||||
echo "error: iPXE file path not found: ${ipxe_efi_path}" >&2
|
||||
exit 2
|
||||
fi
|
||||
ipxe_dest="${tftp_dir}/${ipxe_efi_filename}"
|
||||
src_resolved="$(realpath -m "${ipxe_efi_path}")"
|
||||
dst_resolved="$(realpath -m "${ipxe_dest}")"
|
||||
# When embedded iPXE already wrote to the destination path, skip self-copy.
|
||||
if [[ "${src_resolved}" != "${dst_resolved}" ]]; then
|
||||
cp -f "${ipxe_efi_path}" "${ipxe_dest}"
|
||||
fi
|
||||
else
|
||||
if [[ "${allow_remote_ipxe}" != "true" ]]; then
|
||||
echo "error: remote iPXE download is disabled by default" >&2
|
||||
echo "hint: build local iPXE with ./scripts/netboot-build-ipxe.sh and set EVERY_CHANNEL_IPXE_EFI_PATH" >&2
|
||||
echo "hint: if you must download from URL, set EVERY_CHANNEL_NETBOOT_ALLOW_REMOTE_IPXE=true" >&2
|
||||
exit 2
|
||||
fi
|
||||
curl -fsSL -o "${tftp_dir}/${ipxe_efi_filename}" "${ipxe_efi_url}"
|
||||
fi
|
||||
if [[ -n "${ipxe_efi_sha256}" ]]; then
|
||||
check_sha256 "${tftp_dir}/${ipxe_efi_filename}" "${ipxe_efi_sha256}"
|
||||
fi
|
||||
curl -fsSL -o "${tftp_dir}/ipxe.efi" "${ipxe_efi_url}"
|
||||
cp -f "${http_dir}/netboot.ipxe" "${tftp_dir}/netboot.ipxe"
|
||||
|
||||
chain_url="http://${netboot_hostname}:${http_port}/netboot.ipxe"
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
chain_url="${chain_url}?token=${chain_token}"
|
||||
fi
|
||||
|
||||
cat > "${tftp_dir}/bootstrap.ipxe" <<EOF
|
||||
cat > "${tftp_dir}/bootstrap.ipxe" <<'EOF'
|
||||
#!ipxe
|
||||
dhcp
|
||||
chain ${chain_url}
|
||||
chain http://__NETBOOT_HOST__:__HTTP_PORT__/netboot.ipxe
|
||||
EOF
|
||||
sed -i.bak \
|
||||
-e "s#__NETBOOT_HOST__#${netboot_hostname}#g" \
|
||||
-e "s#__HTTP_PORT__#${http_port}#g" \
|
||||
"${tftp_dir}/bootstrap.ipxe"
|
||||
rm -f "${tftp_dir}/bootstrap.ipxe.bak"
|
||||
|
||||
echo "ok: staged netboot content"
|
||||
echo "ok: http root: ${http_dir}"
|
||||
echo "ok: tftp root: ${tftp_dir}"
|
||||
echo "ok: boot filename: ${ipxe_efi_filename}"
|
||||
echo "ok: netboot hostname: ${netboot_hostname}"
|
||||
echo "ok: netboot http port: ${http_port}"
|
||||
if [[ -n "${release_asset_url}" ]]; then
|
||||
|
|
@ -240,7 +127,4 @@ if [[ -n "${release_asset_url}" ]]; then
|
|||
else
|
||||
echo "ok: source asset: ${local_tarball}"
|
||||
fi
|
||||
if [[ -n "${chain_token}" ]]; then
|
||||
echo "ok: chain token enabled"
|
||||
fi
|
||||
echo "hint: run sudo ./scripts/netboot-serve.sh to expose HTTP+TFTP (+ optional ProxyDHCP)"
|
||||
echo "hint: run sudo ./scripts/netboot-serve.sh to expose HTTP+TFTP+ProxyDHCP"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue