h5ai - 私有云

瀑 布

之前一直在使用 ssh 来做文件存储与分享,因为 SSH 的特殊性,使用非常不便。便想建一个轻便的私人网盘。
经过对开源私有云(ownCloud/nextCloud/kodExplorer/…)的查找、安装、卸载流程,最终留下了德国人 larsjung 开发的 h5ai

更新:
2021-01-17 新增Caddy 2的配置

简介

h5ai 相对于其它私有云来说功能简单、体积轻便(1MB左右)、页面排版自动适应 PC 和移动端、支持包括中文在内的多语言显示,采用 NodeJs 框架开发,编译完成后为 PHP 程序。

h5ai 通过不同的视图来对服务器端的目录和文件进行查看。可以在线预览 PDF、文档、脚本(语言高亮)、图片、视频、下载(二维码)等等。
因为它重在文件索引与分享,所以不支持文件上传。
脚本高亮

它有许多设置,可以对页面排版进行修改,如皮肤、侧边栏、搜索、下载,生成视频和图片缩略图等等。

作者提供的一个 Demo:H5ai - Demo

h5ai可以部署在 Apache / lighttpd / nginx / caddy 等等 Web 服务器上。

第一、WEB 环境和 H5ai 的准备

1、Web 环境

我们需要准备一个 WEB 环境,可以使用 Nginx、LighttpdCaddy, Apache 等等都可以。在这里,我们使用 Web 服务器界的新起之秀 Caddy

  1. 安装 Caddy

    详见Web server - Caddy

  2. 安装 PHP
    1
    :~$ apt-get install php7.4-fpm php7.4-cgi php7.4-gd php7.4-json

2、h5ai 程序下载

h5ai 下载目录
v0.29.2,截止2019-09-22最新的正式版本。
v0.29.2+025~a1bb755,截止2019-09-22最新的开发版。

在这里,我们下载最新的开发版「v0.29.2+025~a1bb755」并解压至网站目录/var/www/下。

1
:~$ 7z x h5ai-0.29.2+025~a1bb755.zip -o/var/www

第二、h5ai 的安装与部署

1、网站目录示意图

1
2
3
4
5
6
7
网站根目录
├─—— _h5ai
├─—— _h5ai —— private
├─—— _h5ai —— public
├─—— 分享的文件
├─—— 分享的文件夹 ———— 分享的文件
└─—— 分享的文件夹

将需要分享的文件放在网站根目录下即可,而 h5ai 程序全部在_h5ai文件夹里。
程序默认“_h5ai”和“.”开头的文件不显示,可以修改配置options.json文件来自定义不想分享的文件。

2、运行网站

h5aiindex.php不在根目录下,所以需要在服务器上指定它的路径。

  • lighttptd,使用index-file.names来指定首页的路径。
    1
    index-file.names += ("/_h5ai/public/index.php")
  • Caddy,使用index来指定首页的路径。
    1
    index "/_h5ai/public/index.php"

因为h5ai所有的URI都会在服务端重定向至/_h5a/public/index.php并由它来生成页面,所以在Caddy里使用index指定网站首页后,浏览我们的网站时会出现很多404错误。

这点在 lighttpd / nginx / apache 下默认自动修正了,所以不需要其它设置。

Caddy 与 PHP 存在一些兼容性问题,无法自动处理这个问题。所以这里需要而使用rewrite指令来将所有的[URI]重写至/_h5ai/public/index.php

假设我的网站域名为haven200.com

  • 下面是Caddy v1的配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    :~$ cat >> /etc/caddy/Caddyfile <<EOF
    haven200.com:80 {
    root /var/www
    gzip
    tls off
    # index /_h5ai/public/index.php
    fastcgi / /run/php/php7.4-fpm.sock php
    rewrite {
    # 将所有路径全部重写至首页
    if {path} ends_with /
    to /_h5ai/public/index.php
    }
    }
    EOF
    :~$ sudo systemctl start php7.4-fpm caddy
  • 配置Caddy v2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    :~$ cat > /etc/caddy/Caddyfile <<EOF
    # -----------Global options---------------
    # Note: This must be the first block of the Caddyfile
    {
    http_port 80
    https_port 443
    servers :443 {
    protocol {
    experimental_http3
    }
    }
    servers :80 {
    protocol {
    allow_h2c
    }
    }
    }

    haven200.com {
    root * /var/www/h5ai
    tls haven200@haven200.com
    file_server
    php_fastcgi unix//run/php/php7.4-fpm.sock

    # redirect to index.php with all request
    @redirected {
    not path /_h5ai/*
    path */
    }
    rewrite @redirected /_h5ai/public/index.php
    }
    EOF
    :~$ sudo systemctl restart php7.4-fpm caddy

此时,在浏览器中输入网址http://haven200.com即可浏览网站。

/run/php/php7.4-fpm.sockphp7.4-fpm 所绑定的网络端口,如果查找它?

1
2
:~$ netstat -anpl | grep php | grep LISTENING
unix 2 [ ACC ] STREAM LISTENING 816733 7346/php-fpm: pool /run/php/php7.4-fpm.sock

3、h5ai 环境与功能的自检

  • 打开网址 http://haven200.com/_h5ai/public/index.php
    查看 h5ai 当前运行信息,即对当前运行环境与功能模块的自检结果。
    默认密码为空,直接点击登录。
    信息登录界面
  • 查看自我检测结果
    自检结果
  • 绿色,即代表检测通过
  • 红色,存在兼容性问题。如此处的server software(服务器)一栏
  • 红色的no,即此功能缺失,需要安装依赖环境。
    如此处的PDF thumbs(PDF缩略图),提示缺少convert(imagemagick)程序,此功能为 PDF文档生成缩略图。
    解决此问题只需在服务器安装imagemagick
    1
    :~$ apt-get install imagemagick

至此,我们私有云就布置完成了,可以为我们提供服务了。

第三、h5ai 的配置文件

h5ai 的配置文件为_h5ai/private/conf/options.json,修改它对h5ai的功能进行增减。

1、功能自检页面的密码设置

1
2
3
4
5
6
/*
Password hash.
SHA512 hash of the info page password, the preset password is the empty string.
Online hash generator: https://md5hashing.net/hash/sha512
*/
"passhash": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",

passhashpassword hash 的缩写,它的值为密码经过 SHA512 hash 计算后的结果。
cf83e1357....da3e这串字符为空字符的 SHA512 哈希值。
所以我们在上面3、h5ai 环境与功能的自检步骤里不用输入密码即可登录。

如何修改默认密码?

  1. 生成密码的 SHA512 hash 值。
  2. cf83e1357....da3e换成我们刚刚生成的 SHA512 值即可。

配置文件里给出的密码在线生成网址md5hashing.net不安全,它默认保存我们的密码与对应的 Hash 值,这不就是在人为增加地下黑产的密码数据库么?

所以在这里推荐使用 emn178 开源的静态网页版的密码生成工具「online-tools」。
emn178-online-tools

  • 方法一: emn178 提供的在线版,emn178.github.io/online-tools
  • 方法二:将online-tools源码下载下来离线使用。(使用浏览器打开sha512.html)

2、文件列表的设置

文件列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"view": {
"binaryPrefix": false,
"disableSidebar": true,
"fallbackMode": false,
"fastBrowsing": true,
"fonts": ["Ubuntu", "Roboto", "Helvetica", "Arial", "sans-serif"],
"fontsMono": ["Ubuntu Mono", "Monaco", "Lucida Sans Typewriter", "monospace"],
"hidden": ["^\\.", "^_h5ai"],
"hideFolders": false,
"hideIf403": true,
"hideParentFolder": false,
"maxIconSize": 40,
"modes": ["details", "grid", "icons"],
"modeToggle": false,
"setParentFolderLabels": true,
"sizes": [20, 40, 60, 80, 100, 140, 180, 220, 260, 300],
"theme": "default",
"unmanaged": ["index.html", "index.htm", "index.php"],
"unmanagedInNewWindow": false
},
  • disableSidebar: 是否显示左侧边栏(功能栏)
    左侧边栏
  • hidden:此参数可以指定文件夹列表里隐藏哪些文件,可以使用正则来匹配文件名称。
  • ^\\. : 隐藏名称以.开头的文件,如.htaccess,.gitignor等等。
  • ^_h5ai : 隐藏名称以_h5ai开头的文件或文件夹,如_h5ai_h5ai.header.html等等。
  • ^__ : 隐藏名称以__开头的文件或文件夹,如__tmp__aaa__等等。
  • \\.sh$ : 隐藏名称以.sh结尾的文件或文件夹,如bash.sh,scripts.sh等等。
  • ^robots.txt : 隐藏名称以robots.txt开头的文件或文件夹,如robots.txtrobots.txt.bak等等。
  • hideFolders: 是否在文件列表中只显示文件而隐藏文件夹。
  • theme: 默认的文件类型图标,_h5ai/public/images/theme里的每个文件夹都是一种图标,默认带了2套,另一套图标名为comity
  • default 图标
  • comity 图标
  • unmanaged: 如果某个文件夹里包含数组里的文件,则将拒绝显示此文件夹里的所有内容。
  • unmanaged

3、右侧边栏

右侧边栏

1
2
3
4
5
6
7
"info": {
"enabled": false,
"show": false,
"qrcode": true,
"qrFill": "#999",
"qrBack": "#fff"
},
  • enabled:是否显示右侧边栏(详情栏)。
  • show:是否一直显示右侧边栏。
  • qrcode:是否在右侧边栏生成下载链接的二维码以方便移动端下载。

4、文件的选择与下载设置

选择/下载

1
2
3
4
5
6
7
8
9
10
11
"download": {
"enabled": false,
"type": "php-tar",
"packageName": null,
"alwaysVisible": false
},
"select": {
"enabled": true,
"clickndrag": true,
"checkboxes": false
},

选择的设置

  • enabled: 是否允许文件选择,如果禁止文件选择,那么文件就无法下载。
  • clickndrag: 是否允许左键拖动来进行文件多选。
  • checkboxes: 当鼠标悬浮在文件名上时是否显示选择框。
    下载的设置
  • enabled: 是否允许下载文件。
  • type: 选择压缩方式。
  • php-tar: 使用 php 内置功能来下载文件,多线程,可以同时下载多个文件。
  • shell-tar: 使用外部tar程序来下载文件,单线程,同一时间只能下载一个文件。
  • shell-zip: 使用外部zip程序来下载文件,单线程,同一时间只能下载一个文件。
  • packageName: 默认压缩包名称,为null时压缩包名称当前文件夹的名称,
  • alwaysVisible: 下载按钮是否动态显示(只能选中文件后显示)。

5、语言设置

1
2
3
4
5
"l10n": {
"enabled": true,
"lang": "en",
"useBrowserLang": true
},
  • enabled: 是否允许更改界面语言
  • lang: 默认界面语言,en为英文,zh-cn为中文简体,zh-tw中文繁体。
  • useBrowserLang: 是否根据浏览器的语言来自动调整界面语言。

6、Google字体本地化

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
Resources.

Additional script and style tags added to all pages. Paths not beginning
with "http://", "https://" or "/" will be looked up relative to
"_h5ai/public/ext/" (no check for existence).
*/
"resources": {
"scripts": [],
"styles": [
"//fonts.googleapis.com/css?family=Ubuntu:300,400,700%7CUbuntu+Mono:400,700"
]
},

在这里添加的第三方脚本与 CSS 会自动生成<link>标签插入所有页面的<head>中。
默认从_h5ai/public/ext/里加载不以「 http://、https://、/ 」开头的脚本与 CSS 文件。

因为h5ai需要从fonts.googleapis.com下载所需的字体,所以国内的用户加载网页非常缓慢。
解决此问题有三种方法:

  1. 将页面字体更改为常见的字体,如宋体、微软雅黑等等。

  2. fonts.googleapis.com更换为国内的镜像网站。如中科大。

    1
    2
    3
    4
    5
    6
    "resources": {
    "scripts": [],
    "styles": [
    "//fonts.lug.ustc.edu.cn/css?family=Ubuntu:300,400,700%7CUbuntu+Mono:400,700"
    ]
    },
    中科大 对应 谷歌字体
    fonts.lug.ustc.edu.cn --> fonts.googleapis.com
    ajax.lug.ustc.edu.cn --> ajax.googleapis.com
    google-themes.lug.ustc.edu.cn --> themes.googleusercontent.com
    fonts-gstatic.lug.ustc.edu.cn --> fonts.gstatic.com
  3. 将谷歌字体下载到本地。

  • 将谷歌字体的 CSS 文件与字体下载到本地
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/bin/bash

    ext_dir="/var/www/_h5ai/public/ext"

    # 将谷歌字体的 css 下载到 _h5ai/public/ext 下
    curl -sL -H "User-Agent:Mozilla/5.0 (X11; Linux x86_64; rv:69.1) Gecko/20100101 Firefox/69.1" \
    -o "$ext_dir/fonts.google.css" \
    "https://fonts.googleapis.com/css?family=Ubuntu:300,400,700%7CUbuntu+Mono:400,700"

    # 将 css 文件里的所需的字体下载至 _h5ai/public/ext/fonts 下
    # 并将 css 文件里字体的路径更改为 _h5ai/public/ext/fonts
    mkdir -p "$ext_dir/fonts"
    if [ -d "$ext_dir/fonts" ]; then
    cd "$ext_dir/fonts"
    wget $(grep "https" "$ext_dir/fonts.google.css" | awk -F '[()]' '{print $6}')
    sed -i 's|https://.*/|/_h5ai/public/ext/fonts/|' "$ext_dir/fonts.google.css"
    fi
  • options.json将谷歌字体换成本地字体
    1
    2
    3
    4
    5
    6
    "resources": {
    "scripts": [],
    "styles": [
    "fonts.google.css"
    ]
    },

第四、功能扩展

1、页眉与页脚

当分享的文件夹太多时,为了方便区分,可以在文件夹下创建指定名称的文件来提供简介。
这些信息显示在页面文件夹列表的上面或下面。如下面两副图:

文件列表的页眉
文件列表的页脚

页眉

  1. 在文件夹下新建 _h5ai.header.html_h5ai.header.md,以提供此文件夹简介。
  2. 在浏览器中刷新此文件夹的页面即可。

_h5ai.header的内容被添加在<div></div>里。所以页面里不能包含<html><head><body>等标签。

_h5ai.header.html

1
2
3
4
<h1 style="text-align:center">This is a header message</h1>
<p style="text-align:center">
The header is read from file <code>_h5ai.header.html</code>. The content of this file will be enclosed by div tags.
</p>

_h5ai.header.md

1
2
3
# This is a header message

The header is read from file `_h5ai.header.html`.The content of this file will be enclosed by div tags.

页尾

  1. 在文件夹下新建 _h5ai.footer.html_h5ai.footer.md,以提供此文件夹简介。
  2. 在浏览器中刷新此文件夹的页面即可。

_h5ai.footer的内容被添加在<div></div>里。所以页面里不能包含<html><head><body>等标签。

_h5ai.footer.html

1
2
3
4
5
6
<p style="text-align:center">
The footer is read from file _h5ai.footer.html.The content of this file will be enclosed by div tags.
</p>
<p style="text-align:center">
<strong>Note</strong>: all files and directories of name _h5ai* are hidden from the index by default.
</p>

_h5ai.footer.md

1
2
The footer is read from file `_h5ai.footer.html`. The content of this file will be enclosed by div tags.
**Note**: all files and directories of name `_h5ai*` are hidden from the index by default.

2、视频插件

HTML5 视频简介

因为 h5ai 采用原生的 HTML5 标签<video>来播放,所以它只能播放浏览器原生支持的视频格式。

HTML5 原生支持的视频格式

Format Media Type
MP4 video/mp4
WebM video/webm
Ogg video/ogg

各个浏览器支持的视频格式

Browser MP4 WebM Ogg
Internet Explorer YES NO NO
Chrome YES YES YES
Firefox YES YES YES
Safari YES NO NO
Opera YES (from Opera 25) YES YES

因为 HTML5 <video>功能简陋,开发者们就发明了各种视频插件。

  • 添加弹幕、播放速度调整(快速或慢速播放)的 DPlayer
  • 改善视频的传输方式 hls.jsdash.js等等。
  • HLS.js: HTTP Live Streaming, 苹果公司开发的流媒体网络传输协议,也就是我们经常使用的 m3u8 方式,工作原理是把整个视频根据时间(如8s)来进行分割成多个小视频,然后每次只下载一些即可播放。且 HLS 传输基于 HTTP 报文,所以它可以穿过任何允许 HTTP 数据通过的防火墙或者代理服务器。
  • dash.js: Dynamic Adaptive Streaming over HTTP,缩写DASH,也称MPEG-DASH,是一种自适应比特率串流技术,即网络通畅时自动切换至高分辨率的视频,网络卡顿时自动切换至低分辨率的视频。

h5ai 默认的视频播放器

h5ai 默认的视频播放器

DPlayer的播放器

dplayer_hls播放器

h5ai-v0.29.2 添加 DPlayer 插件

国人Pearlulu已经为 h5ai 的 v0.29.2 版本添加了DPlayer插件,并写了个 shell script 以将视频文件转换成hls格式。我们可以直接下载使用。

h5ai_dplayer_hls_0.29.2 下载地址

h5ai-0.29.2+025~a1bb755 添加 DPlayer 插件

这个版本因为作者larsjung更新了许多 JS 基础模块,所以最终由 Node.Js 生成的代码变动比较大。
我们将Pearlulu为 DPlayer 添加的代码微调并添加到h5ai-0.29.2+025~a1bb755版本里。
添加完成后,使用方法同Pearlulu-h5ai_dplayer_hls

  1. options.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /* 添加 hls.js / DPlayer.min.js / DPlayer.min.css */
    "resources": {
    "scripts": [
    "//cdn.jsdelivr.net/npm/hls.js@latest",
    "//github.com/MoePlayer/DPlayer/raw/master/dist/DPlayer.min.js"
    ],
    "styles": [
    "fonts.google.css",
    "//github.com/MoePlayer/DPlayer/raw/master/dist/DPlayer.min.css"
    ]
    },
    ...
    "view":
    ...
    "hidden":
  2. 修改_h5ai/public/js/scripts.js文件
    因为scripts.js被压缩了,所以我们需要先将它解压还原。
    这里我们选择在线解压
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    #!/bin/bash

    _scripts_js="/var/www/_h5ai/public/js/scripts.js"

    if [ $(cat $_scripts_js | wc -l) -lt 3000 ]; then
    echo "请先将 scripts.js 解压"
    echo "解压后的文件行数超过3500行"
    exit 1
    fi


    if [ $(grep "v0.29.2+025\~a1bb755" "$_scripts_js" | wc -l) -eq 0 ]; then
    echo "h5ai的版本不正确"
    exit 1
    fi

    sed -i '/^\s\+e\.stopPropagation(),\s\+e\.preventDefault()$/,/^\s\+var\s*t\s*=\s*e.keyCode;/{
    /^\s\+var\s*\w\s*=\s*e.keyCode;/i\
    if (document.querySelector("#dplayer")) {\
    return;\
    }
    }' "$_scripts_js"

    sed -i '/^\s\+.*<video\s*id="pv-content-vid"/, /^\s\+}(.*"src",\s*n\.absHref)\s*$/{
    /n\.absHref)$/a\
    *\/
    /<video\s*id="pv-content-vid"/i\
    var fileurl = n.absHref;\
    var filepath = fileurl.slice(0,fileurl.lastIndexOf("/"));\
    var filename = fileurl.slice(fileurl.lastIndexOf("/")+1);\
    var filenotype = fileurl.slice(fileurl.lastIndexOf("/")+1,fileurl.lastIndexOf("."));\
    var m3u8 = filepath+"/__"+filename+"__/video.m3u8";\
    var sub = filepath+"/"+filenotype+".vtt";\
    function loadXMLDoc() {\
    var xmlhttp = new XMLHttpRequest();\
    xmlhttp.onreadystatechange = function() {\
    if (xmlhttp.readyState == 4) {\
    if (xmlhttp.status == 200) {\
    loadPlayer(m3u8);\
    console.log("play m3u8");\
    } else if (xmlhttp.status == 404) {\
    console.log("play normal");\
    loadPlayer(fileurl);\
    } else {\
    console.log("other");\
    }\
    }\
    }\
    xmlhttp.open("GET", m3u8, true);\
    xmlhttp.send();\
    }\
    function loadPlayer(url) {\
    document.querySelector("#pv-container").classList.remove("hidden");\
    document.querySelector("#pv-spinner").style.display = "none";\
    var dp = document.createElement("div");\
    dp.id = "dplayer";\
    dp.style.cssText = "width:100%;height:100%";\
    document.querySelector("#pv-container").appendChild(dp);\
    var dplayer = new DPlayer({\
    container: document.querySelector("#dplayer"),\
    autoplay: true,\
    mutex: true,\
    video: {\
    url: url,\
    type: "auto"\
    },\
    subtitle: {\
    url: sub,\
    type: "webvtt"\
    }\
    });\
    s.setLabels([s.item.label],"","");\
    }\
    loadXMLDoc();\
    /*
    }' "$_scripts_js"

修改完成的 h5ai 下载地址:h5ai_dplayer_hls-0.29.2+025~a1bb755.7z
访问密码:8888

第六、php 安全设置

网上关于php.ini的安全设置里,一般为这几项:

  • allow_url_include: Off(不允许包含远程资源文件)
  • allow_url_fopen: Off(不允许打开远程文件)
  • open_basedir/var/www/(网站根目录)
  • disable_functions:
    1
    2
    3
    4
    # 禁止命令执行函数
    disable_functions = system,passthru,exec,shell_exec,popen,pcntl_exec,proc_open
    # 禁止文件操作函数
    isable_functions=chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir,rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chow
  • expose_php: Off(不显示PHP版本信息)
  • display_errors: Off(出错脚本错误时不显示错误信息)
  • log_errors: On(出错脚本错误时记录日志)
  • error_log: /usr/local/apache2/logs/php_error.log(日志文件路径)

因为 h5ai 需要使用 opendir / readdir / scandir / fopen / file_get_contents / exec / passthru 这几个函数,以实现页面功能,所以我们要在disable_functions里面删除它们。

  • file_get_contents:使用此函数读取options.json,如果不允许使用此函数,页面无法显示。
  • fopen:在进行文件下载时使用此函数,但在线查看文件不使用此函数。
  • opendir / readdir / scandir:用来获取多国语言文件,如果禁止此函数,则只能显示默认语言,而无法在线更改。
  • exec / passthru:扩展功能模块需要使用 shell commands,如生成视频/PDF 的缩略图。

Reference:

  • h5ai-Webh5ai-Downloadh5ai-github
  • h5ai-Third Edition-DPlayer-hls
  • DPlayer-WebDPlayer-github
  • hls-wikiPedia
  • dash-wikiPedia
  • sha512-online-tools