Skip to content

Commit

Permalink
v12 base mediaproxy
Browse files Browse the repository at this point in the history
  • Loading branch information
atsu1125 committed Aug 9, 2024
1 parent cbd776f commit 7976395
Show file tree
Hide file tree
Showing 36 changed files with 2,339 additions and 2,968 deletions.
22 changes: 22 additions & 0 deletions .config/default.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# ┌───────────────────────┐
#───┘ Port settings └───────────────────────────────────

# Listen port
port: 3000

# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128

#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]

# Download file size limits (bytes)
#maxFileSize: 262144000
22 changes: 22 additions & 0 deletions .config/example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# ┌───────────────────────┐
#───┘ Port settings └───────────────────────────────────

# Listen port
port: 3000

# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128

#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]

# Download file size limits (bytes)
#maxFileSize: 262144000
6 changes: 3 additions & 3 deletions .github/workflows/dockerimageci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ jobs:
- run: docker image ls my-image-name
- run: docker history my-image-name
- name: Copy Configure
run: cp config-example.js config.js
run: cp .config/{example,default}.yml
- name: Start Docker container
run: |
docker run -d --net=host -v ./config.js:/misskey-media-proxy/config.js:ro --env PORT=61812 --name my-container my-image-name
docker run -d --net=host -v ./.config:/misskey-media-proxy/.config:ro --env PORT=61812 --name my-container my-image-name
sleep 5
- name: Check Docker container logs
run: docker logs my-container
- name: Test HTTP request to container
run: |
response=$(curl --retry 10 -X GET -s -o /dev/null -w "%{http_code}" "http://localhost:61812/?url=https://raw.githubusercontent.com/atsu1125/media-proxy/master/assets/dummy.png")
response=$(curl --retry 10 -X GET -s -o /dev/null -w "%{http_code}" "http://localhost:61812/?url=https://raw.githubusercontent.com/atsu1125/media-proxy-v12/v12/assets/dummy.png")
if [ $response -eq 200 ]; then
echo "HTTP request successful. Status code: $response"
docker logs my-container
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage

# config
/.config/*
!/.config/example.yml

# nyc test coverage
.nyc_output

Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM node:18.20.4-bookworm-slim

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libjemalloc-dev libjemalloc2 \
libjemalloc-dev libjemalloc2 tini \
&& ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists
Expand All @@ -14,7 +14,7 @@ WORKDIR /misskey-media-proxy

COPY . ./

RUN corepack enable pnpm
RUN pnpm install --frozen-lockfile
RUN yarn install

CMD ["pnpm", "run", "start"]
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["npm", "run", "start"]
82 changes: 19 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,41 @@

[→ メディアプロキシの仕様](./SPECIFICATION.md)

Misskeyの/proxyが単体で動作します(Misskeyのコードがほぼそのまま移植されています)。

**Fastifyプラグインとして動作する気がします。**
`pnpm start`[fastify-cli](https://github.com/fastify/fastify-cli)が動作します。

一応AWS Lambdaで動かす実装を用意しましたが、全くおすすめしません。
https://github.com/tamaina/media-proxy-lambda

## Fastifyプラグインとして動作させる
### npm install
Misskey v12の/proxyが単体で動作します(Misskey v12 Latestのコードがほぼそのまま移植されています)。
Misskey v13のFastifyベースのメディアプロキシコードをv12のKoaベースのコードで書き直してます。

## サーバーセットアップ(Dockerの場合)
```
npm install git+https://github.com/misskey-dev/media-proxy.git
git clone https://github.com/atsu1125/media-proxy-v12.git
cd media-proxy-v12
cp compose-example.yml compose.yml
cp .config/{example,default}.yml
docker compose up -d
```

### Fastifyプラグインを書く
```
import MediaProxy from 'misskey-media-proxy';
// ......
fastify.register(MediaProxy);
```

オプションを指定できます。オプションの内容はindex.tsのMediaProxyOptionsに指定してあります。

## サーバーのセットアップ方法
## サーバーのセットアップ方法(Systemdの場合)
まずはgit cloneしてcdしてください。

```
git clone https://github.com/misskey-dev/media-proxy.git
cd media-proxy
git clone https://github.com/atsu1125/media-proxy-v12.git
cd media-proxy-v12
```

### pnpm install
### yarn install
```
NODE_ENV=production pnpm install
NODE_ENV=production yarn install
```

### config.jsを追加

次のような内容で、設定ファイルconfig.jsをルートに作成してください。

```js
import { readFileSync } from 'node:fs';

const repo = JSON.parse(readFileSync('./package.json', 'utf8'));

export default {
// UA
userAgent: `MisskeyMediaProxy/${repo.version}`,

// プライベートネットワークでも許可するIP CIDR(default.ymlと同じ)
allowedPrivateNetworks: [],

// ダウンロードするファイルの最大サイズ (bytes)
maxSize: 262144000,

// CORS
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',

// CSP
'Content-Security-Policy': `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`,

// フォワードプロキシ
// proxy: 'http://127.0.0.1:3128'
}
### configを編集
```
cp .config/{example,default}.yml
```
で設定ファイルをコピーして`.config/default.yml`を編集してください

### サーバーを立てる
適当にサーバーを公開してください。
(ここではmediaproxy.example.comで公開するものとします。)

メモ書き程度にsystemdでの開始方法を残しますが、もしかしたらAWS Lambdaとかで動かしたほうが楽かもしれません。
(サーバーレスだとsharp.jsが動かない可能性が高いため、そこはなんとかしてください)

systemdサービスのファイルを作成…

/etc/systemd/system/misskey-proxy.service
Expand All @@ -93,9 +51,8 @@ Description=Misskey Media Proxy
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/media-proxy
WorkingDirectory=/home/misskey/media-proxy-v12
Environment="NODE_ENV=production"
Environment="PORT=3000"
TimeoutSec=60
StandardOutput=journal
StandardError=journal
Expand All @@ -116,9 +73,8 @@ sudo systemctl start misskey-proxy

### Misskeyのdefault.ymlに追記

mediaProxyの指定をdefault.ymlに追記し、Misskeyを再起動してください。
mediaProxyの指定をMisskeyのdefault.ymlに追記し、Misskeyを再起動してください。

```yml
mediaProxy: https://mediaproxy.example.com
```
2 changes: 2 additions & 0 deletions built/config/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare const _default: import("./types.js").Source & import("./types.js").Mixin;
export default _default;
2 changes: 2 additions & 0 deletions built/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import load from './load.js';
export default load();
5 changes: 5 additions & 0 deletions built/config/load.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Config loader
*/
import { Source, Mixin } from './types.js';
export default function load(): Source & Mixin;
28 changes: 28 additions & 0 deletions built/config/load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Config loader
*/
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import * as yaml from 'js-yaml';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
/**
* Path of configuration directory
*/
const dir = `${_dirname}/../../.config`;
/**
* Path of configuration file
*/
const path = process.env.NODE_ENV === 'test'
? `${dir}/test.yml`
: `${dir}/default.yml`;
const verDir = `${_dirname}/../..`;
const verPath = `${verDir}/package.json`;
export default function load() {
const repo = JSON.parse(fs.readFileSync(verPath, 'utf8'));
const config = yaml.load(fs.readFileSync(path, 'utf-8'));
const mixin = {};
mixin.userAgent = `MisskeyMediaProxy/${repo.version}`;
return Object.assign(config, mixin);
}
16 changes: 16 additions & 0 deletions built/config/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* ユーザーが設定する必要のある情報
*/
export type Source = {
port?: number;
proxy?: string;
maxFileSize?: number;
allowedPrivateNetworks?: string[];
};
/**
* Misskeyが自動的に(ユーザーが設定した情報から推論して)設定する情報
*/
export type Mixin = {
userAgent: string;
};
export type Config = Source & Mixin;
1 change: 1 addition & 0 deletions built/config/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
23 changes: 1 addition & 22 deletions built/download.d.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,3 @@
/// <reference types="node" />
/// <reference types="node" />
import * as http from 'node:http';
import * as https from 'node:https';
export type DownloadConfig = {
[x: string]: any;
userAgent: string;
allowedPrivateNetworks: string[];
maxSize: number;
httpAgent: http.Agent;
httpsAgent: https.Agent;
proxy?: boolean;
};
export declare const defaultDownloadConfig: {
httpAgent: http.Agent;
httpsAgent: https.Agent;
userAgent: string;
allowedPrivateNetworks: never[];
maxSize: number;
proxy: boolean;
};
export declare function downloadUrl(url: string, path: string, settings?: DownloadConfig): Promise<{
export declare function downloadUrl(url: string, path: string): Promise<{
filename: string;
}>;
35 changes: 15 additions & 20 deletions built/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,21 @@ import got, * as Got from 'got';
import IPCIDR from 'ip-cidr';
import PrivateIp from 'private-ip';
import { StatusError } from './status-error.js';
import { getAgents } from './http.js';
import { httpAgent, httpsAgent } from './http.js';
import { parse } from 'content-disposition';
import config from './config/index.js';
const pipeline = util.promisify(stream.pipeline);
export const defaultDownloadConfig = {
userAgent: `MisskeyMediaProxy/0.0.0`,
allowedPrivateNetworks: [],
maxSize: 262144000,
proxy: false,
...getAgents()
};
export async function downloadUrl(url, path, settings = defaultDownloadConfig) {
export async function downloadUrl(url, path) {
if (process.env.NODE_ENV !== 'production')
console.log(`Downloading ${url} to ${path} ...`);
const timeout = 30 * 1000;
const operationTimeout = 60 * 1000;
const maxSize = config.maxFileSize || 262144000;
const urlObj = new URL(url);
let filename = urlObj.pathname.split('/').pop() ?? 'unknown';
const req = got.stream(url, {
headers: {
'User-Agent': settings.userAgent,
'User-Agent': config.userAgent,
},
timeout: {
lookup: timeout,
Expand All @@ -36,26 +31,26 @@ export async function downloadUrl(url, path, settings = defaultDownloadConfig) {
request: operationTimeout, // whole operation timeout
},
agent: {
http: settings.httpAgent,
https: settings.httpsAgent,
http: httpAgent,
https: httpsAgent,
},
http2: false,
retry: {
limit: 0,
},
enableUnixSockets: false,
}).on('response', (res) => {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !settings.proxy && res.ip) {
if (isPrivateIp(res.ip, settings.allowedPrivateNetworks)) {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !config.proxy && res.ip) {
if (isPrivateIp(res.ip)) {
console.log(`Blocked address: ${res.ip}`);
req.destroy();
}
}
const contentLength = res.headers['content-length'];
if (contentLength != null) {
const size = Number(contentLength);
if (size > settings.maxSize) {
console.log(`maxSize exceeded (${size} > ${settings.maxSize}) on response`);
if (size > maxSize) {
console.log(`maxSize exceeded (${size} > ${maxSize}) on response`);
req.destroy();
}
}
Expand All @@ -72,8 +67,8 @@ export async function downloadUrl(url, path, settings = defaultDownloadConfig) {
}
}
}).on('downloadProgress', (progress) => {
if (progress.transferred > settings.maxSize) {
console.log(`maxSize exceeded (${progress.transferred} > ${settings.maxSize}) on downloadProgress`);
if (progress.transferred > maxSize) {
console.log(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
req.destroy();
}
});
Expand All @@ -94,8 +89,8 @@ export async function downloadUrl(url, path, settings = defaultDownloadConfig) {
filename,
};
}
function isPrivateIp(ip, allowedPrivateNetworks) {
for (const net of allowedPrivateNetworks ?? []) {
function isPrivateIp(ip) {
for (const net of config.allowedPrivateNetworks || []) {
const cidr = new IPCIDR(net);
if (cidr.contains(ip)) {
return false;
Expand Down
Loading

0 comments on commit 7976395

Please sign in to comment.