Merge branch 'develop' of https://github.com/syuilo/misskey into develop
This commit is contained in:
		
						commit
						cf304f88d4
					
				
					 19 changed files with 195 additions and 125 deletions
				
			
		|  | @ -4,9 +4,10 @@ | |||
| 
 | ||||
| import * as fs from 'fs'; | ||||
| import { URL } from 'url'; | ||||
| import $ from 'cafy'; | ||||
| import * as yaml from 'js-yaml'; | ||||
| import { Source, Mixin } from './types'; | ||||
| import * as pkg from '../../package.json'; | ||||
| import { fromNullable } from '../prelude/maybe'; | ||||
| 
 | ||||
| /** | ||||
|  * Path of configuration directory | ||||
|  | @ -21,30 +22,148 @@ const path = process.env.NODE_ENV == 'test' | |||
| 	: `${dir}/default.yml`; | ||||
| 
 | ||||
| export default function load() { | ||||
| 	const config = yaml.safeLoad(fs.readFileSync(path, 'utf-8')) as Source; | ||||
| 	const config = yaml.safeLoad(fs.readFileSync(path, 'utf-8')); | ||||
| 
 | ||||
| 	const mixin = {} as Mixin; | ||||
| 	if (typeof config.url !== 'string') { | ||||
| 		throw 'You need to configure the URL.'; | ||||
| 	} | ||||
| 
 | ||||
| 	const url = validateUrl(config.url); | ||||
| 	config.url = normalizeUrl(config.url); | ||||
| 
 | ||||
| 	mixin.host = url.host; | ||||
| 	mixin.hostname = url.hostname; | ||||
| 	mixin.scheme = url.protocol.replace(/:$/, ''); | ||||
| 	mixin.ws_scheme = mixin.scheme.replace('http', 'ws'); | ||||
| 	mixin.ws_url = `${mixin.ws_scheme}://${mixin.host}`; | ||||
| 	mixin.api_url = `${mixin.scheme}://${mixin.host}/api`; | ||||
| 	mixin.auth_url = `${mixin.scheme}://${mixin.host}/auth`; | ||||
| 	mixin.dev_url = `${mixin.scheme}://${mixin.host}/dev`; | ||||
| 	mixin.docs_url = `${mixin.scheme}://${mixin.host}/docs`; | ||||
| 	mixin.stats_url = `${mixin.scheme}://${mixin.host}/stats`; | ||||
| 	mixin.status_url = `${mixin.scheme}://${mixin.host}/status`; | ||||
| 	mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`; | ||||
| 	mixin.user_agent = `Misskey/${pkg.version} (${config.url})`; | ||||
| 	if (typeof config.port !== 'number') { | ||||
| 		throw 'You need to configure the port.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (config.autoAdmin == null) config.autoAdmin = false; | ||||
| 	if (config.https != null) { | ||||
| 		if (typeof config.https.key !== 'string') { | ||||
| 			throw 'You need to configure the https key.'; | ||||
| 		} | ||||
| 		if (typeof config.https.cert !== 'string') { | ||||
| 			throw 'You need to configure the https cert.'; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return Object.assign(config, mixin); | ||||
| 	if (config.mongodb == null) { | ||||
| 		throw 'You need to configure the MongoDB.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof config.mongodb.host !== 'string') { | ||||
| 		throw 'You need to configure the MongoDB host.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof config.mongodb.port !== 'number') { | ||||
| 		throw 'You need to configure the MongoDB port.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof config.mongodb.db !== 'string') { | ||||
| 		throw 'You need to configure the MongoDB database name.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (config.drive == null) { | ||||
| 		throw 'You need to configure the drive.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof config.drive.storage !== 'string') { | ||||
| 		throw 'You need to configure the drive storage type.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!$.str.or(['db', 'minio']).ok(config.drive.storage)) { | ||||
| 		throw 'Unrecognized drive storage type is specified.'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (config.drive.storage === 'minio') { | ||||
| 		if (typeof config.drive.storage.bucket !== 'string') { | ||||
| 			throw 'You need to configure the minio bucket.'; | ||||
| 		} | ||||
| 
 | ||||
| 		if (typeof config.drive.storage.prefix !== 'string') { | ||||
| 			throw 'You need to configure the minio prefix.'; | ||||
| 		} | ||||
| 
 | ||||
| 		if (config.drive.storage.prefix.config == null) { | ||||
| 			throw 'You need to configure the minio.'; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (config.redis != null) { | ||||
| 		if (typeof config.redis.host !== 'string') { | ||||
| 			throw 'You need to configure the Redis host.'; | ||||
| 		} | ||||
| 
 | ||||
| 		if (typeof config.redis.port !== 'number') { | ||||
| 			throw 'You need to configure the Redis port.'; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (config.elasticsearch != null) { | ||||
| 		if (typeof config.elasticsearch.host !== 'string') { | ||||
| 			throw 'You need to configure the Elasticsearch host.'; | ||||
| 		} | ||||
| 
 | ||||
| 		if (typeof config.elasticsearch.port !== 'number') { | ||||
| 			throw 'You need to configure the Elasticsearch port.'; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	const source = { | ||||
| 		url: normalizeUrl(config.url as string), | ||||
| 		port: config.port as number, | ||||
| 		https: fromNullable(config.https).map(x => ({ | ||||
| 			key: x.key as string, | ||||
| 			cert: x.cert as string, | ||||
| 			ca: fromNullable<string>(x.ca) | ||||
| 		})), | ||||
| 		mongodb: { | ||||
| 			host: config.mongodb.host as string, | ||||
| 			port: config.mongodb.port as number, | ||||
| 			db: config.mongodb.db as string, | ||||
| 			user: fromNullable<string>(config.mongodb.user), | ||||
| 			pass: fromNullable<string>(config.mongodb.pass) | ||||
| 		}, | ||||
| 		redis: fromNullable(config.redis).map(x => ({ | ||||
| 			host: x.host as string, | ||||
| 			port: x.port as number, | ||||
| 			pass: fromNullable<string>(x.pass) | ||||
| 		})), | ||||
| 		elasticsearch: fromNullable(config.elasticsearch).map(x => ({ | ||||
| 			host: x.host as string, | ||||
| 			port: x.port as number, | ||||
| 			pass: fromNullable<string>(x.pass) | ||||
| 		})), | ||||
| 		disableHsts: typeof config.disableHsts === 'boolean' ? config.disableHsts as boolean : false, | ||||
| 		drive: { | ||||
| 			storage: config.drive.storage as string, | ||||
| 			bucket: config.drive.bucket as string, | ||||
| 			prefix: config.drive.prefix as string, | ||||
| 			baseUrl: fromNullable<string>(config.drive.baseUrl), | ||||
| 			config: config.drive.config | ||||
| 		}, | ||||
| 		autoAdmin: typeof config.autoAdmin === 'boolean' ? config.autoAdmin as boolean : false, | ||||
| 		proxy: fromNullable<string>(config.proxy), | ||||
| 		clusterLimit: typeof config.clusterLimit === 'number' ? config.clusterLimit as number : Infinity, | ||||
| 	}; | ||||
| 
 | ||||
| 	const host = url.host; | ||||
| 	const scheme = url.protocol.replace(/:$/, ''); | ||||
| 	const ws_scheme = scheme.replace('http', 'ws'); | ||||
| 
 | ||||
| 	const mixin = { | ||||
| 		host: url.host, | ||||
| 		hostname: url.hostname, | ||||
| 		scheme: scheme, | ||||
| 		ws_scheme: ws_scheme, | ||||
| 		ws_url: `${ws_scheme}://${host}`, | ||||
| 		api_url: `${scheme}://${host}/api`, | ||||
| 		auth_url: `${scheme}://${host}/auth`, | ||||
| 		dev_url: `${scheme}://${host}/dev`, | ||||
| 		docs_url: `${scheme}://${host}/docs`, | ||||
| 		stats_url: `${scheme}://${host}/stats`, | ||||
| 		status_url: `${scheme}://${host}/status`, | ||||
| 		drive_url: `${scheme}://${host}/files`, | ||||
| 		user_agent: `Misskey/${pkg.version} (${config.url})` | ||||
| 	}; | ||||
| 
 | ||||
| 	return Object.assign(source, mixin); | ||||
| } | ||||
| 
 | ||||
| function tryCreateUrl(url: string) { | ||||
|  |  | |||
|  | @ -1,64 +1,3 @@ | |||
| /** | ||||
|  * ユーザーが設定する必要のある情報 | ||||
|  */ | ||||
| export type Source = { | ||||
| 	repository_url?: string; | ||||
| 	feedback_url?: string; | ||||
| 	url: string; | ||||
| 	port: number; | ||||
| 	https?: { [x: string]: string }; | ||||
| 	disableHsts?: boolean; | ||||
| 	mongodb: { | ||||
| 		host: string; | ||||
| 		port: number; | ||||
| 		db: string; | ||||
| 		user: string; | ||||
| 		pass: string; | ||||
| 	}; | ||||
| 	redis: { | ||||
| 		host: string; | ||||
| 		port: number; | ||||
| 		pass: string; | ||||
| 	}; | ||||
| 	elasticsearch: { | ||||
| 		host: string; | ||||
| 		port: number; | ||||
| 		pass: string; | ||||
| 	}; | ||||
| 	drive?: { | ||||
| 		storage: string; | ||||
| 		bucket?: string; | ||||
| 		prefix?: string; | ||||
| 		baseUrl?: string; | ||||
| 		config?: any; | ||||
| 	}; | ||||
| import load from "./load"; | ||||
| 
 | ||||
| 	autoAdmin?: boolean; | ||||
| 
 | ||||
| 	proxy?: string; | ||||
| 
 | ||||
| 	accesslog?: string; | ||||
| 
 | ||||
| 	clusterLimit?: number; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Misskeyが自動的に(ユーザーが設定した情報から推論して)設定する情報 | ||||
|  */ | ||||
| export type Mixin = { | ||||
| 	host: string; | ||||
| 	hostname: string; | ||||
| 	scheme: string; | ||||
| 	ws_scheme: string; | ||||
| 	api_url: string; | ||||
| 	ws_url: string; | ||||
| 	auth_url: string; | ||||
| 	docs_url: string; | ||||
| 	stats_url: string; | ||||
| 	status_url: string; | ||||
| 	dev_url: string; | ||||
| 	drive_url: string; | ||||
| 	user_agent: string; | ||||
| }; | ||||
| 
 | ||||
| export type Config = Source & Mixin; | ||||
| export type Config = ReturnType<typeof load>; | ||||
|  |  | |||
|  | @ -42,9 +42,10 @@ const index = { | |||
| }; | ||||
| 
 | ||||
| // Init ElasticSearch connection
 | ||||
| const client = config.elasticsearch ? new elasticsearch.Client({ | ||||
| 	host: `${config.elasticsearch.host}:${config.elasticsearch.port}` | ||||
| }) : null; | ||||
| 
 | ||||
| const client = config.elasticsearch.map(({ host, port }) => { | ||||
| 	return new elasticsearch.Client({ host: `${host}:${port}` }); | ||||
| }).getOrElse(null); | ||||
| 
 | ||||
| if (client) { | ||||
| 	// Send a HEAD request
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import config from '../config'; | ||||
| 
 | ||||
| const u = config.mongodb.user ? encodeURIComponent(config.mongodb.user) : null; | ||||
| const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null; | ||||
| const u = config.mongodb.user.map(x => encodeURIComponent(x)).getOrElse(null); | ||||
| const p = config.mongodb.pass.map(x => encodeURIComponent(x)).getOrElse(null); | ||||
| 
 | ||||
| const uri = `mongodb://${u && p ? `${u}:${p}@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,8 @@ | |||
| import * as redis from 'redis'; | ||||
| import config from '../config'; | ||||
| 
 | ||||
| export default config.redis ? redis.createClient( | ||||
| 	config.redis.port, | ||||
| 	config.redis.host, | ||||
| 	{ | ||||
| 		auth_pass: config.redis.pass | ||||
| 	} | ||||
| ) : null; | ||||
| export default config.redis.map(({ host, port, pass }) => { | ||||
| 	return redis.createClient(port, host, { | ||||
| 		auth_pass: pass.getOrElse(null) | ||||
| 	}); | ||||
| }).getOrElse(null); | ||||
|  |  | |||
|  | @ -228,7 +228,7 @@ async function init(): Promise<Config> { | |||
| 	return config; | ||||
| } | ||||
| 
 | ||||
| async function spawnWorkers(limit: number = Infinity) { | ||||
| async function spawnWorkers(limit: number) { | ||||
| 	const workers = Math.min(limit, os.cpus().length); | ||||
| 	bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); | ||||
| 	await Promise.all([...Array(workers)].map(spawnWorker)); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| export interface Maybe<T> { | ||||
| 	isJust(): this is Just<T>; | ||||
| 	map<S>(f: (x: T) => S): Maybe<S>; | ||||
| 	getOrElse(x: T): T; | ||||
| } | ||||
| 
 | ||||
| export type Just<T> = Maybe<T> & { | ||||
|  | @ -9,6 +11,8 @@ export type Just<T> = Maybe<T> & { | |||
| export function just<T>(value: T): Just<T> { | ||||
| 	return { | ||||
| 		isJust: () => true, | ||||
| 		getOrElse: (_: T) => value, | ||||
| 		map: <S>(f: (x: T) => S) => just(f(value)), | ||||
| 		get: () => value | ||||
| 	}; | ||||
| } | ||||
|  | @ -16,5 +20,11 @@ export function just<T>(value: T): Just<T> { | |||
| export function nothing<T>(): Maybe<T> { | ||||
| 	return { | ||||
| 		isJust: () => false, | ||||
| 		getOrElse: (value: T) => value, | ||||
| 		map: <S>(_: (x: T) => S) => nothing<S>() | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function fromNullable<T>(value: T): Maybe<T> { | ||||
| 	return value == null ? nothing() : just(value); | ||||
| } | ||||
|  |  | |||
|  | @ -8,17 +8,17 @@ import handler from './processors'; | |||
| import { queueLogger } from './logger'; | ||||
| 
 | ||||
| const enableQueue = !program.disableQueue; | ||||
| const queueAvailable = config.redis != null; | ||||
| const queueAvailable = config.redis.isJust(); | ||||
| 
 | ||||
| const queue = initializeQueue(); | ||||
| 
 | ||||
| function initializeQueue() { | ||||
| 	if (queueAvailable) { | ||||
| 	return config.redis.map(({ port, host, pass }) => { | ||||
| 		return new Queue('misskey', { | ||||
| 			redis: { | ||||
| 				port: config.redis.port, | ||||
| 				host: config.redis.host, | ||||
| 				password: config.redis.pass | ||||
| 				port: port, | ||||
| 				host: host, | ||||
| 				password: pass.getOrElse(null) | ||||
| 			}, | ||||
| 
 | ||||
| 			removeOnSuccess: true, | ||||
|  | @ -27,9 +27,7 @@ function initializeQueue() { | |||
| 			sendEvents: false, | ||||
| 			storeJobs: false | ||||
| 		}); | ||||
| 	} else { | ||||
| 		return null; | ||||
| 	} | ||||
| 	}).getOrElse(null); | ||||
| } | ||||
| 
 | ||||
| export function deliver(user: ILocalUser, content: any, to: any) { | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ export default class Resolver { | |||
| 
 | ||||
| 		const object = await request({ | ||||
| 			url: value, | ||||
| 			proxy: config.proxy, | ||||
| 			proxy: config.proxy.getOrElse(null), | ||||
| 			timeout: this.timeout, | ||||
| 			headers: { | ||||
| 				'User-Agent': config.user_agent, | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { | |||
| 		description: instance.description, | ||||
| 		langs: instance.langs, | ||||
| 
 | ||||
| 		secure: config.https != null, | ||||
| 		secure: config.https.isJust(), | ||||
| 		machine: os.hostname(), | ||||
| 		os: os.platform(), | ||||
| 		node: process.version, | ||||
|  | @ -83,9 +83,9 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { | |||
| 			registration: !instance.disableRegistration, | ||||
| 			localTimeLine: !instance.disableLocalTimeline, | ||||
| 			globalTimeLine: !instance.disableGlobalTimeline, | ||||
| 			elasticsearch: config.elasticsearch ? true : false, | ||||
| 			elasticsearch: config.elasticsearch.isJust(), | ||||
| 			recaptcha: instance.enableRecaptcha, | ||||
| 			objectStorage: config.drive && config.drive.storage === 'minio', | ||||
| 			objectStorage: config.drive.storage === 'minio', | ||||
| 			twitter: instance.enableTwitterIntegration, | ||||
| 			github: instance.enableGithubIntegration, | ||||
| 			discord: instance.enableDiscordIntegration, | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { | |||
| 
 | ||||
| 		request({ | ||||
| 			url: url, | ||||
| 			proxy: config.proxy, | ||||
| 			proxy: config.proxy.getOrElse(null), | ||||
| 			timeout: timeout, | ||||
| 			json: true, | ||||
| 			followRedirect: true, | ||||
|  |  | |||
|  | @ -23,10 +23,10 @@ module.exports = (server: http.Server) => { | |||
| 
 | ||||
| 		let ev: EventEmitter; | ||||
| 
 | ||||
| 		if (config.redis) { | ||||
| 		if (config.redis.isJust()) { | ||||
| 			// Connect to Redis
 | ||||
| 			const subscriber = redis.createClient( | ||||
| 				config.redis.port, config.redis.host); | ||||
| 				config.redis.get().port, config.redis.get().host); | ||||
| 
 | ||||
| 			subscriber.subscribe('misskey'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -96,13 +96,14 @@ app.use(router.routes()); | |||
| app.use(mount(require('./web'))); | ||||
| 
 | ||||
| function createServer() { | ||||
| 	if (config.https) { | ||||
| 		const certs: any = {}; | ||||
| 		for (const k of Object.keys(config.https)) { | ||||
| 			certs[k] = fs.readFileSync(config.https[k]); | ||||
| 		} | ||||
| 		certs['allowHTTP1'] = true; | ||||
| 		return http2.createSecureServer(certs, app.callback()) as https.Server; | ||||
| 	if (config.https.isJust()) { | ||||
| 		const opts = { | ||||
| 			key: fs.readFileSync(config.https.get().key), | ||||
| 			cert: fs.readFileSync(config.https.get().cert), | ||||
| 			...config.https.get().ca.map<any>(path => ({ ca: fs.readFileSync(path) })).getOrElse({}), | ||||
| 			allowHTTP1: true | ||||
| 		}; | ||||
| 		return http2.createSecureServer(opts, app.callback()) as https.Server; | ||||
| 	} else { | ||||
| 		return http.createServer(app.callback()); | ||||
| 	} | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ async function fetch(url: string, path: string) { | |||
| 
 | ||||
| 		const req = request({ | ||||
| 			url: requestUrl, | ||||
| 			proxy: config.proxy, | ||||
| 			proxy: config.proxy.getOrElse(null), | ||||
| 			timeout: 10 * 1000, | ||||
| 			headers: { | ||||
| 				'User-Agent': config.user_agent | ||||
|  |  | |||
|  | @ -31,7 +31,9 @@ const app = new Koa(); | |||
| app.use(views(__dirname + '/views', { | ||||
| 	extension: 'pug', | ||||
| 	options: { | ||||
| 		config | ||||
| 		config: { | ||||
| 			url: config.url | ||||
| 		} | ||||
| 	} | ||||
| })); | ||||
| 
 | ||||
|  |  | |||
|  | @ -50,8 +50,9 @@ async function save(path: string, name: string, type: string, hash: string, size | |||
| 			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 = config.drive.baseUrl.getOrElse( | ||||
| 			`${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }` | ||||
| 		); | ||||
| 
 | ||||
| 		// for original
 | ||||
| 		const key = `${config.drive.prefix}/${uuid.v4()}${ext}`; | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ export default async ( | |||
| 
 | ||||
| 		const req = request({ | ||||
| 			url: requestUrl, | ||||
| 			proxy: config.proxy, | ||||
| 			proxy: config.proxy.getOrElse(null), | ||||
| 			timeout: 10 * 1000, | ||||
| 			headers: { | ||||
| 				'User-Agent': config.user_agent | ||||
|  |  | |||
|  | @ -500,7 +500,7 @@ async function insertNote(user: IUser, data: Option, tags: string[], emojis: str | |||
| } | ||||
| 
 | ||||
| function index(note: INote) { | ||||
| 	if (note.text == null || config.elasticsearch == null) return; | ||||
| 	if (note.text == null || !config.elasticsearch.isJust()) return; | ||||
| 
 | ||||
| 	es.index({ | ||||
| 		index: 'misskey', | ||||
|  |  | |||
|  | @ -37,8 +37,9 @@ async function job(file: IDriveFile): Promise<any> { | |||
| 	const thumbnailKeyDir = `${config.drive.prefix}/${uuid.v4()}`; | ||||
| 	const thumbnailKey = `${thumbnailKeyDir}/${name}.thumbnail.jpg`; | ||||
| 
 | ||||
| 	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 = config.drive.baseUrl.getOrElse( | ||||
| 		`${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }` | ||||
| 	); | ||||
| 
 | ||||
| 	const bucket = await getDriveFileBucket(); | ||||
| 	const readable = bucket.openDownloadStream(file._id); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue