# How Configuration Files Are Managed in Bilistream: YAML-Based Runtime Loading

> Discover how bilistream expertly manages runtime configuration using a single YAML file. Learn about strongly-typed Rust structs and optional field support for flexible setup.

- Repository: [InitCool/bilistream](https://github.com/limitcool/bilistream)
- Tags: internals
- Published: 2026-03-06

---

**Bilistream manages runtime configuration through a single [`config.yaml`](https://github.com/limitcool/bilistream/blob/main/config.yaml) file parsed into a strongly-typed Rust struct using Serde, with optional fields supported via `Option<T>` types.**

The `limitcool/bilistream` project centralizes **configuration files** through a YAML-based system that prioritizes type safety and runtime flexibility. All settings are defined in a strongly-typed Rust struct and loaded at startup in [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs), allowing users to customize streaming parameters without recompiling the application.

## Configuration File Schema in [`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs)

The structure for **configuration files** is defined in [`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs), where the `Config` struct (lines 5-27 and 30-44) declares every configurable field using Serde annotations. This schema covers BiliBili authentication credentials, Twitch and YouTube room identifiers, polling intervals, and optional integrations like Gotify notifications or email alerts.

Optional sections use `Option<T>` wrappers, enabling the YAML file to omit unused features without causing deserialization errors. Fields utilize `#[serde(rename = "...")]` attributes to maintain user-friendly lowercase YAML keys while preserving Rust's naming conventions in the codebase.

## Loading Configuration Files at Runtime

The application loads **configuration files** at startup through the `load_config` function implemented in [`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs) at lines 88-94. This function accepts a `Path` argument, opens the YAML file, and deserializes its contents into the `Config` struct using `serde_yaml`.

In [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) at lines 35-36, the entry point invokes `load_config(Path::new("./config.yaml"))` to initialize the configuration. The resulting `Config` instance is cloned and distributed to modules requiring access to streaming credentials or platform settings, ensuring consistent state across the application.

## Working with Configuration Files

### Example [`config.yaml`](https://github.com/limitcool/bilistream/blob/main/config.yaml) Structure

Place this file at the repository root alongside the binary:

```yaml
BiliLive:
  SESSDATA: "your_sessdata"
  bili_jct: "your_csrf_token"
  DedeUserID: "your_user_id"
  DedeUserID__ckMd5: "user_id_md5"
  Room: 123456
  BiliRtmpUrl: "rtmp://example.com/live/"
  BiliRtmpKey: "stream_key"

Twitch:
  Room: "twitch_channel_name"

Youtube:
  Room: "youtube_channel_id"
  AccessToken: "ya29.xxxxxx"

Interval: 60
Platform: "BiliLive"
Email: null
YoutubePreviewLive:
  ChannelId: "UCxxxxxxx"
FfmpegProxy: null
Gotify:
  Url: "https://gotify.example.com"
  Token: "your_gotify_token"
Cookies: null

```

### Accessing Parsed Configuration Data

Retrieve nested values by accessing struct fields directly after loading:

```rust
let cfg = load_config(Path::new("./config.yaml")).unwrap();
let rtmp_url = format!("{}{}", cfg.bililive.bili_rtmp_url, cfg.bililive.bili_rtmp_key);

```

The configuration instance is typically cloned when passed to streaming modules:

```rust
let mut r = select_live(cfg.clone()).await.unwrap();

```

## Summary

- Bilistream uses a single [`config.yaml`](https://github.com/limitcool/bilistream/blob/main/config.yaml) file located at the project root for all runtime settings.
- The `Config` struct in [`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs) defines the schema with `Option<T>` fields for optional features like Gotify and email notifications.
- The `load_config` function handles YAML parsing via Serde, returning a strongly-typed configuration instance.
- The main entry point in [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) initializes the configuration at startup and distributes clones to dependent modules.

## Frequently Asked Questions

### Where does Bilistream look for the configuration file?

The application searches for [`./config.yaml`](https://github.com/limitcool/bilistream/blob/main/./config.yaml) in the working directory relative to the binary execution path. The [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) entry point explicitly calls `load_config(Path::new("./config.yaml"))` at lines 35-36.

### What happens if optional configuration sections are missing?

The program continues without error because optional fields like `Email`, `Gotify`, and `Cookies` use `Option<T>` types in the `Config` struct. Serde deserializes missing YAML keys as `None` values rather than raising validation errors.

### Which Rust crates handle the configuration parsing?

Bilistream relies on `serde` for struct serialization traits and `serde_yaml` for YAML deserialization, as declared in [`Cargo.toml`](https://github.com/limitcool/bilistream/blob/main/Cargo.toml). These dependencies map the [`config.yaml`](https://github.com/limitcool/bilistream/blob/main/config.yaml) contents directly onto the strongly-typed `Config` struct.

### Can I modify the configuration file while the application is running?

Changes require an application restart because `load_config` executes once at startup in [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs). The configuration is loaded into memory as a `Config` instance and cloned for use across modules, meaning runtime file modifications do not trigger hot-reloads.