diff --git a/admin/ci/.gitattributes b/admin/ci/.gitattributes new file mode 100644 index 000000000..827418463 --- /dev/null +++ b/admin/ci/.gitattributes @@ -0,0 +1 @@ +/sdk/** linguist-generated diff --git a/admin/ci/.gitignore b/admin/ci/.gitignore new file mode 100644 index 000000000..040187c61 --- /dev/null +++ b/admin/ci/.gitignore @@ -0,0 +1,4 @@ +/sdk +/**/node_modules/** +/**/.pnpm-store/** +/.env diff --git a/admin/ci/bun.lock b/admin/ci/bun.lock new file mode 100644 index 000000000..a130d84e6 --- /dev/null +++ b/admin/ci/bun.lock @@ -0,0 +1,52 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "jamkazam-admin-ci", + "dependencies": { + "chokidar": "^3.6.0", + "typescript": "5.9.3", + }, + "devDependencies": { + "@types/node": "^20.0.0", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + } +} diff --git a/admin/ci/package.json b/admin/ci/package.json new file mode 100644 index 000000000..31950b0ab --- /dev/null +++ b/admin/ci/package.json @@ -0,0 +1,10 @@ +{ + "name": "jamkazam-admin-ci", + "version": "1.0.0", + "dependencies": { + "chokidar": "^3.6.0" + ,"typescript":"5.9.3"}, + "devDependencies": { + "@types/node": "^20.0.0" + } +,"type":"module"} \ No newline at end of file diff --git a/admin/ci/src/index.ts b/admin/ci/src/index.ts index d93f2e696..b037b488b 100644 --- a/admin/ci/src/index.ts +++ b/admin/ci/src/index.ts @@ -43,6 +43,19 @@ export class Admin { return `Shipped ${version}`; } + /** + * Generate a unique tag based on CalVer + User + Source Digest + */ + @func() + async devTag(source: Directory): Promise { + const hash = await source.digest(); + const shortHash = hash.split(":")[1].substring(0, 8); + const d = new Date(); + const calVer = `${d.getFullYear()}.${(d.getMonth() + 1)}.${d.getDate()}-${d.getHours()}${d.getMinutes()}`; + const user = process.env.USER || "unknown"; + return `${calVer}-${user}-${shortHash}`; + } + private async buildWithNix(source: Directory, platform: Platform): Promise { // 1. Start with a Nix-enabled container let builder = dag.container({ platform }) @@ -52,6 +65,7 @@ export class Admin { // 2. Build the image tarball via Nix // Add a dummy env var to bust cache if needed .withEnvVariable("CACHE_BUST", new Date().getTime().toString()) + .withEnvVariable("TIMESTAMP", new Date().getTime().toString()) .withExec(["nix", "--extra-experimental-features", "nix-command flakes", "build", ".#appImageTarball", "--out-link", "result-tarball"]); try { diff --git a/admin/ci/tsconfig.json b/admin/ci/tsconfig.json new file mode 100644 index 000000000..4ec0c2da6 --- /dev/null +++ b/admin/ci/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "moduleResolution": "Node", + "experimentalDecorators": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "@dagger.io/dagger": ["./sdk/index.ts"], + "@dagger.io/dagger/telemetry": ["./sdk/telemetry.ts"] + } + } +} diff --git a/admin/ci/watch.ts b/admin/ci/watch.ts new file mode 100644 index 000000000..788aa9b40 --- /dev/null +++ b/admin/ci/watch.ts @@ -0,0 +1,31 @@ +import chokidar from "chokidar"; +import { execSync } from "child_process"; + +// Configuration +const WATCH_PATHS = ["app/**/*.rb", "spec/**/*.rb", "config/**/*.rb", "db/schema.rb"]; +const DAGGER_CLI = "dagger call"; + +console.log("๐Ÿš€ SOTA Rails Watcher Started. Monitoring for changes..."); + +const watcher = chokidar.watch(WATCH_PATHS, { + ignored: /(^|[\/\])\../, // ignore dotfiles + persistent: true, +}); + +watcher.on("change", (path) => { + console.log(` +๐Ÿ“„ File changed: ${path}`); + + if (path.endsWith("_spec.rb") || path.startsWith("app/")) { + console.log("๐Ÿงช Running Targeted RSpec via Dagger..."); + try { + execSync(`${DAGGER_CLI} validate --source=.`, { stdio: "inherit" }); + } catch (e) { + console.error("โŒ Test failed."); + } + } + + if (path.startsWith("config/deploy")) { + console.log("๐Ÿšข Infrastructure change detected. Skipping full deploy in dev loop."); + } +}); diff --git a/admin/dagger.json b/admin/dagger.json new file mode 100644 index 000000000..e3f8eef8a --- /dev/null +++ b/admin/dagger.json @@ -0,0 +1,8 @@ +{ + "name": "admin", + "engineVersion": "v0.20.1", + "sdk": { + "source": "typescript" + }, + "source": "ci" +} diff --git a/admin/flake.nix b/admin/flake.nix index 9ef35dbf5..bc9b2b202 100644 --- a/admin/flake.nix +++ b/admin/flake.nix @@ -73,13 +73,15 @@ libxslt.dev jemalloc vips + tzdata + cacert bash coreutils git gnumake gcc pkg-config - binutils + nodejs ]; }; @@ -88,6 +90,7 @@ export PATH=${allDeps}/bin:$PATH export LD_LIBRARY_PATH=${allDeps}/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH=${allDeps}/lib/pkgconfig + export TZDIR=/share/zoneinfo cd /jam-cloud/admin @@ -98,13 +101,24 @@ echo "๐Ÿ’Ž Installing gems..." bundle install + echo "๐ŸŽจ Precompiling assets..." + SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile + echo "๐Ÿš€ Starting Rails..." exec bundle exec rails server -b 0.0.0.0 ''; - appImage = pkgs.dockerTools.buildLayeredImage { - name = "jamkazam-admin-v3"; + appImage = pkgs.dockerTools.buildImage { + name = "jamkazam-admin"; tag = "local"; + copyToRoot = [ + allDeps + start-jam-admin + pkgs.libyaml.dev + pkgs.openssl.dev + pkgs.postgresql_16.lib + pkgs.zlib.dev + ]; config = { Cmd = [ "${start-jam-admin}/bin/start-jam-admin" ]; Env = [ @@ -113,17 +127,12 @@ "RAILS_ENV=production" "RAILS_SERVE_STATIC_FILES=true" "PKG_CONFIG_PATH=${allDeps}/lib/pkgconfig" + "TZDIR=/share/zoneinfo" + "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt" + "JAM_VERSION=0.0.1" ]; ExposedPorts = { "3000/tcp" = {}; }; }; - contents = [ - allDeps - start-jam-admin - pkgs.libyaml.dev - pkgs.openssl.dev - pkgs.postgresql_16.lib - pkgs.zlib.dev - ]; }; in { inherit appImage; diff --git a/admin/justfile b/admin/justfile index e291af5a3..365138d80 100644 --- a/admin/justfile +++ b/admin/justfile @@ -2,72 +2,107 @@ set shell := ["bash", "-c"] NIX := "/nix/var/nix/profiles/default/bin/nix" -# Start local backing services (one-time, no boot persistence) +# Start local backing services infra: @echo "๐Ÿฐ Starting RabbitMQ..." brew services run rabbitmq || true @echo "๐Ÿ’พ Starting Redis..." brew services run redis || true - @echo "โœ… Local infra is running. Use 'brew services list' to verify." + @echo "โœ… Local infra is running." # Setup environment setup: @echo "๐Ÿ”ง Installing SOTA stack dependencies..." cd ci && ~/.bun/bin/bun install @if [ ! -f dagger.json ]; then \ - echo "Initializing Dagger module..."; \ dagger init --sdk=typescript --source=ci; \ fi -# Start coding natively (Track 1) -watch: - ~/.bun/bin/bun run ci/watch.ts - # Enter the native Nix development environment shell: {{NIX}} develop -# Fast native validation in Dagger (Track 2) -validate: - dagger call --progress=plain validate --source=. - # Build local ARM64 image via Dagger and export to OrbStack -build: - @echo "๐Ÿ”จ Building image via Dagger..." - @rm -f ./admin.tar - dagger call --progress=plain build-local --source=. export --path=./admin.tar - @echo "๐Ÿงน Cleaning up old Docker artifacts to free space..." - docker system prune -f - @echo "๐Ÿ“ฆ Loading image into Docker..." +build tag="latest": #!/bin/bash + set -e + echo "๐Ÿ”จ Building image via Dagger..." + rm -f ./admin.tar + dagger call --progress=plain build-local --source=. export --path=./admin.tar + + echo "๐Ÿ“ฆ Loading image into Docker..." LOAD_OUTPUT=$(docker load < ./admin.tar) echo "$LOAD_OUTPUT" + IMAGE_ID=$(echo "$LOAD_OUTPUT" | awk '/Loaded image ID: sha256:/ {print $4}' | cut -d: -f2) if [ -z "$IMAGE_ID" ]; then - IMAGE_ID=$(echo "$LOAD_OUTPUT" | grep -oE '[0-9a-f]{12,}' | tail -n 1) + IMAGE_ID=$(echo "$LOAD_OUTPUT" | grep -oE '[0-9a-f]{12,}' | tail -n 1) + fi + + if [ -z "$IMAGE_ID" ]; then + echo "โŒ Failed to extract image ID from docker load output." + exit 1 fi - echo "Tagging $IMAGE_ID as jamkazam-admin:local-v2..." - docker tag "$IMAGE_ID" jamkazam-admin:local-v2 - rm ./admin.tar - echo "โœ… Image loaded as 'jamkazam-admin:local-v2'" -# Run Rails server natively using the Nix shell (SOTA Inner Loop) + echo "๐Ÿท๏ธ Tagging $IMAGE_ID as jamkazam-admin:{{tag}}..." + docker tag "$IMAGE_ID" jamkazam-admin:{{tag}} + echo "โœ… Image loaded as 'jamkazam-admin:{{tag}}'" + +# Run Rails server natively dev: infra {{NIX}} develop --command bash -c "bundle install && bundle exec rails server" -# Run the local image in OrbStack, connected to host infra +# Smart Run: Detects changes, builds if needed, then runs run: + #!/bin/bash + set -e + echo "๐Ÿ” Checking for source changes..." + TAG=$(dagger call --quiet dev-tag --source=. | tr -d '\n' | tr -d '"') + echo "๐Ÿท๏ธ Current Source Tag: $TAG" + + if ! docker image inspect jamkazam-admin:$TAG >/dev/null 2>&1; then + echo "โœจ Source changed or image missing. Building..." + just build "$TAG" + else + echo "๐Ÿš€ Source unchanged. Using cached image." + fi + docker run -it --rm \ - -p 3000:3000 \ + -p 0.0.0.0:3333:3000 \ -v {{invocation_directory()}}/..:/jam-cloud \ -w /jam-cloud/admin \ -e DATABASE_URL="postgres://postgres:postgres@host.orb.internal:5432/jam" \ -e REDIS_URL="redis://host.orb.internal:6379/1" \ -e RABBITMQ_URL="amqp://guest:guest@host.orb.internal:5672" \ -e RAILS_MASTER_KEY=$RAILS_MASTER_KEY \ - jamkazam-admin:local-v2 + -e SECRET_KEY_BASE=${SECRET_KEY_BASE:-dummy_secret_key_base_for_sanity_check} \ + -e AWS_KEY=$AWS_ACCESS_KEY_ID \ + -e AWS_SECRET=$AWS_SECRET_ACCESS_KEY \ + jamkazam-admin:$TAG -# The Big Red Button -ship: - @echo "๐Ÿš€ Shipping to Production..." - dagger call ship --source=. --kubeconfig=file:$KUBECONFIG --registry="your-registry.io" +# Purge everything to reclaim disk space +clean: clean-docker clean-dagger clean-nix clean-rails + @echo "๐Ÿงน All systems cleaned." + rm -f *.tar result-tarball + +# Aggressive Docker prune (including volumes and unused images) +clean-docker: + @echo "๐Ÿณ Cleaning Docker (Images + Volumes)..." + docker system prune -a -f || true + docker volume prune -f || true + +# Prune Dagger cache specifically +clean-dagger: + @echo "๐Ÿ—ก๏ธ Cleaning Dagger cache..." + dagger core engine prune || true + # Also remove the dagger engine container if it's hanging on to space + docker rm -f $(docker ps -a -q --filter "name=dagger-engine") || true + +# Hard-vacuum Nix store +clean-nix: + {{NIX}}-collect-garbage -d + {{NIX}}-store --gc + {{NIX}}-store --optimise + +clean-rails: + rm -rf log/*.log tmp/* diff --git a/dagger.json b/dagger.json new file mode 100644 index 000000000..ede6e8fa2 --- /dev/null +++ b/dagger.json @@ -0,0 +1,22 @@ +{ + "name": "jam-cloud", + "engineVersion": "v0.20.1", + "sdk": { + "source": "typescript" + }, + "dependencies": [ + { + "name": "admin", + "source": "admin" + }, + { + "name": "web", + "source": "web" + }, + { + "name": "websocket-gatewaysocket-gateway", + "source": "websocket-gateway" + } + ], + "source": "ci" +}