经常听说 Kodi,却一直没有时间了解一下,最近整理盒子 应用,突然间想起了这个被很多人称为的神器。
Kodi 是一个强大媒体播放软件,支持 Macos, Windows, Linux, iOs 以及 Android 等众多平台,能在各种手机、电脑、平板以及机顶盒中运行。并且因为 Kodi 强大的媒体播放能力,被很多人成为家庭影院必不可少的应用。Kodi 拥有上千种插件,大大扩展了它的功能,丰富的 Kodi 插件为 Kodi 提供了无数的可能。Kodi 的插件种类很多,有音视频源插件,有字幕插件,有影视内容索引插件,有链接搜索助手插件等等。
从官网上下载 kodi 写下文章时最新的版本为 17.6,选安卓 64 位
Kodi:https://kodi.tv/
备份地址:
比如要在斐讯盒子 T1 中安装 Kodi,首先去盒子的设置 - 高级 - 远程调试打开,如果可能就把盒子的 IP 设置为固定 IP,这个 IP 下面命令中需要使用到。
通过 adb 安装过程如下:
adb connect <ip>
adb shell
adb install /path/to/kodi.apk
http://srp.nu
, 为源取个名字,更多的源地址,可以看下文如何安装源这里有两篇详细的图文教程可以参考:
在安装插件源的时候可能会遇到这几个名词
其实这是 Kodi 的版本代号。
其实看到这里就能看到 Kodi 其实绝大部分的功能都是可代替的,流媒体播放固然强大,但是我更喜欢将资源通过 samba 共享出来,在盒子上使用小白播放器,或者在手机上使用 ES Explorer 访问;Kodi 中的直播流功能强烈的依赖于整理好的直播流,而盒子上的超级直播,HDP 直播等等 应用都能很好的代替直播流;再其次 Kodi 带的 DLNA,AirPlay 投屏功能,其实装一个乐播投屏就能完美解决,更不用说我之前的两台海美迪自身就携带 DLNA,局域网投屏本身问题也不大;最后再是 Kodi 的强大的插件库,虽然强大,却无奈经年无人维护,虽然有无数的插件可以使用,却只有寥寥几个可用。所以最后其实 Kodi 也不是居家必备的良药,对于折腾我始终抱有这样的态度,用起来比怎么用要重要的很多,真正用起来才是一个软件应该提供的,我在盒子上话费时间最多的其实就是播放本地局域网共享的视频,其实本身这个功能并不需要那么强大的 Kodi,当然 Kodi 也可以,至于强大的媒体库,对于我这样看完就删,真的只有经典的才保存的玩家来说,太奢侈。
首先要解释一下 Headless Chrome,通俗的讲就是运行一个没有GUI的Chrome,在 Headless Chrome 出现以前有 PhantomJS ,但是自从 Headless Chrome 出现之后 PhantomJS 活跃度下降,所以维护者就宣布 了停止继续开发。那么 Headless Chrome 能够什么呢?自动化测试,网页截图,网络调试,爬虫等等任务。Google 说在可预见的未来会一直维护。
关于 Headless Chrome 官方有两篇教程
在 Headless Chrome 之前使用 Python 可以结合Chrome推出的 chromedriver 来操作 PhantomJS,那么现在有了Chrome的Headless模式怎么来控制Chrome就是这篇文章要讲的内容。
在此之前几个重要的网址:
Google官方维护了一份协议叫做 Chrome DevTools Protocol,只要是实现了这一份协议就能够编程来控制Chrome,Chrome 自带的开发者工具其实也是这一份协议的实现。
再看几个 Chrome Devtools Protocol 中的概念:
可以用这张图 来显示其结构。
常用API
本地启动 Headless Chrome 可以参考这篇文章。
如果需要使用 Docker 来启动Headless Chrome 也可以使用
docker run -it --rm --name alpine-chrome -p 9222:9222 einverne/alpine-chrome
更加具体的使用介绍可以查看 GitHub
启动之后可以访问: http://localhost:9222/json 来查看是否启动成功。
pychrome 是 Chrome Devtools Protocol 的 Python 实现,其他语言的实现,可以查看这个项目。Chrome官方推荐js/nodejs的实现 chrome-remote-interface 还有 node.js 实现的更高层级的 Puppeteer。
这里主要摘录几个常用的操作
使用 Page.captureScreenshot
def screenshot(browser, url, filename='image.png'):
tab = browser.new_tab()
tab.start()
tab.call_method('Page.navigate', url=url, _timeout=5)
tab.wait(10)
# 截取当前Tab屏幕,结果为图片内容Base64
screen_base64 = tab.call_method("Page.captureScreenshot")
image_data = screen_base64.get('data', '')
with open(filename, 'wb') as f:
f.write(image_data.decode('base64'))
tab.stop()
browser.close_tab(tab)
同样使用 Page
下方法
def print_to_pdf(browser, url, filename='file.pdf'):
tab = browser.new_tab()
tab.start()
tab.call_method('Page.navigate', url=url, _timeout=5)
tab.wait(10)
pdf_data = tab.call_method('Page.printToPDF', landscape=False).get('data')
with open(filename, 'wb') as f:
f.write(pdf_data.decode('base64'))
tab.stop()
browser.close_tab(tab)
def perform_input(browser):
"""向下浏览页面"""
tab = browser.new_tab()
tab.start()
tab.Page.enable()
tab.Page.navigate(url='https://www.douban.com/', _timeout=5)
tab.wait(5)
# 更多 keycode 可以参考 https://msdn.microsoft.com/en-us/library/dd375731(VS.85).aspx
keycode = int(0x22)
tab.Input.dispatchKeyEvent(type='keyDown', windowsVirtualKeyCode=keycode, nativeVirtualKeyCode=keycode)
tab.wait(3)
screen_base64 = tab.call_method("Page.captureScreenshot")
image_data = screen_base64.get('data', '')
with open("keyDown.png", 'wb') as f:
f.write(image_data.decode('base64'))
tab.stop()
browser.close_tab(tab)
获取动态网页的网页内容,等待JS执行完成后获取HTML
def get_html(browser, url):
tab = browser.new_tab()
tab.start()
tab.call_method('Page.navigate', url=url, _timeout=5)
tab.wait(10)
html = tab.Runtime.evaluate(expression="document.documentElement.outerHTML")
tab.stop()
browser.close_tab(tab)
return html['result']['value']
下面这段代码是访问 baidu,输入关键字,并点击搜索按钮,解析搜索结果并打印
def perform_click(browser):
tab = browser.new_tab()
# def loading_finished(**kwargs):
# print "[loading finished]"
#
# # when HTTP request has finished loading
# tab.set_listener("Network.loadingFinished", loading_finished)
tab.start()
# call method
# tab.Network.enable()
tab.Network.enable()
tab.Page.enable()
tab.Runtime.enable()
def dom_content_event_fired(**kwargs):
print "[content] dom content event fired"
tab.DOM.enable()
root = tab.DOM.getDocument()
root_node_id = root.get('root', {}).get('nodeId', '')
# 找到输入框
input_box = tab.DOM.querySelector(nodeId=root_node_id, selector='#kw')
# tab.DOM.setNodeValue(nodeId=input_box, value='hello')
tab.Runtime.evaluate(expression='document.getElementById("kw").value="Chrome"', )
# 找到搜索按钮
search_btn = tab.DOM.querySelector(nodeId=root_node_id, selector='#su')
remote_node = tab.DOM.resolveNode(nodeId=search_btn.get('nodeId', ''))
# 执行点击
tab.Runtime.callFunctionOn(functionDeclaration='(function() { this.click(); })',
objectId=remote_node.get('object', {}).get('objectId', {}))
tab.wait(3)
# 输出结果
html = tab.Runtime.evaluate(expression="document.documentElement.outerHTML")
html_value = html.get('result', {}).get('value', '').encode('utf-8')
soup = BeautifulSoup(html_value, 'html.parser')
l = soup.select('h3 > a')
for result in l:
print result.text
print result['href']
screen_base64 = tab.call_method("Page.captureScreenshot")
image_data = screen_base64.get('data', '')
with open("test.png", 'wb') as f:
f.write(image_data.decode('base64'))
# tab.DOM.performSearch(query='xpath', includeUserAgentShadowDOM=True)
# stop the tab (stop handle events and stop recv message from chrome)
tab.stop()
# close tab
browser.close_tab(tab)
tab.set_listener("Page.domContentEventFired", dom_content_event_fired)
# tab.call_method("Page.reload", ignoreCache=False)
tab.call_method("Page.navigate", url='https://www.baidu.com', _timeout=5)
tab.wait(20)
完整的代码可以参考 GitHub
这篇文章讲述了使用 nodejs 的库 NickJS来爬取网站的要点。
这篇使用 nodejs 的chrome-remote-interface,来抓取网页。
其他
待办事项中一直有一个 Genymotion 模拟器的处理,挂在待办已经好几个月了,终于有时间来找一找 Linux 的安卓模拟器了, Genymotion 曾经很好用,可惜的是如今似乎已经收费。
当然第一想到的就是 Android 开发者官网上提供的官方模拟器,虽然早先被诟病不少,但是似乎更新迭代很快速,现在几乎没有什么特别大的问题。
这是我 Google 出来的第二个结果,他官网的标语就是可以再 GNU/Linux 系统上运行任何 Android 应用程序。根据官网的介绍, Anbox 和 Genymotion 的虚拟化不一样, Genymotion 是在操作系统上完整的虚拟化整个系统,而 Anbox 直接使用宿主机的硬件和内核来运行 Android 程序。目前 Anbox 没有可视化界面可以安装 Apk,可以使用命令 adb install path/to/app.apk
来安装。
Anbox 这个项目还是属于起步阶段,但是体验还是非常不错的,如果遇到安装之后无法打开,或者无法安装 Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]
这种错误时,也不沮丧。
如果要在 Anbox 中安装 arm 和 Google Play 可以参考这个项目
官网地址:https://anbox.io/
很可惜的是他已经不再提供免费的开发者 License。
官网:https://www.genymotion.com/
Grafana 是一个开源的时序性统计和监控平台,或许用这么简单的一句话无法表现 Grafana 的强大。下面是官方列出的 6 大功能,但其实细分起来 Grafana 要强大更多。
更多 Feature 见 官网
非常简单,官网就很详细
默认的账户密码都是 admin
数据源:Grafana 获取数据的配置,支持以下数据源
从 4.3 版本开始支持 MySQL。
每个数据源都有一个独立的查询编辑器,用来动态从数据源获取数据,并展示到 Panel 上。
组织:支持多组织,以支持各种部署模式,包括使用单个 Grafana 实例为多个组织提供服务。
用户、数据源和仪表板都隶属于组织。
用户隶属于某个组织后。就拥有该组织下的所有数据源和仪表板。
用户: Grafana 的帐户。用户可以属于一个或多个组织,并可以通过角色分配不同级别的权限。
在给每个新用户分配组织时,可以指定其角色。角色有:Admin、Editor 、Read Only Editor、Viewer。默认为 Viewer。
仪表板:由一行或多行的面板组成。仪表板可以看做为一个展示单元(栏目),该单元由一行或多行组件构成,每行又可以由一个面板或多个面板组成。
仪表板的时间段可以由仪表板右上方的仪表板时间选择器控制。
行: 仪表板内的逻辑分隔符,用于将面板组合在一起。一行里可以有一列或多列面板。
面板:Grafana 的可视化构建块,所有的数据展示都是在面板上实现的。
每个面板都有各种各样的造型和格式化选项,让你创建完美的图片。
目前有四种面板类型:Graph,Singlestat,Dashlist,Table 和 Text。
可以通过插件安装的方式来扩展面板类型。例如:Pie Chart、Worldmap Panel。
面板上的时间范围通常是仪表板时间选择器中设置的时间范围,但可以通过使用面板特定时间覆盖来覆盖。
查询编辑器:用来显示数据源,并根据数据源查询其包含的指标。
将鼠标放在面板的 Panel Title 位置,然后单击,就会显示出查询编辑,点击 Edit,就显示出查询编辑器了。
Grafana 允许您在查询编辑器中按照它们所在的行来引用查询。如果您向图形添加第二个查询,则可以通过键入#A 来引用第一个查询。这提供了一种简单而方便的方法来构建复合查询。
模板允许更多的交互式和动态的仪表板。可以让你轻松的批量生成同一类型的查询,而不用一个个添加这些 Panel。
可以根据需要自定义变量参数作为查询查询条件,这些变量参数可以是固定的,也可以是通过应用服务提供的数据,还可以是数据源动态查询的数据。
如果是使用的 deb 安装,配置文件路径
/etc/grafana/grafana.ini
此处的数据源与前面提到的数据源不同,此处的数据源用来存储 Grafana 的用户、仪表板等 Grafana 自身的信息。
默认用 SQLite3 数据源,一般也不用修改,也可以修改为 MySQL。
默认的 Grafana 管理员用户名为 admin,密码为 admin,可以进行修改,管理员密码也可以在页面中修改。
admin_user = admin
admin_password = admin
用户设置
# 是否允许新用户注册,默认为 true,如果不想让人随意注册,可以关闭
allow_sign_up = false
# 是否允许用户自己创建组织,默认为 true,为了便于管理,组织统一由管理员创建,不允许普通用户创建
allow_org_create = false
# 是否将新用户自动归属到主要的组织上(组织 id=1)。当设置为 false 时,将自动为该新用户创建一个新的组织。
auto_assign_org = true
# 新用户默认的角色
auto_assign_org_role = Viewer
其他更多见 官网
在 Grafana 中使用 SQL 语句,这部分待补充
Grafana 支持非常多的插件,可以扩展数据源,或者扩展显示,或者用来增强一些功能。
之前用过 https://sm.ms 这个非常好用的图片共享站,界面非常简洁,延迟也低,就想着是不是自己也能够搭建一套这样的服务私用,然而 sm.ms 并没有开源,连其 Android/iOS 客户端也并没有开源,所以只能在网上寻觅代替品,幸而遇到了 linx server。
同样是一个文件分享的站点,通过 Docker 搭建一套服务非常简单,他也能够支持使用 API 上传,界面也同样非常简洁。具体的使用可以参考 Docker 页面
docker pull einverne/linx-server
docker run -p 8080:8080 -d -v /media/meta:/data/meta -v /media/files:/data/files einverne/linx-server
源码中使用的框架和工具:
Workflow 是 iOS 上一款可以实现自动化过程的应用,在 iOS 的框架内是先自定义的流程,比如发送最后拍摄的一张照片到 Instagram 这样的操作。他被 Apple 买下之后就免费开放给所有人使用了。有效的使用 Workflow 能够简化在手机上重复的操作。
Workflow 的功能应该由使用者决定,而不是开发者,所以 Workflow 和编程语言一样,需要学习。
下面先介绍一些 Workflow 能够做到的事情,然后从中去学习 Workflow 制作的过程,从而能够自己完成自动化流程。
将方形图片切割为 9 格图片,Workflow 地址
用 Google 搜索当前粘贴板中的文字或者图片,Workflow
在不启动相机界面的同时拍照,并保存到相册, Workflow 地址 这一个 Workflow 只有一个选项选择前置还是后置摄像头,如果你想要更加复杂的 Workflow 允许你选择连拍数量的可以使用这个
在手机上复制下载链接运行该 Workflow 即可,地址
将横向的全景图切分为多个图片方便分享到 Instagram,地址
支付宝去年推出的分享领红包活动,虽然一开始诚意满满,不过后来就只能每天领 1 毛了,不过 1 毛也是肉嘛,反正 Workflow 点一下够省事,地址
一键打开网易云音乐听歌识曲,地址
下载文件到本地或者 Dropbox
还有更多好用的可以从下面的网站上获取,我平时常用的
更多的 Workflow 可以从下面网址获取
还有一个实用技巧就是实用 Google 的 site 搜索语法,比如想要搜索 Instagram 相关的 Workflow,则可以在 Google 中输入 Instagram site:https://workflow.is/workflows/
。
如果你常用 RSS,那么可以在 InoReader 中订阅该组合包:http://www.inoreader.com/bundle/0014cd63bb38
通过上面的一些例子应该可以看到一些端倪,其实 Workflow 就是把一些常用的操作给编程化,比如最简单的支付宝领取红包,其实就是使用 Safari 打开一个链接。如果再复杂一些比如切割图片,涉及到一些宽高计算,可能还有些 IF 的判断。如果再复杂一些比如 Instagram 图片下载,涉及到了下载网页内容用正则去匹配关心的内容,在比如 Google 以图搜图,涉及到了 Post 请求,然后拼凑网页链接再打开。
最最重要的一点就是网络服务提供足够的 API 能够让 Workflow 能够轻易的调用,如果有翻译服务提供了公开可调用的 API,我们就可以在 Workflow 中,获取粘贴板内容,将内容发送给翻译服务请求结果,并且将结果以通知或者其他形式告诉给用户,这样一个翻译的 Workflow 就实现了。
Squid 是一个Web代理软件,可以轻松的实现 HTTP,HTTPS,FTP 代理,通过缓存常用请求,Squid 能够减少带宽使用,提高响应速度。
sudo apt-get update
sudo apt-get install squid
Squid 的默认配置文件存放在 /etc/squid/squid.conf
下
sudo vim /etc/squid/squid.conf
Squid 的默认端口是 3128,配置文件中可以 http_port 3128
来设置
Squid 默认是不允许任何客户端连接的,通过修改配置允许所有客户端连接
http_access allow all
修改完成后重启代理服务
sudo service squid restart
通常情况下,我们并不使用 docker commit
方法来构建镜像,而是使用 Dockerfile
的定义文件和 docker build
命令来构建镜像。更多 Docker 入门 的内容可以参考之前的文章。
每条指令都会创建一个新的镜像层并对镜像进行提交,Docker 大致上按照下面的流程执行 Dockerfile 中的指令:
常见的命令 RUN
,EXPOSE
之外,Dockerfile 还支持其他很多指令。
CMD 指令用于指定容器启动时要运行的命令,类似 RUN,但是 RUN 指令是指镜像被构建时要运行的命令,而 CMD 是指定容器被启动时要运行的命令。运行的命令放在一个数组结构中
docker run
命令可以覆盖 CMD 指令
如果 Dockerfile 有多个 CMD 指令,只有最后一个会生效。
CMD 指令有很多种格式,最常见,也是官方推荐的格式是:
CMD ["executable", "param1", "param2", ...]
ENTRYPOINT 指令和 CMD 指令非常类似,之前说过 CMD 指令会被 docker run 命令覆盖,而 ENTRYPOINT 指令提供的命令不容易在启动容器时被覆盖。实际上, docker run 命令行中的指定的任何参数都会被当做参数再次传递给 ENTRYPOINT 指令中指定的命令。
ENTRYPOINT 可以让容器以应用程序或者服务的形式运行。
ENTRYPOINT 也有很多使用方式,比较推荐的是:
ENTRYPOINT ["executable", "param1", "param2" ]
WORKDIR 指令用来在从镜像创建一个新的容器时,在容器内部设置一个工作目录,ENTRYPOINT 或 CMD 指定的程序会在该目录下执行。
可以通过 -w
参数在运行时覆盖工作目录
sudo docker run -it -w /var/log ubuntu pwd
ENV 指令用来在镜像构建过程中设置环境变量,新的环境变量能在后续任何 RUN 指令中使用。
ENV RVM_PATH /home/rvm
同样也可以使用 -e
参数赖在运行时添加环境变量。
USER 指令用来指定镜像会以什么用户去运行
USER nginx
镜像会以 nginx 用户去运行,同样也可以使用 -u
选项来覆盖该指令
VOLUME 指令用来像基于镜像创建的容器添加卷,一个卷可以存在一个或者多个容器内的特定目录,这个目录可以绕过联合文件系统,提供共享数据或者对数据持久化功能。
卷功能让我们可以将数据,比如源代码,数据库等其他持久化内容添加到镜像中,而不是预先将这些内容提交到镜像内部,卷允许我们在多个容器间共享内容,可以利用此功能测试容器和内部应用代码,管理日志,处理内部数据库等等。
VOLUME ["/opt/project"]
该指令会基于此镜像创建的任何容器创建一个名为 /opt/project
的挂载点。
ADD 指令用来将构建环境下的文件和目录复制到镜像中。
ADD 指令通过目的地址参数末尾的字符来判断文件源是目录还是文件,如果目标地址以 /
结尾,那么 Docker 就认为源地址指向的是一个目录,如果目的地址不是 /
结尾,那么源地址就是文件。
COPY 指令和 ADD 很类似,根本不同是 COPY 只关心在构建上下文中复制本地文件,而不会去做文件提取和解压的工作。
如果目的位置不存在, Docker 会自动创建所需要的目录结构
ONBUILD 指令能为镜像添加触发器,当一个镜像被用作其他镜像的基础镜像时,该镜像中的触发器会被执行。触发器会在构建过程中插入新指令,我们认为这些指令是紧跟 FROM 之后执行的。触发器可以是任何构建指令。
mysql 是 Docker 和 MySQL 官方提供的一个镜像。
拉取镜像
docker pull mysql
启动镜像
docker run --name first-mysql -e MYSQL_ROOT_PASSWORD=password -d mysql:5.7
这样就创建了一个名为 first-mysql
的 mysql 5.7 实例。
docker exec -it first-mysql bash
日志
docker logs first-mysql
当启动 mysql 容器时,可以传入环境变量来改变容器的配置:
MYSQL_ROOT_PASSWORD
:必须。用于设置MySQLroot用户的密码MYSQL_DATABASE
:可选。用于指定镜像启动容器时要创建的数据库。如果提供了用户/密码,则会将该用户做为此数据库的超级用户。MYSQL_USER
,MYSQL_PASSWORD
:可选。用于创建一个新用户并设置密码。MYSQL_ALLOW_EMPTY_PASSWORD
:可选。设置为yes时,则可以使用空密码登录MYSQL_RANDOM_ROOT_PASSWORD
:可选。设置为yes时会为root用户设置一个随机密码(使用pwgen),所生成的随机密码会被输出到stdoutMYSQL_ONETIME_PASSWORD
:可选。为root用户指定一个一次性密码,该密码会在用户首次登录时强制修改mysql 镜像创建容器时,数据库存储会以下面两种方式存储:
使用外部数据卷时,假设宿主机有 /my/data/ 目录,可以使用 -v
选项来讲宿主机挂载到容器:
docker run --name mysql -v /my/data/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password -d mysql:5.7
数据备份
docker exec first-mysql sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > /some/path/on/your/host/all-databases.sql
官方的镜像中导出的是默认 3306 端口,其他容器可以使用 --link
参数来连接mysql容器
docker run --name other-app --link first-mysql:mysql -d other-application-use-mysql
任何服务器监控是一项很重要的事情,以前在 VPS 上一直使用 nodequery ,虽然设置方便简洁,但是数据不够详细也是他很大问题,所以经过一番寻找又发现了这个 [[Netdata]]。下面是 netdata 一系列的特性
官方提供了一些列的 demo : https://my-netdata.io/
按照下面的教程安装:
现在官方也提供了 Docker 的方式安装,更加方便。
upstream backend {
server 127.0.0.1:19999;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
index index.html index.htm index.nginx-debian.html;
server_name status.einverne.info;
auth_basic "Protected";
auth_basic_user_file passwords;
location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_set_header Connection "keep-alive";
proxy_store off;
}
}
主要配置如上,可以加上一个密码保护,具体 Nginx 的配置 可以参考链接。
htpasswd -c -d /etc/nginx/passwords yourusername
这行命令中的 passwords
就是之前 Nginx 配置中的 auth_basic_user_file
后的参数,通过该选项设置用户名密码。
netdata 是不提供卸载脚本的,如果要在系统中删除,可以使用如下
killall netdata
apt-get remove zlib1g-dev gcc make git autoconf autogen automake pkg-config
rm -Rf /usr/sbin/netdata
rm -Rf /etc/netdata
rm -Rf /usr/share/netdata
rm -Rf /usr/libexec/netdata
rm -Rf /var/cache/netdata
rm -Rf /var/log/netdata
rm -Rf /opt/netdata
userdel netdata