diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2781986 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.git +.github +.svelte-kit +.vscode +node_modules +.pnpm-store +.DS_Store +Thumbs.db +coverage +dist +.env +.env.* +data/network.json diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..d3aeb35 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,56 @@ +name: Docker + +on: + pull_request: + branches: + - main + push: + branches: + - main + tags: + - "v*.*.*" + +env: + IMAGE_NAME: jcreek23/open-network-diagram + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: docker.io/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha,prefix=sha- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build (PR) / Build+Push (push) + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: ${{ github.event_name == 'push' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 3b462cb..e2cbe7a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ Thumbs.db !.env.example !.env.test +# User-provided Docker data +data/network.json + # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6dca253 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +ENV PNPM_HOME=/pnpm +ENV PATH=$PNPM_HOME:$PATH + +RUN corepack enable + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile --store-dir /pnpm/store + +COPY . . + +ENV DEPLOY_TARGET=docker +RUN pnpm run build:docker + +FROM nginxinc/nginx-unprivileged:1.27-alpine AS runner + +USER root + +RUN apk add --no-cache libcap && \ + setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx + +COPY nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/build /usr/share/nginx/html + +RUN mkdir -p /usr/share/nginx/html/data && \ + chown -R 101:101 /usr/share/nginx/html /var/cache/nginx /var/run /etc/nginx/conf.d + +USER 101 + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ + CMD wget -qO- http://127.0.0.1/ > /dev/null || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 510857f..afcb8f1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ **Open Network Diagram** is an **open-source, self-hosted tool** for creating **interactive network and infrastructure diagrams** using a **declarative JSON format**. ✅ **Fully self-hostable via Docker** -✅ **JSON-based network structure** (editable in UI or manually) +✅ **Docker-first deployment target** (Netlify optional for demo hosting) ✅ **Interactive network visualisation** ✅ **Simple file-based configuration** ✅ **Lightweight Svelte** @@ -20,25 +20,41 @@ Use it to **document your home lab, office network, or cloud infrastructure** wi ## **🚀 Quick Start (For Users)** -### **1️⃣ Run Open Network Diagram via Docker** +### **1️⃣ Create Your Runtime Data File** -Pull and run the latest image: +Copy the template and edit your own network data: ```bash -docker run -d -p 8080:3000 jcreek23/open-network-diagram +cp data/network.json.example data/network.json ``` -- **`-p 8080:3000`** → Maps the app to `http://localhost:8080` +### **2️⃣ Run Open Network Diagram via Docker** -### **2️⃣ Open the Web UI** +From this repo: + +```bash +docker compose up --build +``` + +Or pull and run directly: + +```bash +docker run -d -p 8080:80 -v "$(pwd)/data:/usr/share/nginx/html/data:ro" jcreek23/open-network-diagram +``` + +- **`-p 8080:80`** → Maps the app to `http://localhost:8080` +- **`-v .../data:/usr/share/nginx/html/data:ro`** → Uses your local `data/network.json` + +### **3️⃣ Open the Web UI** Visit **`http://localhost:8080`** to view your network diagram. -### **3️⃣ Modify Your Network (JSON-Based)** +### **4️⃣ Modify Your Network (JSON-Based)** -- The app reads **`/data/network.json`** from static assets. -- In this repo, the default file is **`static/data/network.json`**. -- Update that file to change the diagram data. +- Docker runtime reads **`/data/network.json`** from the mounted volume. +- In this repo, your editable file is **`data/network.json`** (gitignored). +- Netlify demo builds use committed sample data at **`static/data/network.json`**. +- After editing JSON, click **Reload Default JSON** in the UI. --- @@ -65,17 +81,28 @@ pnpm run dev - Runs at `http://localhost:5173` +### **4️⃣ Build Targets** + +```bash +pnpm run build # default (Docker/static target) +pnpm run build:docker # explicit Docker/static target +pnpm run build:netlify # explicit Netlify target +``` + --- ## **🛠️ Project Structure** -``` +```text open-network-diagram/ -│ ├── src/ # Svelte components -│ ├── package.json # Dependencies -│── Dockerfile # Docker setup -│── .github/workflows/ # CI/CD pipelines -│── README.md # Documentation +├── src/ # Svelte app source +├── static/data/network.json # Demo dataset (Netlify/demo) +├── data/network.json.example # User data template (Docker) +├── Dockerfile # Docker build/runtime +├── docker-compose.yml # Local Docker run with mounted data +├── netlify.toml # Netlify build config +├── .github/workflows/ # CI workflows (Docker image build/push) +└── README.md # Documentation ``` --- @@ -91,13 +118,14 @@ docker build -t open-network-diagram . ### **Run Locally** ```bash -docker run -p 8080:3000 open-network-diagram +docker run --rm -p 8080:80 -v "$(pwd)/data:/usr/share/nginx/html/data:ro" open-network-diagram ``` -### **CI/CD (GitHub Actions)** +### **CI/CD (GitHub Actions + Netlify)** -- Automatic Docker builds when changes are pushed to `main`. -- Pushes the latest image to **Docker Hub**. +- GitHub Actions workflow (`.github/workflows/docker.yml`) builds Docker on PRs. +- Pushes to `main` publish Docker images to **Docker Hub** (`jcreek23/open-network-diagram`). +- Netlify uses its own CI/CD pipeline with `netlify.toml` (`pnpm run build:netlify`). --- @@ -114,10 +142,7 @@ Define your network using **`network.json`**: "role": "Hypervisor", "operatingSystem": "Proxmox", "software": { - "vms": [ - { "name": "OpnSense", "role": "Router", "ipAddress": "10.0.0.4" }, - { "name": "PiVPN", "role": "VPN Server", "ipAddress": "10.0.0.5" } - ] + "vms": [{ "name": "OpnSense", "role": "Router", "ipAddress": "10.0.0.4" }] }, "hardware": { "cpu": "Intel N100", @@ -125,7 +150,8 @@ Define your network using **`network.json`**: "networkPorts": 4 } } - ] + ], + "devices": [{ "name": "Switch", "ipAddress": "10.0.0.2", "type": "Nintendo Switch" }] } ``` @@ -163,11 +189,3 @@ We welcome contributions! To contribute: **Author:** [Joshua Creek](https://github.com/jcreek) **Project Repo:** [GitHub](https://github.com/jcreek/OpenNetworkDiagram) - ---- - -### **🔥 Next Steps** - -✅ **Set up GitHub Repo** -✅ **Push initial project structure** -✅ **Add CI/CD for automated Docker builds** diff --git a/data/network.json.example b/data/network.json.example new file mode 100644 index 0000000..7620d12 --- /dev/null +++ b/data/network.json.example @@ -0,0 +1,54 @@ +{ + "machines": [ + { + "machineName": "Example Host", + "ipAddress": "192.168.1.10", + "role": "Hypervisor", + "operatingSystem": "Linux", + "ports": [ + { + "portName": "eth0", + "speedGbps": 1, + "connectedTo": { + "device": "Example Switch", + "port": "1" + } + } + ], + "software": { + "vms": [ + { + "name": "Example VM", + "role": "Service", + "ipAddress": "192.168.1.20" + } + ] + }, + "hardware": { + "cpu": "CPU model", + "ram": "16GB", + "networkPorts": 1, + "networkPortSpeedGbps": 1, + "gpu": "Optional GPU" + } + } + ], + "devices": [ + { + "name": "Example Switch", + "ipAddress": "192.168.1.2", + "type": "Network Switch", + "notes": "Optional notes", + "ports": [ + { + "portName": "1", + "speedGbps": 1, + "connectedTo": { + "device": "Example Host", + "port": "eth0" + } + } + ] + } + ] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d4f567c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + open-network-diagram: + build: + context: . + dockerfile: Dockerfile + image: open-network-diagram:local + ports: + - "8080:80" + volumes: + - ./data:/usr/share/nginx/html/data:ro + restart: unless-stopped diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..eb2e028 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,5 @@ +[build] + command = "pnpm run build:netlify" + +[build.environment] + DEPLOY_TARGET = "netlify" diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..6d3f25b --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location /data/ { + try_files $uri =404; + add_header Cache-Control "no-cache"; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/package.json b/package.json index 0d9979f..ec7bcf1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "vite dev", "build": "vite build", + "build:docker": "DEPLOY_TARGET=docker vite build", + "build:netlify": "DEPLOY_TARGET=netlify vite build", "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -19,6 +21,7 @@ "@eslint/js": "^8.57.1", "@sveltejs/adapter-auto": "^4.0.0", "@sveltejs/adapter-netlify": "^5.0.0", + "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/typography": "^0.5.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9ef4af..b834361 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: '@sveltejs/adapter-netlify': specifier: ^5.0.0 version: 5.0.0(@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))) + '@sveltejs/adapter-static': + specifier: ^3.0.10 + version: 3.0.10(@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))) '@sveltejs/kit': specifier: ^2.16.0 version: 2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) @@ -595,6 +598,11 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 + '@sveltejs/adapter-static@3.0.10': + resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + '@sveltejs/kit@2.20.4': resolution: {integrity: sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q==} engines: {node: '>=18.13'} @@ -2420,6 +2428,10 @@ snapshots: esbuild: 0.24.2 set-cookie-parser: 2.7.1 + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))': + dependencies: + '@sveltejs/kit': 2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) + '@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))': dependencies: '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) diff --git a/svelte.config.js b/svelte.config.js index 57aec4b..49b4fa4 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,6 +1,15 @@ -import adapter from '@sveltejs/adapter-netlify'; +import netlifyAdapter from '@sveltejs/adapter-netlify'; +import staticAdapter from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +const deployTarget = (process.env.DEPLOY_TARGET ?? 'docker').toLowerCase(); +const adapter = deployTarget === 'netlify' + ? netlifyAdapter() + : staticAdapter({ + fallback: 'index.html', + strict: false + }); + /** @type {import('@sveltejs/kit').Config} */ const config = { // Consult https://svelte.dev/docs/kit/integrations @@ -8,10 +17,7 @@ const config = { preprocess: vitePreprocess(), kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() + adapter } };