diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5713e04 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# Copy this file to .env and load it in your shell or CI before building. +# Pishoo does not load .env files automatically. + +DHTTP_BOOTSTRAP_URL=https://bootstrap.genmeta.net +DHTTP_H3_DNS_SERVER=https://ddns.genmeta.net:4433 +DHTTP_MDNS_SERVICE="_dhttp.local" +DHTTP_ROOT_CA_PEM="-----BEGIN CERTIFICATE----- +MIICVTCCAdqgAwIBAgIUcBI2Xq2ZS+SYgLrPjqN0eqCf5a8wCgYIKoZIzj0EAwMw +WTELMAkGA1UEBhMCQ04xETAPBgNVBAgMCEhvbmdLb25nMRwwGgYDVQQKDBNHZW5t +ZXRhIEVDQyBSb290IENBMRkwFwYDVQQDDBByb290Lmdlbm1ldGEubmV0MB4XDTI1 +MDQyMTA1MDEwNFoXDTQ1MDQxNjA1MDEwNFowWTELMAkGA1UEBhMCQ04xETAPBgNV +BAgMCEhvbmdLb25nMRwwGgYDVQQKDBNHZW5tZXRhIEVDQyBSb290IENBMRkwFwYD +VQQDDBByb290Lmdlbm1ldGEubmV0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA81Y +m6nFz+c58mDquLk2KhZufUejy6js+Ru9AsrRyFIHCHbTQSOlkK+QXZMqRBkBSA+n +Gy0mnkf0zeXj9NFqTYveIrgDtXv/WoD3eadAyD8CAu5O0/XsyQGaP/bS+sEeo2Mw +YTAdBgNVHQ4EFgQUXgbKWqQlEhGkIdqW+slZ0wTtCS8wHwYDVR0jBBgwFoAUXgbK +WqQlEhGkIdqW+slZ0wTtCS8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwCgYIKoZIzj0EAwMDaQAwZgIxALCdmweGeSRn+B5IblH1emNjT2kw3CKO8l1g +jYBag7kqGK8ZGjwx9xpC5QMn3hw8qQIxAKKnzCktBfssKJ5HMEHuWCg0rw/FHmpu +PrFCruzsQiLcBa+GAP2O7Qbl+XAlK+MNiA== +-----END CERTIFICATE-----" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b91386b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000..d131826 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,28 @@ +name: Coverage + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install PAM development libraries + run: | + sudo apt-get update + sudo apt-get install --assume-yes --no-install-recommends libpam0g-dev + - uses: taiki-e/install-action@cargo-llvm-cov + # Limit test parallelism to 1 thread to avoid resource contention + - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info -- --test-threads=1 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: true diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..dda4393 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,14 @@ +name: Commitlint + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: wagoid/commitlint-github-action@v5 diff --git a/.github/workflows/feishu-bot.yml b/.github/workflows/feishu-bot.yml new file mode 100644 index 0000000..ff5162f --- /dev/null +++ b/.github/workflows/feishu-bot.yml @@ -0,0 +1,66 @@ +name: feishu bot + +on: + branch_protection_rule: + types: [created, deleted] + check_run: + types: [rerequested, completed] + check_suite: + types: [completed] + create: + delete: + deployment_status: + discussion: + types: [created, edited, answered] + discussion_comment: + types: [created, deleted] + fork: + gollum: + issues: + types: [opened, edited, milestoned, pinned, reopened] + issue_comment: + types: [created, deleted] + label: + types: [created, deleted] + merge_group: + types: [checks_requested] + milestone: + types: [opened, deleted] + page_build: + project: + types: [created, deleted, reopened] + project_card: + types: [created, deleted] + project_column: + types: [created, deleted] + public: + pull_request: + branches: ["main"] + types: [opened, reopened] + pull_request_review: + types: [edited, dismissed, submitted] + pull_request_review_comment: + types: [created, edited, deleted] + pull_request_target: + types: [assigned, opened, synchronize, reopened] + push: + branches: ["main"] + registry_package: + types: [published] + release: + types: [published] + status: + watch: + types: [started] + # schedule: + # - cron: "30 2 * * *" + +jobs: + send-event: + name: Webhook + runs-on: ubuntu-latest + steps: + - uses: KaminariOS/feishu-bot-webhook-action@main + with: + webhook: ${{ secrets.FEISHU_BOT_WEBHOOK }} + signkey: ${{ secrets.FEISHU_BOT_SIGNKEY }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..07f9a85 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,76 @@ +name: Rust + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + matrix: + target: [ubuntu, macos, windows] + fail-fast: false + runs-on: ${{ matrix.target }}-latest + steps: + - uses: actions/checkout@v4 + - name: Install latest rust stable toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Build + run: cargo build --verbose + - name: Run tests + # Limit test parallelism to 1 thread to avoid resource contention + # GitHub runners have limited cores (ubuntu/windows=2, macos=3) + run: cargo test --workspace --verbose -- --test-threads=1 + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install latest rust nightly toolchain and rustfmt + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + components: rustfmt + - name: Run rustfmt + run: cargo +nightly fmt --all -- --check + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install latest rust nightly toolchain with clippy + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + components: clippy + - name: Run clippy + run: cargo +nightly clippy --all-targets --all-features -- -Dwarnings + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install latest rust nightly toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + - name: Run doc + run: RUSTDOCFLAGS="-D warnings" cargo +nightly doc --no-deps + + msrv: + strategy: + matrix: + target: [ubuntu, macos, windows] + fail-fast: false + runs-on: ${{ matrix.target }}-latest + steps: + - uses: actions/checkout@v4 + - name: Install msrv toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.89.0 + - name: Build with msrv + run: cargo build --workspace --release diff --git a/.gitignore b/.gitignore index 347277f..adc8861 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,40 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +xtask/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock -/target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# cago-tarpaulin (coverage tool) generates this +tarpaulin-report.html + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb +.vscode/* +.idea/ +*.log +.DS_Store + +# Local environment configuration (keep the shared template in version control) +.env +.env.local +.env.*.local +!.env.example + +# Agent instructions AGENTS.md +.serena + +# Reference repos +refs/ +.ace-tool/ +.sisyphus/ + +doc +tmp/ diff --git a/home/src/identity/ssl.rs b/home/src/identity/ssl.rs index 7355be3..bf3131c 100644 --- a/home/src/identity/ssl.rs +++ b/home/src/identity/ssl.rs @@ -407,6 +407,10 @@ impl DhttpHome { Err(error) if error.kind() == io::ErrorKind::NotFound => { resolve_identity_profile_error::WildcardNotFoundSnafu { path: profile_path }.fail() } + #[cfg(windows)] + Err(error) if error.kind() == io::ErrorKind::InvalidFilename => { + resolve_identity_profile_error::WildcardNotFoundSnafu { path: profile_path }.fail() + } Err(error) => { Err(error).context(resolve_identity_profile_error::WildcardMetadataSnafu { path: profile_path, @@ -449,13 +453,14 @@ impl DhttpHome { let Some(e) = read_dir.next_entry().await.context(ReadDirSnafu { path })? else { return Ok(None); }; - if let (entry_path, name) = (e.path(), e.file_name()) - && e.file_type() - .await - .context(ReadFtySnafu { - path: entry_path.clone(), - })? - .is_dir() + let entry_path = e.path(); + let name = e.file_name(); + if e.file_type() + .await + .context(ReadFtySnafu { + path: entry_path.clone(), + })? + .is_dir() && let Ok(name) = name.to_string_lossy().as_ref().parse::() && fs::metadata(entry_path.join(SSL_DIR_NAME)).await.is_ok() { @@ -929,18 +934,22 @@ mod tests { } #[tokio::test] - async fn missing_key_reports_key_metadata_path() { + async fn missing_key_reports_key_path() { let temp = TempDir::new("missing-key"); let profile = IdentityProfile::try_from(temp.path().join("reimu.pilot")).unwrap(); let error = profile.load_key().await.unwrap_err(); - match error { - LoadKeyError::Metadata { path, .. } => { - assert_eq!(path, profile.ssl_dir().join(KEY_FILE_NAME)); - } - other => panic!("expected key metadata error, got {other:?}"), - } + #[cfg(unix)] + assert!(matches!( + error, + LoadKeyError::Metadata { path, .. } if path == profile.ssl_dir().join(KEY_FILE_NAME) + )); + #[cfg(not(unix))] + assert!(matches!( + error, + LoadKeyError::Read { path, .. } if path == profile.ssl_dir().join(KEY_FILE_NAME) + )); } #[tokio::test] diff --git a/identity/src/name.rs b/identity/src/name.rs index c84d7fc..d792822 100644 --- a/identity/src/name.rs +++ b/identity/src/name.rs @@ -338,7 +338,7 @@ pub enum InvalidName { // Name<'a> — DNS name, always lowercase // ============================================================================ -/// A DNS name stored as either a borrowed `&str` or an owned [`BytesStr`]. +/// A DNS name stored as either a borrowed `&str` or an owned byte-backed string. /// /// All names are normalised to ASCII lowercase. The type implements /// [`Borrow`] so that it can be used as a key in `HashMap` / `DashMap` diff --git a/log/src/record.rs b/log/src/record.rs index aac1b20..d5c9dfe 100644 --- a/log/src/record.rs +++ b/log/src/record.rs @@ -14,7 +14,7 @@ pub const MAX_RECORD_LEN: usize = 64 * 1024; /// - no carriage return or line feed appears before the record delimiter; /// - exactly one line feed terminates the record. /// -/// Construction is private to [`RecordBuilder::finish`]. +/// Construction is private to `RecordBuilder::finish`. #[derive(Clone, Debug, Eq, PartialEq)] pub struct FormattedRecord(Box<[u8]>);