diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..75e2f34 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Build artifacts – rebuilt inside Docker +target/ + +# Version control +.git/ +.gitignore + +# End-to-end tests not needed in the image +end2end/ + +# Editor / IDE files +.vscode/ +.idea/ +*.swp + +# Docs and notes (not needed at build time) +*.md +*.txt +documentation.md +toollist_data.md +toollist_notes.txt diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml new file mode 100644 index 0000000..acc8744 --- /dev/null +++ b/.gitea/workflows/docker.yml @@ -0,0 +1,63 @@ +name: Build & Push Docker Image + +env: + REGISTRY: git.revwal.de + +on: + push: + branches: + - main + - master + tags: + - "v*" + workflow_dispatch: # allow manual runs from the Gitea UI + +jobs: + build-and-push: + name: Build and push + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # ── Docker setup ──────────────────────────────────────────────────────── + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # ── Authenticate to the Gitea container registry ──────────────────────── + - name: Log in to Gitea container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ gitea.actor }} + password: ${{ secrets.GITEATOKEN }} + + # ── Tag strategy ──────────────────────────────────────────────────────── + # Produces: + # • latest – on every push to the default branch + # • – on every push to a non-default branch + # • v1.2.3 / 1.2 – on version tags (v*) + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ gitea.repository }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + # ── Build and push ─────────────────────────────────────────────────────── + # Layer cache is stored in the Gitea/act_runner GHA cache so the slow + # cargo-leptos compilation is reused across runs when nothing changes. + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + 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 6985cf1..ee97e34 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb +data/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9b5ee38 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# ─── Stage 1: Builder ──────────────────────────────────────────────────────── +FROM rust:1.96 AS builder + +WORKDIR /app + +# System deps needed to compile OpenSSL and link the binary +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Add the WASM compilation target required by cargo-leptos +RUN rustup target add wasm32-unknown-unknown + +# Install cargo-leptos (pinned for reproducibility). +# Done BEFORE copying source so this expensive step is cached +# as a separate layer and only re-runs when the Dockerfile changes. +RUN cargo install cargo-leptos --locked + +# Copy the full source tree (respects .dockerignore). +# Everything above this line is layer-cached by Docker / the CI cache. +COPY . . + +# Build server binary + WASM/CSS/JS site bundle in release mode +RUN cargo leptos build --release + +# ─── Stage 2: Runtime ──────────────────────────────────────────────────────── +FROM debian:bookworm-slim AS runtime + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Server binary +COPY --from=builder /app/target/release/toollist ./toollist + +# JS / WASM / CSS site bundle (default site-root = "target/site") +COPY --from=builder /app/target/site ./target/site + +# Cargo.toml is read at startup by leptos get_configuration() +COPY --from=builder /app/Cargo.toml ./Cargo.toml + +# Seed data – in production this directory is mounted as a volume +COPY --from=builder /app/data ./data + +EXPOSE 3000 + +ENV LEPTOS_ENV=PROD + +CMD ["./toollist"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..53b42f0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + toollist: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + volumes: + # Persist the TOML data file across container restarts/upgrades. + # On first run the seed data baked into the image is used; + # subsequent writes go straight to this named volume. + - toollist-data:/app/data + environment: + LEPTOS_ENV: PROD + restart: unless-stopped + +volumes: + toollist-data: diff --git a/end2end/package-lock.json b/end2end/package-lock.json new file mode 100644 index 0000000..260e8eb --- /dev/null +++ b/end2end/package-lock.json @@ -0,0 +1,167 @@ +{ + "name": "end2end", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "end2end", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.44.1", + "@types/node": "^20.12.12", + "typescript": "^5.4.5" + } + }, + "node_modules/@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + } + }, + "dependencies": { + "@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "requires": { + "playwright": "1.44.1" + } + }, + "@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.44.1" + } + }, + "playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b232038..b004ac5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "512"] + pub mod app; pub mod components; pub mod models; diff --git a/src/main.rs b/src/main.rs index c7176d0..e614840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "512"] + #[cfg(feature = "ssr")] #[tokio::main] async fn main() {