From dad9efb76ca66f4078004f102fc8449082b981f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 01:16:36 +0000 Subject: [PATCH 1/3] Initial plan From 8b6959d42c011438e391af86b4db4e78ad67e78c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 01:39:55 +0000 Subject: [PATCH 2/3] Add multi-page Astro site with themes and GitHub Pages deployment Co-authored-by: cywf <100837335+cywf@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/documentation.md | 23 +++ .github/ISSUE_TEMPLATE/script-tool.md | 44 +++++ .github/workflows/pages.yml | 86 +++++++++ .gitignore | 13 ++ README.md | 47 +++++ site/astro.config.mjs | 16 ++ site/package.json | 31 ++++ site/public/data/discussions.json | 1 + site/public/data/projects.json | 4 + site/public/data/scripts.json | 37 ++++ site/public/data/stats.json | 7 + site/public/favicon.svg | 3 + site/scripts/fetch_discussions.ts | 113 ++++++++++++ site/scripts/fetch_projects.ts | 215 ++++++++++++++++++++++ site/scripts/fetch_repo_data.ts | 103 +++++++++++ site/scripts/scan_scripts.ts | 131 ++++++++++++++ site/scripts/tsconfig.json | 16 ++ site/src/components/Breadcrumbs.astro | 25 +++ site/src/components/DevelopmentBoard.tsx | 141 +++++++++++++++ site/src/components/Discussions.tsx | 184 +++++++++++++++++++ site/src/components/MermaidViewer.tsx | 151 ++++++++++++++++ site/src/components/Statistics.tsx | 219 +++++++++++++++++++++++ site/src/components/ThemeSwitcher.tsx | 128 +++++++++++++ site/src/env.d.ts | 1 + site/src/layouts/Layout.astro | 167 +++++++++++++++++ site/src/pages/404.astro | 39 ++++ site/src/pages/create-issue.astro | 137 ++++++++++++++ site/src/pages/development-board.astro | 21 +++ site/src/pages/discussions.astro | 21 +++ site/src/pages/docs/[...slug].astro | 101 +++++++++++ site/src/pages/docs/index.astro | 109 +++++++++++ site/src/pages/index.astro | 125 +++++++++++++ site/src/pages/statistics.astro | 11 ++ site/src/pages/visualizer.astro | 45 +++++ site/src/styles/global.css | 68 +++++++ site/tailwind.config.mjs | 70 ++++++++ site/tsconfig.json | 8 + 37 files changed, 2661 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/documentation.md create mode 100644 .github/ISSUE_TEMPLATE/script-tool.md create mode 100644 .github/workflows/pages.yml create mode 100644 site/astro.config.mjs create mode 100644 site/package.json create mode 100644 site/public/data/discussions.json create mode 100644 site/public/data/projects.json create mode 100644 site/public/data/scripts.json create mode 100644 site/public/data/stats.json create mode 100644 site/public/favicon.svg create mode 100644 site/scripts/fetch_discussions.ts create mode 100644 site/scripts/fetch_projects.ts create mode 100644 site/scripts/fetch_repo_data.ts create mode 100644 site/scripts/scan_scripts.ts create mode 100644 site/scripts/tsconfig.json create mode 100644 site/src/components/Breadcrumbs.astro create mode 100644 site/src/components/DevelopmentBoard.tsx create mode 100644 site/src/components/Discussions.tsx create mode 100644 site/src/components/MermaidViewer.tsx create mode 100644 site/src/components/Statistics.tsx create mode 100644 site/src/components/ThemeSwitcher.tsx create mode 100644 site/src/env.d.ts create mode 100644 site/src/layouts/Layout.astro create mode 100644 site/src/pages/404.astro create mode 100644 site/src/pages/create-issue.astro create mode 100644 site/src/pages/development-board.astro create mode 100644 site/src/pages/discussions.astro create mode 100644 site/src/pages/docs/[...slug].astro create mode 100644 site/src/pages/docs/index.astro create mode 100644 site/src/pages/index.astro create mode 100644 site/src/pages/statistics.astro create mode 100644 site/src/pages/visualizer.astro create mode 100644 site/src/styles/global.css create mode 100644 site/tailwind.config.mjs create mode 100644 site/tsconfig.json diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 0000000..6cb0886 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,23 @@ +--- +name: Documentation Update +about: Suggest improvements or corrections to documentation +title: 'docs: ' +labels: 'documentation' +assignees: '' + +--- + +**Which documentation needs updating?** +Please specify which document(s) need to be updated (e.g., README.md, docs/ctf-overview.md) + +**What needs to be changed?** +A clear and concise description of what should be updated, corrected, or added. + +**Why is this change needed?** +Explain why this documentation change would be helpful. + +**Proposed content (optional)** +If you have specific wording or content to suggest, please include it here. + +**Additional context** +Add any other context, screenshots, or links about the documentation update. diff --git a/.github/ISSUE_TEMPLATE/script-tool.md b/.github/ISSUE_TEMPLATE/script-tool.md new file mode 100644 index 0000000..172143c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/script-tool.md @@ -0,0 +1,44 @@ +--- +name: Script/Tool Contribution +about: Share a script or tool for CTF challenges +title: 'tool: ' +labels: 'script,tool' +assignees: '' + +--- + +**Script/Tool Name** +What would you like to call this script or tool? + +**Purpose** +What CTF challenge category or problem does this script/tool address? +- [ ] Enumeration +- [ ] Exploitation +- [ ] Cryptography +- [ ] Forensics +- [ ] Web Exploitation +- [ ] Reverse Engineering +- [ ] Other (please specify) + +**Description** +Provide a clear description of what the script/tool does and how it works. + +**Usage Example** +Show an example of how to use the script/tool: +```bash +# Example usage +./your-script.sh [arguments] +``` + +**Dependencies** +List any dependencies or requirements: +- Language/Runtime: (e.g., Python 3.x, Bash, etc.) +- External tools: (e.g., nmap, metasploit, etc.) +- Libraries: (e.g., pip packages, etc.) + +**Script/Tool Location** +Where should this be added in the repository? +- Suggested path: `scripts/[category]/your-script.ext` + +**Additional Notes** +Any additional context, links, or references that would be helpful. diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..9c665a4 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,86 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: site/package-lock.json + + - name: Install dependencies + working-directory: ./site + run: npm ci + + - name: Fetch repository data + working-directory: ./site + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Fetching repository statistics..." + node --loader tsx scripts/fetch_repo_data.ts || npm exec tsx scripts/fetch_repo_data.ts + + - name: Fetch discussions + working-directory: ./site + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Fetching discussions..." + node --loader tsx scripts/fetch_discussions.ts || npm exec tsx scripts/fetch_discussions.ts + + - name: Fetch projects + working-directory: ./site + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Fetching project data..." + node --loader tsx scripts/fetch_projects.ts || npm exec tsx scripts/fetch_projects.ts + + - name: Scan scripts + working-directory: ./site + run: | + echo "Scanning scripts directory..." + node --loader tsx scripts/scan_scripts.ts || npm exec tsx scripts/scan_scripts.ts + + - name: Build site + working-directory: ./site + run: npm run build + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./site/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 2ba6e74..eccdb97 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,16 @@ secrets.txt # CTF-specific writeups/drafts/ notes/private/ + +# Node.js +node_modules/ +package-lock.json + +# Astro +.astro/ +dist/ + +# Site build artifacts +site/dist/ +site/.astro/ +site/node_modules/ diff --git a/README.md b/README.md index 7ca295b..27e5f00 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Welcome to the CTF-Kit Repository +🌐 **Live Site**: [https://cywf.github.io/ctf-kit/](https://cywf.github.io/ctf-kit/) + ## Introduction Welcome to the CTF-Kit repository! This project is designed to be a collaborative and evolving resource for anyone interested in participating in Capture The Flag (CTF) competitions. Whether you're a seasoned veteran or a newcomer to the world of CTFs, this repository aims to provide you with the tools, resources, and knowledge you need to succeed. @@ -69,3 +71,48 @@ Your input is valuable! If you have ideas for new features, tools, or improvemen ## Conclusion Thank you for being a part of the CTF-Kit community! Whether you’re here to learn, contribute, or both, we’re excited to have you with us. This repository is a living project that grows and improves with each contribution, so don’t hesitate to get involved. Fork the repo, create your branch, and start tackling those CTFs! + +## Contributing to the Documentation + +### Adding Documentation +Documentation files are stored in the `/docs/` directory and are automatically rendered on the [live site](https://cywf.github.io/ctf-kit/docs). To add or update documentation: + +1. Create or edit markdown files in `/docs/` or subdirectories +2. Use clear headings and formatting +3. Submit a pull request with your changes +4. The site will automatically update when changes are merged to main + +### Adding Mermaid Diagrams +Visual diagrams help explain complex concepts and workflows. To add a diagram: + +1. Create a `.mmd` file in the `/mermaid/` directory (create this directory if it doesn't exist) +2. Write your diagram using [Mermaid syntax](https://mermaid.js.org/intro/) +3. Commit and push your changes +4. The CI workflow will process the diagram and make it available on the [visualizer page](https://cywf.github.io/ctf-kit/visualizer) + +Example diagram structure: +```mermaid +graph TD + A[Start] --> B{Is it?} + B -->|Yes| C[OK] + B -->|No| D[End] +``` + +## How the Site Works + +### CI/CD Pipeline +Our GitHub Actions workflow automatically: +1. Fetches live repository statistics (stars, forks, languages, commits) +2. Retrieves discussions from GitHub Discussions +3. Syncs project board items or falls back to issues +4. Indexes scripts with descriptions from comments +5. Builds and deploys the site to GitHub Pages + +### Data Snapshots +The site uses JSON data snapshots created during the CI build: +- `stats.json` - Repository metrics and activity +- `discussions.json` - Latest community discussions +- `projects.json` - Development board status +- `scripts.json` - Available scripts with descriptions + +These snapshots are generated server-side and consumed client-side for optimal performance. diff --git a/site/astro.config.mjs b/site/astro.config.mjs new file mode 100644 index 0000000..97f035c --- /dev/null +++ b/site/astro.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig } from 'astro/config'; +import react from '@astrojs/react'; +import tailwind from '@astrojs/tailwind'; + +// https://astro.build/config +export default defineConfig({ + site: 'https://cywf.github.io', + base: '/ctf-kit', + integrations: [ + react(), + tailwind({ + applyBaseStyles: false, + }), + ], + output: 'static', +}); diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..1617018 --- /dev/null +++ b/site/package.json @@ -0,0 +1,31 @@ +{ + "name": "ctf-kit-site", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "typecheck": "astro check" + }, + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/react": "^3.6.2", + "@astrojs/tailwind": "^5.1.2", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "astro": "^4.16.12", + "chart.js": "^4.4.6", + "daisyui": "^4.12.14", + "mermaid": "^11.4.0", + "react": "^18.3.1", + "react-chartjs-2": "^5.2.0", + "react-dom": "^18.3.1", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3" + }, + "devDependencies": { + "@types/node": "^22.9.0", + "tsx": "^4.19.2" + } +} diff --git a/site/public/data/discussions.json b/site/public/data/discussions.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/site/public/data/discussions.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/site/public/data/projects.json b/site/public/data/projects.json new file mode 100644 index 0000000..addfcd3 --- /dev/null +++ b/site/public/data/projects.json @@ -0,0 +1,4 @@ +{ + "name": "Project Board", + "items": [] +} \ No newline at end of file diff --git a/site/public/data/scripts.json b/site/public/data/scripts.json new file mode 100644 index 0000000..bc856dd --- /dev/null +++ b/site/public/data/scripts.json @@ -0,0 +1,37 @@ +[ + { + "path": "README.md", + "name": "README.md", + "description": "Script: README.md" + }, + { + "path": "Networking/README.md", + "name": "README.md", + "description": "Script: README.md" + }, + { + "path": "Networking/advanced_nmap_scan.sh", + "name": "advanced_nmap_scan.sh", + "description": "############################################################################" + }, + { + "path": "Networking/basic_nmap_scan.sh", + "name": "basic_nmap_scan.sh", + "description": "############################################################################" + }, + { + "path": "Enumeration/README.md", + "name": "README.md", + "description": "Script: README.md" + }, + { + "path": "Enumeration/smb_share_enum.sh", + "name": "smb_share_enum.sh", + "description": "############################################################################" + }, + { + "path": "Enumeration/web_server_enum.sh", + "name": "web_server_enum.sh", + "description": "############################################################################" + } +] \ No newline at end of file diff --git a/site/public/data/stats.json b/site/public/data/stats.json new file mode 100644 index 0000000..5d4b5da --- /dev/null +++ b/site/public/data/stats.json @@ -0,0 +1,7 @@ +{ + "stars": 0, + "forks": 0, + "watchers": 0, + "languages": {}, + "commitActivity": [] +} \ No newline at end of file diff --git a/site/public/favicon.svg b/site/public/favicon.svg new file mode 100644 index 0000000..56b0182 --- /dev/null +++ b/site/public/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/site/scripts/fetch_discussions.ts b/site/scripts/fetch_discussions.ts new file mode 100644 index 0000000..26a2f4c --- /dev/null +++ b/site/scripts/fetch_discussions.ts @@ -0,0 +1,113 @@ +#!/usr/bin/env node + +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +const REPO_OWNER = 'cywf'; +const REPO_NAME = 'ctf-kit'; + +interface Discussion { + title: string; + author: string; + url: string; + createdAt: string; + category: string; +} + +async function fetchGraphQL(query: string) { + const headers: Record = { + 'Content-Type': 'application/json', + 'User-Agent': 'CTF-Kit-Site', + }; + + if (GITHUB_TOKEN) { + headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`; + } + + const response = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers, + body: JSON.stringify({ query }), + }); + + if (!response.ok) { + throw new Error(`GitHub GraphQL error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +async function fetchDiscussions(): Promise { + console.log('Fetching discussions...'); + + const query = ` + query { + repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") { + discussions(first: 25, orderBy: {field: CREATED_AT, direction: DESC}) { + nodes { + title + url + createdAt + author { + login + } + category { + name + } + } + } + } + } + `; + + try { + const result = await fetchGraphQL(query); + + if (result.errors) { + console.warn('GraphQL errors:', result.errors); + } + + const discussions: Discussion[] = result.data?.repository?.discussions?.nodes?.map((node: any) => ({ + title: node.title, + author: node.author?.login || 'Unknown', + url: node.url, + createdAt: node.createdAt, + category: node.category?.name || 'General', + })) || []; + + console.log(`βœ“ Fetched ${discussions.length} discussions`); + return discussions; + } catch (error) { + console.warn('Failed to fetch discussions:', error); + return []; + } +} + +async function main() { + try { + const discussions = await fetchDiscussions(); + + // Ensure directory exists + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + + // Write to file + const outputPath = join(dataDir, 'discussions.json'); + await writeFile(outputPath, JSON.stringify(discussions, null, 2)); + + console.log(`βœ“ Discussions written to ${outputPath}`); + } catch (error) { + console.error('Error fetching discussions:', error); + + // Write empty array as fallback + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + const outputPath = join(dataDir, 'discussions.json'); + await writeFile(outputPath, JSON.stringify([], null, 2)); + + console.log('βœ“ Wrote empty discussions array'); + } +} + +main(); diff --git a/site/scripts/fetch_projects.ts b/site/scripts/fetch_projects.ts new file mode 100644 index 0000000..2f37374 --- /dev/null +++ b/site/scripts/fetch_projects.ts @@ -0,0 +1,215 @@ +#!/usr/bin/env node + +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +const REPO_OWNER = 'cywf'; +const REPO_NAME = 'ctf-kit'; + +interface ProjectItem { + title: string; + status: string; + url: string; + labels: string[]; + assignees: string[]; +} + +interface ProjectData { + name: string; + items: ProjectItem[]; +} + +async function fetchGitHub(path: string) { + const url = `https://api.github.com${path}`; + const headers: Record = { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'CTF-Kit-Site', + }; + + if (GITHUB_TOKEN) { + headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`; + } + + const response = await fetch(url, { headers }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +async function fetchGraphQL(query: string) { + const headers: Record = { + 'Content-Type': 'application/json', + 'User-Agent': 'CTF-Kit-Site', + }; + + if (GITHUB_TOKEN) { + headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`; + } + + const response = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers, + body: JSON.stringify({ query }), + }); + + if (!response.ok) { + throw new Error(`GitHub GraphQL error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +async function fetchProjectsV2(): Promise { + console.log('Attempting to fetch Projects v2...'); + + const query = ` + query { + repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") { + projectsV2(first: 1) { + nodes { + title + items(first: 100) { + nodes { + content { + ... on Issue { + title + url + labels(first: 10) { + nodes { + name + } + } + assignees(first: 5) { + nodes { + login + } + } + } + } + fieldValues(first: 10) { + nodes { + ... on ProjectV2ItemFieldSingleSelectValue { + name + } + } + } + } + } + } + } + } + } + `; + + try { + const result = await fetchGraphQL(query); + + if (result.errors) { + console.warn('GraphQL errors:', result.errors); + return null; + } + + const project = result.data?.repository?.projectsV2?.nodes?.[0]; + if (!project) { + console.log('No Projects v2 found'); + return null; + } + + const items: ProjectItem[] = project.items.nodes + .filter((item: any) => item.content) + .map((item: any) => { + const status = item.fieldValues?.nodes?.find((field: any) => field.name)?.name || 'Todo'; + return { + title: item.content.title, + status, + url: item.content.url, + labels: item.content.labels?.nodes?.map((label: any) => label.name) || [], + assignees: item.content.assignees?.nodes?.map((assignee: any) => assignee.login) || [], + }; + }); + + console.log(`βœ“ Fetched Projects v2 with ${items.length} items`); + return { + name: project.title, + items, + }; + } catch (error) { + console.warn('Failed to fetch Projects v2:', error); + return null; + } +} + +async function fetchIssuesFallback(): Promise { + console.log('Using issues fallback...'); + + const issues = await fetchGitHub(`/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=all&per_page=100`); + + const items: ProjectItem[] = issues.map((issue: any) => { + // Determine status from labels + let status = 'Todo'; + const labelNames = issue.labels.map((label: any) => label.name); + + if (labelNames.some((name: string) => name.toLowerCase().includes('done') || name.toLowerCase().includes('completed'))) { + status = 'Done'; + } else if (labelNames.some((name: string) => name.toLowerCase().includes('doing') || name.toLowerCase().includes('progress'))) { + status = 'In Progress'; + } + + return { + title: issue.title, + status, + url: issue.html_url, + labels: labelNames, + assignees: issue.assignees?.map((assignee: any) => assignee.login) || [], + }; + }); + + console.log(`βœ“ Fetched ${items.length} issues as fallback`); + return { + name: 'Issues (Fallback)', + items, + }; +} + +async function main() { + try { + // Try Projects v2 first + let projectData = await fetchProjectsV2(); + + // Fallback to issues if Projects v2 not available + if (!projectData) { + projectData = await fetchIssuesFallback(); + } + + // Ensure directory exists + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + + // Write to file + const outputPath = join(dataDir, 'projects.json'); + await writeFile(outputPath, JSON.stringify(projectData, null, 2)); + + console.log(`βœ“ Project data written to ${outputPath}`); + } catch (error) { + console.error('Error fetching project data:', error); + + // Write empty fallback data + const fallbackData: ProjectData = { + name: 'Project Board', + items: [], + }; + + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + const outputPath = join(dataDir, 'projects.json'); + await writeFile(outputPath, JSON.stringify(fallbackData, null, 2)); + + console.log('βœ“ Wrote fallback project data'); + } +} + +main(); diff --git a/site/scripts/fetch_repo_data.ts b/site/scripts/fetch_repo_data.ts new file mode 100644 index 0000000..445c62d --- /dev/null +++ b/site/scripts/fetch_repo_data.ts @@ -0,0 +1,103 @@ +#!/usr/bin/env node + +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +const REPO_OWNER = 'cywf'; +const REPO_NAME = 'ctf-kit'; + +interface RepoStats { + stars: number; + forks: number; + watchers: number; + languages: Record; + commitActivity: Array<{ week: string; commits: number }>; +} + +async function fetchGitHub(path: string) { + const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}${path}`; + const headers: Record = { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'CTF-Kit-Site', + }; + + if (GITHUB_TOKEN) { + headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`; + } + + const response = await fetch(url, { headers }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +async function fetchRepoData(): Promise { + console.log('Fetching repository data...'); + + // Fetch repo info + const repoInfo = await fetchGitHub(''); + + // Fetch languages + const languages = await fetchGitHub('/languages'); + + // Fetch commit activity + const commitActivity = await fetchGitHub('/stats/commit_activity'); + + // Process commit activity for last 12 weeks + const last12Weeks = commitActivity.slice(-12).map((week: any) => ({ + week: new Date(week.week * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), + commits: week.total, + })); + + const stats: RepoStats = { + stars: repoInfo.stargazers_count || 0, + forks: repoInfo.forks_count || 0, + watchers: repoInfo.watchers_count || 0, + languages, + commitActivity: last12Weeks, + }; + + console.log(`Stats: ${stats.stars} stars, ${stats.forks} forks, ${stats.watchers} watchers`); + + return stats; +} + +async function main() { + try { + const stats = await fetchRepoData(); + + // Ensure directory exists + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + + // Write to file + const outputPath = join(dataDir, 'stats.json'); + await writeFile(outputPath, JSON.stringify(stats, null, 2)); + + console.log(`βœ“ Repository stats written to ${outputPath}`); + } catch (error) { + console.error('Error fetching repository data:', error); + + // Write empty fallback data + const fallbackStats: RepoStats = { + stars: 0, + forks: 0, + watchers: 0, + languages: {}, + commitActivity: [], + }; + + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + const outputPath = join(dataDir, 'stats.json'); + await writeFile(outputPath, JSON.stringify(fallbackStats, null, 2)); + + console.log('βœ“ Wrote fallback stats data'); + } +} + +main(); diff --git a/site/scripts/scan_scripts.ts b/site/scripts/scan_scripts.ts new file mode 100644 index 0000000..7a98f19 --- /dev/null +++ b/site/scripts/scan_scripts.ts @@ -0,0 +1,131 @@ +#!/usr/bin/env node + +import { writeFile, mkdir, readdir, readFile } from 'fs/promises'; +import { join } from 'path'; + +interface ScriptInfo { + path: string; + name: string; + description: string; +} + +async function scanScripts(): Promise { + console.log('Scanning scripts directory...'); + + const scriptsPath = join(process.cwd(), '..', 'scripts'); + const scripts: ScriptInfo[] = []; + + try { + const entries = await readdir(scriptsPath, { recursive: true, withFileTypes: true }); + + for (const entry of entries) { + if (entry.isFile()) { + const filePath = join(entry.parentPath || (entry as any).path || '', entry.name); + const relativePath = filePath.replace(scriptsPath, '').replace(/^\//, ''); + + // Try to extract description from file header + let description = ''; + try { + const content = await readFile(filePath, 'utf-8'); + + // Look for comment blocks at the start of the file + const lines = content.split('\n').slice(0, 20); // First 20 lines + + // Python style comments + if (entry.name.endsWith('.py')) { + const docstring = content.match(/"""([\s\S]*?)"""/); + if (docstring) { + const docText = docstring[1]; + if (typeof docText === 'string') { + const lines = docText.trim().split('\n'); + if (lines.length > 0 && lines[0]) { + description = lines[0]; + } + } + } + if (!description) { + const comment = lines.find(line => line.trim().startsWith('#') && line.length > 2); + if (comment) { + description = comment.replace(/^#\s*/, '').trim(); + } + } + } + + // Shell/Bash comments + else if (entry.name.endsWith('.sh')) { + const comment = lines.find(line => line.trim().startsWith('#') && line.length > 2 && !line.includes('#!/')); + if (comment) { + description = comment.replace(/^#\s*/, '').trim(); + } + } + + // JavaScript/TypeScript comments + else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) { + const blockComment = content.match(/\/\*\*([\s\S]*?)\*\//); + if (blockComment) { + const commentText = blockComment[1]; + if (typeof commentText === 'string') { + const processedLines = commentText.replace(/\*/g, '').trim().split('\n'); + if (processedLines.length > 0 && processedLines[0]) { + description = processedLines[0].trim(); + } + } + } + if (!description) { + const comment = lines.find(line => line.trim().startsWith('//') && line.length > 3); + if (comment) { + description = comment.replace(/^\/\/\s*/, '').trim(); + } + } + } + + if (!description) { + description = `Script: ${entry.name}`; + } + } catch (err) { + description = `Script: ${entry.name}`; + } + + scripts.push({ + path: relativePath, + name: entry.name, + description, + }); + } + } + + console.log(`βœ“ Found ${scripts.length} scripts`); + return scripts; + } catch (error) { + console.warn('Scripts directory not found or empty:', error); + return []; + } +} + +async function main() { + try { + const scripts = await scanScripts(); + + // Ensure directory exists + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + + // Write to file + const outputPath = join(dataDir, 'scripts.json'); + await writeFile(outputPath, JSON.stringify(scripts, null, 2)); + + console.log(`βœ“ Scripts index written to ${outputPath}`); + } catch (error) { + console.error('Error scanning scripts:', error); + + // Write empty array as fallback + const dataDir = join(process.cwd(), 'public', 'data'); + await mkdir(dataDir, { recursive: true }); + const outputPath = join(dataDir, 'scripts.json'); + await writeFile(outputPath, JSON.stringify([], null, 2)); + + console.log('βœ“ Wrote empty scripts array'); + } +} + +main(); diff --git a/site/scripts/tsconfig.json b/site/scripts/tsconfig.json new file mode 100644 index 0000000..3f909db --- /dev/null +++ b/site/scripts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist" + }, + "include": ["scripts/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/site/src/components/Breadcrumbs.astro b/site/src/components/Breadcrumbs.astro new file mode 100644 index 0000000..41f5f15 --- /dev/null +++ b/site/src/components/Breadcrumbs.astro @@ -0,0 +1,25 @@ +--- +interface Props { + items: Array<{ label: string; href?: string }>; +} + +const { items } = Astro.props; +const base = import.meta.env.BASE_URL; +--- + + diff --git a/site/src/components/DevelopmentBoard.tsx b/site/src/components/DevelopmentBoard.tsx new file mode 100644 index 0000000..1db34eb --- /dev/null +++ b/site/src/components/DevelopmentBoard.tsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from 'react'; + +interface ProjectItem { + title: string; + status: string; + url: string; + labels: string[]; + assignees: string[]; +} + +interface ProjectData { + name: string; + items: ProjectItem[]; +} + +export default function DevelopmentBoard() { + const [projectData, setProjectData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchProjects = async () => { + try { + const base = import.meta.env.BASE_URL || ''; + const response = await fetch(`${base}/data/projects.json`); + if (!response.ok) { + throw new Error('Failed to load project data'); + } + const data = await response.json(); + setProjectData(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + + fetchProjects(); + }, []); + + if (loading) { + return ( +
+
+
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
+
+ ); + } + + if (error || !projectData) { + return ( +
+ + + + Error loading project data: {error} +
+ ); + } + + // Group items by status + const todoItems = projectData.items.filter((item) => item.status === 'Todo' || item.status === 'todo'); + const inProgressItems = projectData.items.filter((item) => item.status === 'In Progress' || item.status === 'doing'); + const doneItems = projectData.items.filter((item) => item.status === 'Done' || item.status === 'done'); + + const renderColumn = (title: string, items: ProjectItem[], colorClass: string) => ( +
+
+
+

{title}

+
{items.length}
+
+
+
+ {items.length === 0 ? ( +
+
+

No items

+
+
+ ) : ( + items.map((item, index) => ( +
+
+

+ + {item.title} + +

+ {item.labels.length > 0 && ( +
+ {item.labels.map((label, idx) => ( + + {label} + + ))} +
+ )} + {item.assignees.length > 0 && ( +
+ πŸ‘€ + {item.assignees.join(', ')} +
+ )} +
+
+ )) + )} +
+
+ ); + + return ( +
+
+ + + +
+

{projectData.name}

+
This is a read-only view. Visit GitHub to update items.
+
+
+ +
+ {renderColumn('πŸ“‹ To Do', todoItems, 'border-l-4 border-info')} + {renderColumn('🚧 In Progress', inProgressItems, 'border-l-4 border-warning')} + {renderColumn('βœ… Done', doneItems, 'border-l-4 border-success')} +
+
+ ); +} diff --git a/site/src/components/Discussions.tsx b/site/src/components/Discussions.tsx new file mode 100644 index 0000000..785c09f --- /dev/null +++ b/site/src/components/Discussions.tsx @@ -0,0 +1,184 @@ +import { useEffect, useState } from 'react'; + +interface Discussion { + title: string; + author: string; + url: string; + createdAt: string; + category: string; +} + +export default function Discussions() { + const [discussions, setDiscussions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + const fetchDiscussions = async () => { + try { + const base = import.meta.env.BASE_URL || ''; + const response = await fetch(`${base}/data/discussions.json`); + if (!response.ok) { + throw new Error('Failed to load discussions'); + } + const data = await response.json(); + setDiscussions(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + + fetchDiscussions(); + }, []); + + const filteredDiscussions = discussions.filter( + (discussion) => + discussion.title.toLowerCase().includes(searchTerm.toLowerCase()) || + discussion.author.toLowerCase().includes(searchTerm.toLowerCase()) || + discussion.category.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + if (loading) { + return ( +
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
+ ); + } + + if (error) { + return ( +
+ + + + Error loading discussions: {error} +
+ ); + } + + if (discussions.length === 0) { + return ( +
+
+
+ + + +

No Discussions Yet

+

+ Be the first to start a conversation! Share your ideas, ask questions, or discuss CTF strategies with the community. +

+ + Start a Discussion + +
+
+
+ ); + } + + return ( +
+ {/* Search Bar */} +
+
+ setSearchTerm(e.target.value)} + /> + +
+
+ + {/* Discussions List */} + {filteredDiscussions.length === 0 ? ( +
+ + + + No discussions match your search. +
+ ) : ( +
+ {filteredDiscussions.map((discussion, index) => ( +
+
+
+
+

+ + {discussion.title} + +

+
+ {discussion.category} + by {discussion.author} + β€’ + {new Date(discussion.createdAt).toLocaleDateString()} +
+
+ + + + + +
+
+
+ ))} +
+ )} +
+ ); +} diff --git a/site/src/components/MermaidViewer.tsx b/site/src/components/MermaidViewer.tsx new file mode 100644 index 0000000..a20b010 --- /dev/null +++ b/site/src/components/MermaidViewer.tsx @@ -0,0 +1,151 @@ +import { useEffect, useRef, useState } from 'react'; +import mermaid from 'mermaid'; + +interface MermaidDiagram { + name: string; + path: string; + content: string; +} + +interface MermaidViewerProps { + diagrams: MermaidDiagram[]; +} + +export default function MermaidViewer({ diagrams }: MermaidViewerProps) { + const [selectedDiagram, setSelectedDiagram] = useState(diagrams[0]?.name || ''); + const [error, setError] = useState(null); + const containerRef = useRef(null); + + useEffect(() => { + mermaid.initialize({ + startOnLoad: true, + theme: 'dark', + themeVariables: { + darkMode: true, + background: '#0f0f0f', + primaryColor: '#00ffff', + primaryTextColor: '#fff', + primaryBorderColor: '#00ffff', + lineColor: '#00ffff', + secondaryColor: '#ff00ff', + tertiaryColor: '#00ff00', + fontSize: '16px', + }, + }); + }, []); + + useEffect(() => { + if (!containerRef.current || !selectedDiagram) return; + + const renderDiagram = async () => { + try { + setError(null); + const diagram = diagrams.find((d) => d.name === selectedDiagram); + if (!diagram) { + setError('Diagram not found'); + return; + } + + const container = containerRef.current; + if (!container) return; + + container.innerHTML = ''; + const id = `mermaid-${Date.now()}`; + const { svg } = await mermaid.render(id, diagram.content); + container.innerHTML = svg; + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to render diagram'); + console.error('Mermaid render error:', err); + } + }; + + renderDiagram(); + }, [selectedDiagram, diagrams]); + + if (diagrams.length === 0) { + return ( +
+
+
+ + + +

No Diagrams Yet

+

+ Add Mermaid diagrams to visualize your project architecture, workflows, and more. Create .mmd files in the /mermaid/ directory. +

+
+
+ + + +
+
How to add diagrams:
+
    +
  1. Create a .mmd file in /mermaid/
  2. +
  3. Write your Mermaid diagram syntax
  4. +
  5. Commit and push your changes
  6. +
  7. The CI will automatically process it
  8. +
+
+
+ + Learn Mermaid Syntax + +
+
+
+
+ ); + } + + return ( +
+ {/* Diagram Selector */} +
+ {diagrams.map((diagram) => ( + + ))} +
+ + {/* Error Display */} + {error && ( +
+ + + + Error rendering diagram: {error} +
+ )} + + {/* Diagram Display */} +
+
+
+
+
+
+ ); +} diff --git a/site/src/components/Statistics.tsx b/site/src/components/Statistics.tsx new file mode 100644 index 0000000..02e63a4 --- /dev/null +++ b/site/src/components/Statistics.tsx @@ -0,0 +1,219 @@ +import { useEffect, useState } from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + ArcElement, + Title, + Tooltip, + Legend, + type ChartOptions, +} from 'chart.js'; +import { Bar, Pie } from 'react-chartjs-2'; + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + ArcElement, + Title, + Tooltip, + Legend +); + +interface RepoStats { + stars: number; + forks: number; + watchers: number; + languages: Record; + commitActivity: Array<{ week: string; commits: number }>; +} + +export default function Statistics() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchStats = async () => { + try { + const base = import.meta.env.BASE_URL || ''; + const response = await fetch(`${base}/data/stats.json`); + if (!response.ok) { + throw new Error('Failed to load statistics'); + } + const data = await response.json(); + setStats(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + + fetchStats(); + }, []); + + if (loading) { + return ( +
+
+
+
+
+ ); + } + + if (error || !stats) { + return ( +
+ + + + Error loading statistics: {error} +
+ ); + } + + const languageData = { + labels: Object.keys(stats.languages), + datasets: [ + { + label: 'Language Usage', + data: Object.values(stats.languages), + backgroundColor: [ + 'rgba(255, 99, 132, 0.8)', + 'rgba(54, 162, 235, 0.8)', + 'rgba(255, 206, 86, 0.8)', + 'rgba(75, 192, 192, 0.8)', + 'rgba(153, 102, 255, 0.8)', + 'rgba(255, 159, 64, 0.8)', + ], + borderColor: [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)', + ], + borderWidth: 2, + }, + ], + }; + + const commitData = { + labels: stats.commitActivity.map((item) => item.week), + datasets: [ + { + label: 'Commits', + data: stats.commitActivity.map((item) => item.commits), + backgroundColor: 'rgba(54, 162, 235, 0.8)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 2, + }, + ], + }; + + const chartOptions: ChartOptions<'bar'> = { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + labels: { + color: 'rgba(255, 255, 255, 0.8)', + }, + }, + }, + scales: { + y: { + ticks: { + color: 'rgba(255, 255, 255, 0.8)', + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)', + }, + }, + x: { + ticks: { + color: 'rgba(255, 255, 255, 0.8)', + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)', + }, + }, + }, + }; + + const pieOptions: ChartOptions<'pie'> = { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + labels: { + color: 'rgba(255, 255, 255, 0.8)', + }, + }, + }, + }; + + return ( +
+ {/* Stats Cards */} +
+
+
+ + + +
+
Stars
+
{stats.stars}
+
+ +
+
+ + + +
+
Forks
+
{stats.forks}
+
+ +
+
+ + + + +
+
Watchers
+
{stats.watchers}
+
+
+ + {/* Charts */} +
+
+
+

Language Distribution

+
+ +
+
+
+ +
+
+

Commit Activity (12 Weeks)

+
+ +
+
+
+
+
+ ); +} diff --git a/site/src/components/ThemeSwitcher.tsx b/site/src/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..8c9a72f --- /dev/null +++ b/site/src/components/ThemeSwitcher.tsx @@ -0,0 +1,128 @@ +import { useEffect, useState } from 'react'; + +const themes = [ + { id: 'night', name: 'Nightfall' }, + { id: 'dracula', name: 'Dracula' }, + { id: 'cyberpunk', name: 'Cyberpunk' }, + { id: 'dark-neon', name: 'Dark Neon' }, + { id: 'hackerman', name: 'Hackerman' }, + { id: 'gamecore', name: 'Gamecore' }, + { id: 'neon-accent', name: 'Neon Accent' }, +]; + +const DEFAULT_THEME = import.meta.env.DEFAULT_THEME || 'night'; + +export default function ThemeSwitcher() { + const [currentTheme, setCurrentTheme] = useState(DEFAULT_THEME); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + + // Check localStorage first + const savedTheme = localStorage.getItem('theme'); + if (savedTheme && themes.some(t => t.id === savedTheme)) { + setCurrentTheme(savedTheme); + document.documentElement.setAttribute('data-theme', savedTheme); + return; + } + + // Check prefers-color-scheme + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const initialTheme = prefersDark ? DEFAULT_THEME : 'night'; + setCurrentTheme(initialTheme); + document.documentElement.setAttribute('data-theme', initialTheme); + }, []); + + const handleThemeChange = (themeId: string) => { + setCurrentTheme(themeId); + document.documentElement.setAttribute('data-theme', themeId); + localStorage.setItem('theme', themeId); + }; + + if (!mounted) { + return ( +
+ +
+ ); + } + + return ( +
+ +
    + {themes.map((theme) => ( +
  • + +
  • + ))} +
+
+ ); +} diff --git a/site/src/env.d.ts b/site/src/env.d.ts new file mode 100644 index 0000000..9bc5cb4 --- /dev/null +++ b/site/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/site/src/layouts/Layout.astro b/site/src/layouts/Layout.astro new file mode 100644 index 0000000..259933a --- /dev/null +++ b/site/src/layouts/Layout.astro @@ -0,0 +1,167 @@ +--- +import '../styles/global.css'; +import ThemeSwitcher from '../components/ThemeSwitcher'; +import Breadcrumbs from '../components/Breadcrumbs.astro'; + +interface Props { + title: string; + description?: string; + breadcrumbs?: Array<{ label: string; href?: string }>; +} + +const { title, description = 'CTF-Kit - Your collaborative CTF resource hub', breadcrumbs = [] } = Astro.props; +const base = import.meta.env.BASE_URL; +const currentPath = Astro.url.pathname; + +const navItems = [ + { label: 'Home', href: `${base}/` }, + { label: 'Statistics', href: `${base}/statistics` }, + { label: 'Discussions', href: `${base}/discussions` }, + { label: 'Dev Board', href: `${base}/development-board` }, + { label: 'Docs', href: `${base}/docs` }, + { label: 'Visualizer', href: `${base}/visualizer` }, + { label: 'Create Issue', href: `${base}/create-issue` }, +]; +--- + + + + + + + + + + {title} | CTF-Kit + + + Skip to content + + + + + + {breadcrumbs.length > 0 && ( +
+ +
+ )} + + +
+ +
+ + + + + + + diff --git a/site/src/pages/404.astro b/site/src/pages/404.astro new file mode 100644 index 0000000..dbd9702 --- /dev/null +++ b/site/src/pages/404.astro @@ -0,0 +1,39 @@ +--- +import Layout from '../layouts/Layout.astro'; + +const base = import.meta.env.BASE_URL; +--- + + +
+
+
+

404

+

Page Not Found

+

+ Oops! The page you're looking for doesn't exist. It might have been moved or deleted. +

+ + +
+
+
+
diff --git a/site/src/pages/create-issue.astro b/site/src/pages/create-issue.astro new file mode 100644 index 0000000..77703b1 --- /dev/null +++ b/site/src/pages/create-issue.astro @@ -0,0 +1,137 @@ +--- +import Layout from '../layouts/Layout.astro'; + +const breadcrumbs = [{ label: 'Create Issue' }]; + +const issueTemplates = [ + { + title: 'πŸ› Bug Report', + description: 'Report a bug or unexpected behavior', + url: 'https://github.com/cywf/ctf-kit/issues/new?template=bug_report.md&labels=bug', + icon: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z', + color: 'error', + }, + { + title: '✨ Feature Request', + description: 'Suggest a new feature or enhancement', + url: 'https://github.com/cywf/ctf-kit/issues/new?template=feature_request.md&labels=enhancement', + icon: 'M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z', + color: 'primary', + }, + { + title: 'πŸ“š Documentation', + description: 'Improve or fix documentation', + url: 'https://github.com/cywf/ctf-kit/issues/new?labels=documentation&title=docs:', + icon: 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253', + color: 'info', + }, + { + title: 'πŸ”§ CTF Script/Tool', + description: 'Share or request a CTF script or tool', + url: 'https://github.com/cywf/ctf-kit/issues/new?labels=script,tool&title=tool:', + icon: 'M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4', + color: 'secondary', + }, + { + title: '🎯 CTF Challenge', + description: 'Document a CTF challenge or solution', + url: 'https://github.com/cywf/ctf-kit/issues/new?labels=ctf-challenge&title=challenge:', + icon: 'M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z', + color: 'accent', + }, + { + title: 'πŸ’¬ General Question', + description: 'Ask a question or start a discussion', + url: 'https://github.com/cywf/ctf-kit/discussions/new', + icon: 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z', + color: 'success', + }, +]; +--- + + +
+
+

Create an Issue

+

+ Choose a template below to create an issue or start a discussion. This will take you to GitHub where you can provide more details. +

+
+ + + +
+ + + +
+

Need help with something else?

+
+ Visit our issue templates page for more options, or browse existing issues. +
+
+
+ +
+
+

Before Creating an Issue

+
    +
  • Search existing issues to avoid duplicates
  • +
  • Provide as much detail as possible
  • +
  • Use descriptive titles that summarize the issue
  • +
  • For bug reports, include steps to reproduce
  • +
  • Add relevant labels to help categorize your issue
  • +
+
+
+
+
diff --git a/site/src/pages/development-board.astro b/site/src/pages/development-board.astro new file mode 100644 index 0000000..e51203b --- /dev/null +++ b/site/src/pages/development-board.astro @@ -0,0 +1,21 @@ +--- +import Layout from '../layouts/Layout.astro'; +import DevelopmentBoardComponent from '../components/DevelopmentBoard'; + +const breadcrumbs = [{ label: 'Development Board' }]; +--- + + +
+

Development Board

+ + View on GitHub + +
+ +
diff --git a/site/src/pages/discussions.astro b/site/src/pages/discussions.astro new file mode 100644 index 0000000..4ea2f42 --- /dev/null +++ b/site/src/pages/discussions.astro @@ -0,0 +1,21 @@ +--- +import Layout from '../layouts/Layout.astro'; +import DiscussionsComponent from '../components/Discussions'; + +const breadcrumbs = [{ label: 'Discussions' }]; +--- + + +
+

Community Discussions

+ + New Discussion + +
+ +
diff --git a/site/src/pages/docs/[...slug].astro b/site/src/pages/docs/[...slug].astro new file mode 100644 index 0000000..41e4b73 --- /dev/null +++ b/site/src/pages/docs/[...slug].astro @@ -0,0 +1,101 @@ +--- +import Layout from '../../layouts/Layout.astro'; +import { readFile, readdir } from 'node:fs/promises'; +import { join } from 'node:path'; + +export async function getStaticPaths() { + const docsPath = join(process.cwd(), '..', 'docs'); + const allDocs = await readdir(docsPath, { recursive: true, withFileTypes: true }); + + const paths = allDocs + .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md')) + .map((dirent) => { + const relativePath = join(dirent.parentPath || (dirent as any).path || '', dirent.name) + .replace(docsPath, '') + .replace(/^\//, '') + .replace('.md', ''); + + return { + params: { slug: relativePath }, + }; + }); + + return paths; +} + +const { slug } = Astro.params; +const slugPath = slug || ''; + +const docsPath = join(process.cwd(), '..', 'docs'); +const filePath = join(docsPath, `${slugPath}.md`); + +let content = ''; +let title: string = 'Documentation'; +try { + content = await readFile(filePath, 'utf-8'); + + // Extract title from markdown + const titleMatch = content.match(/^#\s+(.+)$/m); + if (titleMatch && titleMatch[1]) { + title = titleMatch[1]; + } else { + const fallback = slugPath.split('/').pop()?.replace(/-/g, ' '); + title = fallback || 'Documentation'; + } +} catch (error) { + content = `# Document Not Found\n\nThe requested document could not be found.`; + title = 'Not Found'; +} + +const breadcrumbs = [ + { label: 'Documentation', href: `${import.meta.env.BASE_URL}/docs` }, + { label: title }, +]; + +// Simple markdown to HTML conversion (for basic rendering) +// In production, you'd want to use a proper markdown parser +const htmlContent = content + .replace(/^### (.+)$/gm, '

$1

') + .replace(/^## (.+)$/gm, '

$1

') + .replace(/^# (.+)$/gm, '

$1

') + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1') + .replace(/`(.+?)`/g, '$1') + .replace(/^\- (.+)$/gm, '
  • $1
  • ') + .replace(/(
  • .*<\/li>\n?)+/g, '
      $&
    ') + .replace(/^\d+\. (.+)$/gm, '
  • $1
  • ') + .replace(/\[(.+?)\]\((.+?)\)/g, '$1') + .replace(/\n\n/g, '

    ') + .replace(/^(?!<[h\|ul\|li])/gm, '

    '); + +const githubUrl = `https://github.com/cywf/ctf-kit/blob/main/docs/${slugPath}.md`; +--- + + +

    + + +
    +
    +
    +
    +
    +
    + diff --git a/site/src/pages/docs/index.astro b/site/src/pages/docs/index.astro new file mode 100644 index 0000000..a33e198 --- /dev/null +++ b/site/src/pages/docs/index.astro @@ -0,0 +1,109 @@ +--- +import Layout from '../../layouts/Layout.astro'; +import { readdir } from 'node:fs/promises'; +import { join } from 'node:path'; + +const breadcrumbs = [{ label: 'Documentation' }]; + +// Get all markdown files from docs directory +const docsPath = join(process.cwd(), '..', 'docs'); +const allDocs = await readdir(docsPath, { recursive: true, withFileTypes: true }); +const mdFiles = allDocs + .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md')) + .map((dirent) => { + const relativePath = join(dirent.parentPath || (dirent as any).path || '', dirent.name).replace(docsPath, '').replace(/^\//, ''); + return { + name: dirent.name.replace('.md', ''), + path: relativePath, + category: relativePath.includes('/') ? relativePath.split('/')[0] : 'root', + }; + }); + +// Group by category +const groupedDocs = mdFiles.reduce((acc, doc) => { + const category = doc.category || 'root'; + if (!acc[category]) { + acc[category] = []; + } + acc[category].push(doc); + return acc; +}, {} as Record); + +const base = import.meta.env.BASE_URL; +--- + + +
    +
    +

    Documentation

    +

    + Browse our comprehensive guides and documentation for CTF competitions. +

    +
    + +
    + + + +
    +
    Contribute to Documentation
    +
    + Found an error or want to add more content? Contributions are welcome! Visit the docs folder on GitHub. +
    +
    +
    + + {Object.entries(groupedDocs).map(([category, docs]) => ( +
    +

    + {category === 'root' ? 'General' : category.replace(/-/g, ' ')} +

    + +
    + ))} +
    +
    diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro new file mode 100644 index 0000000..d95d237 --- /dev/null +++ b/site/src/pages/index.astro @@ -0,0 +1,125 @@ +--- +import Layout from '../layouts/Layout.astro'; + +const base = import.meta.env.BASE_URL; +--- + + + +
    +
    +
    +

    Welcome to CTF-Kit

    +

    + Your collaborative and evolving resource for Capture The Flag competitions +

    + +
    +
    +
    + + +
    +
    +
    +

    Introduction

    +

    + Welcome to the CTF-Kit repository! This project is designed to be a collaborative and evolving resource for anyone interested in participating in Capture The Flag (CTF) competitions. Whether you're a seasoned veteran or a newcomer to the world of CTFs, this repository aims to provide you with the tools, resources, and knowledge you need to succeed. +

    +
    +
    + + +
    +
    +

    Vision and Goals

    + +

    Our Vision

    +

    + The vision for this repository is to become a central hub for CTF learning and collaboration. We aim to create a dynamic space where participants can share their insights, strategies, and resources to help each other improve and grow. +

    + +

    Goals

    +
      +
    • Facilitate Collaboration: Encourage participants to work together, share their findings, and contribute to the collective knowledge base.
    • +
    • Improve CTF Strategies: Through shared experiences and resources, help users develop more effective strategies for tackling various types of CTF challenges.
    • +
    • Create a Rich Database: Build a repository of scripts, tools, and writeups that serve as valuable references for future CTFs.
    • +
    +
    +
    + + +
    +
    +

    How to Use This Repository

    + +
    +
    +

    1. Fork the Repository

    +

    Start by forking this repository to your own GitHub account.

    +
    + +
    +

    2. Create a Branch

    +

    When you participate in a CTF, create a new branch in your forked repo. Name the branch after the CTF event you are participating in (e.g., metactf-august2024).

    +
    + +
    +

    3. Work in Your Branch

    +

    Use this branch to document your progress, add scripts, and write up your solutions.

    +
    + +
    +

    4. Contribute Back

    +

    After the CTF, you can submit a pull request to contribute your findings and improvements back to the main repository.

    +
    +
    + + +
    +
    + + +
    +
    +
    +

    Start a CTF Branch

    +

    Follow our branching strategy to participate in CTF events and contribute your solutions.

    + +
    +
    + +
    +
    +

    Browse Scripts

    +

    Explore our collection of tools and scripts for various CTF challenges.

    + +
    +
    +
    +
    +
    diff --git a/site/src/pages/statistics.astro b/site/src/pages/statistics.astro new file mode 100644 index 0000000..a77b074 --- /dev/null +++ b/site/src/pages/statistics.astro @@ -0,0 +1,11 @@ +--- +import Layout from '../layouts/Layout.astro'; +import StatisticsComponent from '../components/Statistics'; + +const breadcrumbs = [{ label: 'Statistics' }]; +--- + + +

    Repository Statistics

    + +
    diff --git a/site/src/pages/visualizer.astro b/site/src/pages/visualizer.astro new file mode 100644 index 0000000..a64ba43 --- /dev/null +++ b/site/src/pages/visualizer.astro @@ -0,0 +1,45 @@ +--- +import Layout from '../layouts/Layout.astro'; +import MermaidViewer from '../components/MermaidViewer'; +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +const breadcrumbs = [{ label: 'Visualizer' }]; + +// Try to read mermaid files +let diagrams: Array<{ name: string; path: string; content: string }> = []; +try { + const mermaidPath = join(process.cwd(), '..', 'mermaid'); + const files = await readdir(mermaidPath, { recursive: true, withFileTypes: true }); + + const mmdFiles = files.filter((dirent) => dirent.isFile() && dirent.name.endsWith('.mmd')); + + diagrams = await Promise.all( + mmdFiles.map(async (dirent) => { + const filePath = join(dirent.parentPath || (dirent as any).path || '', dirent.name); + const content = await readFile(filePath, 'utf-8'); + return { + name: dirent.name.replace('.mmd', ''), + path: filePath, + content, + }; + }) + ); +} catch (error) { + // Mermaid directory doesn't exist or is empty + console.log('No mermaid diagrams found'); +} +--- + + +
    +
    +

    Project Visualizer

    +

    + Interactive Mermaid diagrams showing project architecture, workflows, and relationships. +

    +
    + + +
    +
    diff --git a/site/src/styles/global.css b/site/src/styles/global.css new file mode 100644 index 0000000..5a5d71e --- /dev/null +++ b/site/src/styles/global.css @@ -0,0 +1,68 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + color-scheme: dark; + } + + html { + scroll-behavior: smooth; + } + + body { + @apply bg-base-100 text-base-content; + } + + @media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + } +} + +@layer components { + .skip-to-content { + @apply absolute -top-10 left-0 z-50 bg-primary text-primary-content px-4 py-2 focus:top-0; + } + + .prose-dark { + @apply max-w-none text-base-content; + } + + .prose-dark h1 { + @apply text-4xl font-bold mb-6; + } + + .prose-dark h2 { + @apply text-2xl font-bold mt-8 mb-4; + } + + .prose-dark h3 { + @apply text-xl font-bold mt-6 mb-3; + } + + .prose-dark p { + @apply mb-4; + } + + .prose-dark pre { + @apply bg-base-300 p-4 rounded-lg overflow-x-auto; + } + + .prose-dark code { + @apply bg-base-300 px-2 py-1 rounded text-sm; + } + + .prose-dark a { + @apply text-primary hover:underline; + } + + .prose-dark ul, + .prose-dark ol { + @apply list-disc list-inside space-y-2 my-4 ml-4; + } +} diff --git a/site/tailwind.config.mjs b/site/tailwind.config.mjs new file mode 100644 index 0000000..7af1646 --- /dev/null +++ b/site/tailwind.config.mjs @@ -0,0 +1,70 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], + theme: { + extend: {}, + }, + plugins: [require('daisyui')], + daisyui: { + themes: [ + 'night', + 'dracula', + 'cyberpunk', + { + 'dark-neon': { + 'primary': '#00ffff', + 'secondary': '#ff00ff', + 'accent': '#00ff00', + 'neutral': '#0a0a0a', + 'base-100': '#0f0f0f', + 'base-200': '#1a1a1a', + 'base-300': '#252525', + 'info': '#00d4ff', + 'success': '#00ff88', + 'warning': '#ffaa00', + 'error': '#ff0055', + }, + 'hackerman': { + 'primary': '#00ff00', + 'secondary': '#00aa00', + 'accent': '#00ffaa', + 'neutral': '#000000', + 'base-100': '#0d0d0d', + 'base-200': '#151515', + 'base-300': '#1d1d1d', + 'info': '#00ccff', + 'success': '#00ff00', + 'warning': '#ffcc00', + 'error': '#ff0000', + }, + 'gamecore': { + 'primary': '#8b5cf6', + 'secondary': '#ec4899', + 'accent': '#f59e0b', + 'neutral': '#1e1e2e', + 'base-100': '#0f0f1e', + 'base-200': '#161628', + 'base-300': '#1e1e3f', + 'info': '#3b82f6', + 'success': '#10b981', + 'warning': '#f59e0b', + 'error': '#ef4444', + }, + 'neon-accent': { + 'primary': '#ff006e', + 'secondary': '#8338ec', + 'accent': '#3a86ff', + 'neutral': '#14141f', + 'base-100': '#0a0a12', + 'base-200': '#12121d', + 'base-300': '#1a1a28', + 'info': '#06b6d4', + 'success': '#14f195', + 'warning': '#fbbf24', + 'error': '#f43f5e', + }, + }, + ], + darkTheme: 'night', + }, +}; diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000..0f7c346 --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "astro/tsconfigs/strictest", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "react", + "types": ["node"] + } +} From efbcbc7abeaa0a6057262c5a875cd7d7ae39b2a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 01:42:24 +0000 Subject: [PATCH 3/3] Add example Mermaid diagrams and rebuild site Co-authored-by: cywf <100837335+cywf@users.noreply.github.com> --- mermaid/contribution-flow.mmd | 26 ++++++++++++++++++++++++++ mermaid/ctf-workflow.mmd | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 mermaid/contribution-flow.mmd create mode 100644 mermaid/ctf-workflow.mmd diff --git a/mermaid/contribution-flow.mmd b/mermaid/contribution-flow.mmd new file mode 100644 index 0000000..de9bc7f --- /dev/null +++ b/mermaid/contribution-flow.mmd @@ -0,0 +1,26 @@ +graph LR + A[CTF-Kit Repository] --> B[Fork Repository] + B --> C[Create Branch] + C --> D{CTF Event} + + D --> E[Use Tools & Scripts] + E --> F[Document Findings] + F --> G[Add Solutions] + + G --> H{Event Complete?} + H -->|No| E + H -->|Yes| I[Submit Pull Request] + + I --> J[Code Review] + J --> K{Approved?} + K -->|Yes| L[Merge to Main] + K -->|No| M[Make Changes] + M --> I + + L --> N[Community Benefits] + N --> O[Enhanced Repository] + + style A fill:#8b5cf6,stroke:#8b5cf6,color:#fff + style I fill:#00ffff,stroke:#00ffff,color:#000 + style L fill:#00ff00,stroke:#00ff00,color:#000 + style O fill:#ff00ff,stroke:#ff00ff,color:#000 diff --git a/mermaid/ctf-workflow.mmd b/mermaid/ctf-workflow.mmd new file mode 100644 index 0000000..5c554c2 --- /dev/null +++ b/mermaid/ctf-workflow.mmd @@ -0,0 +1,27 @@ +graph TD + A[Start CTF] --> B{Choose Challenge Type} + B -->|Web| C[Web Exploitation] + B -->|Crypto| D[Cryptography] + B -->|Forensics| E[Forensics] + B -->|Reversing| F[Reverse Engineering] + B -->|Misc| G[Miscellaneous] + + C --> H[Enumerate & Scan] + D --> H + E --> H + F --> H + G --> H + + H --> I[Find Vulnerability] + I --> J{Exploit Successful?} + J -->|Yes| K[Capture Flag] + J -->|No| L[Try Different Approach] + L --> H + + K --> M[Document Solution] + M --> N[Submit Flag] + N --> O[End] + + style A fill:#00ffff,stroke:#00ffff,color:#000 + style K fill:#00ff00,stroke:#00ff00,color:#000 + style O fill:#ff00ff,stroke:#ff00ff,color:#000