Resolve #4928
This commit is contained in:
		
							parent
							
								
									3d8bbedf1b
								
							
						
					
					
						commit
						3f5b96bf62
					
				
					 12 changed files with 370 additions and 160 deletions
				
			
		|  | @ -6,8 +6,6 @@ mongodb: | |||
|   db: misskey | ||||
|   user: syuilo | ||||
|   pass: '' | ||||
| drive: | ||||
|   storage: 'db' | ||||
| redis: | ||||
|   host: localhost | ||||
|   port: 6379 | ||||
|  |  | |||
|  | @ -6,8 +6,6 @@ mongodb: | |||
|   db: test-misskey | ||||
|   user: admin | ||||
|   pass: '' | ||||
| drive: | ||||
|   storage: 'db' | ||||
| # __REDIS__ | ||||
| redis: | ||||
|   host: localhost | ||||
|  |  | |||
|  | @ -78,61 +78,6 @@ redis: | |||
| #  port: 9200 | ||||
| #  pass: null | ||||
| 
 | ||||
| #   ┌────────────────────────────────────┐ | ||||
| #───┘ File storage (Drive) configuration └────────────────────── | ||||
| 
 | ||||
| drive: | ||||
|   storage: 'fs' | ||||
| 
 | ||||
| # OR | ||||
| 
 | ||||
| #drive: | ||||
| #  storage: 'minio' | ||||
| #  bucket: | ||||
| #  prefix: | ||||
| #  config: | ||||
| #    endPoint: | ||||
| #    port: | ||||
| #    useSSL: | ||||
| #    accessKey: | ||||
| #    secretKey: | ||||
| 
 | ||||
| # S3/GCS example | ||||
| # | ||||
| # * Replace <endpoint> to | ||||
| #     S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region | ||||
| #     GCS: use 'storage.googleapis.com' | ||||
| # | ||||
| # * Replace <region> to | ||||
| #     S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region | ||||
| #     GCS: not needed (just delete the region line) | ||||
| # | ||||
| #drive: | ||||
| #  storage: 'minio' | ||||
| #  bucket: bucket-name | ||||
| #  prefix: files | ||||
| #  baseUrl: https://bucket-name.<endpoint> | ||||
| #  config: | ||||
| #    endPoint: <endpoint> | ||||
| #    region: <region> | ||||
| #    useSSL: true | ||||
| #    accessKey: XXX | ||||
| #    secretKey: YYY | ||||
| 
 | ||||
| # S3/GCS example (with CDN, custom domain) | ||||
| # | ||||
| #drive: | ||||
| #  storage: 'minio' | ||||
| #  bucket: drive.example.com | ||||
| #  prefix: files | ||||
| #  baseUrl: https://drive.example.com | ||||
| #  config: | ||||
| #    endPoint: <endpoint> | ||||
| #    region: <region> | ||||
| #    useSSL: true | ||||
| #    accessKey: XXX | ||||
| #    secretKey: YYY | ||||
| 
 | ||||
| #   ┌───────────────┐ | ||||
| #───┘ ID generation └─────────────────────────────────────────── | ||||
| 
 | ||||
|  |  | |||
|  | @ -1232,6 +1232,19 @@ admin/views/instance.vue: | |||
|   advanced-config: "その他の設定" | ||||
|   note-and-tl: "投稿とタイムライン" | ||||
|   drive-config: "ドライブの設定" | ||||
|   use-object-storage: "オブジェクトストレージを使用する" | ||||
|   object-storage-base-url: "URL" | ||||
|   object-storage-bucket: "バケット名" | ||||
|   object-storage-prefix: "プレフィックス" | ||||
|   object-storage-endpoint: "エンドポイント" | ||||
|   object-storage-region: "リージョン" | ||||
|   object-storage-port: "ポート" | ||||
|   object-storage-access-key: "アクセスキー" | ||||
|   object-storage-secret-key: "シークレットキー" | ||||
|   object-storage-use-ssl: "SSLを使用" | ||||
|   object-storage-s3-info: "Amazon S3をオブジェクトストレージとして使用する場合の「エンドポイント」と「リージョン」の設定については{0}をご確認ください。" | ||||
|   object-storage-s3-info-here: "こちら" | ||||
|   object-storage-gcs-info: "Google Cloud Storageをオブジェクトストレージとして使用する場合、「エンドポイント」は storage.googleapis.com に設定し、「リージョン」は空欄にします。" | ||||
|   cache-remote-files: "リモートのファイルをキャッシュする" | ||||
|   cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。" | ||||
|   local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量" | ||||
|  |  | |||
							
								
								
									
										31
									
								
								migration/1557932705754-ObjectStorageSetting.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								migration/1557932705754-ObjectStorageSetting.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
| 
 | ||||
| export class ObjectStorageSetting1557932705754 implements MigrationInterface { | ||||
| 
 | ||||
|     public async up(queryRunner: QueryRunner): Promise<any> { | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "useObjectStorage" boolean NOT NULL DEFAULT false`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageBucket" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStoragePrefix" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageBaseUrl" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageEndpoint" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageRegion" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageAccessKey" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageSecretKey" character varying(512)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStoragePort" integer`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageUseSSL" boolean NOT NULL DEFAULT true`); | ||||
|     } | ||||
| 
 | ||||
|     public async down(queryRunner: QueryRunner): Promise<any> { | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageUseSSL"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStoragePort"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageSecretKey"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageAccessKey"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRegion"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageEndpoint"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageBaseUrl"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStoragePrefix"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageBucket"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useObjectStorage"`); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -53,6 +53,32 @@ | |||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa icon="cloud"/> {{ $t('drive-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-switch v-model="useObjectStorage">{{ $t('use-object-storage') }}</ui-switch> | ||||
| 			<template v-if="useObjectStorage"> | ||||
| 				<ui-info> | ||||
| 					<i18n path="object-storage-s3-info"> | ||||
| 						<a href="https://docs.aws.amazon.com/general/latest/gr/rande.html" target="_blank">{{ $t('object-storage-s3-info-here') }}</a> | ||||
| 					</i18n> | ||||
| 				</ui-info> | ||||
| 				<ui-info>{{ $t('object-storage-gcs-info') }}</ui-info> | ||||
| 				<ui-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">{{ $t('object-storage-base-url') }}</ui-input> | ||||
| 				<ui-horizon-group inputs> | ||||
| 					<ui-input v-model="objectStorageBucket" :disabled="!useObjectStorage">{{ $t('object-storage-bucket') }}</ui-input> | ||||
| 					<ui-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">{{ $t('object-storage-prefix') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">{{ $t('object-storage-endpoint') }}</ui-input> | ||||
| 				<ui-horizon-group inputs> | ||||
| 					<ui-input v-model="objectStorageRegion" :disabled="!useObjectStorage">{{ $t('object-storage-region') }}</ui-input> | ||||
| 					<ui-input v-model="objectStoragePort" type="number" :disabled="!useObjectStorage">{{ $t('object-storage-port') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-horizon-group inputs> | ||||
| 					<ui-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa icon="key"/></template>{{ $t('object-storage-access-key') }}</ui-input> | ||||
| 					<ui-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa icon="key"/></template>{{ $t('object-storage-secret-key') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('object-storage-use-ssl') }}</ui-switch> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-switch v-model="cacheRemoteFiles">{{ $t('cache-remote-files') }}<template #desc>{{ $t('cache-remote-files-desc') }}</template></ui-switch> | ||||
| 		</section> | ||||
|  | @ -65,69 +91,6 @@ | |||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="farEnvelope"/> {{ $t('email-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-switch v-model="enableEmail">{{ $t('enable-email') }}<template #desc>{{ $t('email-config-info') }}</template></ui-switch> | ||||
| 			<ui-input v-model="email" type="email" :disabled="!enableEmail">{{ $t('email') }}</ui-input> | ||||
| 			<ui-horizon-group inputs> | ||||
| 				<ui-input v-model="smtpHost" :disabled="!enableEmail">{{ $t('smtp-host') }}</ui-input> | ||||
| 				<ui-input v-model="smtpPort" type="number" :disabled="!enableEmail">{{ $t('smtp-port') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-switch v-model="smtpAuth">{{ $t('smtp-auth') }}</ui-switch> | ||||
| 			<ui-horizon-group inputs> | ||||
| 				<ui-input v-model="smtpUser" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-user') }}</ui-input> | ||||
| 				<ui-input v-model="smtpPass" type="password" :with-password-toggle="true" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-pass') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-secure') }}<template #desc>{{ $t('smtp-secure-info') }}</template></ui-switch> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faGhost"/> {{ $t('proxy-account-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-info>{{ $t('proxy-account-info') }}</ui-info> | ||||
| 			<ui-input v-model="proxyAccount"><template #prefix>@</template>{{ $t('proxy-account-username') }}<template #desc>{{ $t('proxy-account-username-desc') }}</template></ui-input> | ||||
| 			<ui-info warn>{{ $t('proxy-account-warn') }}</ui-info> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faBolt"/> {{ $t('serviceworker-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-switch v-model="enableServiceWorker">{{ $t('enable-serviceworker') }}<template #desc>{{ $t('serviceworker-info') }}</template></ui-switch> | ||||
| 			<ui-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></ui-info> | ||||
| 			<ui-horizon-group inputs class="fit-bottom"> | ||||
| 				<ui-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-publickey') }}</ui-input> | ||||
| 				<ui-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-privatekey') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faShieldAlt"/> {{ $t('recaptcha-config') }}</template> | ||||
| 		<section class="fit-bottom"> | ||||
| 			<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch> | ||||
| 			<ui-info>{{ $t('recaptcha-info') }}</ui-info> | ||||
| 			<ui-horizon-group inputs> | ||||
| 				<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-site-key') }}</ui-input> | ||||
| 				<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-secret-key') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faThumbtack"/> {{ $t('pinned-users') }}</template> | ||||
| 		<section class="fit-top"> | ||||
|  | @ -138,34 +101,109 @@ | |||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faGhost"/> {{ $t('proxy-account-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-info>{{ $t('proxy-account-info') }}</ui-info> | ||||
| 			<ui-input v-model="proxyAccount"><template #prefix>@</template>{{ $t('proxy-account-username') }}<template #desc>{{ $t('proxy-account-username-desc') }}</template></ui-input> | ||||
| 			<ui-info warn>{{ $t('proxy-account-warn') }}</ui-info> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="farEnvelope"/> {{ $t('email-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-switch v-model="enableEmail">{{ $t('enable-email') }}<template #desc>{{ $t('email-config-info') }}</template></ui-switch> | ||||
| 			<template v-if="enableEmail"> | ||||
| 				<ui-input v-model="email" type="email" :disabled="!enableEmail">{{ $t('email') }}</ui-input> | ||||
| 				<ui-horizon-group inputs> | ||||
| 					<ui-input v-model="smtpHost" :disabled="!enableEmail">{{ $t('smtp-host') }}</ui-input> | ||||
| 					<ui-input v-model="smtpPort" type="number" :disabled="!enableEmail">{{ $t('smtp-port') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-switch v-model="smtpAuth">{{ $t('smtp-auth') }}</ui-switch> | ||||
| 				<ui-horizon-group inputs> | ||||
| 					<ui-input v-model="smtpUser" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-user') }}</ui-input> | ||||
| 					<ui-input v-model="smtpPass" type="password" :with-password-toggle="true" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-pass') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-secure') }}<template #desc>{{ $t('smtp-secure-info') }}</template></ui-switch> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faBolt"/> {{ $t('serviceworker-config') }}</template> | ||||
| 		<section> | ||||
| 			<ui-switch v-model="enableServiceWorker">{{ $t('enable-serviceworker') }}<template #desc>{{ $t('serviceworker-info') }}</template></ui-switch> | ||||
| 			<template v-if="enableServiceWorker"> | ||||
| 				<ui-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></ui-info> | ||||
| 				<ui-horizon-group inputs class="fit-bottom"> | ||||
| 					<ui-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-publickey') }}</ui-input> | ||||
| 					<ui-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-privatekey') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faShieldAlt"/> {{ $t('recaptcha-config') }}</template> | ||||
| 		<section :class="enableRecaptcha ? 'fit-bottom' : ''"> | ||||
| 			<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch> | ||||
| 			<template v-if="enableRecaptcha"> | ||||
| 				<ui-info>{{ $t('recaptcha-info') }}</ui-info> | ||||
| 				<ui-horizon-group inputs> | ||||
| 					<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-site-key') }}</ui-input> | ||||
| 					<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-secret-key') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| 
 | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faShieldAlt"/> {{ $t('external-service-integration-config') }}</template> | ||||
| 		<section> | ||||
| 			<header><fa :icon="['fab', 'twitter']"/> {{ $t('twitter-integration-config') }}</header> | ||||
| 			<ui-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</ui-switch> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-key') }}</ui-input> | ||||
| 				<ui-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-secret') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</ui-info> | ||||
| 			<template v-if="enableTwitterIntegration"> | ||||
| 				<ui-horizon-group> | ||||
| 					<ui-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-key') }}</ui-input> | ||||
| 					<ui-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-secret') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</ui-info> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<header><fa :icon="['fab', 'github']"/> {{ $t('github-integration-config') }}</header> | ||||
| 			<ui-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</ui-switch> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-id') }}</ui-input> | ||||
| 				<ui-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-secret') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</ui-info> | ||||
| 			<template v-if="enableGithubIntegration"> | ||||
| 				<ui-horizon-group> | ||||
| 					<ui-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-id') }}</ui-input> | ||||
| 					<ui-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-secret') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</ui-info> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<header><fa :icon="['fab', 'discord']"/> {{ $t('discord-integration-config') }}</header> | ||||
| 			<ui-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</ui-switch> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-id') }}</ui-input> | ||||
| 				<ui-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-secret') }}</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</ui-info> | ||||
| 			<template v-if="enableDiscordIntegration"> | ||||
| 				<ui-horizon-group> | ||||
| 					<ui-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-id') }}</ui-input> | ||||
| 					<ui-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-secret') }}</ui-input> | ||||
| 				</ui-horizon-group> | ||||
| 				<ui-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</ui-info> | ||||
| 			</template> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||
|  | @ -261,6 +299,16 @@ export default Vue.extend({ | |||
| 			swPrivateKey: null, | ||||
| 			pinnedUsers: '', | ||||
| 			hiddenTags: '', | ||||
| 			useObjectStorage: false, | ||||
| 			objectStorageBaseUrl: null, | ||||
| 			objectStorageBucket: null, | ||||
| 			objectStoragePrefix: null, | ||||
| 			objectStorageEndpoint: null, | ||||
| 			objectStorageRegion: null, | ||||
| 			objectStoragePort: null, | ||||
| 			objectStorageAccessKey: null, | ||||
| 			objectStorageSecretKey: null, | ||||
| 			objectStorageUseSSL: false, | ||||
| 			faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt, faThumbtack, faPencilAlt, faSave, faHashtag | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -315,6 +363,16 @@ export default Vue.extend({ | |||
| 			this.swPrivateKey = meta.swPrivateKey; | ||||
| 			this.pinnedUsers = meta.pinnedUsers.join('\n'); | ||||
| 			this.hiddenTags = meta.hiddenTags.join('\n'); | ||||
| 			this.useObjectStorage = meta.useObjectStorage; | ||||
| 			this.objectStorageBaseUrl = meta.objectStorageBaseUrl; | ||||
| 			this.objectStorageBucket = meta.objectStorageBucket; | ||||
| 			this.objectStoragePrefix = meta.objectStoragePrefix; | ||||
| 			this.objectStorageEndpoint = meta.objectStorageEndpoint; | ||||
| 			this.objectStorageRegion = meta.objectStorageRegion; | ||||
| 			this.objectStoragePort = meta.objectStoragePort; | ||||
| 			this.objectStorageAccessKey = meta.objectStorageAccessKey; | ||||
| 			this.objectStorageSecretKey = meta.objectStorageSecretKey; | ||||
| 			this.objectStorageUseSSL = meta.objectStorageUseSSL; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -382,6 +440,16 @@ export default Vue.extend({ | |||
| 				swPrivateKey: this.swPrivateKey, | ||||
| 				pinnedUsers: this.pinnedUsers.split('\n'), | ||||
| 				hiddenTags: this.hiddenTags.split('\n'), | ||||
| 				useObjectStorage: this.useObjectStorage, | ||||
| 				objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null, | ||||
| 				objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null, | ||||
| 				objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null, | ||||
| 				objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null, | ||||
| 				objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null, | ||||
| 				objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null, | ||||
| 				objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null, | ||||
| 				objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null, | ||||
| 				objectStorageUseSSL: this.objectStorageUseSSL, | ||||
| 			}).then(() => { | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
|  |  | |||
|  | @ -27,13 +27,6 @@ export type Source = { | |||
| 		port: number; | ||||
| 		pass: string; | ||||
| 	}; | ||||
| 	drive?: { | ||||
| 		storage: string; | ||||
| 		bucket?: string; | ||||
| 		prefix?: string; | ||||
| 		baseUrl?: string; | ||||
| 		config?: any; | ||||
| 	}; | ||||
| 
 | ||||
| 	autoAdmin?: boolean; | ||||
| 
 | ||||
|  |  | |||
|  | @ -288,4 +288,61 @@ export class Meta { | |||
| 		nullable: true | ||||
| 	}) | ||||
| 	public feedbackUrl: string | null; | ||||
| 
 | ||||
| 	@Column('boolean', { | ||||
| 		default: false, | ||||
| 	}) | ||||
| 	public useObjectStorage: boolean; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStorageBucket: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStoragePrefix: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStorageBaseUrl: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStorageEndpoint: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStorageRegion: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStorageAccessKey: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStorageSecretKey: string | null; | ||||
| 
 | ||||
| 	@Column('integer', { | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public objectStoragePort: number | null; | ||||
| 
 | ||||
| 	@Column('boolean', { | ||||
| 		default: true, | ||||
| 	}) | ||||
| 	public objectStorageUseSSL: boolean; | ||||
| } | ||||
|  |  | |||
|  | @ -357,7 +357,47 @@ export const meta = { | |||
| 			desc: { | ||||
| 				'ja-JP': 'フィードバックのURL' | ||||
| 			} | ||||
| 		} | ||||
| 		}, | ||||
| 
 | ||||
| 		useObjectStorage: { | ||||
| 			validator: $.optional.bool | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageBaseUrl: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageBucket: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStoragePrefix: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageEndpoint: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageRegion: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStoragePort: { | ||||
| 			validator: $.optional.nullable.num | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageAccessKey: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageSecretKey: { | ||||
| 			validator: $.optional.nullable.str | ||||
| 		}, | ||||
| 
 | ||||
| 		objectStorageUseSSL: { | ||||
| 			validator: $.optional.bool | ||||
| 		}, | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
|  | @ -560,6 +600,46 @@ export default define(meta, async (ps) => { | |||
| 		set.feedbackUrl = ps.feedbackUrl; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.useObjectStorage !== undefined) { | ||||
| 		set.useObjectStorage = ps.useObjectStorage; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageBaseUrl !== undefined) { | ||||
| 		set.objectStorageBaseUrl = ps.objectStorageBaseUrl; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageBucket !== undefined) { | ||||
| 		set.objectStorageBucket = ps.objectStorageBucket; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStoragePrefix !== undefined) { | ||||
| 		set.objectStoragePrefix = ps.objectStoragePrefix; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageEndpoint !== undefined) { | ||||
| 		set.objectStorageEndpoint = ps.objectStorageEndpoint; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageRegion !== undefined) { | ||||
| 		set.objectStorageRegion = ps.objectStorageRegion; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStoragePort !== undefined) { | ||||
| 		set.objectStoragePort = ps.objectStoragePort; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageAccessKey !== undefined) { | ||||
| 		set.objectStorageAccessKey = ps.objectStorageAccessKey; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageSecretKey !== undefined) { | ||||
| 		set.objectStorageSecretKey = ps.objectStorageSecretKey; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.objectStorageUseSSL !== undefined) { | ||||
| 		set.objectStorageUseSSL = ps.objectStorageUseSSL; | ||||
| 	} | ||||
| 
 | ||||
| 	await getConnection().transaction(async transactionalEntityManager => { | ||||
| 		const meta = await transactionalEntityManager.findOne(Meta, { | ||||
| 			order: { | ||||
|  |  | |||
|  | @ -153,7 +153,7 @@ export default define(meta, async (ps, me) => { | |||
| 			globalTimeLine: !instance.disableGlobalTimeline, | ||||
| 			elasticsearch: config.elasticsearch ? true : false, | ||||
| 			recaptcha: instance.enableRecaptcha, | ||||
| 			objectStorage: config.drive && config.drive.storage === 'minio', | ||||
| 			objectStorage: instance.useObjectStorage, | ||||
| 			twitter: instance.enableTwitterIntegration, | ||||
| 			github: instance.enableGithubIntegration, | ||||
| 			discord: instance.enableDiscordIntegration, | ||||
|  | @ -182,6 +182,16 @@ export default define(meta, async (ps, me) => { | |||
| 		response.smtpUser = instance.smtpUser; | ||||
| 		response.smtpPass = instance.smtpPass; | ||||
| 		response.swPrivateKey = instance.swPrivateKey; | ||||
| 		response.useObjectStorage = instance.useObjectStorage; | ||||
| 		response.objectStorageBaseUrl = instance.objectStorageBaseUrl; | ||||
| 		response.objectStorageBucket = instance.objectStorageBucket; | ||||
| 		response.objectStoragePrefix = instance.objectStoragePrefix; | ||||
| 		response.objectStorageEndpoint = instance.objectStorageEndpoint; | ||||
| 		response.objectStorageRegion = instance.objectStorageRegion; | ||||
| 		response.objectStoragePort = instance.objectStoragePort; | ||||
| 		response.objectStorageAccessKey = instance.objectStorageAccessKey; | ||||
| 		response.objectStorageSecretKey = instance.objectStorageSecretKey; | ||||
| 		response.objectStorageUseSSL = instance.objectStorageUseSSL; | ||||
| 	} | ||||
| 
 | ||||
| 	return response; | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import * as sharp from 'sharp'; | |||
| 
 | ||||
| import { publishMainStream, publishDriveStream } from '../stream'; | ||||
| import delFile from './delete-file'; | ||||
| import config from '../../config'; | ||||
| import { fetchMeta } from '../../misc/fetch-meta'; | ||||
| import { GenerateVideoThumbnail } from './generate-video-thumbnail'; | ||||
| import { driveLogger } from './logger'; | ||||
|  | @ -37,7 +36,9 @@ async function save(file: DriveFile, path: string, name: string, type: string, h | |||
| 	// thunbnail, webpublic を必要なら生成
 | ||||
| 	const alts = await generateAlts(path, type, !file.uri); | ||||
| 
 | ||||
| 	if (config.drive && config.drive.storage == 'minio') { | ||||
| 	const meta = await fetchMeta(); | ||||
| 
 | ||||
| 	if (meta.useObjectStorage) { | ||||
| 		//#region ObjectStorage params
 | ||||
| 		let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) || ['']); | ||||
| 
 | ||||
|  | @ -47,11 +48,11 @@ async function save(file: DriveFile, path: string, name: string, type: string, h | |||
| 			if (type === 'image/webp') ext = '.webp'; | ||||
| 		} | ||||
| 
 | ||||
| 		const baseUrl = config.drive.baseUrl | ||||
| 			|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }`; | ||||
| 		const baseUrl = meta.objectStorageBaseUrl | ||||
| 			|| `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; | ||||
| 
 | ||||
| 		// for original
 | ||||
| 		const key = `${config.drive.prefix}/${uuid.v4()}${ext}`; | ||||
| 		const key = `${meta.objectStoragePrefix}/${uuid.v4()}${ext}`; | ||||
| 		const url = `${ baseUrl }/${ key }`; | ||||
| 
 | ||||
| 		// for alts
 | ||||
|  | @ -68,7 +69,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h | |||
| 		]; | ||||
| 
 | ||||
| 		if (alts.webpublic) { | ||||
| 			webpublicKey = `${config.drive.prefix}/${uuid.v4()}.${alts.webpublic.ext}`; | ||||
| 			webpublicKey = `${meta.objectStoragePrefix}/${uuid.v4()}.${alts.webpublic.ext}`; | ||||
| 			webpublicUrl = `${ baseUrl }/${ webpublicKey }`; | ||||
| 
 | ||||
| 			logger.info(`uploading webpublic: ${webpublicKey}`); | ||||
|  | @ -76,7 +77,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h | |||
| 		} | ||||
| 
 | ||||
| 		if (alts.thumbnail) { | ||||
| 			thumbnailKey = `${config.drive.prefix}/${uuid.v4()}.${alts.thumbnail.ext}`; | ||||
| 			thumbnailKey = `${meta.objectStoragePrefix}/${uuid.v4()}.${alts.thumbnail.ext}`; | ||||
| 			thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; | ||||
| 
 | ||||
| 			logger.info(`uploading thumbnail: ${thumbnailKey}`); | ||||
|  | @ -194,7 +195,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool | |||
|  * Upload to ObjectStorage | ||||
|  */ | ||||
| async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { | ||||
| 	const minio = new Minio.Client(config.drive!.config); | ||||
| 	const meta = await fetchMeta(); | ||||
| 
 | ||||
| 	const minio = new Minio.Client({ | ||||
| 		endPoint: meta.objectStorageEndpoint!, | ||||
| 		port: meta.objectStoragePort ? meta.objectStoragePort : undefined, | ||||
| 		useSSL: meta.objectStorageUseSSL, | ||||
| 		accessKey: meta.objectStorageAccessKey!, | ||||
| 		secretKey: meta.objectStorageSecretKey!, | ||||
| 	}); | ||||
| 
 | ||||
| 	const metadata = { | ||||
| 		'Content-Type': type, | ||||
|  | @ -203,7 +212,7 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, | |||
| 
 | ||||
| 	if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename); | ||||
| 
 | ||||
| 	await minio.putObject(config.drive!.bucket!, key, stream, undefined, metadata); | ||||
| 	await minio.putObject(meta.objectStorageBucket!, key, stream, undefined, metadata); | ||||
| } | ||||
| 
 | ||||
| async function deleteOldFile(user: IRemoteUser) { | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import * as Minio from 'minio'; | ||||
| import config from '../../config'; | ||||
| import { DriveFile } from '../../models/entities/drive-file'; | ||||
| import { InternalStorage } from './internal-storage'; | ||||
| import { DriveFiles, Instances, Notes } from '../../models'; | ||||
| import { driveChart, perUserDriveChart, instanceChart } from '../chart'; | ||||
| import { fetchMeta } from '../../misc/fetch-meta'; | ||||
| 
 | ||||
| export default async function(file: DriveFile, isExpired = false) { | ||||
| 	if (file.storedInternal) { | ||||
|  | @ -17,16 +17,24 @@ export default async function(file: DriveFile, isExpired = false) { | |||
| 			InternalStorage.del(file.webpublicAccessKey!); | ||||
| 		} | ||||
| 	} else if (!file.isLink) { | ||||
| 		const minio = new Minio.Client(config.drive!.config); | ||||
| 		const meta = await fetchMeta(); | ||||
| 
 | ||||
| 		await minio.removeObject(config.drive!.bucket!, file.accessKey!); | ||||
| 		const minio = new Minio.Client({ | ||||
| 			endPoint: meta.objectStorageEndpoint!, | ||||
| 			port: meta.objectStoragePort ? meta.objectStoragePort : undefined, | ||||
| 			useSSL: meta.objectStorageUseSSL, | ||||
| 			accessKey: meta.objectStorageAccessKey!, | ||||
| 			secretKey: meta.objectStorageSecretKey!, | ||||
| 		}); | ||||
| 
 | ||||
| 		await minio.removeObject(meta.objectStorageBucket!, file.accessKey!); | ||||
| 
 | ||||
| 		if (file.thumbnailUrl) { | ||||
| 			await minio.removeObject(config.drive!.bucket!, file.thumbnailAccessKey!); | ||||
| 			await minio.removeObject(meta.objectStorageBucket!, file.thumbnailAccessKey!); | ||||
| 		} | ||||
| 
 | ||||
| 		if (file.webpublicUrl) { | ||||
| 			await minio.removeObject(config.drive!.bucket!, file.webpublicAccessKey!); | ||||
| 			await minio.removeObject(meta.objectStorageBucket!, file.webpublicAccessKey!); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue