From a9fa4c28240c1640117aade61e52e19949666910 Mon Sep 17 00:00:00 2001 From: Libin YANG Date: Mon, 23 Dec 2024 19:59:59 +0800 Subject: [PATCH] feat: build and deploy docker image (#495) --- .github/workflows/docker.yml | 39 +++++++++++ docker/latest/.env | 4 ++ docker/latest/Dockerfile.base | 14 ++++ docker/latest/Dockerfile.nginx | 6 ++ docker/latest/Dockerfile.standalone | 19 ++++++ docker/latest/Dockerfile.static | 12 ++++ docker/latest/patch/vite.config.ts | 66 +++++++++++++++++++ docker/latest/server/main.go | 21 ++++++ eslint.config.mjs | 2 +- package.json | 2 +- scripts/build-base-image.sh | 25 +++++++ scripts/build-nginx.sh | 25 +++++++ scripts/build-standalone.sh | 25 +++++++ scripts/build-static.sh | 25 +++++++ scripts/push-images.sh | 30 +++++++++ {bin => scripts}/release.js | 0 .../CodemirrorEditor/EditorHeader/index.vue | 2 +- 17 files changed, 314 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/docker.yml create mode 100644 docker/latest/.env create mode 100644 docker/latest/Dockerfile.base create mode 100644 docker/latest/Dockerfile.nginx create mode 100644 docker/latest/Dockerfile.standalone create mode 100644 docker/latest/Dockerfile.static create mode 100644 docker/latest/patch/vite.config.ts create mode 100644 docker/latest/server/main.go create mode 100644 scripts/build-base-image.sh create mode 100644 scripts/build-nginx.sh create mode 100644 scripts/build-standalone.sh create mode 100644 scripts/build-static.sh create mode 100644 scripts/push-images.sh rename {bin => scripts}/release.js (100%) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..35d954a0a --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,39 @@ +name: Build and Push Docker Images + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build base image + run: bash scripts/build-base-image.sh + + - name: Build nginx image + run: bash scripts/build-nginx.sh + + - name: Build standalone image + run: bash scripts/build-standalone.sh + + - name: Build static image + run: bash scripts/build-static.sh + + - name: Push images to Docker Hub + run: bash scripts/push-images.sh diff --git a/docker/latest/.env b/docker/latest/.env new file mode 100644 index 000000000..947399743 --- /dev/null +++ b/docker/latest/.env @@ -0,0 +1,4 @@ +VER_APP=latest +VER_NGX=1.21.6-alpine +VER_GOLANG=1.17.6-alpine3.15 +VER_ALPINE=3.15 \ No newline at end of file diff --git a/docker/latest/Dockerfile.base b/docker/latest/Dockerfile.base new file mode 100644 index 000000000..f76a75eb9 --- /dev/null +++ b/docker/latest/Dockerfile.base @@ -0,0 +1,14 @@ +FROM node:20-alpine3.19 AS builder +ENV LANG="en_US.UTF-8" +ENV LANGUAGE="en_US.UTF-8" +ENV LC_ALL="en_US.UTF-8" +RUN apk add curl +RUN curl -L "https://github.com/doocs/md/archive/refs/heads/main.zip" -o "main.zip" && unzip "main.zip" && mv "md-main" /app +WORKDIR /app +COPY ./patch/vite.config.ts /app/vite.config.ts +ENV NODE_OPTIONS="--openssl-legacy-provider" +RUN npm i && npm run build + +FROM scratch +LABEL MAINTAINER="ylb" +COPY --from=builder /app/dist /app/assets diff --git a/docker/latest/Dockerfile.nginx b/docker/latest/Dockerfile.nginx new file mode 100644 index 000000000..b97106326 --- /dev/null +++ b/docker/latest/Dockerfile.nginx @@ -0,0 +1,6 @@ +ARG VER_NGX="1.21.6-alpine" + +FROM "doocs/md:latest-assets" AS assets +FROM "nginx:$VER_NGX" +LABEL MAINTAINER="ylb" +COPY --from=assets /app/* /usr/share/nginx/html diff --git a/docker/latest/Dockerfile.standalone b/docker/latest/Dockerfile.standalone new file mode 100644 index 000000000..755ec7869 --- /dev/null +++ b/docker/latest/Dockerfile.standalone @@ -0,0 +1,19 @@ +ARG VER_GOLANG=1.17.6-alpine3.15 +ARG VER_ALPINE=3.15 + +FROM "doocs/md:latest-assets" AS assets + +FROM "golang:$VER_GOLANG" AS gobuilder +COPY --from=assets /app/* /app/assets/ +COPY server/main.go /app +RUN apk add git bash gcc musl-dev upx +WORKDIR /app +RUN go build -ldflags "-w -s" -o md main.go && \ + apk add upx && \ + upx -9 -o md.minify md + +FROM "alpine:$VER_ALPINE" +LABEL MAINTAINER="ylb" +COPY --from=gobuilder /app/md.minify /bin/md +EXPOSE 80 +CMD ["md"] \ No newline at end of file diff --git a/docker/latest/Dockerfile.static b/docker/latest/Dockerfile.static new file mode 100644 index 000000000..c8ba628f9 --- /dev/null +++ b/docker/latest/Dockerfile.static @@ -0,0 +1,12 @@ +FROM doocs/md:latest-assets AS assets + +# detail https://github.com/lipanski/docker-static-website/blob/master/Dockerfile +FROM lipanski/docker-static-website + +WORKDIR /home/static + +COPY --from=assets /app/* /home/static + +EXPOSE 80 + +CMD ["/busybox-httpd", "-f", "-v", "-p", "80", "-c", "httpd.conf"] diff --git a/docker/latest/patch/vite.config.ts b/docker/latest/patch/vite.config.ts new file mode 100644 index 000000000..20eb26abb --- /dev/null +++ b/docker/latest/patch/vite.config.ts @@ -0,0 +1,66 @@ +import path from 'node:path' +import process from 'node:process' + +import vue from '@vitejs/plugin-vue' +import { visualizer } from 'rollup-plugin-visualizer' +import UnoCSS from 'unocss/vite' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { defineConfig } from 'vite' +import { nodePolyfills } from 'vite-plugin-node-polyfills' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vitejs.dev/config/ +export default defineConfig({ + base: `/`, // 基本路径, 建议以绝对路径跟随访问目录 + define: { + process, + }, + plugins: [ + vue(), + UnoCSS(), + vueDevTools(), + nodePolyfills({ + include: [`path`, `util`, `timers`, `stream`, `fs`], + overrides: { + // Since `fs` is not supported in browsers, we can use the `memfs` package to polyfill it. + // fs: 'memfs', + }, + }), + process.env.ANALYZE === `true` && visualizer({ + emitFile: true, + filename: `stats.html`, + }), + AutoImport({ + imports: [ + `vue`, + `pinia`, + `@vueuse/core`, + ], + dirs: [ + `./src/stores`, + `./src/utils/toast`, + ], + }), + Components({ + resolvers: [], + }), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, `./src`), + }, + }, + css: { + devSourcemap: true, + }, + build: { + rollupOptions: { + output: { + chunkFileNames: `static/js/md-[name]-[hash].js`, + entryFileNames: `static/js/md-[name]-[hash].js`, + assetFileNames: `static/[ext]/md-[name]-[hash].[ext]`, + }, + }, + }, +}) diff --git a/docker/latest/server/main.go b/docker/latest/server/main.go new file mode 100644 index 000000000..f45d64601 --- /dev/null +++ b/docker/latest/server/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "embed" + "io/fs" + "log" + "net/http" +) + +//go:embed assets +var assets embed.FS + +func main() { + mutex := http.NewServeMux() + md, _ := fs.Sub(assets, "assets") + mutex.Handle("/", http.FileServer(http.FS(md))) + err := http.ListenAndServe(":80", mutex) + if err != nil { + log.Fatal(err) + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs index e99a17789..ff4c663cd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,7 @@ export default antfu({ unocss: true, typescript: true, formatters: true, - ignores: [`.github`, `bin`, `md-cli`, `src/assets`, `example`], + ignores: [`.github`, `scripts`, `docker`, `md-cli`, `src/assets`, `example`], }, { rules: { 'semi': [`error`, `never`], diff --git a/package.json b/package.json index 049170a53..2091a785d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build:cli": "npm run build && npx shx rm -rf md-cli/dist && npx shx rm -rf dist/**/*.map && npx shx cp -r dist md-cli/ && cd md-cli && npm pack", "build:analyze": "cross-env ANALYZE=true vite build", "preview": "npm run build && vite preview", - "release:cli": "node ./bin/release.js", + "release:cli": "node ./scripts/release.js", "ext:dev": "wxt", "ext:zip": "wxt zip", "lint": "eslint . --fix", diff --git a/scripts/build-base-image.sh b/scripts/build-base-image.sh new file mode 100644 index 000000000..024cc3b78 --- /dev/null +++ b/scripts/build-base-image.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +RELEASE_DIR='./docker'; +REPO_NAME='doocs/md' + +for app_ver in $RELEASE_DIR/*; do + + if [ -f "$app_ver/Dockerfile.base" ]; then + + tag=$(echo $app_ver | cut -b 10-); + echo "Build: $tag"; + set -a + . "$app_ver/.env" + set +a + + echo $app_ver + echo "VER_APP: $VER_APP" + echo "VER_NGX: $VER_NGX" + echo "VER_GOLANG: $VER_GOLANG" + echo "VER_ALPINE: $VER_ALPINE" + + docker build --build-arg VER_APP=$VER_APP -f "$app_ver/Dockerfile.base" -t "$REPO_NAME:${VER_APP}-assets" "$app_ver" + fi + +done \ No newline at end of file diff --git a/scripts/build-nginx.sh b/scripts/build-nginx.sh new file mode 100644 index 000000000..b5a08fece --- /dev/null +++ b/scripts/build-nginx.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +RELEASE_DIR='./docker'; +REPO_NAME='doocs/md' + +for app_ver in $RELEASE_DIR/*; do + + if [ -f "$app_ver/Dockerfile.nginx" ]; then + + tag=$(echo $app_ver | cut -b 10-); + echo "Build: $tag"; + set -a + . "$app_ver/.env" + set +a + + echo $app_ver + echo "VER_APP: $VER_APP" + echo "VER_NGX: $VER_NGX" + echo "VER_GOLANG: $VER_GOLANG" + echo "VER_ALPINE: $VER_ALPINE" + + docker build --build-arg VER_APP=$VER_APP --build-arg VER_NGX=$VER_NGX -f "$app_ver/Dockerfile.nginx" -t "$REPO_NAME:${VER_APP}-nginx" "$app_ver" + fi + +done \ No newline at end of file diff --git a/scripts/build-standalone.sh b/scripts/build-standalone.sh new file mode 100644 index 000000000..0b011e104 --- /dev/null +++ b/scripts/build-standalone.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +RELEASE_DIR='./docker'; +REPO_NAME='doocs/md' + +for app_ver in $RELEASE_DIR/*; do + + if [ -f "$app_ver/Dockerfile.standalone" ]; then + + tag=$(echo $app_ver | cut -b 10-); + echo "Build: $tag"; + set -a + . "$app_ver/.env" + set +a + + echo $app_ver + echo "VER_APP: $VER_APP" + echo "VER_NGX: $VER_NGX" + echo "VER_GOLANG: $VER_GOLANG" + echo "VER_ALPINE: $VER_ALPINE" + + docker build --build-arg VER_APP=$VER_APP --build-arg VER_NGX=$VER_NGX -f "$app_ver/Dockerfile.standalone" -t "$REPO_NAME:${VER_APP}" "$app_ver" + fi + +done \ No newline at end of file diff --git a/scripts/build-static.sh b/scripts/build-static.sh new file mode 100644 index 000000000..36abd2a1c --- /dev/null +++ b/scripts/build-static.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +RELEASE_DIR='./docker'; +REPO_NAME='doocs/md' + +for app_ver in $RELEASE_DIR/*; do + + if [ -f "$app_ver/Dockerfile.static" ]; then + + tag=$(echo $app_ver | cut -b 10-); + echo "Build: $tag"; + set -a + . "$app_ver/.env" + set +a + + echo $app_ver + echo "VER_APP: $VER_APP" + echo "VER_NGX: $VER_NGX" + echo "VER_GOLANG: $VER_GOLANG" + echo "VER_ALPINE: $VER_ALPINE" + + docker build --build-arg VER_APP=$VER_APP --build-arg VER_NGX=$VER_NGX -f "$app_ver/Dockerfile.static" -t "$REPO_NAME:${VER_APP}-static" "$app_ver" + fi + +done \ No newline at end of file diff --git a/scripts/push-images.sh b/scripts/push-images.sh new file mode 100644 index 000000000..d38c4b645 --- /dev/null +++ b/scripts/push-images.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +RELEASE_DIR='./docker'; +REPO_NAME='doocs/md' + +for app_ver in $RELEASE_DIR/*; do + + tag=$(echo $app_ver | cut -b 10-); + + if [ -f "$app_ver/Dockerfile.base" ]; then + # 推送构建产物,方便其他的用户和爱好者进行二次封装 + docker push $REPO_NAME:$tag-assets + fi + + if [ -f "$app_ver/Dockerfile.standalone" ]; then + # 推送单个二进制的镜像 + docker push $REPO_NAME:$tag + fi + + if [ -f "$app_ver/Dockerfile.nginx" ]; then + # 推送使用 Nginx 的镜像 + docker push $REPO_NAME:$tag-nginx + fi + + if [ -f "$app_ver/Dockerfile.static" ]; then + # 推送使用 lipanski/docker-static-website 的镜像 + docker push $REPO_NAME:$tag-static + fi + +done \ No newline at end of file diff --git a/bin/release.js b/scripts/release.js similarity index 100% rename from bin/release.js rename to scripts/release.js diff --git a/src/components/CodemirrorEditor/EditorHeader/index.vue b/src/components/CodemirrorEditor/EditorHeader/index.vue index 6d8fdf403..009dbc162 100644 --- a/src/components/CodemirrorEditor/EditorHeader/index.vue +++ b/src/components/CodemirrorEditor/EditorHeader/index.vue @@ -64,7 +64,7 @@ const copyMode = useStorage(addPrefix(`copyMode`), `txt`) const source = ref(``) const { copy: copyContent } = useClipboard({ source }) -const creatEmptyNode = () => { +function creatEmptyNode() { const node = document.createElement(`p`) node.style.fontSize = `0` node.style.lineHeight = `0`