feat: license violation protection (#13285)

* spec(frontend): aboutページにリポジトリ・フィードバックのURLを表示させる

Cherry-picked from MisskeyIO#441
Cherry-picked from MisskeyIO#438

* feat: license violation protection

* build: fix typo

* build: fix typo

* fix: farewell to the static type land

* fix: key typo

* fix: import typo

* fix: properly interpret `prominently`

* docs: add disclaimer

* docs: update CHANGELOG

* chore: add gap

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
Acid Chicken (硫酸鶏) 2024-02-17 13:34:50 +09:00 committed by GitHub
parent fa243276c5
commit acba96c1d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 371 additions and 26 deletions

View file

@ -2,6 +2,63 @@
# Misskey configuration # Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌──────────────────────────────┐
#───┘ a boring but important thing └────────────────────────────
#
# First of all, let me tell you a story that may possibly be
# boring to you and possibly important to you.
#
# Misskey is licensed under the AGPLv3 license. This license is
# known to be often misunderstood. Please read the following
# instructions carefully and select the appropriate option so
# that you do not negligently cause a license violation.
#
# --------
# Option 1: If you host Misskey AS-IS (without any changes to
# the source code. forks are not included).
#
# Step 1: Congratulations! You don't need to do anything.
# --------
# Option 2: If you have made changes to the source code (forks
# are included) and publish a Git repository of source
# code. There should be no access restrictions on
# this repository. Strictly speaking, it doesn't have
# to be a Git repository, but you'll probably use Git!
#
# Step 1: Build and run the Misskey server first.
# Step 2: Open <https://your.misskey.example/admin/settings> in
# your browser with the administrator account.
# Step 3: Enter the URL of your Git repository in the
# "Repository URL" field.
# --------
# Option 3: If neither of the above applies to you.
# (In this case, the source code should be published
# on the Misskey interface. IT IS NOT ENOUGH TO
# DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY
# E-MAIL OR OTHER MEANS. If you are not satisfied
# with this, it is recommended that you read the
# license again carefully. Anyway, enabling this
# option will automatically generate and publish a
# tarball at build time, protecting you from
# inadvertent license violations. (There is no legal
# guarantee, of course.) The tarball will generated
# from the root directory of your codebase. So it is
# also recommended to check <built/tarball> directory
# once after building and before activating the server
# to avoid ACCIDENTAL LEAKING OF SENSITIVE INFORMATION.
# To prevent certain files from being included in the
# tarball, add a glob pattern after line 15 in
# <scripts/tarball.mjs>. DO NOT FORGET TO BUILD AFTER
# ENABLING THIS OPTION!)
#
# Step 1: Uncomment the following line.
#
# publishTarballInsteadOfProvideRepositoryUrl: true
# ┌─────┐ # ┌─────┐
#───┘ URL └───────────────────────────────────────────────────── #───┘ URL └─────────────────────────────────────────────────────
@ -118,7 +175,7 @@ redis:
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global # You can set scope to local (default value) or global
# (include notes from remote). # (include notes from remote).
#meilisearch: #meilisearch:
@ -214,7 +271,7 @@ proxyRemoteFiles: true
signToActivityPubGet: true signToActivityPubGet: true
# For security reasons, uploading attachments from the intranet is prohibited, # For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined". # but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [ #allowedPrivateNetworks: [
# '127.0.0.1/32' # '127.0.0.1/32'

View file

@ -20,6 +20,9 @@
### General ### General
- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
- Feat: Add support for TrueMail - Feat: Add support for TrueMail
- Feat: AGPLv3ライセンスに誤って違反するのを防止する機能を追加
- 管理者がrepositoryUrlを変更したり、またはソースコードを直接頒布することを選択できるようになります
- 本体のソースコードに改変を加えた際に、ライセンスに基づく適切な案内を表示します
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように - Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 - Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正 - Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正

40
locales/index.d.ts vendored
View file

@ -3980,6 +3980,10 @@ export interface Locale extends ILocale {
* Misskeyは{host}使 * Misskeyは{host}使
*/ */
"pleaseDonate": ParameterizedString<"host">; "pleaseDonate": ParameterizedString<"host">;
/**
* {anchor}
*/
"correspondingSourceIsAvailable": ParameterizedString<"anchor">;
/** /**
* *
*/ */
@ -4684,6 +4688,34 @@ export interface Locale extends ILocale {
* *
*/ */
"externalServices": string; "externalServices": string;
/**
*
*/
"sourceCode": string;
/**
*
*/
"sourceCodeIsNotYetProvided": string;
/**
* URL
*/
"repositoryUrl": string;
/**
* URLを記入しますMisskeyを現状のまま使 https://github.com/misskey-dev/misskey と記入します。
*/
"repositoryUrlDescription": string;
/**
* tarballを提供する必要があります.config/example.ymlを参照してください
*/
"repositoryUrlOrTarballRequired": string;
/**
*
*/
"feedback": string;
/**
* URL
*/
"feedbackUrl": string;
/** /**
* *
*/ */
@ -6813,6 +6845,14 @@ export interface Locale extends ILocale {
* *
*/ */
"source": string; "source": string;
/**
*
*/
"original": string;
/**
* {name}Misskeyを改変したバージョンを使用しています
*/
"thisIsModifiedVersion": ParameterizedString<"name">;
/** /**
* Misskeyを翻訳 * Misskeyを翻訳
*/ */

View file

@ -991,6 +991,7 @@ neverShow: "今後表示しない"
remindMeLater: "また後で" remindMeLater: "また後で"
didYouLikeMisskey: "Misskeyを気に入っていただけましたか" didYouLikeMisskey: "Misskeyを気に入っていただけましたか"
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!" pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
correspondingSourceIsAvailable: "対応するソースコードは{anchor}から利用可能です。"
roles: "ロール" roles: "ロール"
role: "ロール" role: "ロール"
noRole: "ロールはありません" noRole: "ロールはありません"
@ -1167,6 +1168,13 @@ hideRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返
confirmShowRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか" confirmShowRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか"
confirmHideRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか" confirmHideRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか"
externalServices: "外部サービス" externalServices: "外部サービス"
sourceCode: "ソースコード"
sourceCodeIsNotYetProvided: "ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。"
repositoryUrl: "リポジトリURL"
repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Misskeyを現状のままソースコードにいかなる変更も加えずに使用している場合は https://github.com/misskey-dev/misskey と記入します。"
repositoryUrlOrTarballRequired: "リポジトリを公開していない場合、代わりにtarballを提供する必要があります。詳細は.config/example.ymlを参照してください。"
feedback: "フィードバック"
feedbackUrl: "フィードバックURL"
impressum: "運営者情報" impressum: "運営者情報"
impressumUrl: "運営者情報URL" impressumUrl: "運営者情報URL"
impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。" impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。"
@ -1778,6 +1786,8 @@ _aboutMisskey:
contributors: "コントリビューター" contributors: "コントリビューター"
allContributors: "全てのコントリビューター" allContributors: "全てのコントリビューター"
source: "ソースコード" source: "ソースコード"
original: "オリジナル"
thisIsModifiedVersion: "{name}はオリジナルのMisskeyを改変したバージョンを使用しています。"
translation: "Misskeyを翻訳" translation: "Misskeyを翻訳"
donate: "Misskeyに寄付" donate: "Misskeyに寄付"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰" morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class MakeRepositoryUrlNullable1707808106310 {
name = 'MakeRepositoryUrlNullable1707808106310'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" DROP NOT NULL`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET NOT NULL`);
}
}

View file

@ -57,6 +57,8 @@ type Source = {
scope?: 'local' | 'global' | string[]; scope?: 'local' | 'global' | string[];
}; };
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
proxy?: string; proxy?: string;
proxySmtp?: string; proxySmtp?: string;
proxyBypassHosts?: string[]; proxyBypassHosts?: string[];
@ -145,6 +147,7 @@ export type Config = {
signToActivityPubGet: boolean | undefined; signToActivityPubGet: boolean | undefined;
version: string; version: string;
publishTarballInsteadOfProvideRepositoryUrl: boolean;
host: string; host: string;
hostname: string; hostname: string;
scheme: string; scheme: string;
@ -209,6 +212,7 @@ export function loadConfig(): Config {
return { return {
version, version,
publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
url: url.origin, url: url.origin,
port: config.port ?? parseInt(process.env.PORT ?? '', 10), port: config.port ?? parseInt(process.env.PORT ?? '', 10),
socket: config.socket, socket: config.socket,

View file

@ -357,9 +357,9 @@ export class MiMeta {
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,
default: 'https://github.com/misskey-dev/misskey', default: 'https://github.com/misskey-dev/misskey',
nullable: false, nullable: true,
}) })
public repositoryUrl: string; public repositoryUrl: string | null;
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,

View file

@ -429,7 +429,7 @@ export const meta = {
}, },
repositoryUrl: { repositoryUrl: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: true,
}, },
summalyProxy: { summalyProxy: {
type: 'string', type: 'string',

View file

@ -104,8 +104,8 @@ export const paramDef = {
swPublicKey: { type: 'string', nullable: true }, swPublicKey: { type: 'string', nullable: true },
swPrivateKey: { type: 'string', nullable: true }, swPrivateKey: { type: 'string', nullable: true },
tosUrl: { type: 'string', nullable: true }, tosUrl: { type: 'string', nullable: true },
repositoryUrl: { type: 'string' }, repositoryUrl: { type: 'string', nullable: true },
feedbackUrl: { type: 'string' }, feedbackUrl: { type: 'string', nullable: true },
impressumUrl: { type: 'string', nullable: true }, impressumUrl: { type: 'string', nullable: true },
privacyPolicyUrl: { type: 'string', nullable: true }, privacyPolicyUrl: { type: 'string', nullable: true },
useObjectStorage: { type: 'boolean' }, useObjectStorage: { type: 'boolean' },
@ -402,7 +402,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
if (ps.repositoryUrl !== undefined) { if (ps.repositoryUrl !== undefined) {
set.repositoryUrl = ps.repositoryUrl; set.repositoryUrl = URL.canParse(ps.repositoryUrl!) ? ps.repositoryUrl : null;
} }
if (ps.feedbackUrl !== undefined) { if (ps.feedbackUrl !== undefined) {

View file

@ -37,6 +37,10 @@ export const meta = {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
}, },
providesTarball: {
type: 'boolean',
optional: false, nullable: false,
},
name: { name: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
@ -69,12 +73,12 @@ export const meta = {
}, },
repositoryUrl: { repositoryUrl: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: true,
default: 'https://github.com/misskey-dev/misskey', default: 'https://github.com/misskey-dev/misskey',
}, },
feedbackUrl: { feedbackUrl: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: true,
default: 'https://github.com/misskey-dev/misskey/issues/new', default: 'https://github.com/misskey-dev/misskey/issues/new',
}, },
defaultDarkTheme: { defaultDarkTheme: {
@ -352,6 +356,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
maintainerEmail: instance.maintainerEmail, maintainerEmail: instance.maintainerEmail,
version: this.config.version, version: this.config.version,
providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl,
name: instance.name, name: instance.name,
shortName: instance.shortName, shortName: instance.shortName,

View file

@ -11,6 +11,7 @@ import { alert, confirm, popup, post, toast } from '@/os.js';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import { $i, signout, updateAccount } from '@/account.js'; import { $i, signout, updateAccount } from '@/account.js';
import { fetchInstance, instance } from '@/instance.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js'; import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { makeHotkey } from '@/scripts/hotkey.js'; import { makeHotkey } from '@/scripts/hotkey.js';
import { reactionPicker } from '@/scripts/reaction-picker.js'; import { reactionPicker } from '@/scripts/reaction-picker.js';
@ -234,6 +235,13 @@ export async function mainBoot() {
} }
} }
fetchInstance().then(() => {
const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read');
if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/misskey-dev/misskey') {
popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
}
});
if ('Notification' in window) { if ('Notification' in window) {
// 許可を得ていなかったらリクエスト // 許可を得ていなかったらリクエスト
if (Notification.permission === 'default') { if (Notification.permission === 'default') {

View file

@ -0,0 +1,112 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="_panel _shadow" :class="$style.root">
<div :class="$style.icon">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-open-source" width="40" height="40" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 3a9 9 0 0 1 3.618 17.243l-2.193 -5.602a3 3 0 1 0 -2.849 0l-2.193 5.603a9 9 0 0 1 3.617 -17.244z"/>
</svg>
</div>
<div :class="$style.main">
<div :class="$style.title">
<I18n :src="i18n.ts.aboutX" tag="span">
<template #x>
{{ instance.name ?? host }}
</template>
</I18n>
</div>
<div :class="$style.text">
<I18n :src="i18n.ts._aboutMisskey.thisIsModifiedVersion" tag="span">
<template #name>
{{ instance.name ?? host }}
</template>
</I18n>
<I18n :src="i18n.ts.correspondingSourceIsAvailable" tag="span">
<template #anchor>
<MkA to="/about-misskey" class="_link">{{ i18n.ts.aboutMisskey }}</MkA>
</template>
</I18n>
</div>
<div class="_buttons">
<MkButton @click="close">{{ i18n.ts.gotIt }}</MkButton>
</div>
</div>
<button class="_button" :class="$style.close" @click="close"><i class="ti ti-x"></i></button>
</div>
</template>
<script lang="ts" setup>
import MkButton from '@/components/MkButton.vue';
import { host } from '@/config.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { miLocalStorage } from '@/local-storage.js';
import * as os from '@/os.js';
const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const zIndex = os.claimZIndex('low');
function close() {
miLocalStorage.setItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read', 'true');
emit('closed');
}
</script>
<style lang="scss" module>
.root {
position: fixed;
z-index: v-bind(zIndex);
bottom: var(--margin);
left: 0;
right: 0;
margin: auto;
box-sizing: border-box;
width: calc(100% - (var(--margin) * 2));
max-width: 500px;
display: flex;
}
.icon {
text-align: center;
padding-top: 25px;
width: 100px;
color: var(--accent);
}
@media (max-width: 500px) {
.icon {
width: 80px;
}
}
@media (max-width: 450px) {
.icon {
width: 70px;
}
}
.main {
padding: 25px 25px 25px 0;
flex: 1;
}
.close {
position: absolute;
top: 8px;
right: 8px;
padding: 8px;
}
.title {
font-weight: bold;
}
.text {
margin: 0.7em 0 1em 0;
}
</style>

View file

@ -12,6 +12,7 @@ type Keys =
'latestDonationInfoShownAt' | 'latestDonationInfoShownAt' |
'neverShowDonationInfo' | 'neverShowDonationInfo' |
'neverShowLocalOnlyInfo' | 'neverShowLocalOnlyInfo' |
'modifiedVersionMustProminentlyOfferInAgplV3Section13Read' |
'lastUsed' | 'lastUsed' |
'lang' | 'lang' |
'drafts' | 'drafts' |

View file

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s"> <div class="_gaps_s">
<FormLink to="https://github.com/misskey-dev/misskey" external> <FormLink to="https://github.com/misskey-dev/misskey" external>
<template #icon><i class="ti ti-code"></i></template> <template #icon><i class="ti ti-code"></i></template>
{{ i18n.ts._aboutMisskey.source }} {{ i18n.ts._aboutMisskey.source }} ({{ i18n.ts._aboutMisskey.original }})
<template #suffix>GitHub</template> <template #suffix>GitHub</template>
</FormLink> </FormLink>
<FormLink to="https://crowdin.com/project/misskey" external> <FormLink to="https://crowdin.com/project/misskey" external>
@ -46,6 +46,25 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormLink> </FormLink>
</div> </div>
</FormSection> </FormSection>
<FormSection v-if="instance.repositoryUrl !== 'https://github.com/misskey-dev/misskey'">
<div class="_gaps_s">
<MkInfo>
{{ i18n.tsx._aboutMisskey.thisIsModifiedVersion({ name: instance.name }) }}
</MkInfo>
<FormLink v-if="instance.repositoryUrl" :to="instance.repositoryUrl" external>
<template #icon><i class="ti ti-code"></i></template>
{{ i18n.ts._aboutMisskey.source }}
</FormLink>
<FormLink v-if="instance.providesTarball" :to="`/tarball/misskey-${version}.tar.gz`" external>
<template #icon><i class="ti ti-download"></i></template>
{{ i18n.ts._aboutMisskey.source }}
<template #suffix>Tarball</template>
</FormLink>
<MkInfo v-if="!instance.repositoryUrl && !instance.providesTarball" warn>
{{ i18n.ts.sourceCodeIsNotYetProvided }}
</MkInfo>
</div>
</FormSection>
<FormSection> <FormSection>
<template #label>{{ i18n.ts._aboutMisskey.projectMembers }}</template> <template #label>{{ i18n.ts._aboutMisskey.projectMembers }}</template>
<div :class="$style.contributors"> <div :class="$style.contributors">
@ -118,9 +137,10 @@ import { version } from '@/config.js';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkLink from '@/components/MkLink.vue'; import MkInfo from '@/components/MkInfo.vue';
import { physics } from '@/scripts/physics.js'; import { physics } from '@/scripts/physics.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';

View file

@ -31,7 +31,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue> </MkKeyValue>
<div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })"> <div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })">
</div> </div>
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink> <FormLink to="/about-misskey">
<template #icon><i class="ti ti-info-circle"></i></template>
{{ i18n.ts.aboutMisskey }}
</FormLink>
<FormLink v-if="instance.repositoryUrl || instance.providesTarball" :to="instance.repositoryUrl || `/tarball/misskey-${version}.tar.gz`" external>
<template #icon><i class="ti ti-code"></i></template>
{{ i18n.ts.sourceCode }}
</FormLink>
<MkInfo v-else warn>
{{ i18n.ts.sourceCodeIsNotYetProvided }}
</MkInfo>
</div> </div>
</FormSection> </FormSection>
@ -47,17 +57,33 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #value>{{ instance.maintainerEmail }}</template> <template #value>{{ instance.maintainerEmail }}</template>
</MkKeyValue> </MkKeyValue>
</FormSplit> </FormSplit>
<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>{{ i18n.ts.impressum }}</FormLink> <FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>
<template #icon><i class="ti ti-user-shield"></i></template>
{{ i18n.ts.impressum }}
</FormLink>
<div class="_gaps_s"> <div class="_gaps_s">
<MkFolder v-if="instance.serverRules.length > 0"> <MkFolder v-if="instance.serverRules.length > 0">
<template #label>{{ i18n.ts.serverRules }}</template> <template #label>
<i class="ti ti-checkup-list"></i>
{{ i18n.ts.serverRules }}
</template>
<ol class="_gaps_s" :class="$style.rules"> <ol class="_gaps_s" :class="$style.rules">
<li v-for="item, index in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li> <li v-for="(item, index) in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
</ol> </ol>
</MkFolder> </MkFolder>
<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink> <FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>
<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>{{ i18n.ts.privacyPolicy }}</FormLink> <template #icon><i class="ti ti-license"></i></template>
{{ i18n.ts.termsOfService }}
</FormLink>
<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>
<template #icon><i class="ti ti-shield-lock"></i></template>
{{ i18n.ts.privacyPolicy }}
</FormLink>
<FormLink v-if="instance.feedbackUrl" :to="instance.feedbackUrl" external>
<template #icon><i class="ti ti-message"></i></template>
{{ i18n.ts.feedback }}
</FormLink>
</div> </div>
</div> </div>
</FormSection> </FormSection>
@ -86,7 +112,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink> <FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
<FormLink :to="`/robots.txt`" external>robots.txt</FormLink> <FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
<FormLink :to="`/manifest.json`" external>manifest.json</FormLink> <FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
<FormLink :to="`/tarball/misskey-${version}.tar.gz`" external>source code</FormLink>
</div> </div>
</FormSection> </FormSection>
</div> </div>
@ -116,6 +141,7 @@ import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue'; import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkInstanceStats from '@/components/MkInstanceStats.vue'; import MkInstanceStats from '@/components/MkInstanceStats.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';

View file

@ -76,6 +76,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
</MkTextarea> </MkTextarea>
<MkInput v-model="repositoryUrl" type="url">
<template #prefix><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts.repositoryUrl }}</template>
</MkInput>
<MkInput v-model="feedbackUrl" type="url">
<template #prefix><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts.feedbackUrl }}</template>
</MkInput>
<MkTextarea v-model="manifestJsonOverride"> <MkTextarea v-model="manifestJsonOverride">
<template #label>{{ i18n.ts._serverSettings.manifestJsonOverride }}</template> <template #label>{{ i18n.ts._serverSettings.manifestJsonOverride }}</template>
</MkTextarea> </MkTextarea>
@ -120,6 +130,8 @@ const defaultDarkTheme = ref<string | null>(null);
const serverErrorImageUrl = ref<string | null>(null); const serverErrorImageUrl = ref<string | null>(null);
const infoImageUrl = ref<string | null>(null); const infoImageUrl = ref<string | null>(null);
const notFoundImageUrl = ref<string | null>(null); const notFoundImageUrl = ref<string | null>(null);
const repositoryUrl = ref<string | null>(null);
const feedbackUrl = ref<string | null>(null);
const manifestJsonOverride = ref<string>('{}'); const manifestJsonOverride = ref<string>('{}');
async function init() { async function init() {
@ -135,6 +147,8 @@ async function init() {
serverErrorImageUrl.value = meta.serverErrorImageUrl; serverErrorImageUrl.value = meta.serverErrorImageUrl;
infoImageUrl.value = meta.infoImageUrl; infoImageUrl.value = meta.infoImageUrl;
notFoundImageUrl.value = meta.notFoundImageUrl; notFoundImageUrl.value = meta.notFoundImageUrl;
repositoryUrl.value = meta.repositoryUrl;
feedbackUrl.value = meta.feedbackUrl;
manifestJsonOverride.value = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t'); manifestJsonOverride.value = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t');
} }
@ -151,6 +165,8 @@ function save() {
infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value, infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value,
notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value, notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value,
serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value, serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value,
repositoryUrl: repositoryUrl.value === '' ? null : repositoryUrl.value,
feedbackUrl: feedbackUrl.value === '' ? null : feedbackUrl.value,
manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)), manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)),
}).then(() => { }).then(() => {
fetchInstance(); fetchInstance();

View file

@ -34,6 +34,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput> </MkInput>
</FormSplit> </FormSplit>
<MkInput v-model="repositoryUrl" type="url">
<template #label>{{ i18n.ts.repositoryUrl }}</template>
<template #prefix><i class="ti ti-link"></i></template>
<template #caption>{{ i18n.ts.repositoryUrlDescription }}</template>
</MkInput>
<MkInfo v-if="!instance.providesTarball && !repositoryUrl" warn>
{{ i18n.ts.repositoryUrlOrTarballRequired }}
</MkInfo>
<MkInput v-model="impressumUrl" type="url"> <MkInput v-model="impressumUrl" type="url">
<template #label>{{ i18n.ts.impressumUrl }}</template> <template #label>{{ i18n.ts.impressumUrl }}</template>
<template #prefix><i class="ti ti-link"></i></template> <template #prefix><i class="ti ti-link"></i></template>
@ -159,7 +169,7 @@ import FormSplit from '@/components/form/split.vue';
import FormSuspense from '@/components/form/suspense.vue'; import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { fetchInstance } from '@/instance.js'; import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -169,6 +179,7 @@ const shortName = ref<string | null>(null);
const description = ref<string | null>(null); const description = ref<string | null>(null);
const maintainerName = ref<string | null>(null); const maintainerName = ref<string | null>(null);
const maintainerEmail = ref<string | null>(null); const maintainerEmail = ref<string | null>(null);
const repositoryUrl = ref<string | null>(null);
const impressumUrl = ref<string | null>(null); const impressumUrl = ref<string | null>(null);
const pinnedUsers = ref<string>(''); const pinnedUsers = ref<string>('');
const cacheRemoteFiles = ref<boolean>(false); const cacheRemoteFiles = ref<boolean>(false);
@ -191,6 +202,7 @@ async function init(): Promise<void> {
description.value = meta.description; description.value = meta.description;
maintainerName.value = meta.maintainerName; maintainerName.value = meta.maintainerName;
maintainerEmail.value = meta.maintainerEmail; maintainerEmail.value = meta.maintainerEmail;
repositoryUrl.value = meta.repositoryUrl;
impressumUrl.value = meta.impressumUrl; impressumUrl.value = meta.impressumUrl;
pinnedUsers.value = meta.pinnedUsers.join('\n'); pinnedUsers.value = meta.pinnedUsers.join('\n');
cacheRemoteFiles.value = meta.cacheRemoteFiles; cacheRemoteFiles.value = meta.cacheRemoteFiles;
@ -214,6 +226,7 @@ async function save(): void {
description: description.value, description: description.value,
maintainerName: maintainerName.value, maintainerName: maintainerName.value,
maintainerEmail: maintainerEmail.value, maintainerEmail: maintainerEmail.value,
repositoryUrl: repositoryUrl.value,
impressumUrl: impressumUrl.value, impressumUrl: impressumUrl.value,
pinnedUsers: pinnedUsers.value.split('\n'), pinnedUsers: pinnedUsers.value.split('\n'),
cacheRemoteFiles: cacheRemoteFiles.value, cacheRemoteFiles: cacheRemoteFiles.value,

View file

@ -4845,7 +4845,7 @@ export type operations = {
shortName: string | null; shortName: string | null;
objectStorageS3ForcePathStyle: boolean; objectStorageS3ForcePathStyle: boolean;
privacyPolicyUrl: string | null; privacyPolicyUrl: string | null;
repositoryUrl: string; repositoryUrl: string | null;
summalyProxy: string | null; summalyProxy: string | null;
themeColor: string | null; themeColor: string | null;
tosUrl: string | null; tosUrl: string | null;
@ -8757,8 +8757,8 @@ export type operations = {
swPublicKey?: string | null; swPublicKey?: string | null;
swPrivateKey?: string | null; swPrivateKey?: string | null;
tosUrl?: string | null; tosUrl?: string | null;
repositoryUrl?: string; repositoryUrl?: string | null;
feedbackUrl?: string; feedbackUrl?: string | null;
impressumUrl?: string | null; impressumUrl?: string | null;
privacyPolicyUrl?: string | null; privacyPolicyUrl?: string | null;
useObjectStorage?: boolean; useObjectStorage?: boolean;
@ -19450,6 +19450,7 @@ export type operations = {
maintainerName: string | null; maintainerName: string | null;
maintainerEmail: string | null; maintainerEmail: string | null;
version: string; version: string;
providesTarball: boolean;
name: string; name: string;
shortName: string | null; shortName: string | null;
/** /**
@ -19461,9 +19462,9 @@ export type operations = {
langs: string[]; langs: string[];
tosUrl: string | null; tosUrl: string | null;
/** @default https://github.com/misskey-dev/misskey */ /** @default https://github.com/misskey-dev/misskey */
repositoryUrl: string; repositoryUrl: string | null;
/** @default https://github.com/misskey-dev/misskey/issues/new */ /** @default https://github.com/misskey-dev/misskey/issues/new */
feedbackUrl: string; feedbackUrl: string | null;
defaultDarkTheme: string | null; defaultDarkTheme: string | null;
defaultLightTheme: string | null; defaultLightTheme: string | null;
disableRegistration: boolean; disableRegistration: boolean;

View file

@ -5,7 +5,9 @@
import * as fs from 'node:fs/promises'; import * as fs from 'node:fs/promises';
import * as path from 'node:path'; import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import cssnano from 'cssnano'; import cssnano from 'cssnano';
import * as yaml from 'js-yaml';
import postcss from 'postcss'; import postcss from 'postcss';
import * as terser from 'terser'; import * as terser from 'terser';
@ -14,8 +16,19 @@ import generateDTS from '../locales/generateDTS.js';
import meta from '../package.json' assert { type: "json" }; import meta from '../package.json' assert { type: "json" };
import buildTarball from './tarball.mjs'; import buildTarball from './tarball.mjs';
const configDir = fileURLToPath(new URL('../.config', import.meta.url));
const configPath = process.env.MISSKEY_CONFIG_YML
? path.resolve(configDir, process.env.MISSKEY_CONFIG_YML)
: process.env.NODE_ENV === 'test'
? path.resolve(configDir, 'test.yml')
: path.resolve(configDir, 'default.yml');
let locales = buildLocales(); let locales = buildLocales();
async function loadConfig() {
return fs.readFile(configPath, 'utf-8').then(data => yaml.load(data)).catch(() => null);
}
async function copyFrontendFonts() { async function copyFrontendFonts() {
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true }); await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
} }
@ -78,7 +91,7 @@ async function build() {
copyBackendViews(), copyBackendViews(),
buildBackendScript(), buildBackendScript(),
buildBackendStyle(), buildBackendStyle(),
buildTarball(), loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
]); ]);
} }