惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
V2EX
大猫的无限游戏
大猫的无限游戏
腾讯CDC
博客园 - Franky
WordPress大学
WordPress大学
Jina AI
Jina AI
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
B
Blog RSS Feed
Last Week in AI
Last Week in AI
The Cloudflare Blog
V
Visual Studio Blog
P
Proofpoint News Feed
博客园 - 叶小钗
L
LangChain Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Recorded Future
Recorded Future
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
罗磊的独立博客
雷峰网
雷峰网
博客园 - 【当耐特】
Microsoft Security Blog
Microsoft Security Blog
L
LINUX DO - 热门话题
Cisco Talos Blog
Cisco Talos Blog
L
Lohrmann on Cybersecurity
Martin Fowler
Martin Fowler
Spread Privacy
Spread Privacy
MongoDB | Blog
MongoDB | Blog
Engineering at Meta
Engineering at Meta
C
Cybersecurity and Infrastructure Security Agency CISA
小众软件
小众软件
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Recent Announcements
Recent Announcements
T
Threat Research - Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
量子位
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
宝玉的分享
宝玉的分享
D
DataBreaches.Net
T
The Exploit Database - CXSecurity.com
Vercel News
Vercel News
IT之家
IT之家
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Troy Hunt's Blog
aimingoo的专栏
aimingoo的专栏

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工具? | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog 对博客与笔记的思考 | Yesterday17's Blog Project Anni 之旅(3)自动化 Flutter 应用 CI/CD 上架流程 | Yesterday17's Blog AsobiStage 直接播放链接 | Yesterday17 如何在后分P时代进行投稿——sswa使用详解 | Yesterday17's Blog JSON RPC 与 LSP 协议基础 | Yesterday17's Blog Grajapa Shueisha / BookEnd 加密方式调查 | Yesterday17's Blog 【2022篇+WriteUp】如何再收一个新年红包? | Yesterday17's Blog 如何将良心云的良心功能清理干净 | Yesterday17's Blog 【油猴脚本】bilibili 投稿页面返回旧版+旧版页面强制允许分P上传 | Yesterday17's Blog Cloudr1v1 授权方式分析 | Yesterday17 Typora 1.0.2 逆向实录 | Yesterday17's Blog Project Anni 之旅(2)ValueAfterTable——toml-rs的实现与限制 | Yesterday17's Blog IPv4透明代理+IPv6 Passthrough——树莓派单臂软路由折腾记 | Yesterday17's Blog Chaos; Child 汉化补丁 神秘编码探索 | Yesterday17's Blog 镣铐与舞蹈——个性与共性之迷思 | Yesterday17's Blog Go 学习笔记 02 - 找准 io 之道 | Yesterday17's Blog NAT Slipstreaming v1 原理浅析 | Yesterday17's Blog 绕过「9-nine-」的 CDKEY 验证——KrkrPlugin 正(?)向实录 | Yesterday17's Blog 静流的青春纪念册——「サクラノ刻 -櫻の森の下を歩む-」体验版感言 | Yesterday17's Blog Project Anni 之旅 01 - 从 clap-builder 到 derive | Yesterday17's Blog [Google CTF 2021] CPP WriteUp | Yesterday17's Blog 获取 アソビステージ 的实际播放链接 | Yesterday17's Blog 90 行 Rust 代码实现 AsyncTeeReader | Yesterday17's Blog 或许还算有价值一读的文章列表 | Yesterday17's Blog 从零开始的 Seedbox 之旅 | Yesterday17's Blog [随笔]技术型博客行文迷思(1) | Yesterday17's Blog 浅谈 git fetch 的工作方式 | Yesterday17's Blog 『ソーサレス*アライヴ! ~the World's End Fallen Star~』通关感想" | Yesterday17's Blog Rust std::fmt 格式语法简述 | Yesterday17's Blog 日亚修改居住国的解决方案 | Yesterday17's Blog [Windows/Linux] GC553 的 Switch 完美采集之路 | Yesterday17's Blog 【翻译】Subtyping and Variance / 子类型与变型 | Yesterday17's Blog Berd's Red Envelope 2021 WriteUp | Yesterday17's Blog 【中英对照】ALSA 音频 API 使用教程/A Tutorial on Using the ALSA Audio API | Yesterday17's Blog 从 cue_scanner.l 看 CUE Sheet 的词法单元 | Yesterday17's Blog Postman 历史记录导出的解决方案 | Yesterday17's Blog 《恋爱绮谭 不存在的夏天》通关感想 | Yesterday17's Blog [微机实验/TD-PITE] 微机接口综合实验 | Yesterday17's Blog [微机实验/TD-PITE] 键盘扫描及数码管显示实验 | Yesterday17's Blog [微机实验/TD-PITE] 数码管显示实验 | Yesterday17's Blog X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog [微机实验/TD-PITE] 8251 串行接口实验 | Yesterday17's Blog Node.js child_process.fork 与 env 污染 RCE | Yesterday17's Blog EP.01 「夜の向日葵」 | Yesterday17's Blog [微机实验/TD-PITE] 8254 定时/计数器实验+选做实验 | Yesterday17's Blog [JLU CTF/2020] babywasm WriteUp | Yesterday17's Blog PHP 反序列化与经典利用 | Yesterday17's Blog WebAssembly 逆向简述 | Yesterday17's Blog 『彼女、お借りします』一期完结点评 | Yesterday17's Blog [微机实验/TD-PITE] D/A 转换实验+选做实验 | Yesterday17's Blog [微机实验/TD-PITE] A/D 转换实验+选做实验 | Yesterday17's Blog 开源项目申请 JetBrains Open Source License 简单流程 | Yesterday17's Blog 微软拼音与 JetBrains 搜索快捷键冲突的解决方案 | Yesterday17's Blog [微机实验/TD-PITE] 8259 中断优先级实验+选做实验 | Yesterday17's Blog IFTTT 测试(续) | Yesterday17 IFTTT 测试 | Yesterday17's Blog [微机实验/TD-PITE] 存储器扩展实验+选做实验 | Yesterday17's Blog 新版 GCC 针对 -fdump-translation-unit 的替代方案 | Yesterday17's Blog 一次 HSTS 策略配置的排错之旅 | Yesterday17's Blog YukiNative 踩坑记——Windows 的消息队列 | Yesterday17's Blog 我是我自己——论获取 HTTPS 证书时的验证步骤 | Yesterday17's Blog 【设计文档】对 PUG 的大规模设计修订(1.1) | Yesterday17's Blog GS65 折腾记(2)加装固态,分区,Grub2 引导 Manjaro LiveCD | Yesterday17's Blog 「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | Yesterday17's Blog GSuite 探索篇(1)使用 Service Account 向 Google Drive 传输文件 | Yesterday17's Blog 『サクラノ詩 -櫻の森の上を舞う-』通关感想 | Yesterday17's Blog 《ATRI -My Dear Moments-》通关感想 | Yesterday17's Blog [工具][VSCode 扩展] AegiKit——方便 Aegisub 使用的工具箱 | Yesterday17's Blog 贝塞尔曲线、字体矢量化与曲线运算 | Yesterday17's Blog NAT 类型初探 | Yesterday17's Blog
Airsonic Advanced+Google Drive+Caddy 部署纪实 | Yesterday17's Blog
Yesterday17 · 2020-11-07 · via Yesterday17's Blog

前言

最近总算是想要摆脱尴尬的听歌处境了。

自从网抑云百万版权倒了之后,下载到本地就变成了唯一的结果。下载的过程也花不了多少时间,下完之后直接就能听,没有任何版权损失的风险。中间试图转成过 QQ,但果然还是被劝退了。

上述列举的都是本地音乐的好处,但如果本地音乐全是好处的话那自然也不会有云音乐的兴起。本地音乐的缺点也很明显,就是多设备同步。

设备一多,事情就开始不对劲了。你开始希望在什么地方都能听到你库存的音乐,但听的前提是你需要把音乐拷贝一份到对应的目标机器上;当你想要分享音乐时,你只能分享一个小至几 M,大至百余兆的音乐文件;以及由于失去了云端的优势,播放记录、播放列表之类的功能也就难以实现与同步。这都是本地播放的劣势。

不知道又没有人说过,解决一切问题的终极手段就是 self-host,音乐也不例外。在有前人 SJ 吃过桃子的情况下[1],我们再摸起来相对就轻松了很多。

目标

为这个平台,我们定下了以下几个目标:

  1. 音乐文件全部存储在 Google Drive 上。
  2. 通过 rclone 挂载目录的形式使 Airsonic 能够读取。
  3. 通过 docker 部署。

目标 1 的出现是因为服务器的硬盘大小不够,不足以支撑将全部文件存下;而目标 2 则是为实现目标 1 而选择的具体方案;至于目标 3,则是为了快速部署而提出的要求。

Google Drive 的实现方案

在目标 2 中,我们指出了计划的实现方案,即通过 rclone 挂载。但 rclone 直接挂载存在两个问题:

  1. 需要守护进程,这一点可以通过 systemd 简单解决。
  2. 迁移过程复杂,这一点有一部分恰好是上一点的解决方案带来的。

并且,由于我们提出了 docker 部署的目标,挂载在外部反而某种意义上使得整个程序过于分散。因此最后我们选择将 Google Drive 挂载为 dockervolume

docker-volume-rclone[2] 就是最终的目标了。我们利用这个 Plugin 运行 rclone mount

安装

安装的话只要通过 docker plugin 就可以完成了:

sudo docker plugin install sapk/plugin-rclone

在安装完成后,我们需要配置 rclone,这里直接把你的 rclone.conf 复制过来就可以了。我选择存放在 ~/.config/rclone/rclone.conf

Airsonic Advanced

在准备完音乐源之后,接下来就是本体的搭建了。我们选择的是 Airsonic Advanced[3],这是一个快速迭代的 Airsonic fork,相比原版慢悠悠的开发基调能够更快地发现并解决问题也可以更快地创造问题。

作者在 Readme 中给出了 docker 的部署方案,我们只要照着抄一份就行了(

部署

这里我采用了 docker-compose 进行部署,主要是因为需要挂载的 volume 数量有点多了……

version: "3.4"

services:

airsonic:

image: airsonicadvanced/airsonic-advanced

ports:

- 127.0.0.1:4040:4040

environment:

- PUID=0

- PGID=0

- TZ=Asia/Shanghai

- CONTEXT_PATH=

- JAVA_OPTS=-Xmx512m

volumes:

- data:/var/airsonic

- playlists:/var/playlists

- podcasts:/var/podcast

- airsonic-stable:/var/mount/00.stable

- airsonic-radio:/var/mount/10.radio

healthcheck:

test: "ls /var/mount/00.stable && ls /var/mount/10.radio || exit 1"

interval: 1m

timeout: 15s

retries: 3

start_period: 15s

volumes:

data:

driver: local

playlists:

driver: local

podcasts:

driver: local

airsonic-stable:

driver: sapk/plugin-rclone

driver_opts:

config: "${RCLONE_CONF_BASE64}"

remote: "airsonic-stable:/"

args: "--allow-other --dir-cache-time=12h --vfs-read-chunk-size=32M --vfs-read-chunk-size-limit=1G"

airsonic-radio:

driver: sapk/plugin-rclone

driver_opts:

config: "${RCLONE_CONF_BASE64}"

remote: "airsonic-radio:/"

args: "--allow-other --dir-cache-time=12h --vfs-read-chunk-size=32M --vfs-read-chunk-size-limit=1G"

environments 中可以看到 PUIDPGID,可以修改成你想要的 UIDGIDCONTEXT_PATH 是为反向代理不在根目录准备的,如在 /music 下,不能以 / 结尾

airsonic 的最后是 healthcheck,这部分主要是为防止 rclone 异常退出而准备的。当挂载目录无法访问,即 rclone 出现故障时,走正常 docker 错误流程就可以了。

最后是 volumes。前三个 local 都没什么问题,最后的两个 airsonic 挂载盘的 args 需要加上 --allow-other 才能让 Airsonic 访问到。而为了使 allow-other 不报错,你还需要修改 /etc/fuse.conf

# mount_max = 1000

user_allow_other

user_allow_other 的注释去掉,或者如果没有这一行的话,加上即可。

启动

最后就是启动的步骤了。如果你仔细观察了上面的 yaml,你会发现 RCLONE_CONF_BASE64 这个变量。这是我们为了使用 docker-compose 需要准备的。但问题在于我们需要使用 sudo 执行 docker-compose,因此最后我们需要通过 bash -c 执行脚本:

sudo bash -c 'export RCLONE_CONF_BASE64="$(base64 /home/yesterday17/.config/rclone/rclone.conf)" && docker-compose up -d'

这里需要注意的是 RCLONE_CONF_BASE64=等号右边,需要用一对引号括住 base64 命令的执行结果,防止由于换行引起的问题。

反向代理

最后的最后就是反代了,这也是让外界能够访问这个服务的关键所在。我简单给一个 Caddy 的配置吧:

music.mmf.moe {

gzip

tls self_signed

proxy / localhost:4040 {

transparent

header_upstream X-Forwarded-Proto https

header_upstream Connection {>Connection}

header_upstream Upgrade {>Upgrade}

}

header / Content-Security-Policy upgrade-insecure-requests

}

自己部署记得把上面的 tls self_signed 改成其他正常的 TLS 方案(

配置中的 transparent 是让 Airsonic 使用的,而 X-Forwarded-Proto 则是历史遗留,我也不清楚这样配置能不能让 Airsonic 强制走 https

下面的两行 header_upstream 则是为 WebSocket 准备的,使 Airsonic AdvancedWebSocket 同步能够正常使用。不难发现,这两行和 websocket 预设的效果是一样的[4]。

而最后的 CSP 则是强制 https 的关键,通过 CSP 策略强制浏览器升级不安全的请求。支持 CSP 的浏览器会在读到这个 header 后,会把页面中所有 http 请求都升级到 https

什么?你说浏览器不支持 CSP 怎么办?这样的浏览器还有存在的必要吗(无慈悲)

结语

平台的搭建可以说是费了一番周折,主要可能是在平台选取这一方面耽误了太多的时间。JavaAirsonic 一开始并没有在我的考虑范围之内,因为我觉得我的服务器似乎顶不住它的占用。但经过一番尝试后,最终我还是用回了 Airsonic

Airsonic Advanced 的问题是什么呢?由于它是 Airsonicfork,因此不可能逃出 Airsonic 的局限框架之外。虽然它在 Airsonic 原版的基础上改进了很多,但有一些核心功能的缺失却是没有办法避免的,比如歌词

不过事物的发展是不可能一蹴而就的。在目前的阶段,能有自行车就不错了,毕竟音乐本身才是重点,因此我将眼光更多地放在了音乐内容的整理上。我创建了一个共享盘专门用来让 Airsonic 挂载,并尝试对手头的音乐进行整理。

在我看来,问题最大的是 Artist 的标签。在设计之初他们就没有考虑到多艺术家的复杂之处。艺术家不仅能有多个,还应该有别名。因此计划的第一步就是将手头音乐的艺术家全部都整理成统一的格式:

文档中的说明
文档中的说明

如果你有愿加入音乐的整理,而且我也认识,可以在下面留言或者 TG/TwiPM 我(

嘛,就是这样(

参考

  1. https://blog.seraphjack.top/posts/2020/03/airsonic-setup/
  2. https://github.com/sapk/docker-volume-rclone
  3. https://github.com/airsonic-advanced/airsonic-advanced
  4. https://www.bookstack.cn/read/caddy-cn-doc/src-http.proxy.md#ghf86d