From 9ece66a9ec7ddb59b971e1e6ac2adddac6e986e9 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sun, 18 May 2025 23:22:13 -0700 Subject: [PATCH 01/19] Make challenge image build Dockerfile relative to specified build context This is what docker, podman, etc all do and we should match that expected behaviour here. Signed-off-by: Robert Detjens --- src/builder/docker.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/builder/docker.rs b/src/builder/docker.rs index e3fb4b4..f336974 100644 --- a/src/builder/docker.rs +++ b/src/builder/docker.rs @@ -39,7 +39,10 @@ pub async fn build_image( let client = docker().await?; let build_opts = BuildImageOptions { - dockerfile: options.dockerfile.clone(), + dockerfile: context + .join(&options.dockerfile) + .to_string_lossy() + .into_owned(), // coerce Cow str to String explicitly buildargs: options.args.clone(), t: tag.to_string(), forcerm: true, From 33599f7518c15f15b0bbd24f309c9105c68da1ca Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sun, 18 May 2025 23:24:22 -0700 Subject: [PATCH 02/19] Rename challenge.yaml flag string option to be clearer text is less intuitive than string since we all are programmers and strings are strings, not text Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 17 +++++++++++++---- src/deploy/frontend.rs | 2 +- src/tests/parsing/challenges.rs | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index 18c675d..20f1e21 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -231,10 +231,19 @@ fn default_difficulty() -> i64 { #[fully_pub] enum FlagType { RawString(String), - File { file: PathBuf }, - Text { text: String }, - Regex { regex: String }, - Verifier { verifier: String }, + File { + file: PathBuf, + }, + String { + #[serde(alias = "text")] + string: String, + }, + Regex { + regex: String, + }, + Verifier { + verifier: String, + }, } // Parse each distinct kind of Provide action as a separate enum variant diff --git a/src/deploy/frontend.rs b/src/deploy/frontend.rs index 8e063e6..4a511fb 100644 --- a/src/deploy/frontend.rs +++ b/src/deploy/frontend.rs @@ -75,7 +75,7 @@ pub async fn update_frontend( .read_to_string(&mut flag); flag } - FlagType::Text { text } => text.clone(), + FlagType::String { string: text } => text.clone(), FlagType::Regex { regex } => unimplemented!(), FlagType::Verifier { verifier } => unimplemented!(), }; diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index 5e6a319..ff10521 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -88,8 +88,8 @@ fn challenge_two_levels() { category: "foo".to_string(), directory: PathBuf::from("foo/test"), - flag: FlagType::Text { - text: "test{it-works}".to_string() + flag: FlagType::String { + string: "test{it-works}".to_string() }, provide: vec![], From b3d6af907431f6eaefaea1d7354be44ef7d9023f Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Mon, 19 May 2025 00:07:17 -0700 Subject: [PATCH 03/19] Apply default resources from global config to challenge pods Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 11 ++- src/configparser/config.rs | 2 +- src/tests/parsing/challenges.rs | 124 ++++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index 20f1e21..bb8fb74 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -74,9 +74,11 @@ pub fn parse_one(path: &PathBuf) -> Result { .merge(Serialized::default("category", category)) .extract()?; - // coerce pod env lists to maps - // TODO: do this in serde deserialize? + let config = get_config()?; + for pod in parsed.pods.iter_mut() { + // coerce pod env lists to maps + // TODO: do this in serde deserialize? pod.env = match pod.env.clone() { ListOrMap::Map(m) => ListOrMap::Map(m), ListOrMap::List(l) => { @@ -104,6 +106,11 @@ pub fn parse_one(path: &PathBuf) -> Result { }); ListOrMap::Map(map) } + }; + + // set default resources from global config + if pod.resources.is_none() { + pod.resources = Some(config.defaults.resources.clone()) } } diff --git a/src/configparser/config.rs b/src/configparser/config.rs index 6527213..727c870 100644 --- a/src/configparser/config.rs +++ b/src/configparser/config.rs @@ -111,7 +111,7 @@ struct UserPass { pass: String, } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[fully_pub] struct Resource { cpu: i64, diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index ff10521..212ab80 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -5,7 +5,28 @@ use std::path::PathBuf; #[cfg(test)] use pretty_assertions::{assert_eq, assert_ne}; -use crate::configparser::challenge::*; +use crate::configparser::{challenge::*, config::Resource}; + +const VALID_CONFIG: &str = r#" + flag_regex: ctf{[a-zA-Z_\-0-9]*} + + defaults: + difficulty: 1 + resources: { cpu: 1, memory: 200Mi } + + points: + - difficulty: 1 + min: 0 + max: 1337 + + registry: + domain: images.example.ctf + build: { user: "", pass: "" } + cluster: { user: "", pass: "" } + deploy: {} + profiles: {} + +"#; const VALID_CHAL: &str = r#" name: testchal @@ -24,6 +45,7 @@ const VALID_CHAL: &str = r#" /// No challenge files should parse correctly fn no_challenges() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let chals = parse_all(); assert!(chals.is_ok()); @@ -37,6 +59,7 @@ fn no_challenges() { /// Challenge yaml at repo root should not parse fn challenge_in_root() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; jail.create_file("challenge.yaml", "name: test")?; let chals = parse_all(); @@ -52,6 +75,7 @@ fn challenge_in_root() { /// Challenge yaml one folder down should not parse fn challenge_one_level() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo")?; jail.create_file(dir.join("challenge.yaml"), "name: test")?; @@ -68,6 +92,7 @@ fn challenge_one_level() { /// Challenge yaml two folders down should be parsed fn challenge_two_levels() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file(dir.join("challenge.yaml"), VALID_CHAL)?; @@ -105,6 +130,7 @@ fn challenge_two_levels() { /// Challenge yaml three folders down should not parsed fn challenge_three_levels() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("chals/foo/test")?; jail.create_file(dir.join("challenge.yaml"), VALID_CHAL)?; @@ -121,6 +147,7 @@ fn challenge_three_levels() { fn challenge_missing_fields() { figment::Jail::expect_with(|jail| { let dir = jail.create_dir("test/noflag")?; + jail.create_file("rcds.yaml", VALID_CONFIG)?; jail.create_file( dir.join("challenge.yaml"), r#" @@ -171,6 +198,7 @@ fn challenge_missing_fields() { /// Challenges can omit both provides and pods fields if needed fn challenge_no_provides_or_pods() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -198,6 +226,7 @@ fn challenge_no_provides_or_pods() { /// Challenge provide files parse correctly fn challenge_provide() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -285,6 +314,7 @@ fn challenge_provide() { /// Challenge provide files dont parse if include is missing from object form fn challenge_provide_no_include() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -315,6 +345,7 @@ fn challenge_provide_no_include() { /// Challenges should be able to have multiple pods fn challenge_pods() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -356,7 +387,10 @@ fn challenge_pods() { image_source: ImageSource::Image("nginx".to_string()), replicas: 2, env: ListOrMap::Map(HashMap::new()), - resources: None, + resources: Some(Resource { + cpu: 1, + memory: "200Mi".to_string() + }), architecture: "amd64".to_string(), ports: vec![PortConfig { internal: 80, @@ -373,7 +407,10 @@ fn challenge_pods() { }), replicas: 1, env: ListOrMap::Map(HashMap::new()), - resources: None, + resources: Some(Resource { + cpu: 1, + memory: "200Mi".to_string() + }), architecture: "amd64".to_string(), ports: vec![PortConfig { internal: 8000, @@ -392,6 +429,7 @@ fn challenge_pods() { /// Challenge pods can use simple or complex build options fn challenge_pod_build() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -443,7 +481,10 @@ fn challenge_pod_build() { }), replicas: 1, env: ListOrMap::Map(HashMap::new()), - resources: None, + resources: Some(Resource { + cpu: 1, + memory: "200Mi".to_string() + }), architecture: "amd64".to_string(), ports: vec![PortConfig { internal: 80, @@ -463,7 +504,10 @@ fn challenge_pod_build() { }), replicas: 1, env: ListOrMap::Map(HashMap::new()), - resources: None, + resources: Some(Resource { + cpu: 1, + memory: "200Mi".to_string() + }), architecture: "amd64".to_string(), ports: vec![PortConfig { internal: 80, @@ -482,6 +526,7 @@ fn challenge_pod_build() { /// Challenge pod envvars can be set as either string list or map fn challenge_pod_env() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -533,7 +578,10 @@ fn challenge_pod_env() { ("FOO".to_string(), "this".to_string()), ("BAR".to_string(), "that".to_string()), ])), - resources: None, + resources: Some(Resource { + cpu: 1, + memory: "200Mi".to_string() + }), architecture: "amd64".to_string(), ports: vec![PortConfig { internal: 80, @@ -549,7 +597,10 @@ fn challenge_pod_env() { ("FOO".to_string(), "this".to_string()), ("BAR".to_string(), "that".to_string()), ])), - resources: None, + resources: Some(Resource { + cpu: 1, + memory: "200Mi".to_string() + }), architecture: "amd64".to_string(), ports: vec![PortConfig { internal: 80, @@ -568,6 +619,7 @@ fn challenge_pod_env() { /// Challenge pod envvar strings error if malformed fn challenge_pod_bad_env() { figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; let dir = jail.create_dir("foo/test")?; jail.create_file( dir.join("challenge.yaml"), @@ -601,3 +653,61 @@ fn challenge_pod_bad_env() { Ok(()) }) } + +#[test] +/// Challenge pod resources override config defaults +fn challenge_pod_resources() { + figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", VALID_CONFIG)?; + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + pods: + - name: foo + image: nginx + replicas: 1 + resources: + cpu: 4 + memory: 1Gi + ports: + - internal: 80 + expose: + http: test.chals.example.com + "#, + )?; + + let chals = parse_all().unwrap(); + + assert_eq!( + chals[0].pods, + vec![Pod { + name: "foo".to_string(), + + image_source: ImageSource::Image("nginx".to_string()), + replicas: 1, + env: ListOrMap::Map(HashMap::new()), + resources: Some(Resource { + cpu: 4, + memory: "1Gi".to_string() + }), + architecture: "amd64".to_string(), + ports: vec![PortConfig { + internal: 80, + expose: ExposeType::Http("test.chals.example.com".to_string()) + }], + volume: None + }] + ); + + Ok(()) + }) +} From 2598a9c8623c5255b47c19490feccbc082c24e96 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Mon, 19 May 2025 00:10:15 -0700 Subject: [PATCH 04/19] Initial documentation site setup Signed-off-by: Robert Detjens --- docs/.gitignore | 136 +++ docs/.vitepress/config.mts | 59 ++ docs/index.md | 30 + docs/mise.toml | 2 + docs/package.json | 21 + docs/pnpm-lock.yaml | 1868 ++++++++++++++++++++++++++++++++++++ 6 files changed, 2116 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/.vitepress/config.mts create mode 100644 docs/index.md create mode 100644 docs/mise.toml create mode 100644 docs/package.json create mode 100644 docs/pnpm-lock.yaml diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..1170717 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,136 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..4912e66 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,59 @@ +import { defineConfig } from "vitepress"; + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "beaverCDS Docs", + description: "Next-generation CTF deployment framework", + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: "Home", link: "/" }, + // { text: "Examples", link: "/markdown-examples" }, + ], + + sidebar: [ + { + text: "Guides", + items: [ + { text: "Deployment Quickstart", link: "for-sysadmins/quickstart" }, + { text: "Add new challenge", link: "for-authors/quickstart" }, + ], + }, + + { + text: "Infra Setup", + items: [ + { text: "Quickstart", link: "/for-sysadmins/quickstart" }, + { text: "Install", link: "/for-sysadmins/quickstart" }, + { text: "Config Reference", link: "/for-sysadmins/config" }, + { text: "Architecture", link: "/for-sysadmins/architecture" }, + ], + }, + { + text: "For Authors", + items: [ + { text: "Challenge Quickstart", link: "/for-authors/quickstart" }, + { + text: "Challenge Config Reference", + link: "/for-authors/challenge-config", + }, + ], + }, + ], + + socialLinks: [ + { icon: "github", link: "https://github.com/osusec/beavercds-ng" }, + ], + }, + + // disable interpolation of {{ and }} in markdown + markdown: { + config(md) { + const defaultCodeInline = md.renderer.rules.code_inline!; + md.renderer.rules.code_inline = (tokens, idx, options, env, self) => { + tokens[idx].attrSet("v-pre", ""); + return defaultCodeInline(tokens, idx, options, env, self); + }; + }, + }, +}); diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..3ba0cf8 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,30 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "beaverCDS" + text: "The next-generation CTF deployment framework" + tagline: "Challenges made simple." + actions: + - theme: brand + text: Get started + link: /getting-started + - theme: alt + text: Writing challenges + link: /for-authors/quickstart + +features: + - title: Deploy + icon: 🚀 + details: Set up a new deployment on a fresh cluster + link: /for-sysadmins/quickstart + - title: Configure + icon: ⚙️ + details: All about the global `rcds.yaml` config + link: /for-sysadmins/config + - title: Add challenge + icon: ✏️ + details: For challenge authors, how to add a new challenge + link: /for-authors/quickstart +--- diff --git a/docs/mise.toml b/docs/mise.toml new file mode 100644 index 0000000..377ec7c --- /dev/null +++ b/docs/mise.toml @@ -0,0 +1,2 @@ +[tools] +node = "lts" diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..f78d285 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,21 @@ +{ + "name": "@beavercds/docs", + "private": true, + "type": "module", + "devDependencies": { + "vitepress": "1.6.3" + }, + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview" + }, + "dependencies": { + "@types/markdown-it": "^14.1.2", + "js-toml": "^1.0.1", + "markdown-it": "^14.1.0", + "vitepress-plugin-group-icons": "^1.3.5", + "vitepress-plugin-tabs": "^0.7.0" + }, + "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a" +} diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml new file mode 100644 index 0000000..297b840 --- /dev/null +++ b/docs/pnpm-lock.yaml @@ -0,0 +1,1868 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@types/markdown-it': + specifier: ^14.1.2 + version: 14.1.2 + js-toml: + specifier: ^1.0.1 + version: 1.0.1 + markdown-it: + specifier: ^14.1.0 + version: 14.1.0 + vitepress-plugin-group-icons: + specifier: ^1.3.5 + version: 1.5.5(markdown-it@14.1.0)(vite@5.4.19) + vitepress-plugin-tabs: + specifier: ^0.7.0 + version: 0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3))(vue@3.5.14) + devDependencies: + vitepress: + specifier: 1.6.3 + version: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3) + +packages: + + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.25.0': + resolution: {integrity: sha512-1pfQulNUYNf1Tk/svbfjfkLBS36zsuph6m+B6gDkPEivFmso/XnRgwDvjAx80WNtiHnmeNjIXdF7Gos8+OLHqQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.25.0': + resolution: {integrity: sha512-AFbG6VDJX/o2vDd9hqncj1B6B4Tulk61mY0pzTtzKClyTDlNP0xaUiEKhl6E7KO9I/x0FJF5tDCm0Hn6v5x18A==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.25.0': + resolution: {integrity: sha512-il1zS/+Rc6la6RaCdSZ2YbJnkQC6W1wiBO8+SH+DE6CPMWBU6iDVzH0sCKSAtMWl9WBxoN6MhNjGBnCv9Yy2bA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.25.0': + resolution: {integrity: sha512-blbjrUH1siZNfyCGeq0iLQu00w3a4fBXm0WRIM0V8alcAPo7rWjLbMJMrfBtzL9X5ic6wgxVpDADXduGtdrnkw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.25.0': + resolution: {integrity: sha512-aywoEuu1NxChBcHZ1pWaat0Plw7A8jDMwjgRJ00Mcl7wGlwuPt5dJ/LTNcg3McsEUbs2MBNmw0ignXBw9Tbgow==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.25.0': + resolution: {integrity: sha512-a/W2z6XWKjKjIW1QQQV8PTTj1TXtaKx79uR3NGBdBdGvVdt24KzGAaN7sCr5oP8DW4D3cJt44wp2OY/fZcPAVA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.25.0': + resolution: {integrity: sha512-9rUYcMIBOrCtYiLX49djyzxqdK9Dya/6Z/8sebPn94BekT+KLOpaZCuc6s0Fpfq7nx5J6YY5LIVFQrtioK9u0g==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.25.0': + resolution: {integrity: sha512-jJeH/Hk+k17Vkokf02lkfYE4A+EJX+UgnMhTLR/Mb+d1ya5WhE+po8p5a/Nxb6lo9OLCRl6w3Hmk1TX1e9gVbQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.25.0': + resolution: {integrity: sha512-Ls3i1AehJ0C6xaHe7kK9vPmzImOn5zBg7Kzj8tRYIcmCWVyuuFwCIsbuIIz/qzUf1FPSWmw0TZrGeTumk2fqXg==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.25.0': + resolution: {integrity: sha512-79sMdHpiRLXVxSjgw7Pt4R1aNUHxFLHiaTDnN2MQjHwJ1+o3wSseb55T9VXU4kqy3m7TUme3pyRhLk5ip/S4Mw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.25.0': + resolution: {integrity: sha512-JLaF23p1SOPBmfEqozUAgKHQrGl3z/Z5RHbggBu6s07QqXXcazEsub5VLonCxGVqTv6a61AAPr8J1G5HgGGjEw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.25.0': + resolution: {integrity: sha512-rtzXwqzFi1edkOF6sXxq+HhmRKDy7tz84u0o5t1fXwz0cwx+cjpmxu/6OQKTdOJFS92JUYHsG51Iunie7xbqfQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.25.0': + resolution: {integrity: sha512-ZO0UKvDyEFvyeJQX0gmZDQEvhLZ2X10K+ps6hViMo1HgE2V8em00SwNsQ+7E/52a+YiBkVWX61pJJJE44juDMQ==} + engines: {node: '>= 14.0.0'} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@antfu/utils@8.1.1': + resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.2': + resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime-corejs3@7.27.1': + resolution: {integrity: sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.1': + resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} + engines: {node: '>=6.9.0'} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@iconify-json/logos@1.2.4': + resolution: {integrity: sha512-XC4If5D/hbaZvUkTV8iaZuGlQCyG6CNOlaAaJaGa13V5QMYwYjgtKk3vPP8wz3wtTVNVEVk3LRx1fOJz+YnSMw==} + + '@iconify-json/simple-icons@1.2.34': + resolution: {integrity: sha512-1FRWEA94hSl5zmBogRh6lQL36l7bVTfrl0n5+QJ+WmXmw70RccPT5phqeiSynwo3IhUWKoW2LiajyUMeweXW8g==} + + '@iconify-json/vscode-icons@1.2.21': + resolution: {integrity: sha512-velkIWAZRxvM9VuhkVeD6obyw0UXjTFk7lqcaxIzY+X7lXx2+yX2MoMbIwgpH3PbgqjvymS/SujBb4aWYcfmhw==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@2.3.0': + resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@rollup/rollup-android-arm-eabi@4.41.0': + resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.41.0': + resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.41.0': + resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.41.0': + resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.41.0': + resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.41.0': + resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.41.0': + resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.41.0': + resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.41.0': + resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.41.0': + resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.41.0': + resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': + resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.41.0': + resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.41.0': + resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.41.0': + resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.41.0': + resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.41.0': + resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.41.0': + resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.41.0': + resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.41.0': + resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} + cpu: [x64] + os: [win32] + + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.14': + resolution: {integrity: sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==} + + '@vue/compiler-dom@3.5.14': + resolution: {integrity: sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==} + + '@vue/compiler-sfc@3.5.14': + resolution: {integrity: sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==} + + '@vue/compiler-ssr@3.5.14': + resolution: {integrity: sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==} + + '@vue/devtools-api@7.7.6': + resolution: {integrity: sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw==} + + '@vue/devtools-kit@7.7.6': + resolution: {integrity: sha512-geu7ds7tem2Y7Wz+WgbnbZ6T5eadOvozHZ23Atk/8tksHMFOFylKi1xgGlQlVn0wlkEf4hu+vd5ctj1G4kFtwA==} + + '@vue/devtools-shared@7.7.6': + resolution: {integrity: sha512-yFEgJZ/WblEsojQQceuyK6FzpFDx4kqrz2ohInxNj5/DnhoX023upTv4OD6lNPLAA5LLkbwPVb10o/7b+Y4FVA==} + + '@vue/reactivity@3.5.14': + resolution: {integrity: sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==} + + '@vue/runtime-core@3.5.14': + resolution: {integrity: sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==} + + '@vue/runtime-dom@3.5.14': + resolution: {integrity: sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==} + + '@vue/server-renderer@3.5.14': + resolution: {integrity: sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==} + peerDependencies: + vue: 3.5.14 + + '@vue/shared@3.5.14': + resolution: {integrity: sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==} + + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + algoliasearch@5.25.0: + resolution: {integrity: sha512-n73BVorL4HIwKlfJKb4SEzAYkR3Buwfwbh+MYxg2mloFph2fFGV58E90QTzdbfzWrLn4HE5Czx/WTjI8fcHaMg==} + engines: {node: '>= 14.0.0'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + birpc@2.3.0: + resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + core-js-pure@3.42.0: + resolution: {integrity: sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + exsolve@1.0.5: + resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + + focus-trap@7.6.4: + resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + js-toml@1.0.1: + resolution: {integrity: sha512-rHd/IolpFm2V5BmHCEY8CckHs8NDsYZZ64H5RNgA6Opsr9vX4QyTiQPplgtqg7b3ztqYShZC38nl6CUg7QuhXg==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + local-pkg@1.1.1: + resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} + engines: {node: '>=14'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + minisearch@7.1.2: + resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.1.0: + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.26.6: + resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.41.0: + resolution: {integrity: sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitepress-plugin-group-icons@1.5.5: + resolution: {integrity: sha512-eVnBL3lVOYxByQg5xo44QZtGPv41JyxWI7YxuwrGcNUU+W8MMIjb9XlivBXb3W8CosFllJlLGiqNCBTnFZHFTA==} + peerDependencies: + markdown-it: '>=14' + vite: '>=3' + + vitepress-plugin-tabs@0.7.1: + resolution: {integrity: sha512-jxJvsicxnMSIYX9b8mAFLD2nwyKUcMO10dEt4nDSbinZhM8cGvAmMFOHPdf6TBX6gYZRl+/++/iYHtoM14fERQ==} + peerDependencies: + vitepress: ^1.0.0 + vue: ^3.5.0 + + vitepress@1.6.3: + resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vue@3.5.14: + resolution: {integrity: sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + xregexp@5.1.2: + resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0) + '@algolia/client-search': 5.25.0 + algoliasearch: 5.25.0 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0)': + dependencies: + '@algolia/client-search': 5.25.0 + algoliasearch: 5.25.0 + + '@algolia/client-abtesting@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/client-analytics@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/client-common@5.25.0': {} + + '@algolia/client-insights@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/client-personalization@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/client-query-suggestions@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/client-search@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/ingestion@1.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/monitoring@1.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/recommend@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + '@algolia/requester-browser-xhr@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + + '@algolia/requester-fetch@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + + '@algolia/requester-node-http@5.25.0': + dependencies: + '@algolia/client-common': 5.25.0 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 + + '@antfu/utils@8.1.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.27.2': + dependencies: + '@babel/types': 7.27.1 + + '@babel/runtime-corejs3@7.27.1': + dependencies: + core-js-pure: 3.42.0 + + '@babel/types@7.27.1': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.25.0)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.25.0)(search-insights@2.17.3) + preact: 10.26.6 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.25.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.25.0)(algoliasearch@5.25.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.25.0 + optionalDependencies: + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@iconify-json/logos@1.2.4': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify-json/simple-icons@1.2.34': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify-json/vscode-icons@1.2.21': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.3.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 8.1.1 + '@iconify/types': 2.0.0 + debug: 4.4.1 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.1 + mlly: 1.7.4 + transitivePeerDependencies: + - supports-color + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@rollup/rollup-android-arm-eabi@4.41.0': + optional: true + + '@rollup/rollup-android-arm64@4.41.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.41.0': + optional: true + + '@rollup/rollup-darwin-x64@4.41.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.41.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.41.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.41.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.41.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.41.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.41.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.41.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.41.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.41.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.41.0': + optional: true + + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/estree@1.0.7': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.19)(vue@3.5.14)': + dependencies: + vite: 5.4.19 + vue: 3.5.14 + + '@vue/compiler-core@3.5.14': + dependencies: + '@babel/parser': 7.27.2 + '@vue/shared': 3.5.14 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.14': + dependencies: + '@vue/compiler-core': 3.5.14 + '@vue/shared': 3.5.14 + + '@vue/compiler-sfc@3.5.14': + dependencies: + '@babel/parser': 7.27.2 + '@vue/compiler-core': 3.5.14 + '@vue/compiler-dom': 3.5.14 + '@vue/compiler-ssr': 3.5.14 + '@vue/shared': 3.5.14 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.3 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.14': + dependencies: + '@vue/compiler-dom': 3.5.14 + '@vue/shared': 3.5.14 + + '@vue/devtools-api@7.7.6': + dependencies: + '@vue/devtools-kit': 7.7.6 + + '@vue/devtools-kit@7.7.6': + dependencies: + '@vue/devtools-shared': 7.7.6 + birpc: 2.3.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.7.6': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.14': + dependencies: + '@vue/shared': 3.5.14 + + '@vue/runtime-core@3.5.14': + dependencies: + '@vue/reactivity': 3.5.14 + '@vue/shared': 3.5.14 + + '@vue/runtime-dom@3.5.14': + dependencies: + '@vue/reactivity': 3.5.14 + '@vue/runtime-core': 3.5.14 + '@vue/shared': 3.5.14 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.14(vue@3.5.14)': + dependencies: + '@vue/compiler-ssr': 3.5.14 + '@vue/shared': 3.5.14 + vue: 3.5.14 + + '@vue/shared@3.5.14': {} + + '@vueuse/core@12.8.2': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2 + vue: 3.5.14 + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.8.2(focus-trap@7.6.4)': + dependencies: + '@vueuse/core': 12.8.2 + '@vueuse/shared': 12.8.2 + vue: 3.5.14 + optionalDependencies: + focus-trap: 7.6.4 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@12.8.2': + dependencies: + vue: 3.5.14 + transitivePeerDependencies: + - typescript + + acorn@8.14.1: {} + + algoliasearch@5.25.0: + dependencies: + '@algolia/client-abtesting': 5.25.0 + '@algolia/client-analytics': 5.25.0 + '@algolia/client-common': 5.25.0 + '@algolia/client-insights': 5.25.0 + '@algolia/client-personalization': 5.25.0 + '@algolia/client-query-suggestions': 5.25.0 + '@algolia/client-search': 5.25.0 + '@algolia/ingestion': 1.25.0 + '@algolia/monitoring': 1.25.0 + '@algolia/recommend': 5.25.0 + '@algolia/requester-browser-xhr': 5.25.0 + '@algolia/requester-fetch': 5.25.0 + '@algolia/requester-node-http': 5.25.0 + + argparse@2.0.1: {} + + birpc@2.3.0: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + + comma-separated-tokens@2.0.3: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + core-js-pure@3.42.0: {} + + csstype@3.1.3: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + emoji-regex-xs@1.0.0: {} + + entities@4.5.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + estree-walker@2.0.2: {} + + exsolve@1.0.5: {} + + focus-trap@7.6.4: + dependencies: + tabbable: 6.2.0 + + fsevents@2.3.3: + optional: true + + globals@15.15.0: {} + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hookable@5.5.3: {} + + html-void-elements@3.0.0: {} + + is-what@4.1.16: {} + + js-toml@1.0.1: + dependencies: + chevrotain: 11.0.3 + xregexp: 5.1.2 + + kolorist@1.8.0: {} + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.1.0 + quansync: 0.2.10 + + lodash-es@4.17.21: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + mark.js@8.11.1: {} + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdurl@2.0.0: {} + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + minisearch@7.1.2: {} + + mitt@3.0.1: {} + + mlly@1.7.4: + dependencies: + acorn: 8.14.1 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.0.1 + regex-recursion: 6.0.2 + + package-manager-detector@1.3.0: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + pkg-types@2.1.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.5 + pathe: 2.0.3 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.26.6: {} + + property-information@7.1.0: {} + + punycode.js@2.3.1: {} + + quansync@0.2.10: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + rfdc@1.4.1: {} + + rollup@4.41.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.41.0 + '@rollup/rollup-android-arm64': 4.41.0 + '@rollup/rollup-darwin-arm64': 4.41.0 + '@rollup/rollup-darwin-x64': 4.41.0 + '@rollup/rollup-freebsd-arm64': 4.41.0 + '@rollup/rollup-freebsd-x64': 4.41.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.41.0 + '@rollup/rollup-linux-arm-musleabihf': 4.41.0 + '@rollup/rollup-linux-arm64-gnu': 4.41.0 + '@rollup/rollup-linux-arm64-musl': 4.41.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.41.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.41.0 + '@rollup/rollup-linux-riscv64-gnu': 4.41.0 + '@rollup/rollup-linux-riscv64-musl': 4.41.0 + '@rollup/rollup-linux-s390x-gnu': 4.41.0 + '@rollup/rollup-linux-x64-gnu': 4.41.0 + '@rollup/rollup-linux-x64-musl': 4.41.0 + '@rollup/rollup-win32-arm64-msvc': 4.41.0 + '@rollup/rollup-win32-ia32-msvc': 4.41.0 + '@rollup/rollup-win32-x64-msvc': 4.41.0 + fsevents: 2.3.3 + + search-insights@2.17.3: {} + + shiki@2.5.0: + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + speakingurl@14.0.1: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + tabbable@6.2.0: {} + + tinyexec@1.0.1: {} + + trim-lines@3.0.1: {} + + uc.micro@2.1.0: {} + + ufo@1.6.1: {} + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite@5.4.19: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.41.0 + optionalDependencies: + fsevents: 2.3.3 + + vitepress-plugin-group-icons@1.5.5(markdown-it@14.1.0)(vite@5.4.19): + dependencies: + '@iconify-json/logos': 1.2.4 + '@iconify-json/vscode-icons': 1.2.21 + '@iconify/utils': 2.3.0 + markdown-it: 14.1.0 + vite: 5.4.19 + transitivePeerDependencies: + - supports-color + + vitepress-plugin-tabs@0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3))(vue@3.5.14): + dependencies: + vitepress: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3) + vue: 3.5.14 + + vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.25.0)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.34 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.4(vite@5.4.19)(vue@3.5.14) + '@vue/devtools-api': 7.7.6 + '@vue/shared': 3.5.14 + '@vueuse/core': 12.8.2 + '@vueuse/integrations': 12.8.2(focus-trap@7.6.4) + focus-trap: 7.6.4 + mark.js: 8.11.1 + minisearch: 7.1.2 + shiki: 2.5.0 + vite: 5.4.19 + vue: 3.5.14 + optionalDependencies: + postcss: 8.5.3 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vue@3.5.14: + dependencies: + '@vue/compiler-dom': 3.5.14 + '@vue/compiler-sfc': 3.5.14 + '@vue/runtime-dom': 3.5.14 + '@vue/server-renderer': 3.5.14(vue@3.5.14) + '@vue/shared': 3.5.14 + + xregexp@5.1.2: + dependencies: + '@babel/runtime-corejs3': 7.27.1 + + zwitch@2.0.4: {} From bcc3741464789ef9d7d007f95b0a6b5fcc0de64b Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Mon, 19 May 2025 00:10:24 -0700 Subject: [PATCH 05/19] Add some placeholder files for structure Signed-off-by: Robert Detjens --- docs/for-authors/challenge-config.md | 1 + .../quickstart.md} | 13 ++++++++----- docs/for-sysadmins/architechture.md | 1 + docs/for-sysadmins/config.md | 5 +++++ docs/for-sysadmins/install.md | 1 + docs/for-sysadmins/quickstart.md | 4 ++++ docs/repo-structure.md | 1 + 7 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 docs/for-authors/challenge-config.md rename docs/{for-challenge-authors.md => for-authors/quickstart.md} (90%) create mode 100644 docs/for-sysadmins/architechture.md create mode 100644 docs/for-sysadmins/config.md create mode 100644 docs/for-sysadmins/install.md create mode 100644 docs/for-sysadmins/quickstart.md create mode 100644 docs/repo-structure.md diff --git a/docs/for-authors/challenge-config.md b/docs/for-authors/challenge-config.md new file mode 100644 index 0000000..8dfbee6 --- /dev/null +++ b/docs/for-authors/challenge-config.md @@ -0,0 +1 @@ +schema reference diff --git a/docs/for-challenge-authors.md b/docs/for-authors/quickstart.md similarity index 90% rename from docs/for-challenge-authors.md rename to docs/for-authors/quickstart.md index f9f13c7..1f67d26 100644 --- a/docs/for-challenge-authors.md +++ b/docs/for-authors/quickstart.md @@ -1,6 +1,9 @@ -# How to write beaverCDS challenge.yaml config +# Challenge Config Quickstart -tldr: see [the TCP example](#full-tcp-example) or [the web example](#full-http-example). +All information about a challenge is configured in a `challenge.yaml` in the +challenge's directory in the repo, generally `/`. + +For a tldr: see a full example for [a TCP/`nc` challenge](#full-tcp-example) or [a web challenge](#full-http-example). ### Metadata @@ -112,15 +115,15 @@ provide: # file from the challenge folder in the repo - somefile.txt - # multiple files from chal_folder/src/, zipped as together.zip + # multiple files from src/ in the challenge folder, zipped as together.zip - as: together.zip include: - src/file1 - src/file2 - src/file3 - # extract these files from inside of the container image - # for the `main` pod (see previous section) + # multiple files pulled from the container image for the `main` pod + # (see previous Pods section) - from: main include: - /chal/notsh diff --git a/docs/for-sysadmins/architechture.md b/docs/for-sysadmins/architechture.md new file mode 100644 index 0000000..33c689b --- /dev/null +++ b/docs/for-sysadmins/architechture.md @@ -0,0 +1 @@ +architecture of deployed resources diff --git a/docs/for-sysadmins/config.md b/docs/for-sysadmins/config.md new file mode 100644 index 0000000..7ff002d --- /dev/null +++ b/docs/for-sysadmins/config.md @@ -0,0 +1,5 @@ +# rcds.yaml + +`rcds.yaml` is the 'global' config for beaverCDS. This defines what challenges are available, where to deploy them to, and what credentials to use for building and deploying them. + +This will always be at `/rcds.yaml` in the challenges repository. diff --git a/docs/for-sysadmins/install.md b/docs/for-sysadmins/install.md new file mode 100644 index 0000000..8b7d8d6 --- /dev/null +++ b/docs/for-sysadmins/install.md @@ -0,0 +1 @@ +installing beavercds diff --git a/docs/for-sysadmins/quickstart.md b/docs/for-sysadmins/quickstart.md new file mode 100644 index 0000000..836e488 --- /dev/null +++ b/docs/for-sysadmins/quickstart.md @@ -0,0 +1,4 @@ +quick install instructions +requirements - cluster, registry, bucket +cluster-setup +configuring rcds.yaml diff --git a/docs/repo-structure.md b/docs/repo-structure.md new file mode 100644 index 0000000..4d8acc9 --- /dev/null +++ b/docs/repo-structure.md @@ -0,0 +1 @@ +expected layout of challenges repo From c53abeff91b158193e847d4a79b528eae9d0935e Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Mon, 19 May 2025 00:11:23 -0700 Subject: [PATCH 06/19] partial schema referecne for challenge.yaml Signed-off-by: Robert Detjens --- docs/for-authors/challenge-config.md | 258 ++++++++++++++++++++++++++- 1 file changed, 257 insertions(+), 1 deletion(-) diff --git a/docs/for-authors/challenge-config.md b/docs/for-authors/challenge-config.md index 8dfbee6..bb88e2d 100644 --- a/docs/for-authors/challenge-config.md +++ b/docs/for-authors/challenge-config.md @@ -1 +1,257 @@ -schema reference +# `challenge.yaml` Reference + +[[toc]] + +## `name` + +The name of the challenge, as shown to players in the frontend UI. + +```yaml +name: notsh + +# can have spaces: +name: Revenge of the FIPS +``` + +## `author` + +Author or authors of the challenge, as shown to players in the frontend UI. If there are multiple authors, specify them as one string. + +```yaml +author: John Author + +# multiple authors: +author: Alice, Bob, and others +``` + +## `description` + +Description and flavortext for the challenge, as shown to players in the frontend UI. Supports templating to include information about the challenge, such as the link or command to connect. + +| Available fields | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------- | +| `domain` | The domain/hostname the challenge is listening on
(`.chals.example.ctf`) | +| `port` | The port that the challenge is listening on | +| `nc` | The `nc` command to connect to TCP challenges, with Markdown backticks
(`` `nc {{domain}} {{port}}` ``) | +| `url` | The URL to the exposed web domain for web challenges, plus port if needed
(`https://{{domain}}:{{port}}`) | +| `link` | Create a Markdown link to `url` | +| `challenge` | The full challenge.yaml config object for this challenge, with subfields | + +```yaml +description: | + Some example challenge. Blah blah blah flavor text. + + In case you missed it, this was written by {{ challenge.author }} + and is called {{ challenge.name }}. + + {{ link }} # -becomes-> [https://somechal.chals.example.ctf](https://somechal.chals.example.ctf) + {{ nc }} # -becomes-> `nc somechal.chals.example.ctf 12345` +``` + +## `category` + +The category for the challenge. + +::: warning +Automatically set! This is set from the expected directory structure of `//challenge.yaml` and overwrites whatever is set in the file. +::: + +## `difficulty` + +::: info +Not implemented yet, does nothing +::: + +The difficulty from the challenge, used to set point values. Values correspond to entries in the [rcds.yaml difficulty settings](/for-sysadmins/config#difficulty). + +```yaml +difficulty: 1 # the current default +``` + +## `flag` + +Where to find the flag for the challenge. The flag can be in a file, a regex, or a direct string. + +```yaml +# directly set (equivalent) +flag: ctf{example-flag} +flag: + string: ctf{example-flag} + +# from a file in in the challenge directory +flag: + file: flag + +# regex +flag: + regex: /ctf\{(foo|bar|baz+)\}/ +``` + +::: info +Regex flags are not implemented yet and setting one does nothing +::: + +## `provide` + +List of files to provide to the players on the frontend UI. These files can be from the challenge directory or from a container image built for a [challenge pod](#pods), and uploaded individually or zipped together. + +If there are no files to upload for this challenge, this can be omitted or set to an empty array. + +```yaml +provide: + # files from the challenge folder in the repo + - somefile.txt + - otherfile.txt + + # rename a really long name as something shorter during upload + - as: short.h + include: some_really_long_name.h + + # multiple files from src/ in the challenge folder, zipped as together.zip + - as: together.zip + include: + - src/file1 + - src/file2 + - src/file3 + + # multiple files pulled from the container image for the `main` pod, + # uploaded individually as `notsh` and `libc.so.6` + - from: main + include: + - /chal/notsh + - /lib/x86_64-linux-gnu/libc.so.6 + + # single file pulled from the main container and renamed + - from: main + as: libc.so + include: /lib/x86_64-linux-gnu/libc.so.6 + + # multiple files pulled from the main container and zipped together + - from: main + as: notsh.zip + include: + - /chal/notsh + - /lib/x86_64-linux-gnu/libc.so.6 + + +# if no files need to be provided: +provide: [] +# or omit entirely +``` + +### `.include` + +File or list of files to upload individually, or include in a zip if `as` is set. + +When uploading, only the basename is used and the path to the file is discarded. + +### `.as` + +If `include` is a single file, rename to the given name while uploading. +If multiple files, zip them together into the given zip file. + +### `.from` + +Fetch these files from the corresponding [challenge pod](#pods) image. + +## `pods` + +Defines how to build and deploy any services needed for the challenge. + +Challenge pods can be built from a local Dockerfile in the challenge folder or use an upstream image directly. + +If there are no pods or images needed for this challenge, this can be omitted or set to an empty array. + +```yaml +pods: + - name: main + build: . + ports: + - internal: 1337 # expose a container listening on port 1337 ... + expose: + http: examplechal # as a web chal at https://examplechal. + + - name: db + image: postgres:alpine + architecture: arm64 + env: + POSTGRES_USER: someuser + POSTGRES_PASSWORD: notsecure + +# if no containers or pods need to be deployed: +pods: [] +# or omit entirely +``` + +### `.name` + +Name of the pod, used to refer to this container as [a source for `provide` files](#provide) and for generated resource names. + +Cannot contain spaces or punctuation, only alphanumeric and `-`. + +### `.build` + +Build the container image for this pod from a local `Dockerfile`. Supports a subset of the [docker-compose build spec](https://docs.docker.com/reference/compose-file/build/#illustrative-example) (`dockerfile`, `context`, `args`). + +Conflicts with [`image`](#image). + +```yaml + # build a container from a Dockerfile in the challenge folder + build: . + + # equivalent to the above but with explicit build context and Dockerfile name + build: + context: . + dockerfile: Dockerfile + + # build from a subfolder with a custom Dockerfile and some build args + build: + context: src/ + dockerfile: Containerfile.remote + args: + CC_OPTS: "-Osize" +``` + +### `.image` + +Use an available container image for the pod instead of building one from source. + +Conflicts with [`build`](#build). + +### `.env` + +Any environment variables to set for the running pod. Specify as `name: value`. + +```yaml + env: + SOME_ENVVAR: foo bar +``` + +### `.architecture` + +Set the desired CPU architecture to run this pod on. + +```yaml + architecture: amd64 # AKA x86_64; the default + architecture: arm64 # for ARM +``` + +### `.resources` + +The resource usage request and limits for the pod. Kubernetes will make sure the requested resources will be available for this pod to use, and will also restart the pod if it goes over these limits. + +If not set, the default set in [`rcds.yaml`](config#rcds.yaml) is used. + +### `.replicas` + +How many instances of the pod to run. Traffic is load-balanced between instances. + +Default is 2 and this is probably fine unless the challenge is very resource intensive. + +```yaml + replicas: 2 # the default +``` + +### `.ports` + +### `.volume` From c05ff3f79bb7a048ba25bac7f56abebb06af8b5a Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 23 May 2025 19:29:39 -0700 Subject: [PATCH 07/19] more wip challenge schema docs Signed-off-by: Robert Detjens --- docs/for-authors/challenge-config.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/for-authors/challenge-config.md b/docs/for-authors/challenge-config.md index bb88e2d..8c47f4d 100644 --- a/docs/for-authors/challenge-config.md +++ b/docs/for-authors/challenge-config.md @@ -254,4 +254,14 @@ Default is 2 and this is probably fine unless the challenge is very resource int ### `.ports` +Specfies how to expose this pod to players, either as a raw TCP port or HTTP at a specific domain. + +#### `.ports.internal` + +What the container itself is listening on + +#### `.ports.expose` + +How to expose the internal container port + ### `.volume` From e18d4a716b45702e1cf274e1f5fab3e31e9f3c0d Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 23 May 2025 20:50:39 -0700 Subject: [PATCH 08/19] Generate docs sidebar automatically Signed-off-by: Robert Detjens --- docs/.vitepress/config.mts | 42 ++--- docs/package.json | 3 +- docs/pnpm-lock.yaml | 355 +++++++++++++++++++++++++++++++++++++ 3 files changed, 370 insertions(+), 30 deletions(-) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 4912e66..e595679 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,4 +1,5 @@ import { defineConfig } from "vitepress"; +import { generateSidebar } from "vitepress-sidebar"; // https://vitepress.dev/reference/site-config export default defineConfig({ @@ -11,35 +12,18 @@ export default defineConfig({ // { text: "Examples", link: "/markdown-examples" }, ], - sidebar: [ - { - text: "Guides", - items: [ - { text: "Deployment Quickstart", link: "for-sysadmins/quickstart" }, - { text: "Add new challenge", link: "for-authors/quickstart" }, - ], - }, - - { - text: "Infra Setup", - items: [ - { text: "Quickstart", link: "/for-sysadmins/quickstart" }, - { text: "Install", link: "/for-sysadmins/quickstart" }, - { text: "Config Reference", link: "/for-sysadmins/config" }, - { text: "Architecture", link: "/for-sysadmins/architecture" }, - ], - }, - { - text: "For Authors", - items: [ - { text: "Challenge Quickstart", link: "/for-authors/quickstart" }, - { - text: "Challenge Config Reference", - link: "/for-authors/challenge-config", - }, - ], - }, - ], + // auto generate sidebar from directory structure, via vitepress-sidebar + sidebar: generateSidebar({ + documentRootPath: "./", + // pull title from markdown not filename + useTitleFromFileHeading: true, + useTitleFromFrontmatter: true, + keepMarkdownSyntaxFromTitle: true, + // transform name to sentence case + hyphenToSpace: true, + underscoreToSpace: true, + capitalizeEachWords: true, + }), socialLinks: [ { icon: "github", link: "https://github.com/osusec/beavercds-ng" }, diff --git a/docs/package.json b/docs/package.json index f78d285..e06b358 100644 --- a/docs/package.json +++ b/docs/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "devDependencies": { - "vitepress": "1.6.3" + "vitepress": "1.6.3", + "vitepress-sidebar": "^1.31.1" }, "scripts": { "docs:dev": "vitepress dev", diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 297b840..3b1954b 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: vitepress: specifier: 1.6.3 version: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3) + vitepress-sidebar: + specifier: ^1.31.1 + version: 1.31.1 packages: @@ -320,9 +323,17 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@rollup/rollup-android-arm-eabi@4.41.0': resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} cpu: [arm] @@ -578,12 +589,37 @@ packages: resolution: {integrity: sha512-n73BVorL4HIwKlfJKb4SEzAYkR3Buwfwbh+MYxg2mloFph2fFGV58E90QTzdbfzWrLn4HE5Czx/WTjI8fcHaMg==} engines: {node: '>= 14.0.0'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + birpc@2.3.0: resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -596,6 +632,13 @@ packages: chevrotain@11.0.3: resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -612,6 +655,10 @@ packages: core-js-pure@3.42.0: resolution: {integrity: sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -631,9 +678,18 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -643,24 +699,45 @@ packages: engines: {node: '>=12'} hasBin: true + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} exsolve@1.0.5: resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + focus-trap@7.6.4: resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + globals@15.15.0: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} @@ -673,13 +750,35 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + js-toml@1.0.1: resolution: {integrity: sha512-rHd/IolpFm2V5BmHCEY8CckHs8NDsYZZ64H5RNgA6Opsr9vX4QyTiQPplgtqg7b3ztqYShZC38nl6CUg7QuhXg==} + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -693,6 +792,9 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -724,6 +826,14 @@ packages: micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + minisearch@7.1.2: resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} @@ -744,9 +854,20 @@ packages: oniguruma-to-es@3.1.1: resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -776,6 +897,10 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} + qsu@1.10.0: + resolution: {integrity: sha512-60UGE7IEYXX/xy/n1w7vDm+is43pmePajAdXAnFOczvVDJKbVqZ5/RvRy+yobjA4iQitr4H/4zojVFtAkNWW9g==} + engines: {node: '>=18.0.0'} + quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} @@ -799,9 +924,25 @@ packages: search-insights@2.17.3: resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + shiki@2.5.0: resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -813,9 +954,32 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + superjson@2.2.2: resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} engines: {node: '>=16'} @@ -899,6 +1063,10 @@ packages: vitepress: ^1.0.0 vue: ^3.5.0 + vitepress-sidebar@1.31.1: + resolution: {integrity: sha512-Hx10z5le87jIIXVfKq4AtRrVqVJJ/1cQsZhmwT+ghVR/j4Yor9FjNMszyigJ54ktrEtoxSLO6C9tvuLauT4lZA==} + engines: {node: '>=18.0.0'} + vitepress@1.6.3: resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} hasBin: true @@ -919,6 +1087,19 @@ packages: typescript: optional: true + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + xregexp@5.1.2: resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==} @@ -1193,8 +1374,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/sourcemap-codec@1.5.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@rollup/rollup-android-arm-eabi@4.41.0': optional: true @@ -1442,10 +1635,30 @@ snapshots: '@algolia/requester-fetch': 5.25.0 '@algolia/requester-node-http': 5.25.0 + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} + balanced-match@1.0.2: {} + birpc@2.3.0: {} + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + ccount@2.0.1: {} character-entities-html4@2.1.0: {} @@ -1461,6 +1674,12 @@ snapshots: '@chevrotain/utils': 11.0.3 lodash-es: 4.17.21 + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} confbox@0.1.8: {} @@ -1473,6 +1692,12 @@ snapshots: core-js-pure@3.42.0: {} + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + csstype@3.1.3: {} debug@4.4.1: @@ -1485,8 +1710,14 @@ snapshots: dependencies: dequal: 2.0.3 + eastasianwidth@0.2.0: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + entities@4.5.0: {} esbuild@0.21.5: @@ -1515,19 +1746,46 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esprima@4.0.1: {} + estree-walker@2.0.2: {} exsolve@1.0.5: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + focus-trap@7.6.4: dependencies: tabbable: 6.2.0 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fsevents@2.3.3: optional: true + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@15.15.0: {} + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.1 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 @@ -1550,13 +1808,32 @@ snapshots: html-void-elements@3.0.0: {} + is-extendable@0.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + is-what@4.1.16: {} + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + js-toml@1.0.1: dependencies: chevrotain: 11.0.3 xregexp: 5.1.2 + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + kind-of@6.0.3: {} + kolorist@1.8.0: {} linkify-it@5.0.0: @@ -1571,6 +1848,8 @@ snapshots: lodash-es@4.17.21: {} + lru-cache@10.4.3: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -1617,6 +1896,12 @@ snapshots: micromark-util-types@2.0.2: {} + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + minisearch@7.1.2: {} mitt@3.0.1: {} @@ -1638,8 +1923,17 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 + package-json-from-dist@1.0.1: {} + package-manager-detector@1.3.0: {} + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + pathe@2.0.3: {} perfect-debounce@1.0.0: {} @@ -1670,6 +1964,8 @@ snapshots: punycode.js@2.3.1: {} + qsu@1.10.0: {} + quansync@0.2.10: {} regex-recursion@6.0.2: @@ -1712,6 +2008,17 @@ snapshots: search-insights@2.17.3: {} + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + shiki@2.5.0: dependencies: '@shikijs/core': 2.5.0 @@ -1723,17 +2030,43 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} space-separated-tokens@2.0.2: {} speakingurl@14.0.1: {} + sprintf-js@1.0.3: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom-string@1.0.0: {} + superjson@2.2.2: dependencies: copy-anything: 3.0.5 @@ -1804,6 +2137,12 @@ snapshots: vitepress: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3) vue: 3.5.14 + vitepress-sidebar@1.31.1: + dependencies: + glob: 10.4.5 + gray-matter: 4.0.3 + qsu: 1.10.0 + vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.3)(search-insights@2.17.3): dependencies: '@docsearch/css': 3.8.2 @@ -1861,6 +2200,22 @@ snapshots: '@vue/server-renderer': 3.5.14(vue@3.5.14) '@vue/shared': 3.5.14 + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + xregexp@5.1.2: dependencies: '@babel/runtime-corejs3': 7.27.1 From bd9b934aaa8ea8e67006ee8569803a46c84900e3 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 23 May 2025 20:51:18 -0700 Subject: [PATCH 09/19] Docs deployment CI Signed-off-by: Robert Detjens --- .github/workflows/docs-deploy.yml | 75 +++++++++++++++++++++++++++++++ docs/mise.toml | 1 + 2 files changed, 76 insertions(+) create mode 100644 .github/workflows/docs-deploy.yml diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 0000000..72cf340 --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,75 @@ +name: Deploy Docs + +on: + # build for docs changes on main + push: + # TODO: reenable when ready to merge + # branches: + # - main + paths: + - docs/** + + # or manual triggers from web ui + workflow_dispatch: + +# allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + # Build job + docs-build: + name: Docs Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install tools + uses: jdx/mise-action@v2 + with: + working_directory: docs + + # - uses: pnpm/action-setup@v3 + # - name: Setup Node + # uses: actions/setup-node@v4 + # with: + # node-version: lts + # cache: pnpm + + - name: Install dependencies + working-directory: docs + run: pnpm install + + - name: Build with VitePress + working-directory: docs + run: pnpm run docs:build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + # Deployment job + docs-deploy: + name: Docs Deploy + runs-on: ubuntu-latest + needs: docs-build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/mise.toml b/docs/mise.toml index 377ec7c..7ac45e7 100644 --- a/docs/mise.toml +++ b/docs/mise.toml @@ -1,2 +1,3 @@ [tools] node = "lts" +"npm:corepack" = "latest" From ae8c0b13657e477eb6227f111a723f91baf7e81e Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 23 May 2025 20:51:39 -0700 Subject: [PATCH 10/19] Improve cards on docs landing page Signed-off-by: Robert Detjens --- docs/index.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/index.md b/docs/index.md index 3ba0cf8..8a4b79c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,26 +5,28 @@ layout: home hero: name: "beaverCDS" text: "The next-generation CTF deployment framework" - tagline: "Challenges made simple." + tagline: "Challenge management made simple." actions: - theme: brand text: Get started - link: /getting-started + link: /for-sysadmins/quickstart - theme: alt - text: Writing challenges + text: Add a challenge link: /for-authors/quickstart features: - - title: Deploy + - title: Quick setup icon: 🚀 - details: Set up a new deployment on a fresh cluster + details: beaverCDS can get you from a fresh cluster to ready for challenges in minutes. link: /for-sysadmins/quickstart - - title: Configure + + - title: Control Challenge Deployment icon: ⚙️ - details: All about the global `rcds.yaml` config + details: Quickly enable or disable challenges to control exactly what is live for players. link: /for-sysadmins/config - - title: Add challenge + + - title: Simple yet flexible icon: ✏️ - details: For challenge authors, how to add a new challenge + details: Adding a new challenge is simpler than ever. link: /for-authors/quickstart --- From 0a83323ec9778ae82fe6ad817e427b49cf4f86f6 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 23 May 2025 20:52:14 -0700 Subject: [PATCH 11/19] Support single string provide include as same as bare string Found this missing case when writing docs. Specifiying `include:` as a single string should be equivalent to specifying a bare string, which is already treated as a one-element array. Renaming only happens when as: is present, and we should support the `include:` single string like a 1-element array, like the bare string case. Signed-off-by: Robert Detjens --- src/builder/artifacts.rs | 2 ++ src/configparser/challenge.rs | 8 ++++++-- src/tests/parsing/challenges.rs | 9 +++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/builder/artifacts.rs b/src/builder/artifacts.rs index 1d6fc79..7f5b47b 100644 --- a/src/builder/artifacts.rs +++ b/src/builder/artifacts.rs @@ -41,6 +41,8 @@ pub async fn extract_asset( let extracted_files = match provide { // Repo file paths are relative to the challenge directory, so prepend chal dir + // No action necessary, return path as-is + ProvideConfig::FromRepoSingle { file } => Ok(vec![chal.directory.join(file)]), // No action necessary, return path as-is ProvideConfig::FromRepo { files } => { Ok(files.iter().map(|f| chal.directory.join(f)).collect_vec()) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index bb8fb74..c82d5e4 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -262,6 +262,10 @@ enum ProvideConfig { /// Upload file(s) as-is. /// Single or multiple files with no as: or from: /// Default if only a string is given. + FromRepoSingle { + #[serde(rename = "include")] + file: PathBuf, + }, FromRepo { #[serde(rename = "include")] files: Vec, @@ -315,8 +319,8 @@ enum ProvideConfig { impl FromStr for ProvideConfig { type Err = Void; fn from_str(s: &str) -> std::result::Result { - Ok(ProvideConfig::FromRepo { - files: vec![PathBuf::from(s)], + Ok(ProvideConfig::FromRepoSingle { + file: PathBuf::from(s), }) } } diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index 212ab80..0da3bce 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -242,6 +242,8 @@ fn challenge_provide() { provide: - foo.txt + - include: foo2.txt + - include: - bar.txt - baz.txt @@ -275,8 +277,11 @@ fn challenge_provide() { assert_eq!( chals[0].provide, vec![ - ProvideConfig::FromRepo { - files: vec!["foo.txt".into()] + ProvideConfig::FromRepoSingle { + file: "foo.txt".into() + }, + ProvideConfig::FromRepoSingle { + file: "foo2.txt".into() }, ProvideConfig::FromRepo { files: vec!["bar.txt".into(), "baz.txt".into()] From 0f11f1e6f116df1042cd53b2c11de3fb5b050e81 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 23 May 2025 20:55:50 -0700 Subject: [PATCH 12/19] Rename and tweak some more docs stuff Signed-off-by: Robert Detjens --- ...lenge-config.md => challenge-reference.md} | 60 +++++++++++-------- docs/for-authors/quickstart.md | 3 +- 2 files changed, 36 insertions(+), 27 deletions(-) rename docs/for-authors/{challenge-config.md => challenge-reference.md} (78%) diff --git a/docs/for-authors/challenge-config.md b/docs/for-authors/challenge-reference.md similarity index 78% rename from docs/for-authors/challenge-config.md rename to docs/for-authors/challenge-reference.md index 8c47f4d..ce702f6 100644 --- a/docs/for-authors/challenge-config.md +++ b/docs/for-authors/challenge-reference.md @@ -1,4 +1,6 @@ -# `challenge.yaml` Reference +# Challenge Config Reference + +Challenge configuration is expected to be at `//challenge.yaml`. [[toc]] @@ -28,14 +30,16 @@ author: Alice, Bob, and others Description and flavortext for the challenge, as shown to players in the frontend UI. Supports templating to include information about the challenge, such as the link or command to connect. -| Available fields | Description | -| ---------------- | -------------------------------------------------------------------------------------------------------------- | -| `domain` | The domain/hostname the challenge is listening on
(`.chals.example.ctf`) | -| `port` | The port that the challenge is listening on | -| `nc` | The `nc` command to connect to TCP challenges, with Markdown backticks
(`` `nc {{domain}} {{port}}` ``) | -| `url` | The URL to the exposed web domain for web challenges, plus port if needed
(`https://{{domain}}:{{port}}`) | -| `link` | Create a Markdown link to `url` | -| `challenge` | The full challenge.yaml config object for this challenge, with subfields | +Most challenges only need `{{ nc }}` or `{{ link }}`. + +| Available fields | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `domain` | Full domain the challenge is exposed at, e.g. `.chals.example.ctf` | +| `port` | Port the challenge is listening on | +| `nc` | `nc` command to connect to TCP challenges, with Markdown backticks
(equivalent to `` `nc {{domain}} {{port}}` ``) | +| `url` | URL to the exposed web domain for web challenges, plus port if needed
(equivalent to `https://{{domain}}:{{port}}`) | +| `link` | Markdown link to `url` | +| `challenge` | The full challenge.yaml config object for this challenge, with subfields | ```yaml description: | @@ -44,8 +48,8 @@ description: | In case you missed it, this was written by {{ challenge.author }} and is called {{ challenge.name }}. - {{ link }} # -becomes-> [https://somechal.chals.example.ctf](https://somechal.chals.example.ctf) - {{ nc }} # -becomes-> `nc somechal.chals.example.ctf 12345` + {{ nc }} # `nc somechal.chals.example.ctf 12345` + {{ link }} # [https://somechal.chals.example.ctf](https://somechal.chals.example.ctf) ``` ## `category` @@ -53,7 +57,7 @@ description: | The category for the challenge. ::: warning -Automatically set! This is set from the expected directory structure of `//challenge.yaml` and overwrites whatever is set in the file. +This is set from the expected directory structure of `//challenge.yaml` and will overwrite whatever is set in the file. ::: ## `difficulty` @@ -62,7 +66,7 @@ Automatically set! This is set from the expected directory structure of `). +The pod `name` is also used for extracting files, see [Providing files to users](#Providing files to users). `build` works similar to [Docker Compose](https://docs.docker.com/reference/compose-file/build/#illustrative-example), either: From 62c57d0c5cea27f3803900fc25f3083841f497ce Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Tue, 26 Aug 2025 21:25:26 -0700 Subject: [PATCH 13/19] Add titles to most pages, fix links Signed-off-by: Robert Detjens --- docs/.vitepress/config.mts | 17 ++++++-- docs/for-authors/challenge-reference.md | 6 ++- docs/for-authors/index.md | 3 ++ docs/for-authors/quickstart.md | 42 +++++++++++++++---- .../challenge-repo-structure.md} | 2 + docs/for-sysadmins/config-reference.md | 11 +++++ docs/for-sysadmins/config.md | 5 --- docs/for-sysadmins/index.md | 3 ++ docs/for-sysadmins/install.md | 1 - ...hitechture.md => resource-architecture.md} | 0 docs/index.md | 2 +- docs/install.md | 22 ++++++++++ 12 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 docs/for-authors/index.md rename docs/{repo-structure.md => for-sysadmins/challenge-repo-structure.md} (55%) create mode 100644 docs/for-sysadmins/config-reference.md delete mode 100644 docs/for-sysadmins/config.md create mode 100644 docs/for-sysadmins/index.md delete mode 100644 docs/for-sysadmins/install.md rename docs/for-sysadmins/{architechture.md => resource-architecture.md} (100%) create mode 100644 docs/install.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index e595679..d105b2a 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -8,8 +8,15 @@ export default defineConfig({ themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ - { text: "Home", link: "/" }, - // { text: "Examples", link: "/markdown-examples" }, + { text: "Setup", link: "for-sysadmins/quickstart" }, + { + text: "Config Reference", + link: "for-sysadmins/config-reference", + }, + { + text: "Challenge Reference", + link: "for-authors/challenge-reference", + }, ], // auto generate sidebar from directory structure, via vitepress-sidebar @@ -19,10 +26,14 @@ export default defineConfig({ useTitleFromFileHeading: true, useTitleFromFrontmatter: true, keepMarkdownSyntaxFromTitle: true, + useFolderTitleFromIndexFile: true, // transform name to sentence case hyphenToSpace: true, underscoreToSpace: true, - capitalizeEachWords: true, + // capitalizeEachWords: true, + + sortFolderTo: "bottom", + sortMenusByFrontmatterOrder: true, }), socialLinks: [ diff --git a/docs/for-authors/challenge-reference.md b/docs/for-authors/challenge-reference.md index ce702f6..45e4ba3 100644 --- a/docs/for-authors/challenge-reference.md +++ b/docs/for-authors/challenge-reference.md @@ -1,4 +1,8 @@ -# Challenge Config Reference +--- +title: challenge.yaml Reference +--- + +# `challenge.yaml` Config Reference Challenge configuration is expected to be at `//challenge.yaml`. diff --git a/docs/for-authors/index.md b/docs/for-authors/index.md new file mode 100644 index 0000000..a1a854d --- /dev/null +++ b/docs/for-authors/index.md @@ -0,0 +1,3 @@ +--- +title: For Challenge Authors +--- diff --git a/docs/for-authors/quickstart.md b/docs/for-authors/quickstart.md index c9a5c6b..cf362f2 100644 --- a/docs/for-authors/quickstart.md +++ b/docs/for-authors/quickstart.md @@ -1,16 +1,25 @@ -# Challenge Config Quickstart +# Challenge Quickstart All information about a challenge is configured in a `challenge.yaml` in the challenge's directory in the repo, generally `/`. -For a tldr: see a full example for [a TCP/`nc` challenge](#full-tcp-example) or [a web challenge](#full-http-example). +There are a few [complete examples](#examples) at the bottom of the page. -### Metadata +### Name -Self explanatory. +The challenge name can contain any characters, though whitespace or special +characters may be removed in some cases. ```yaml name: yet another pyjail +``` + +### Author + +The author or authors that created this challenge. Multiple authors are not +handled + +```yaml author: somebody, John Author ``` @@ -152,15 +161,15 @@ description: |- {{ nc }} +flag: + file: ./flag + provide: - from: main include: - /chal/notsh - /lib/x86_64-linux-gnu/libc.so.6 -flag: - file: ./flag - pods: - name: main build: . @@ -199,3 +208,22 @@ pods: expose: http: bar # subdomain only ``` + +## Full static file example + +```yaml +name: ghostint +author: the guessers geo +description: | + where was this picture taken? + + flag format: `example{LAT__LONG}`, rounded to five places + +difficulty: 1 + +flag: + string: "example{51.51771__-0.20495}" + +provide: + - the-view-from-your-balcony.png +``` diff --git a/docs/repo-structure.md b/docs/for-sysadmins/challenge-repo-structure.md similarity index 55% rename from docs/repo-structure.md rename to docs/for-sysadmins/challenge-repo-structure.md index 4d8acc9..c998df1 100644 --- a/docs/repo-structure.md +++ b/docs/for-sysadmins/challenge-repo-structure.md @@ -1 +1,3 @@ +# Challenge Repo Structure + expected layout of challenges repo diff --git a/docs/for-sysadmins/config-reference.md b/docs/for-sysadmins/config-reference.md new file mode 100644 index 0000000..9883b8e --- /dev/null +++ b/docs/for-sysadmins/config-reference.md @@ -0,0 +1,11 @@ +--- +title: rcds.yaml Reference +--- + +# `rcds.yaml` + +`rcds.yaml` is the 'global' config for beaverCDS. This defines what challenges +are available, where to deploy them to, and what credentials to use for building +and deploying them. + +This will always be at `/rcds.yaml` in the challenges repository. diff --git a/docs/for-sysadmins/config.md b/docs/for-sysadmins/config.md deleted file mode 100644 index 7ff002d..0000000 --- a/docs/for-sysadmins/config.md +++ /dev/null @@ -1,5 +0,0 @@ -# rcds.yaml - -`rcds.yaml` is the 'global' config for beaverCDS. This defines what challenges are available, where to deploy them to, and what credentials to use for building and deploying them. - -This will always be at `/rcds.yaml` in the challenges repository. diff --git a/docs/for-sysadmins/index.md b/docs/for-sysadmins/index.md new file mode 100644 index 0000000..b7f4932 --- /dev/null +++ b/docs/for-sysadmins/index.md @@ -0,0 +1,3 @@ +--- +title: For Sysadmins +--- diff --git a/docs/for-sysadmins/install.md b/docs/for-sysadmins/install.md deleted file mode 100644 index 8b7d8d6..0000000 --- a/docs/for-sysadmins/install.md +++ /dev/null @@ -1 +0,0 @@ -installing beavercds diff --git a/docs/for-sysadmins/architechture.md b/docs/for-sysadmins/resource-architecture.md similarity index 100% rename from docs/for-sysadmins/architechture.md rename to docs/for-sysadmins/resource-architecture.md diff --git a/docs/index.md b/docs/index.md index 8a4b79c..0d41c54 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,7 +23,7 @@ features: - title: Control Challenge Deployment icon: ⚙️ details: Quickly enable or disable challenges to control exactly what is live for players. - link: /for-sysadmins/config + link: /for-sysadmins/config-reference - title: Simple yet flexible icon: ✏️ diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..0a19c1e --- /dev/null +++ b/docs/install.md @@ -0,0 +1,22 @@ +# Installing `beavercds` + +Install `beavercds` via Cargo or install from source: + +## Via `cargo` + +`beavercds` is not published to Crates.io (yet), you will need to install +directly from git: + +```sh +cargo install --git https://github.com/osusec/beavercds-ng +``` + +## Future plans + +We do not provide releases with precompiled binaries yet, but we intend to in +the future so that users will not need to compile from git source. + +## Next Steps + +After installing the `beavercds` binary, get started with [setting up the +challenges repo](for-sysadmins/quickstart) or [writing challenges](for-authors/quickstart). From 9c56aaaff9872ee39c62a400bee04cefe096f814 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Tue, 26 Aug 2025 22:06:43 -0700 Subject: [PATCH 14/19] Reorganize docs by guide/reference instead of audience Signed-off-by: Robert Detjens --- docs/.vitepress/config.mts | 6 +++--- .../quickstart.md => Guides/challenge-quickstart.md} | 0 .../quickstart.md => Guides/infra-quickstart.md} | 0 docs/{for-authors => Reference}/challenge-reference.md | 0 .../challenge-repo-structure.md | 0 docs/{for-sysadmins => Reference}/config-reference.md | 0 .../resource-architecture.md | 0 docs/for-authors/index.md | 3 --- docs/for-sysadmins/index.md | 3 --- docs/index.md | 10 +++++----- docs/install.md | 2 +- 11 files changed, 9 insertions(+), 15 deletions(-) rename docs/{for-authors/quickstart.md => Guides/challenge-quickstart.md} (100%) rename docs/{for-sysadmins/quickstart.md => Guides/infra-quickstart.md} (100%) rename docs/{for-authors => Reference}/challenge-reference.md (100%) rename docs/{for-sysadmins => Reference}/challenge-repo-structure.md (100%) rename docs/{for-sysadmins => Reference}/config-reference.md (100%) rename docs/{for-sysadmins => Reference}/resource-architecture.md (100%) delete mode 100644 docs/for-authors/index.md delete mode 100644 docs/for-sysadmins/index.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index d105b2a..51a7f8a 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -8,14 +8,14 @@ export default defineConfig({ themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ - { text: "Setup", link: "for-sysadmins/quickstart" }, + { text: "Setup", link: "guides/infra-quickstart" }, { text: "Config Reference", - link: "for-sysadmins/config-reference", + link: "reference/config-reference", }, { text: "Challenge Reference", - link: "for-authors/challenge-reference", + link: "reference/challenge-reference", }, ], diff --git a/docs/for-authors/quickstart.md b/docs/Guides/challenge-quickstart.md similarity index 100% rename from docs/for-authors/quickstart.md rename to docs/Guides/challenge-quickstart.md diff --git a/docs/for-sysadmins/quickstart.md b/docs/Guides/infra-quickstart.md similarity index 100% rename from docs/for-sysadmins/quickstart.md rename to docs/Guides/infra-quickstart.md diff --git a/docs/for-authors/challenge-reference.md b/docs/Reference/challenge-reference.md similarity index 100% rename from docs/for-authors/challenge-reference.md rename to docs/Reference/challenge-reference.md diff --git a/docs/for-sysadmins/challenge-repo-structure.md b/docs/Reference/challenge-repo-structure.md similarity index 100% rename from docs/for-sysadmins/challenge-repo-structure.md rename to docs/Reference/challenge-repo-structure.md diff --git a/docs/for-sysadmins/config-reference.md b/docs/Reference/config-reference.md similarity index 100% rename from docs/for-sysadmins/config-reference.md rename to docs/Reference/config-reference.md diff --git a/docs/for-sysadmins/resource-architecture.md b/docs/Reference/resource-architecture.md similarity index 100% rename from docs/for-sysadmins/resource-architecture.md rename to docs/Reference/resource-architecture.md diff --git a/docs/for-authors/index.md b/docs/for-authors/index.md deleted file mode 100644 index a1a854d..0000000 --- a/docs/for-authors/index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: For Challenge Authors ---- diff --git a/docs/for-sysadmins/index.md b/docs/for-sysadmins/index.md deleted file mode 100644 index b7f4932..0000000 --- a/docs/for-sysadmins/index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: For Sysadmins ---- diff --git a/docs/index.md b/docs/index.md index 0d41c54..09d3497 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,24 +9,24 @@ hero: actions: - theme: brand text: Get started - link: /for-sysadmins/quickstart + link: /install - theme: alt text: Add a challenge - link: /for-authors/quickstart + link: /guides/challenge-quickstart features: - title: Quick setup icon: 🚀 details: beaverCDS can get you from a fresh cluster to ready for challenges in minutes. - link: /for-sysadmins/quickstart + link: /guides/infra-quickstart - title: Control Challenge Deployment icon: ⚙️ details: Quickly enable or disable challenges to control exactly what is live for players. - link: /for-sysadmins/config-reference + link: /reference/config-reference - title: Simple yet flexible icon: ✏️ details: Adding a new challenge is simpler than ever. - link: /for-authors/quickstart + link: /guides/challenge-quickstart --- diff --git a/docs/install.md b/docs/install.md index 0a19c1e..23fc81f 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,4 +1,4 @@ -# Installing `beavercds` +# Install Install `beavercds` via Cargo or install from source: From d6f770ad3055f9dffd3144730cf14b0394c4e629 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Tue, 26 Aug 2025 23:40:01 -0700 Subject: [PATCH 15/19] lowercase folders, refactor challenge quickstart guide Signed-off-by: Robert Detjens --- docs/.vitepress/config.mts | 4 +- docs/Guides/challenge-quickstart.md | 229 ---------------- docs/{install.md => getting-started.md} | 4 +- docs/guides/challenge-quickstart.md | 244 ++++++++++++++++++ docs/guides/index.md | 3 + docs/{Guides => guides}/infra-quickstart.md | 0 docs/index.md | 4 +- .../challenge-repo-structure.md | 0 .../challenge-yaml-reference.md} | 13 +- docs/reference/index.md | 3 + .../rcds-yaml-reference.md} | 2 +- .../resource-architecture.md | 0 12 files changed, 268 insertions(+), 238 deletions(-) delete mode 100644 docs/Guides/challenge-quickstart.md rename docs/{install.md => getting-started.md} (94%) create mode 100644 docs/guides/challenge-quickstart.md create mode 100644 docs/guides/index.md rename docs/{Guides => guides}/infra-quickstart.md (100%) rename docs/{Reference => reference}/challenge-repo-structure.md (100%) rename docs/{Reference/challenge-reference.md => reference/challenge-yaml-reference.md} (93%) create mode 100644 docs/reference/index.md rename docs/{Reference/config-reference.md => reference/rcds-yaml-reference.md} (90%) rename docs/{Reference => reference}/resource-architecture.md (100%) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 51a7f8a..0fd8657 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -11,11 +11,11 @@ export default defineConfig({ { text: "Setup", link: "guides/infra-quickstart" }, { text: "Config Reference", - link: "reference/config-reference", + link: "reference/rcds-yaml-reference", }, { text: "Challenge Reference", - link: "reference/challenge-reference", + link: "reference/challenge-yaml-reference", }, ], diff --git a/docs/Guides/challenge-quickstart.md b/docs/Guides/challenge-quickstart.md deleted file mode 100644 index cf362f2..0000000 --- a/docs/Guides/challenge-quickstart.md +++ /dev/null @@ -1,229 +0,0 @@ -# Challenge Quickstart - -All information about a challenge is configured in a `challenge.yaml` in the -challenge's directory in the repo, generally `/`. - -There are a few [complete examples](#examples) at the bottom of the page. - -### Name - -The challenge name can contain any characters, though whitespace or special -characters may be removed in some cases. - -```yaml -name: yet another pyjail -``` - -### Author - -The author or authors that created this challenge. Multiple authors are not -handled - -```yaml -author: somebody, John Author -``` - -### Description - -Challenge description supports markdown and Jinja-style templating for challenge info. -The Jinja template fields available are: - -| Field name | Description | -| ----------- | ----------- | -| `hostname` | The hostname or domain for the challenge -| `port` | The port that the challenge is listening on -| `nc` | Insert the `nc` command to connect to TCP challenges (`nc {{hostname}} {{port}}`) -| `link` | Create a Markdown link to the exposed hostname/port -| `url` | The URL from `link` without the accompanying Markdown -| `challenge` | The full challenge.yaml config for this challenge, with subfields - -You probably only want `{{ nc }}` or `{{ link }}`. - -Example: - -```yaml -description: | - Some example challenge. Blah blah blah flavor text. - - In case you missed it, this was written by {{ challenge.author }} - and is called {{ challenge.name }}. - - {{ link }} # -becomes-> [example.chals.thectf.com](https://example.chals.thectf.com) - {{ nc }} # -becomes-> `nc example.chals.thectf.com 12345` -``` - - -### Flag - -Read flag from file: - -```yaml -flag: - file: ./flag -``` - -### Pods - -Defines how any container images for this challenge are built and deployed. - -The pod `name` is also used for extracting files, see [Providing files to users](#Providing files to users). - -`build` works similar to [Docker Compose](https://docs.docker.com/reference/compose-file/build/#illustrative-example), -either: - - a string path to the build context folder - - yaml with explicit `context` path, `dockerfile` path within context folder, and `args` build args \ - (only `context`, `dockerfile`, and `args` are supported for the detailed form) - -`ports` controls how the container is exposed. This should be a list of what port the container is listening, and how -that port should be exposed to players: -- For TCP challenges, set `expose.tcp` to the subdomain and port: `:` -- For HTTP challenges, set `expose.http` to the subdomain only: `` \ - The website domain will automatically be set up with an HTTPS cert. - - -```yaml -pods: - - name: tcp-example - build: . - replicas: 2 - ports: - - internal: 31337 - expose: - tcp: thechal:30124 # exposed at thechal.:30124 - - - name: web-example - build: - context: src/ - dockerfile: Containerfile - replicas: 2 - ports: - - internal: 31337 - expose: - http: webchal # exposed at https://webchal. -``` - - - - -This can be omitted if there are no containers for the challenge. - -### Providing files to users - -Files to give to players as downloads in frontend. - -These can be from the challenge folder in the repository, or from the -challenge's built container. These can also be zipped together into one file, or -uploaded separately. These need to be files, directories or globs are not (yet) -supported. - -This can be omitted if there are no files provided. - -```yaml -provide: - # file from the challenge folder in the repo - - somefile.txt - - # multiple files from src/ in the challenge folder, zipped as together.zip - - as: together.zip - include: - - src/file1 - - src/file2 - - src/file3 - - # multiple files pulled from the container image for the `main` pod - # (see previous Pods section) - - from: main - include: - - /chal/notsh - - /lib/x86_64-linux-gnu/libc.so.6 - - # same as above, but now zipped together - - from: main - as: notsh.zip - include: - - /chal/notsh - - /lib/x86_64-linux-gnu/libc.so.6 -``` - - - - - -# Examples - -## Full TCP example - -```yaml -name: notsh -author: John Author -description: |- - This challenge isn't a shell - - {{ nc }} - -flag: - file: ./flag - -provide: - - from: main - include: - - /chal/notsh - - /lib/x86_64-linux-gnu/libc.so.6 - -pods: - - name: main - build: . - replicas: 2 - ports: - - internal: 31337 - expose: - tcp: 30124 -``` - -## Full HTTP example - -```yaml -name: bar -author: somebody -description: | - can you order a drink from the webserver? - - {{ url }} - -difficulty: 1 - -flag: - file: ./flag - -# no provide: section needed if no files - -pods: - - name: bar - build: - context: . - dockerfile: Containerfile - replicas: 1 - ports: - - internal: 80 - expose: - http: bar # subdomain only -``` - -## Full static file example - -```yaml -name: ghostint -author: the guessers geo -description: | - where was this picture taken? - - flag format: `example{LAT__LONG}`, rounded to five places - -difficulty: 1 - -flag: - string: "example{51.51771__-0.20495}" - -provide: - - the-view-from-your-balcony.png -``` diff --git a/docs/install.md b/docs/getting-started.md similarity index 94% rename from docs/install.md rename to docs/getting-started.md index 23fc81f..8ba7ed6 100644 --- a/docs/install.md +++ b/docs/getting-started.md @@ -1,4 +1,6 @@ -# Install +# Getting Started + +## Installation Install `beavercds` via Cargo or install from source: diff --git a/docs/guides/challenge-quickstart.md b/docs/guides/challenge-quickstart.md new file mode 100644 index 0000000..5ec3be0 --- /dev/null +++ b/docs/guides/challenge-quickstart.md @@ -0,0 +1,244 @@ +# Challenge Quickstart + +This will walk through the steps needed to create a new challenge and configure +its `challenge.yaml` config file. This is what sets all of the information about +the challenge and how it is presented to players. + +For the impatient, there are a few [complete example config files](#examples) at +the bottom of the page. + +::: info +We have an `new-chal` command planned that will prompt for most of this +information, but for now the challenge config needs to be created manually. +::: + + +## 1. Create challenge directory + +Challenges are expected to follow the directory structure of +`///challenge.yaml`. Create the directory and challenge config +file following that convention: + +```sh +$ mkdir -p $CATEGORY/$CHAL_NAME +$ touch $CATEGORY/$CHAL_NAME/challenge.yaml +``` + + +## 2. Add metadata + +Add the name, author, and description fields for your challenge to the `challenge.yaml` file. These will be shown to players on the scoreboard. + +```yaml [challenge.yaml] +name: My First Pyjail 🙂 +author: John Author +description: | + how will you get out of this one? + + {{ nc }} +``` + +The challenge name and author will be shown to players as written. + +The description supports Markdown formatting and Jinja-style templating for challenge info. Most of the time, you will need either `{{ nc }}` for netcat challenges or `{{ link }}` for web challenges. The full list of template options are available in [the reference](/reference/challenge-yaml-reference#description). + + +## 3. Set the flag + +If the flag for your challenge is stored in a file, read it in with: + +```yaml [challenge.yaml] +flag: + file: ./flag +``` + +For static file challenges or others that don't need it to be on disk + +```yaml [challenge.yaml] +flag: example{s0m3-fl4g} +``` + + +## 4. Create container and pod + +::: tip +If your challenge **does not** need a container, skip this section. +::: + +If your challenge has a service that is exposed to players, it needs to run as a +container. BeaverCDS will take care of building and deploying the container, but +you need to create the `Dockerfile` to build your challenge source into one. + +The pod `name` should be kept to one word, and is used to reference this +container for [providing files to users](#_5-provide-files-to-users) in the next section. + +### Build the container + +Once you have the container source and Dockerfile ready, add them to the pod +`build` section. +This is a good starting point for most challenges, where the Dockerfile and container source are in the same folder as this `challenge.yaml`: + +```yaml [challenge.yaml] {4-5} +pods: + - name: main + build: + context: . + dockerfile: Dockerfile + replicas: 2 + ports: + # todo +``` + +### Define how the challenge is exposed + +To expose the container to players, fill in the `ports` section with the +port the container is listening on, and either a port number or http subdomain. + +- For TCP challenges, set `expose.tcp` to a port. This must be a unique port + from other TCP challenges. The challenge will be exposed at + `:`. +- For web challenges, set `expose.http` to a subdomain. The challenge will be + exposed at `.` with an HTTPS cert. + +::: code-group +```yaml [For TCP challenges] {8-10} +pods: + - name: main + build: + context: . + dockerfile: Dockerfile + replicas: 2 + ports: + - internal: 31337 # the port the container listens on + expose: + tcp: 30124 # exposed at :30124 +``` + +```yaml [For web challenges] {8-10} +pods: + - name: main + build: + context: . + dockerfile: Dockerfile + replicas: 2 + ports: + - internal: 31337 # the port the container listens on + expose: + http: my-chal # exposed at https://my-chal. +``` +::: + + + +### 5. Provide files to users + +::: tip +If your challenge **does not** have any file handouts, skip this section. +::: + +If your challenge needs to provide files to users, add a `provide` block that +lists what files and where to find them. + +::: warning +Currently, all `provide` entries need to be files. Directories or globs are not +yet supported. +::: + +```yaml [challenge.yaml] +provide: + # file from the challenge folder in the repo + - somefile.txt + + # multiple files from src/ in the challenge folder, zipped as together.zip + - as: together.zip + include: + - src/foo + - src/bar + - src/baz + + # multiple files pulled from the container image for the `main` pod + # (see previous Pods section) + - from: main + include: + - /chal/notsh + - /lib/x86_64-linux-gnu/libc.so.6 + + # same as above, but now zipped together + - from: main + as: notsh.zip + include: + - /chal/notsh + - /lib/x86_64-linux-gnu/libc.so.6 +``` + + +## Examples + +::: code-group + +```yaml [Full TCP challenge] +name: notsh +author: John Author +description: |- + This challenge isn't a shell + + {{ nc }} + +flag: + file: ./flag + +provide: + - from: main + include: + - /chal/notsh + - /lib/x86_64-linux-gnu/libc.so.6 + +pods: + - name: main + build: . + replicas: 2 + ports: + - internal: 31337 + expose: + tcp: 30124 +``` + +```yaml [Full HTTP challenge] +name: bar +author: somebody +description: | + can you order a drink from the webserver? + + {{ url }} + +flag: + file: ./flag + +# no file handouts, so no provide: section + +pods: + - name: bar + build: + context: . + dockerfile: Containerfile + replicas: 1 + ports: + - internal: 80 + expose: + http: bar # subdomain only +``` + +```yaml [Full file-only challenge] +name: GhostINT +author: The Guessers Geo +description: | + where was this picture taken? + + flag format: `example{LAT__LONG}`, rounded to five places + +flag: "example{51.51771__-0.20495}" + +provide: + - the-view-from-your-balcony.png +``` +::: diff --git a/docs/guides/index.md b/docs/guides/index.md new file mode 100644 index 0000000..5bf01e8 --- /dev/null +++ b/docs/guides/index.md @@ -0,0 +1,3 @@ +--- +title: Guides +--- diff --git a/docs/Guides/infra-quickstart.md b/docs/guides/infra-quickstart.md similarity index 100% rename from docs/Guides/infra-quickstart.md rename to docs/guides/infra-quickstart.md diff --git a/docs/index.md b/docs/index.md index 09d3497..f6ef3bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ hero: actions: - theme: brand text: Get started - link: /install + link: /getting-started - theme: alt text: Add a challenge link: /guides/challenge-quickstart @@ -18,7 +18,7 @@ features: - title: Quick setup icon: 🚀 details: beaverCDS can get you from a fresh cluster to ready for challenges in minutes. - link: /guides/infra-quickstart + link: /getting-started - title: Control Challenge Deployment icon: ⚙️ diff --git a/docs/Reference/challenge-repo-structure.md b/docs/reference/challenge-repo-structure.md similarity index 100% rename from docs/Reference/challenge-repo-structure.md rename to docs/reference/challenge-repo-structure.md diff --git a/docs/Reference/challenge-reference.md b/docs/reference/challenge-yaml-reference.md similarity index 93% rename from docs/Reference/challenge-reference.md rename to docs/reference/challenge-yaml-reference.md index 45e4ba3..53c4c0f 100644 --- a/docs/Reference/challenge-reference.md +++ b/docs/reference/challenge-yaml-reference.md @@ -58,10 +58,10 @@ description: | ## `category` -The category for the challenge. +The category for the challenge, parsed from the directory structure. ::: warning -This is set from the expected directory structure of `//challenge.yaml` and will overwrite whatever is set in the file. +This is automatically set from the expected directory structure of `//challenge.yaml` and should not be set in the file. ::: ## `difficulty` @@ -205,7 +205,14 @@ Cannot contain spaces or punctuation, only alphanumeric and `-`. ### `.build` -Build the container image for this pod from a local `Dockerfile`. Supports a subset of the [docker-compose build spec](https://docs.docker.com/reference/compose-file/build/#illustrative-example) (`dockerfile`, `context`, `args`). +Build the container image for this pod from a local `Dockerfile`. Supports a subset of the [docker-compose build spec](https://docs.docker.com/reference/compose-file/build/#illustrative-example), +either: + - a string path to the build context folder + - yaml with explicit `context` path, `dockerfile` path within context folder, + and `args` build args (only `context`, `dockerfile`, and `args` are + supported) + +The build context directory is relative to the `challenge.yaml`, and the `dockerfile` is relative to the context directory. Conflicts with [`image`](#image). diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 0000000..b983027 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,3 @@ +--- +title: Reference +--- diff --git a/docs/Reference/config-reference.md b/docs/reference/rcds-yaml-reference.md similarity index 90% rename from docs/Reference/config-reference.md rename to docs/reference/rcds-yaml-reference.md index 9883b8e..85c2c5d 100644 --- a/docs/Reference/config-reference.md +++ b/docs/reference/rcds-yaml-reference.md @@ -2,7 +2,7 @@ title: rcds.yaml Reference --- -# `rcds.yaml` +# `rcds.yaml` Config Reference `rcds.yaml` is the 'global' config for beaverCDS. This defines what challenges are available, where to deploy them to, and what credentials to use for building diff --git a/docs/Reference/resource-architecture.md b/docs/reference/resource-architecture.md similarity index 100% rename from docs/Reference/resource-architecture.md rename to docs/reference/resource-architecture.md From c54d86b1d73c8105a32bc495ae67c5747cfa3a9a Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Tue, 26 Aug 2025 23:42:26 -0700 Subject: [PATCH 16/19] fix dead links from reorg Signed-off-by: Robert Detjens --- docs/getting-started.md | 2 +- docs/reference/challenge-yaml-reference.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 8ba7ed6..647d9b1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -21,4 +21,4 @@ the future so that users will not need to compile from git source. ## Next Steps After installing the `beavercds` binary, get started with [setting up the -challenges repo](for-sysadmins/quickstart) or [writing challenges](for-authors/quickstart). +challenges repo](/guides/infra-quickstart) or [writing challenges](/guides/challenge-quickstart). diff --git a/docs/reference/challenge-yaml-reference.md b/docs/reference/challenge-yaml-reference.md index 53c4c0f..8c8387b 100644 --- a/docs/reference/challenge-yaml-reference.md +++ b/docs/reference/challenge-yaml-reference.md @@ -70,7 +70,7 @@ This is automatically set from the expected directory structure of `/< Not implemented yet, does nothing ::: -The difficulty from the challenge, used to set point values. Values correspond to entries in the [rcds.yaml difficulty settings](../for-sysadmins/config#difficulty). +The difficulty from the challenge, used to set point values. Values correspond to entries in the [rcds.yaml difficulty settings](rcds-yaml-reference#difficulty). ```yaml difficulty: 1 # the current default @@ -261,7 +261,7 @@ Set the desired CPU architecture to run this pod on. The resource usage request and limits for the pod. Kubernetes will make sure the requested resources will be available for this pod to use, and will also restart the pod if it goes over these limits. -If not set, the default set in [`rcds.yaml`](../for-sysadmins/config#rcds.yaml) is used. +If not set, the default set in [`rcds.yaml`](rcds-yaml-reference#resources) is used. ### `.replicas` From 8604b82754f4ddf315a8e15a72e1a9f44bdc23f8 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 29 Aug 2025 22:27:04 -0700 Subject: [PATCH 17/19] Add ports.expose, type information to challenge reference Signed-off-by: Robert Detjens --- docs/guides/challenge-quickstart.md | 12 +- docs/reference/challenge-yaml-reference.md | 148 +++++++++++++++++++-- 2 files changed, 141 insertions(+), 19 deletions(-) diff --git a/docs/guides/challenge-quickstart.md b/docs/guides/challenge-quickstart.md index 5ec3be0..3bae004 100644 --- a/docs/guides/challenge-quickstart.md +++ b/docs/guides/challenge-quickstart.md @@ -101,7 +101,7 @@ port the container is listening on, and either a port number or http subdomain. exposed at `.` with an HTTPS cert. ::: code-group -```yaml [For TCP challenges] {8-10} +```yaml [For TCP challenges] {8-11} pods: - name: main build: @@ -109,12 +109,12 @@ pods: dockerfile: Dockerfile replicas: 2 ports: - - internal: 31337 # the port the container listens on + - internal: 31337 # <-- what your container listens on expose: - tcp: 30124 # exposed at :30124 + tcp: 30124 # <-- would expose at chals.example.ctf:30124 ``` -```yaml [For web challenges] {8-10} +```yaml [For web challenges] {8-11} pods: - name: main build: @@ -122,9 +122,9 @@ pods: dockerfile: Dockerfile replicas: 2 ports: - - internal: 31337 # the port the container listens on + - internal: 31337 # <-- what your container listens on expose: - http: my-chal # exposed at https://my-chal. + http: my-chal # <-- would expose at https://my-chal.chals.example.ctf ``` ::: diff --git a/docs/reference/challenge-yaml-reference.md b/docs/reference/challenge-yaml-reference.md index 8c8387b..b0d187f 100644 --- a/docs/reference/challenge-yaml-reference.md +++ b/docs/reference/challenge-yaml-reference.md @@ -6,9 +6,18 @@ title: challenge.yaml Reference Challenge configuration is expected to be at `//challenge.yaml`. +There are some examples available on the [challenge quickstart guide](/guides/challenge-quickstart#examples). + +Available fields: + [[toc]] -## `name` +`*` denotes required fields. + +## `name`* + +- type: `string` +- no default The name of the challenge, as shown to players in the frontend UI. @@ -19,7 +28,10 @@ name: notsh name: Revenge of the FIPS ``` -## `author` +## `author`* + +- type: `string` +- no default Author or authors of the challenge, as shown to players in the frontend UI. If there are multiple authors, specify them as one string. @@ -30,7 +42,10 @@ author: John Author author: Alice, Bob, and others ``` -## `description` +## `description`* + +- type: `string` +- no default Description and flavortext for the challenge, as shown to players in the frontend UI. Supports templating to include information about the challenge, such as the link or command to connect. @@ -58,6 +73,9 @@ description: | ## `category` +- type: `string` +- default: from folder structure + The category for the challenge, parsed from the directory structure. ::: warning @@ -66,6 +84,9 @@ This is automatically set from the expected directory structure of `/< ## `difficulty` +- type: `integer` +- no default + ::: info Not implemented yet, does nothing ::: @@ -76,7 +97,10 @@ The difficulty from the challenge, used to set point values. Values correspond t difficulty: 1 # the current default ``` -## `flag` +## `flag`* + +- type: `string` | `dict` +- no default Where to find the flag for the challenge. The flag can be in a file, a regex, or a direct string. @@ -99,6 +123,9 @@ Regex flags are not implemented yet and setting one does nothing ## `provide` +- type: list of `string`/`dict` +- default: `[]` (no files) + List of files to provide to the players on the frontend UI. These files can be from the challenge directory or from a container image built for a [challenge pod](#pods), and uploaded individually or zipped together. If there are no files to upload for this challenge, this can be omitted or set to an empty array. @@ -152,6 +179,9 @@ provide: [] ### `.include` +- type: list of `string` +- no default + File or list of files to upload individually, or include in a zip if `as` is set. When uploading, only the basename is used and the path to the file is discarded. @@ -160,16 +190,25 @@ If a provide item is specified as a single string, it is interpreted as an `incl ### `.as` +- type: `string` +- no default + If `.include` is a single file, rename to this name while uploading. If multiple files, zip them together into the given zip file. ### `.from` +- type: `string` +- no default + Fetch these files from the corresponding [challenge pod](#pods) image. ## `pods` +- type: list of `dict` +- default: `[]` (no pods) + Defines how to build and deploy any services needed for the challenge. Challenge pods can be built from a local Dockerfile in the challenge folder or use an upstream image directly. @@ -199,12 +238,18 @@ pods: [] ### `.name` +- type: `string` +- no default + Name of the pod, used to refer to this container as [a source for `provide` files](#provide) and for generated resource names. Cannot contain spaces or punctuation, only alphanumeric and `-`. ### `.build` +- type: `string` | `dict` +- no default + Build the container image for this pod from a local `Dockerfile`. Supports a subset of the [docker-compose build spec](https://docs.docker.com/reference/compose-file/build/#illustrative-example), either: - a string path to the build context folder @@ -235,12 +280,18 @@ Conflicts with [`image`](#image). ### `.image` +- type: `string` +- no default + Use an available container image for the pod instead of building one from source. Conflicts with [`build`](#build). ### `.env` +- type: `dict` +- default: `{}` (no envvars) + Any environment variables to set for the running pod. Specify as `name: value`. ```yaml @@ -250,21 +301,38 @@ env: ### `.architecture` -Set the desired CPU architecture to run this pod on. +- type: `string` +- default: `"amd64"` + +Set the desired CPU architecture to run this pod on. Kubernetes uses GOARCH architecture names. ```yaml - architecture: amd64 # AKA x86_64; the default - architecture: arm64 # for ARM +architecture: amd64 +architecture: arm64 ``` ### `.resources` -The resource usage request and limits for the pod. Kubernetes will make sure the requested resources will be available for this pod to use, and will also restart the pod if it goes over these limits. +- type: `dict` +- default: global default from `rcds.yaml` + +The CPU and memory resources that will be reserved for this pod. Kubernetes will make sure the requested amounts will be available for this pod to use, and will also restart the pod if it goes over these limits. + +Uses the [Kubernetes resource units](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-units-in-kubernetes). If not set, the default set in [`rcds.yaml`](rcds-yaml-reference#resources) is used. +```yaml +resources: + cpu: 1 + memory: 512Mi +``` + ### `.replicas` +- type: `number` +- default: `2` + How many instances of the pod to run. Traffic is load-balanced between instances. Default is 2 and this is probably fine unless the challenge is very resource intensive. @@ -275,14 +343,68 @@ replicas: 2 # the default ### `.ports` -Specfies how to expose this pod to players, either as a raw TCP port or HTTP at a specific domain. +- type: list of `dict` +- default: `[]` + +List of ports to expose to players. + +#### `.ports[].internal` -#### `.ports.internal` +- type: `number` +- no default -The port the container is listening on; i.e. `xinetd` or `nginx` etc. +The port that the challenge container (i.e. `xinetd`/`nginx`/etc inside) is listening on. -#### `.ports.expose` +#### `.ports[].expose` -How to expose the internal container port +- type: `dict` +- no default + +How to expose the internal container port to players -- either as a TCP port or a subdirectory for web challenges. Must be one of the following: + +**`.ports[].expose.tcp`** + +- type: `number` +- no default + +The port to expose the challenge over raw TCP at on the challenge subdomain. Must be unique across all other exposed TCP challenges. + +```yaml [For TCP challenges] {8-10} +pods: + - #... + ports: + - internal: 31337 # the port the container listens on + expose: + tcp: 30124 # exposed at :30124 +``` + +**`.ports[].expose.http`** + +- type: `string` +- no default + +The subdomain to expose the challenge at as a website (port 80/443). This is prepended to the global challenge subdomain. The cluster will provision an SSL certificate for the site. + +Must be a valid DNS domain name (alphanumeric, `_`, `-`). + +```yaml [For web challenges] {8-10} +pods: + - name: main + build: + context: . + dockerfile: Dockerfile + replicas: 2 + ports: + - internal: 31337 # the port the container listens on + expose: + http: my-chal # exposed at https://my-chal. +``` ### `.volume` + +- type: `string` +- no default + +::: info +Not implemented yet, does nothing +::: From 471e1f53dedd08e6fb49123b61307e72e77b90eb Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sat, 27 Sep 2025 10:53:46 -0700 Subject: [PATCH 18/19] Initial readme Signed-off-by: Robert Detjens --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 769b907..ef6b38a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,51 @@ -# beavercds-ng -new implementation of beavercds/rcds +# beavercds-backend + +A modern, cloud-native framework for managing CTF challenge deployment. + +## What is this? + + +## Getting Started + +Install like any other Rust binary: +``` +cargo install --git https://github.com/osusec/beavercds-backend +``` + +See the documentation and guides for [challenge authors](https://beavercds.info) and [infrastructure admins](https://beavercds.info). + +## Contributing + +Contributions are welcome! Check out some of the TODO work on the issue tracker +for inspiration. + +We use the standard `cargo build` process for building `beavercds` itself, and +there is a test repository under `tests/repo/` to use during development. + +Dependencies: +- `rust` @ latest (currently 1.88) +- `cargo` + +```bash +# build and run `beavercds check-access` with the config in the test repo +$ (cd tests/repo/ && cargo run -- check-access --profile test) +``` + +## Documentation + +Our documentation is available at [https://beavercds.info] built from source +under `docs/`. + +Dependencies (can be installed via `mise`): +- `node` @ lts +- `pnpm` (should be handled by node's corepack) + +```bash +cd docs/ + +mise install # for node/pnpm +pnpm install # install vitepress + +pnpm docs:build # build only +pnpm docs:dev # build and serve +``` From ec740c3b9610293ffaa60c6a2d2b0a60f0e9ec8b Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sat, 27 Sep 2025 10:54:05 -0700 Subject: [PATCH 19/19] fix links, wrap Signed-off-by: Robert Detjens --- docs/.vitepress/config.mts | 27 ++++++++++++++++++---- docs/reference/challenge-yaml-reference.md | 15 +++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 0fd8657..8d00914 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -10,12 +10,31 @@ export default defineConfig({ nav: [ { text: "Setup", link: "guides/infra-quickstart" }, { - text: "Config Reference", - link: "reference/rcds-yaml-reference", + text: "Guides", + items: [ + { text: "Deployment Quickstart", link: "for-sysadmins/quickstart" }, + { text: "Add new challenge", link: "for-authors/quickstart" }, + ], + }, + + { + text: "Infrastructure Setup", + items: [ + { text: "Quickstart", link: "/for-sysadmins/quickstart" }, + { text: "Install", link: "/for-sysadmins/install" }, + { text: "Config Reference", link: "/for-sysadmins/config" }, + { text: "Architecture", link: "/for-sysadmins/architecture" }, + ], }, { - text: "Challenge Reference", - link: "reference/challenge-yaml-reference", + text: "Challenge Authors", + items: [ + { text: "Challenge Quickstart", link: "/for-authors/quickstart" }, + { + text: "Challenge Config Reference", + link: "/for-authors/challenge-config", + }, + ], }, ], diff --git a/docs/reference/challenge-yaml-reference.md b/docs/reference/challenge-yaml-reference.md index b0d187f..45fcb2e 100644 --- a/docs/reference/challenge-yaml-reference.md +++ b/docs/reference/challenge-yaml-reference.md @@ -19,12 +19,13 @@ Available fields: - type: `string` - no default -The name of the challenge, as shown to players in the frontend UI. +The name of the challenge, as shown to players in the frontend UI. Any +characters are allowed. ```yaml name: notsh - -# can have spaces: +name: cha-cha-cha +# name can have spaces: name: Revenge of the FIPS ``` @@ -33,11 +34,11 @@ name: Revenge of the FIPS - type: `string` - no default -Author or authors of the challenge, as shown to players in the frontend UI. If there are multiple authors, specify them as one string. +Author or authors of the challenge, as shown to players in the frontend UI. If +there are multiple authors, specify them as one string. ```yaml author: John Author - # multiple authors: author: Alice, Bob, and others ``` @@ -47,7 +48,9 @@ author: Alice, Bob, and others - type: `string` - no default -Description and flavortext for the challenge, as shown to players in the frontend UI. Supports templating to include information about the challenge, such as the link or command to connect. +Description and flavortext for the challenge, as shown to players in the +frontend UI. Supports templating to include information about the challenge, +such as the link or command to connect. Most challenges only need `{{ nc }}` or `{{ link }}`.