diff --git a/.circleci/canary-config.yml b/.circleci/canary-config.yml new file mode 100644 index 00000000..09cceb25 --- /dev/null +++ b/.circleci/canary-config.yml @@ -0,0 +1,72 @@ +version: 2.1 + +jobs: + build-amd64: + machine: + image: ubuntu-2004:current + steps: + - checkout + - run: + name: Prepare .env file + command: | + cp .env.production.example .env.production + - run: + name: Build and push AMD64 image + command: | + VERSION=$(node -p "require('./package.json').version") + echo $VERSION + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + docker build --platform linux/amd64 -t dokploy/dokploy:canary-amd64 . + docker push dokploy/dokploy:canary-amd64 + + build-arm64: + machine: + image: ubuntu-2004:current + resource_class: arm.large + steps: + - checkout + + - run: + name: Prepare .env file + command: | + cp .env.production.example .env.production + - run: + name: Build and push ARM64 image + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + docker build --platform linux/arm64 -t dokploy/dokploy:canary-arm64 . + docker push dokploy/dokploy:canary-arm64 + + combine-manifests: + docker: + - image: cimg/base:stable + steps: + - setup_remote_docker + - run: + name: Create and push multi-arch manifest + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + docker manifest create dokploy/dokploy:canary \ + dokploy/dokploy:canary-amd64 \ + dokploy/dokploy:canary-arm64 + docker manifest push dokploy/dokploy:canary + +workflows: + version: 2 + build-all: + jobs: + - build-amd64: + filters: + branches: + only: feat/circle + - build-arm64: + filters: + branches: + only: feat/circle + - combine-manifests: + requires: + - build-amd64 + - build-arm64 + filters: + branches: + only: feat/circle diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..0d93e547 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,104 @@ +version: 2.1 + +jobs: + build-amd64: + machine: + image: ubuntu-2004:current + steps: + - checkout + - run: + name: Prepare .env file + command: | + cp .env.production.example .env.production + - run: + name: Build and push AMD64 image + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + if [ "${CIRCLE_BRANCH}" == "main" ]; then + TAG="latest" + else + TAG="canary" + fi + docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 . + docker push dokploy/dokploy:${TAG}-amd64 + + build-arm64: + machine: + image: ubuntu-2004:current + resource_class: arm.large + steps: + - checkout + - run: + name: Prepare .env file + command: | + cp .env.production.example .env.production + - run: + name: Build and push ARM64 image + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + if [ "${CIRCLE_BRANCH}" == "main" ]; then + TAG="latest" + else + TAG="canary" + fi + docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 . + docker push dokploy/dokploy:${TAG}-arm64 + + combine-manifests: + docker: + - image: cimg/base:stable + steps: + - checkout + - setup_remote_docker + - run: + name: Create and push multi-arch manifest + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + + if [ "${CIRCLE_BRANCH}" == "main" ]; then + VERSION=$(node -p "require('./package.json').version") + echo $VERSION + TAG="latest" + + docker manifest create dokploy/dokploy:${TAG} \ + dokploy/dokploy:${TAG}-amd64 \ + dokploy/dokploy:${TAG}-arm64 + docker manifest push dokploy/dokploy:${TAG} + + docker manifest create dokploy/dokploy:${VERSION} \ + dokploy/dokploy:${TAG}-amd64 \ + dokploy/dokploy:${TAG}-arm64 + docker manifest push dokploy/dokploy:${VERSION} + else + TAG="canary" + docker manifest create dokploy/dokploy:${TAG} \ + dokploy/dokploy:${TAG}-amd64 \ + dokploy/dokploy:${TAG}-arm64 + docker manifest push dokploy/dokploy:${TAG} + fi + +workflows: + version: 2 + build-all: + jobs: + - build-amd64: + filters: + branches: + only: + - main + - canary + - build-arm64: + filters: + branches: + only: + - main + - canary + - combine-manifests: + requires: + - build-amd64 + - build-arm64 + filters: + branches: + only: + - main + - canary diff --git a/.circleci/main-config.yml b/.circleci/main-config.yml new file mode 100644 index 00000000..279c6f44 --- /dev/null +++ b/.circleci/main-config.yml @@ -0,0 +1,76 @@ +version: 2.1 + +jobs: + build-amd64: + machine: + image: ubuntu-2004:current + steps: + - checkout + - run: + name: Prepare .env file + command: | + cp .env.production.example .env.production + - run: + name: Build and push AMD64 image + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + docker build --platform linux/amd64 -t dokploy/dokploy:latest-amd64 . + docker push dokploy/dokploy:latest-amd64 + + build-arm64: + machine: + image: ubuntu-2004:current + resource_class: arm.large + steps: + - checkout + + - run: + name: Prepare .env file + command: | + cp .env.production.example .env.production + - run: + name: Build and push ARM64 image + command: | + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + docker build --platform linux/arm64 -t dokploy/dokploy:latest-arm64 . + docker push dokploy/dokploy:latest-arm64 + + combine-manifests: + docker: + - image: cimg/base:stable + steps: + - setup_remote_docker + - run: + name: Create and push multi-arch manifest + command: | + VERSION=$(node -p "require('./package.json').version") + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN + docker manifest create dokploy/dokploy:latest \ + dokploy/dokploy:latest-amd64 \ + dokploy/dokploy:latest-arm64 + docker manifest push dokploy/dokploy:latest + + docker manifest create dokploy/dokploy:${VERSION} \ + dokploy/dokploy:latest-amd64 \ + dokploy/dokploy:latest-arm64 + docker manifest push dokploy/dokploy:${VERSION} + +workflows: + version: 2 + build-all: + jobs: + - build-amd64: + filters: + branches: + only: main + - build-arm64: + filters: + branches: + only: main + - combine-manifests: + requires: + - build-amd64 + - build-arm64 + filters: + branches: + only: main diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 81910e63..325e92f0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -16,29 +16,34 @@ jobs: matrix: node-version: [18.18.0] steps: - - name: Check out the code - uses: actions/checkout@v4 + - name: Check out the code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Setup pnpm - uses: pnpm/action-setup@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'pnpm' + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" - - name: Install dependencies - run: pnpm install + - name: Install dependencies + run: pnpm install - - name: Run format and lint - run: pnpm biome ci + # - name: Run commitlint + # run: pnpm commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose - - name: Run type check - run: pnpm typecheck + - name: Run format and lint + run: pnpm biome ci - - name: Run Build - run: pnpm build + - name: Run type check + run: pnpm typecheck - - name: Run Tests - run: pnpm run test + - name: Run Build + run: pnpm build + + - name: Run Tests + run: pnpm run test diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 1534e6d2..00000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Push - -on: - push: - branches: - - main - - canary - -env: - HUSKY: 0 - -jobs: - build-and-push-docker-on-push: - runs-on: ubuntu-latest - steps: - - name: Check out the code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Prepare .env file - run: | - cp .env.production.example .env.production - - - name: Build and push Docker image using custom script - run: | - chmod +x ./docker/push.sh - ./docker/push.sh ${{ github.ref_name == 'canary' && 'canary' || '' }} diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 00000000..4f9d7bc2 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm commitlint --edit $1 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..67a228a4 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.18.0 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8075325c..e15e6bd6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,7 +72,7 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ && pnpm install -g tsx # Install buildpacks -RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack +RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack # Expose port EXPOSE 3000 diff --git a/__test__/drop/drop.test.test.ts b/__test__/drop/drop.test.test.ts new file mode 100644 index 00000000..5561999c --- /dev/null +++ b/__test__/drop/drop.test.test.ts @@ -0,0 +1,98 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { APPLICATIONS_PATH } from "@/server/constants"; +import { unzipDrop } from "@/server/utils/builders/drop"; +import AdmZip from "adm-zip"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; + +if (typeof window === "undefined") { + const undici = require("undici"); + globalThis.File = undici.File as any; + globalThis.FileList = undici.FileList as any; +} + +vi.mock("@/server/constants", () => ({ + APPLICATIONS_PATH: "./__test__/drop/zips/output", +})); + +describe("unzipDrop using real zip files", () => { + beforeAll(async () => { + await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true }); + }); + + afterAll(async () => { + await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true }); + }); + + it("should correctly extract a zip with a single root folder", async () => { + const appName = "single-file"; + const outputPath = path.join(APPLICATIONS_PATH, appName, "code"); + const zip = new AdmZip("./__test__/drop/zips/single-file.zip"); + + const zipBuffer = zip.toBuffer(); + const file = new File([zipBuffer], "single.zip"); + await unzipDrop(file, appName); + + const files = await fs.readdir(outputPath, { withFileTypes: true }); + expect(files.some((f) => f.name === "test.txt")).toBe(true); + }); + + it("should correctly extract a zip with a single root folder and a subfolder", async () => { + const appName = "folderwithfile"; + const outputPath = path.join(APPLICATIONS_PATH, appName, "code"); + const zip = new AdmZip("./__test__/drop/zips/folder-with-file.zip"); + + const zipBuffer = zip.toBuffer(); + const file = new File([zipBuffer], "single.zip"); + await unzipDrop(file, appName); + + const files = await fs.readdir(outputPath, { withFileTypes: true }); + expect(files.some((f) => f.name === "folder1.txt")).toBe(true); + }); + + it("should correctly extract a zip with multiple root folders", async () => { + const appName = "two-folders"; + const outputPath = path.join(APPLICATIONS_PATH, appName, "code"); + const zip = new AdmZip("./__test__/drop/zips/two-folders.zip"); + + const zipBuffer = zip.toBuffer(); + const file = new File([zipBuffer], "single.zip"); + await unzipDrop(file, appName); + + const files = await fs.readdir(outputPath, { withFileTypes: true }); + + expect(files.some((f) => f.name === "folder1")).toBe(true); + expect(files.some((f) => f.name === "folder2")).toBe(true); + }); + + it("should correctly extract a zip with a single root with a file", async () => { + const appName = "nested"; + const outputPath = path.join(APPLICATIONS_PATH, appName, "code"); + const zip = new AdmZip("./__test__/drop/zips/nested.zip"); + + const zipBuffer = zip.toBuffer(); + const file = new File([zipBuffer], "single.zip"); + await unzipDrop(file, appName); + + const files = await fs.readdir(outputPath, { withFileTypes: true }); + + expect(files.some((f) => f.name === "folder1")).toBe(true); + expect(files.some((f) => f.name === "folder2")).toBe(true); + expect(files.some((f) => f.name === "folder3")).toBe(true); + }); + + it("should correctly extract a zip with a single root with a folder", async () => { + const appName = "folder-with-sibling-file"; + const outputPath = path.join(APPLICATIONS_PATH, appName, "code"); + const zip = new AdmZip("./__test__/drop/zips/folder-with-sibling-file.zip"); + + const zipBuffer = zip.toBuffer(); + const file = new File([zipBuffer], "single.zip"); + await unzipDrop(file, appName); + + const files = await fs.readdir(outputPath, { withFileTypes: true }); + + expect(files.some((f) => f.name === "folder1")).toBe(true); + expect(files.some((f) => f.name === "test.txt")).toBe(true); + }); +}); diff --git a/__test__/drop/zips/folder-with-file.zip b/__test__/drop/zips/folder-with-file.zip new file mode 100644 index 00000000..b6f9b4c9 Binary files /dev/null and b/__test__/drop/zips/folder-with-file.zip differ diff --git a/__test__/drop/zips/folder-with-sibling-file.zip b/__test__/drop/zips/folder-with-sibling-file.zip new file mode 100644 index 00000000..3bcc0484 Binary files /dev/null and b/__test__/drop/zips/folder-with-sibling-file.zip differ diff --git a/__test__/drop/zips/folder1/folder1.txt b/__test__/drop/zips/folder1/folder1.txt new file mode 100644 index 00000000..b6b61b0b --- /dev/null +++ b/__test__/drop/zips/folder1/folder1.txt @@ -0,0 +1 @@ +Gogogogogogo \ No newline at end of file diff --git a/__test__/drop/zips/folder2/folder2.txt b/__test__/drop/zips/folder2/folder2.txt new file mode 100644 index 00000000..3270d3de --- /dev/null +++ b/__test__/drop/zips/folder2/folder2.txt @@ -0,0 +1 @@ +gogogogogog \ No newline at end of file diff --git a/__test__/drop/zips/folder3/file3.txt b/__test__/drop/zips/folder3/file3.txt new file mode 100644 index 00000000..80474b74 --- /dev/null +++ b/__test__/drop/zips/folder3/file3.txt @@ -0,0 +1 @@ +gogogogogogogogogo \ No newline at end of file diff --git a/__test__/drop/zips/nested.zip b/__test__/drop/zips/nested.zip new file mode 100644 index 00000000..db90bd56 Binary files /dev/null and b/__test__/drop/zips/nested.zip differ diff --git a/__test__/drop/zips/single-file.zip b/__test__/drop/zips/single-file.zip new file mode 100644 index 00000000..b5f39b13 Binary files /dev/null and b/__test__/drop/zips/single-file.zip differ diff --git a/__test__/drop/zips/test.txt b/__test__/drop/zips/test.txt new file mode 100644 index 00000000..79f2fde0 --- /dev/null +++ b/__test__/drop/zips/test.txt @@ -0,0 +1 @@ +dsafasdfasdf \ No newline at end of file diff --git a/__test__/drop/zips/two-folders.zip b/__test__/drop/zips/two-folders.zip new file mode 100644 index 00000000..5e46f98c Binary files /dev/null and b/__test__/drop/zips/two-folders.zip differ diff --git a/components/dashboard/application/advanced/volumes/add-volumes.tsx b/components/dashboard/application/advanced/volumes/add-volumes.tsx index 6b882b7c..e32ad756 100644 --- a/components/dashboard/application/advanced/volumes/add-volumes.tsx +++ b/components/dashboard/application/advanced/volumes/add-volumes.tsx @@ -63,6 +63,7 @@ const mySchema = z.discriminatedUnion("type", [ z .object({ type: z.literal("file"), + filePath: z.string().min(1, "File path required"), content: z.string().optional(), }) .merge(mountSchema), @@ -81,7 +82,7 @@ export const AddVolumes = ({ defaultValues: { type: serviceType === "compose" ? "file" : "bind", hostPath: "", - mountPath: "", + mountPath: serviceType === "compose" ? "/" : "", }, resolver: zodResolver(mySchema), }); @@ -125,6 +126,7 @@ export const AddVolumes = ({ serviceId, content: data.content, mountPath: data.mountPath, + filePath: data.filePath, type: data.type, serviceType, }) @@ -288,41 +290,62 @@ export const AddVolumes = ({ )} {type === "file" && ( + <> + ( + + Content + + +