为基于Docker的Nextcloud私有云集成ONLYOFFICE

2020-09-01 Technical Salty Fish 0条

用Nextcloud搭个私有云是很爽的一件事。在插件加持下,Nextcloud能够做到的事包括但不限于:

  • 作为Markdown编辑器,还带实时预览(这篇博客就是在我的Nextcloud上写出来的)
  • 在线浏览和编辑思维导图
  • 使用标准CardDAVCalDAV协议全平台同步通讯录、提醒事项、日历
  • 同步照片
  • 嵌入一个RainLoop,很方便地处理邮件
注:从Nextcloud 18官方开始推行Text应用,目前还不怎么好用,而且官方竟然下架了旧版files_texteditor应用!从旧版Nextcloud找到这个应用,复制到apps目录就可以使用旧版的编辑器了,Markdown预览应用也能正常工作。

最重要的是,这一切都在自己的掌控之下,没有数据泄漏的隐患,也不会发生存在云上的文件(小电影)莫名其妙被删这种事;没有讨厌的下载限速,外链也没有限制!

上面这些都还是比较容易实现的。Office是个老大难问题,虽然有CollaBora和ONLYOFFICE两家提供在线多人编辑Office文档的功能,但想成功集成进Nextcloud都有一定的难度。今天心情比较好,来试着搞定这个headache。

选择一家Office提供商

首先得决定要用CollaBora还是ONLYOFFICE。

插入一句吐槽:隔壁OwnCloud已经官方支持Office Online了!

前者是在服务器上运行一个完整的LibreOffice实例,并且给用户提供远程控制,后者则是完全browser-oriented的设计。我的所有网络应用都用Docker部署,这俩也都提供Docker镜像,创建一个测试用的容器并不难。经过测试,CollaBora不适合我用。启动需要5-6分钟不说,空闲时占用的内存达到了500多M。对于我捡垃圾凑的服务器来说,压力略大。

决定使用ONLYOFFICE。

关于Community Document Server

ONLYOFFICE不是简单的一个插件:应用商店里的插件只是一个连接ONLYOFFICE文档服务器的入口,真正的文档处理工作不是由Nextcloud完成,而是交给另外的ONLYOFFICE文件服务器。应用商店里有一个Community Document Server应用(以下记作CDS),以比较简单的方式提供了ONLYOFFICE文档服务器的功能。只要安装好,并且在ONLYOFFICE的设置界面把服务器地址留空,CDS就会自动配置好连接。

但是CDS有一个很大的缺点,就是做完修改不能保存回Nextcloud。从Nextcloud打开Office文件的时候,实际上是把文件上传到CDS,再由CDS处理后回传给Nextcloud。ONLYOFFICE是为合作设计的,要等所有编辑session结束后才可以保存修改到Nextcloud,然而一个bug使得用户无法手动结束session,这就使得文件滞留在CDS,并且一直处于打开状态。如此严重的bug是不能忍受的。更重要的是,这个bug已经拖了两年了!

因此发现了这个bug后,我决定停用CDS,改用正规的ONLYOFFICE Document Server。

使用Docker部署ONLYOFFICE Document Server

我的Nextcloud是用docker-compose部署的,因此要加一个ONLYOFFICE只需要在docker-compose.yml文件里添加一个service。添加完毕后的compose配置文件长这样:

version: '2'

services:
  db:
    image: mariadb
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=<REDACTED>
      - MYSQL_PASSWORD=<REDACTED>
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

  app:
    image: nextcloud
    ports:
      - 802:80
    volumes:
      - ./webroot:/var/www/html
      - ./phpconf:/usr/local/etc/php/:ro
    restart: always
  
  office:
    image: onlyoffice/documentserver
    ports:
      - 803:80
    restart: always

执行docker-compose up -d就会自动拉取onlyoffice/documentserver的镜像并创建容器。这样Document Server就已经运行起来了。

这里宿主机803端口到容器80端口的映射可能不是必须的,但是经过我的测试,这样是最适合我的方案。

让Nextcloud连接到Document Server

现在打开Nextcloud,去应用商店安装ONLYOFFICE客户端(使用应用商店可能需要科学的方式)。进入设置界面,在边栏里可以找到ONLYOFFICE。填入刚刚搭建好的服务器地址就能开始愉快地在线编辑Office文档了。是不是很棒?等等!服务器地址到底填什么呢……不要急,真正的难题在后面。

似乎很好的方案:使用Docker内容器互相解析的方式连接Document Server

使用docker-compose运行起来的的service之间是可以直接解析的,比如这里就可以从app容器中执行curl office来访问office容器80端口上的文档服务器。随之而来的一大问题是:在Nextcloud的ONLYOFFICE设置界面填入服务器地址http://office,点保存,就能收到Nextcloud的报错:Error when trying to connect (Mixed Active Content is not allowed. HTTPS address for Document Server is required.)

大致意思是如果Nextcloud使用了HTTPS,那么这里也必须用HTTPS(具体的原因我不是很懂,如果有了解请评论告诉我)。HTTPS的问题在于:这个office申请不到正规CA颁发的证书!

我尝试了使用自签名证书。生成一个自签名证书很简单,解决cURL error 60也很简单(编辑Nextcloud配置文件config/config.php,添加'onlyoffice' => array ( 'verify_peer_off' => TRUE, )就可以跳过证书安全性检查,或者干脆让服务器信任自己签发的证书),但是有一个问题是无法解决的。在Chrome里,在ONLYOFFICE中打开一个文件会使得地址栏指示连接安全性的图标变成红色的Insecure;在Safari里则是根本打不开了(会提示加载ONLYOFFICE出错)。出现这个问题的原因我猜测和上面提到的HTTP/HTTPS不能混用有一定的联系。虽然是自己使用,大可在自己的每台设备上都信任自己的证书,但是万一出现临时需要在其它设备登录的情况,就很尴尬。

参考CDS官方GitHub Repo,我尝试了通过occ导入自签名证书(还要配合在config.php中添加'enable_certificate_management' => true,这个在CDS的Repo里没有提到),然而对解决问题没有帮助。根据Nextcloud官方的说法,这个命令是用于在联合云功能中信任其它采用自签名证书的Nextcloud服务器的,而不是用来信任ONLYOFFICE Document Server的。

鉴于这个原因,我放弃了使用Docker Compose service解析的方式。Docker禁止在service的名字里使用点(.),所以不可以通过在这个容器名上做文章的方式达到使用正规CA证书的目的。再者,如果通过更复杂的hosts自己添加解析,然后使用正规证书,这就把事情搞得过于复杂,失去了这一方案本来的目的。

这样一来唯一的办法就是申请一个正规的证书给Document Server了。Document Server原生有使用HTTPS的能力,只需要在容器内的/var/www/onlyoffice/Data/certs目录下放置证书和密钥(分别命名为onlyoffice.crtonlyoffice.key,可以用Docker Compose的volumes参数实现),就能在容器内的443端口上启用HTTPS。

我的Nextcloud和邮件服务器都是由Docker部署并在宿主机上的Apache2反向代理处套HTTPS,所以为了方便统一管理,我决定给Document Server也做一个反向代理,不过不需要单独占一个Virtual Host,挂在Nextcloud实例的/documentserver路径下即可。

因为这样Document Server就直接暴露在公网下,最好使用ONLYOFFICE的Secret验证避免Document Server被第三方连接使用,不过鉴于我的服务器本身比较隐蔽(采用非标准端口,路径也很难猜到),就没有做相应设置。这不值得表扬。

参考ONLYOFFICE官网的相关文档,我修改了Apache2的site配置文件来添加Document Server的反向代理。一定 一定 一定要照着官网写,否则大概率是会失败的!我就在这里卡了很久,对付各种奇奇怪怪的问题。以下贴出最终的Apache2虚拟主机配置文件:

<VirtualHost *:443>
    ServerName disk.example.org
    <Proxy *>
        Allow from all
    </Proxy>

    Define VPATH /documentserver
    Define DS_ADDRESS localhost:803
    
    <Location ${VPATH}>
      Require all granted
      SetEnvIf Host "^(.*)$" THE_HOST=$1
      RequestHeader setifempty X-Forwarded-Proto https
      RequestHeader setifempty X-Forwarded-Host %{THE_HOST}e
      RequestHeader edit X-Forwarded-Host (.*) $1${VPATH}
      ProxyAddHeaders Off
    </Location>
    
    ProxyPassMatch ^\${VPATH}(.*)(\/websocket)$ "ws://${DS_ADDRESS}/$1$2"
    ProxyPass ${VPATH} "http://${DS_ADDRESS}"
    ProxyPassReverse ${VPATH} "http://${DS_ADDRESS}"

    ProxyPass / http://localhost:802/
    ProxyPassReverse / http://localhost:802/
    ProxyPreserveHost On
    
    SSLEngine On
    SSLProxyEngine On
    SSLCertificateFile <REDACTED>
    SSLCertificateKeyFile <REDACTED>
    SSLCertificateChainFile <REDACTED>
    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"
    </IfModule>
</VirtualHost>

重启Apache,就可以去Nextcloud里填写Document Server地址了:https://disk.example.org:23333/documentserver这是最关键的步骤,可能会出现很多奇怪的错误,请保持耐心,错误会一个一个一个一个一个一个一个一个一个一个一个一个一个一个被解决的(笑)。如果失去了耐心,请睡一觉之后再继续研究。

我的服务器位于自己家,众所周知家用宽带是没有443端口的,所以我在公网上用了另一个端口映射内网的443端口(记作23333)。这里有一个小注意事项,见后文。

公网IP的事就不赘述了,电信宽带打电话以装监控为借口申请都会给的。光猫桥接我是靠TTL破解实现的,具体方法根据不同的地区和光猫型号自行选择。

奇怪的问题之 明明用了正规CA证书还是会cURL error 60

这是一个小坑,解决方案就是上面配置文件里的SSLCertificateChainFile <REDACTED>这句。我起初还怀疑是Let's Encrypt颁发的证书没受到信任导致的,特意去Let's Encrypt官网下载了一份Root CA Certificate导入系统,当然没什么用;我又尝试了curl https://letsencrypt.org(自己家官网当然要用自己的证书XD),没有报错。于是想来想去觉得事情没有那么简单。经过仔细调查,终于被我发现了端倪。

之前我的Web服务器是不提供Full Chain的,仅提供证书文件本身(因为懒,所以没有顺手配置fullchain)。在日常使用中并没有出现问题,Chrome、Firefox、Safari等主流浏览器都标记为安全,但是使用curl访问就会报错。经过研究,报错的原因就是缺少intermediate certificate。以下是一个由BadSSL提供的示例

~ > curl https://incomplete-chain.badssl.com/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

这个页面在主流现代桌面浏览器都可以正常打开。这篇文章提供了一些解释,概括起来就是现代浏览器具有自动补全证书链的功能,然而curl这类较为底层的实现就没这么先进,所以需要网站自己提供完整的证书链。

不知道为什么,MacOS的curl竟然不会报错,可能苹果比较强吧。因为我平时用MacOS作为主力操作系统,所以这个问题隐藏得很深,坑了我好久!

奇怪的问题之 明明地址正确却报cURL error 35

在我的网络情况下,填入地址https://disk.example.org:23333/documentserver后保存就会报错cURL error 35。需要手动调整一个地方才能让ONLYOFFICE正常连接。

原因是我的服务器的hosts文件里把家里的DDNS域名(记作home.example.org)设为FQDN(即指向localhost),网盘disk.example.org则是CNAME到home.example.org,所以在服务器上访问disk.example.org会解析到localhost。配置的地址中指定了23333端口,因此Nextcloud会尝试连接localhost:23333,但是服务器本机上的web监听端口是443,这就会导致连不上。自建的Document Server也是同理,加端口号就会导致访问不到。直接去掉端口号是行不通的,因为浏览器会加载一个位于ONLYOFFICE文档服务器上的js,而地址就是从这里获取。这里不填写端口号虽然能成功连接,但是一打开文档就会报错ONLYOFFICE cannot be reached。正确的解决方法是修改web服务器的HTTPS监听端口使得它和外网一致,或者添加一条iptables rule指示把对于本机23333端口的访问REDIRECT至443端口:sudo iptables -t nat -I OUTPUT -p tcp -o lo --dport 23333 -j REDIRECT --to-ports 443 注意对于回环地址的访问是不经过PREROUTING链的,所以常规的端口转发是不会shen

这个问题并不复杂,但是在我实验的过程中和别的问题搅在一起,给我添了很多乱。鉴于可能有一些普适性,这里记录下来,希望帮助大家少走弯路。

另外一个需要注意的地方:Nextcloud连接Document Server是用php-curl实现的。如果配置了Nextcloud通过科学的方式访问互联网(主要是应用商店需要),那么这个目的地为office的HTTP请求也会通过科学的方式发出去,可能会出现解析不出地址的情况,这样也会报error 35。

完成!

配置好Docker容器和反向代理,在Nextcloud里填好地址,保存成功的话就能看见设置界面多出来了一些其它的选项,比如扩展名关联。此时回到Files应用,点创建文件的按钮,应该就能看到Document、Spreadsheet和Presentation三个选项了。

现在就可以在Nextcloud中直接打开并编辑Office文件。实测编辑后保存并退出,过大约5-6秒就会自动把修改的内容写回Nextcloud(时间戳显示几秒前)。完美解决CDS的蛋疼问题,妈妈再也不怕我丢数据了!

以上就是我排除万难,最终成功为Nextcloud部署ONLYOFFICE文档服务器的过程。很惭愧,就做了一点微小的工作,谢谢大家!