jeblove 最近的时间轴更新
jeblove

jeblove

V2EX 第 500353 号会员,加入于 2020-07-22 19:08:31 +08:00
jeblove 最近回复了
4 天前
回复了 Hundredwz 创建的主题 NAS 小白家庭 nas 搭建方案,求建议
首选叠个甲,我认为当手头上只有一台 nas 的时候,all in one 没有什么不妥,只要数据备份好。
当我只有一台设备那就 all in one (起码组个阵列); 2 台我会把网络分开; 3 台我才会考虑“重要文件相互备份”、“存算分离”
现在的 nas 系统发展也不是真只专注于“存储”,怎么好用、方便,怎么来。

---

先决定底层系统
如果没有过多虚拟系统的需求
- 无花费就选黑裙,因为有 docker 需求所以不建议用 truenas scale ( app 方面经常破坏性更新)
- 花费可首选 unraid
有些虚拟系统才考虑 pve+其它 nas 系统

智能家居,首选 haos ,容器版 hass 功能残缺(没有 add-one 、恢复备份等),只要设备能接入到 hass 就能通过“巴法云”让小爱同学控制(注意不是米家控制)

照片:
- 如果系统是群晖就优先用群晖自带;
- 其它 nas 系统,无花费用 immich (没有其它更好的可选),有花费就 mtphoto (正在使用,没感觉太大问题)
视为较为重要的文件,nas 有 raid 等校验还不算稳,最好定时加密同步、备份到网盘,例如“duplicati”(其它重要文件也一样)

影音:
- 有影片存储到 nas 上就考虑 jellyfin 、emby 、plex (后两者硬解等功能需要付费);
- 不存储影片,可以了解“小雅”,或直接网盘配合软件(那些支持直接链接网盘的播放器软件)

旁路由,普通使用(指有需要的设备才手动指向旁路由)的话虚拟机来也没什么问题;但最好能把网络与 nas 分开。
软路由系统一般都是 openwrt ,只是看装哪个
x86 的话,istoreOS 、骷髅头( https://github.com/DHDAXCW/OpenWRT_x86_x64
还有云编译( https://github.com/kiddin9/OpenWrt_x86-r2s-r4s-r5s-N1

---

容器所在问题,要考虑到容器的文件备份,比较建议用在 nas 系统上的 docker 服务(如果 pve 就加一个 nas 系统),还有虚拟机那些后续有空也最好设置定时备份存储在指定位置。

265g 装些系统还行,加上些容器的文件就不太够了。

外部访问,优先用公网 v4 、v6 ,其次装个 tailscale 作为备用。

还有既然搞 nas 了,就没太必要“付费网盘”,不然直接冲个网盘 vip 不用 nas (不是说用 nas 就不用网盘,而是没必要在网盘上花钱),有了 nas ,就把网盘(阿里云、宽带运营商的云盘等)作为备份渠道。
18 天前
回复了 muzihuaner 创建的主题 分享创造 用 cloudflare worker 搭建一个 Docker 镜像
@lazyyz 应该是的,毕竟得靠 cf 服务器中转流量
19 天前
回复了 messiah163 创建的主题 NAS 百度网盘内容下载 NAS 方案
我用 johngong/baidunetdisk:latest
非会员有 30 秒还是一分钟加速下载,下载不大的文件都是趁它不注意一下子下完,偏大的文件就慢慢挂着。
20 天前
回复了 muzihuaner 创建的主题 分享创造 用 cloudflare worker 搭建一个 Docker 镜像
感谢,用了下发现不能拉取 dockerhub 官方镜像(如 node 、mariadb ),正常输入
```
docker pull 域名/node
```
实际上应该为
```
docker pull 域名/library/node
```
这样有些不太方便

加上判断是否官方镜像,修改调试了些时间,成功可用
```
'use strict'

const hub_host = 'registry-1.docker.io'
const auth_url = 'https://auth.docker.io'
const workers_url = 'https://dh.jeblove.com'
/**
* static files (404.html, sw.js, conf.js)
*/

/** @type {RequestInit} */
const PREFLIGHT_INIT = {
// status: 204,
headers: new Headers({
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
'access-control-max-age': '1728000',
}),
}

/**
* @param {any} body
* @param {number} status
* @param {Object<string, string>} headers
*/
function makeRes(body, status = 200, headers = {}) {
headers['access-control-allow-origin'] = '*'
return new Response(body, {status, headers})
}


/**
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr)
} catch (err) {
return null
}
}


addEventListener('fetch', e => {
const ret = fetchHandler(e)
.catch(err => makeRes('cfworker error:\n' + err.stack, 502))
e.respondWith(ret)
})


/**
* @param {FetchEvent} e
*/
async function fetchHandler(e) {
const getReqHeader = (key) => e.request.headers.get(key);

let url = new URL(e.request.url);

// 修改 pre head get 请求
// 是否含有 %2F ,用于判断是否具有用户名与仓库名之间的连接符
// 同时检查 %3A 的存在
if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) {
let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F');
url = new URL(modifiedUrl);
console.log(`handle_url: ${url}`)
}

if (url.pathname === '/token') {
let token_parameter = {
headers: {
'Host': 'auth.docker.io',
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
}
};
let token_url = auth_url + url.pathname + url.search
return fetch(new Request(token_url, e.request), token_parameter)
}

// 修改 head 请求
if (/^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) {
url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/');
console.log(`modified_url: ${url.pathname}`)
}

url.hostname = hub_host;

let parameter = {
headers: {
'Host': hub_host,
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
},
cacheTtl: 3600
};

if (e.request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}

let original_response = await fetch(new Request(url, e.request), parameter)
let original_response_clone = original_response.clone();
let original_text = original_response_clone.body;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;

if (new_response_headers.get("Www-Authenticate")) {
let auth = new_response_headers.get("Www-Authenticate");
let re = new RegExp(auth_url, 'g');
new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
}

if (new_response_headers.get("Location")) {
return httpHandler(e.request, new_response_headers.get("Location"))
}

let response = new Response(original_text, {
status,
headers: new_response_headers
})
return response;

}


/**
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers

// preflight
if (req.method === 'OPTIONS' &&
reqHdrRaw.has('access-control-request-headers')
) {
return new Response(null, PREFLIGHT_INIT)
}

let rawLen = ''

const reqHdrNew = new Headers(reqHdrRaw)

const refer = reqHdrNew.get('referer')

let urlStr = pathname

const urlObj = newUrl(urlStr)

/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
}
return proxy(urlObj, reqInit, rawLen)
}


/**
*
* @param {URL} urlObj
* @param {RequestInit} reqInit
*/
async function proxy(urlObj, reqInit, rawLen) {
const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld)

// verify
if (rawLen) {
const newLen = resHdrOld.get('content-length') || ''
const badLen = (rawLen !== newLen)

if (badLen) {
return makeRes(res.body, 400, {
'--error': `bad len: ${newLen}, except: ${rawLen}`,
'access-control-expose-headers': '--error',
})
}
}
const status = res.status
resHdrNew.set('access-control-expose-headers', '*')
resHdrNew.set('access-control-allow-origin', '*')
resHdrNew.set('Cache-Control', 'max-age=1500')

resHdrNew.delete('content-security-policy')
resHdrNew.delete('content-security-policy-report-only')
resHdrNew.delete('clear-site-data')

return new Response(res.body, {
status,
headers: resHdrNew
})
}
```
改动处:

- 57 行,添加修改 head 前的 get 请求
- 72 行,添加修改 head 请求

另外好像版本问题有额外的报错,改动有

- 12 行
- 150 行


👇
```
# docker pull xxx.com/node
Using default tag: latest
latest: Pulling from node
c6cf28de8a06: Downloading [> ] 506.8kB/49.58MB
891494355808: Downloading [========> ] 4.184MB/24.05MB
```
62 天前
回复了 bankroft 创建的主题 NAS 躁动的心,想入手 emby/plex
@jeblove 当然,这是我从 jellyfin 、emby 使用者的角度看待,局限性不小,作为个小参考吧
62 天前
回复了 bankroft 创建的主题 NAS 躁动的心,想入手 emby/plex
入正还是比较建议 plex 的。
emby 兼容更新不如 jellyfin ,例如,之前 4.7 一直不支持 qsv (测试过 vaapi 对 12 代没完全适配); 4.8 有个 qsv ,但是对中文特效字幕有异常(偏移)。
jellyfin 就没这些事,就是美化、不支持二级目录等这些小问题。
emby 的组车也不好,有设备数限制,如果设备数少也行,我是开放给朋友所以设备数会多些。
加上 emby 有开心版体验。
总结,感觉 jellyfin 与 emby 差距不大,入正建议 plex
220 天前
回复了 deanwin 创建的主题 优惠信息 便宜看起点付费文章的办法
连载中的小说盗版看之前的部分,接近最新几章就转到起点,看广告获得兑换券(章节卡)就基本能不花钱(花小钱)追看一两本。一天大概是 50 点章节卡
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2343 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 14ms · UTC 11:15 · PVG 19:15 · LAX 04:15 · JFK 07:15
Developed with CodeLauncher
♥ Do have faith in what you're doing.