diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fde7ec0f2..59d395946 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,9 +3,20 @@ "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", - "features": { - "ghcr.io/devcontainers-contrib/features/pnpm:2": {} - }, "forwardPorts": [3000], - "postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh" + "postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh", + "customizations": { + "vscode": { + "extensions": [ + "editorconfig.editorconfig", + "eg2.vscode-npm-script", + "dbaeumer.vscode-eslint", + "Vue.volar", + "Vue.vscode-typescript-vue-plugin", + "Orta.vscode-jest", + "dbaeumer.vscode-eslint", + "mrmlnc.vscode-json5" + ] + } + } } diff --git a/.dockerignore b/.dockerignore index 8f984831e..151ede038 100644 --- a/.dockerignore +++ b/.dockerignore @@ -25,6 +25,8 @@ fluent-emojis/ !.yarn/sdks !.yarn/versions +.pnpm-store + .idea/ packages/*/.vscode/ packages/backend/test/docker-compose.yml diff --git a/.gitignore b/.gitignore index 62b818c62..c413cd4da 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ packages/frontend/.yarn/cache packages/backend/.yarn/cache packages/sw/.yarn/cache +# pnpm +.pnpm-store + # Cypress cypress/screenshots cypress/videos diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 42264548e..6b0c1540f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,9 @@ "eg2.vscode-npm-script", "dbaeumer.vscode-eslint", "Vue.volar", - "Vue.vscode-typescript-vue-plugin" + "Vue.vscode-typescript-vue-plugin", + "Orta.vscode-jest", + "dbaeumer.vscode-eslint", + "mrmlnc.vscode-json5" ] } diff --git a/Dockerfile b/Dockerfile index b439716be..eeff38e48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,9 @@ ARG NODE_VERSION=18.13.0-bullseye -FROM node:${NODE_VERSION} AS builder +# build assets & compile TypeScript + +FROM --platform=$BUILDPLATFORM node:${NODE_VERSION} AS native-builder RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ @@ -33,33 +35,49 @@ RUN git submodule update --init RUN pnpm build RUN rm -rf .git/ -FROM node:${NODE_VERSION}-slim AS runner +# build native dependencies for target platform + +FROM --platform=$TARGETPLATFORM node:${NODE_VERSION} AS target-builder + +RUN apt-get update \ + && apt-get install -yqq --no-install-recommends \ + build-essential + +RUN corepack enable + +WORKDIR /misskey + +COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] +COPY --link ["scripts", "./scripts"] +COPY --link ["packages/backend/package.json", "./packages/backend/"] + +RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ + pnpm i --frozen-lockfile --aggregate-output + +FROM --platform=$TARGETPLATFORM node:${NODE_VERSION}-slim AS runner ARG UID="991" ARG GID="991" -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - rm -f /etc/apt/apt.conf.d/docker-clean \ - ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \ - && apt-get update \ +RUN apt-get update \ && apt-get install -y --no-install-recommends \ ffmpeg tini curl \ && corepack enable \ && groupadd -g "${GID}" misskey \ && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ && find / -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \ - && find / -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; + && find / -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists USER misskey WORKDIR /misskey -COPY --chown=misskey:misskey --from=builder /misskey/node_modules ./node_modules -COPY --chown=misskey:misskey --from=builder /misskey/built ./built -COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules -COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/built ./packages/backend/built -COPY --chown=misskey:misskey --from=builder /misskey/packages/frontend/node_modules ./packages/frontend/node_modules -COPY --chown=misskey:misskey --from=builder /misskey/fluent-emojis /misskey/fluent-emojis +COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules +COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules +COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built +COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built +COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis COPY --chown=misskey:misskey . ./ ENV NODE_ENV=production diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml index d16aece91..d5dd14f59 100644 --- a/chart/templates/Deployment.yml +++ b/chart/templates/Deployment.yml @@ -3,16 +3,16 @@ kind: Deployment metadata: name: {{ include "misskey.fullname" . }} labels: - {{- include "misskey.labels" . | nindent 4 }} + {{- include "misskey.labels" . | nindent 4 }} spec: selector: matchLabels: - {{- include "misskey.selectorLabels" . | nindent 6 }} + {{- include "misskey.selectorLabels" . | nindent 6 }} replicas: 1 template: metadata: labels: - {{- include "misskey.selectorLabels" . | nindent 8 }} + {{- include "misskey.selectorLabels" . | nindent 8 }} spec: containers: - name: misskey diff --git a/chart/templates/Service.yml b/chart/templates/Service.yml index 320958129..afd851a9f 100644 --- a/chart/templates/Service.yml +++ b/chart/templates/Service.yml @@ -11,4 +11,4 @@ spec: protocol: TCP name: http selector: - {{- include "misskey.selectorLabels" . | nindent 4 }} + {{- include "misskey.selectorLabels" . | nindent 4 }} diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ef7046993..f94133c19 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -956,6 +956,7 @@ exploreOtherServers: "他のサーバーを探す" letsLookAtTimeline: "タイムラインを見てみる" disableFederationWarn: "連合が無効になっています。無効にしても投稿が非公開にはなりません。ほとんどの場合、このオプションを有効にする必要はありません。" invitationRequiredToRegister: "現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。" +emailNotSupported: "このサーバーではメール配信はサポートされていません" postToTheChannel: "チャンネルに投稿" _achievements: diff --git a/packages/backend/package.json b/packages/backend/package.json index 42efb881e..35e8dc5c6 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -124,6 +124,7 @@ "seedrandom": "3.0.5", "semver": "7.3.8", "sharp": "0.31.3", + "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "summaly": "github:misskey-dev/summaly", diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts index acf5c1ede..0b6d147dc 100644 --- a/packages/backend/src/misc/is-mime-image.ts +++ b/packages/backend/src/misc/is-mime-image.ts @@ -4,6 +4,8 @@ const dictionary = { 'safe-file': FILE_TYPE_BROWSERSAFE, 'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'], 'sharp-animation-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml'], + 'sharp-convertible-image-with-bmp': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml', 'image/x-icon', 'image/bmp'], + 'sharp-animation-convertible-image-with-bmp': ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml', 'image/x-icon', 'image/bmp'], }; export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 8eeb76f7c..835657b62 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -22,6 +22,7 @@ import { bindThis } from '@/decorators.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import { isMimeImage } from '@/misc/is-mime-image.js'; import sharp from 'sharp'; +import { sharpBmp } from 'sharp-read-bmp'; import { correctFilename } from '@/misc/correct-filename.js'; const _filename = fileURLToPath(import.meta.url); @@ -132,7 +133,7 @@ export class FileServerService { let image: IImageStreamable | null = null; if (file.fileRole === 'thumbnail') { - if (isMimeImage(file.mime, 'sharp-convertible-image')) { + if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) { reply.header('Cache-Control', 'max-age=31536000, immutable'); const url = new URL(`${this.config.mediaProxy}/static.webp`); @@ -257,8 +258,8 @@ export class FileServerService { } try { - const isConvertibleImage = isMimeImage(file.mime, 'sharp-convertible-image'); - const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image'); + const isConvertibleImage = isMimeImage(file.mime, 'sharp-convertible-image-with-bmp'); + const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image-with-bmp'); if ( 'emoji' in request.query || @@ -282,7 +283,7 @@ export class FileServerService { type: file.mime, }; } else { - const data = sharp(file.path, { animated: !('static' in request.query) }) + const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) })) .resize({ height: 'emoji' in request.query ? 128 : 320, withoutEnlargement: true, @@ -296,11 +297,11 @@ export class FileServerService { }; } } else if ('static' in request.query) { - image = this.imageProcessingService.convertToWebpStream(file.path, 498, 280); + image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 280); } else if ('preview' in request.query) { - image = this.imageProcessingService.convertToWebpStream(file.path, 200, 200); + image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200); } else if ('badge' in request.query) { - const mask = sharp(file.path) + const mask = (await sharpBmp(file.path, file.mime)) .resize(96, 96, { fit: 'inside', withoutEnlargement: false, diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue index 1734dcfe4..b1e6f223b 100644 --- a/packages/frontend/src/pages/settings/email.vue +++ b/packages/frontend/src/pages/settings/email.vue @@ -1,5 +1,5 @@