## 1. Project Overview & Quickstart (qishibo/LICENSE)

# LICENSE

Open-source repository qishibo/LICENSE

### Repository Details
- **Repository:** [qishibo/LICENSE](https://github.com/qishibo/LICENSE)
- **Primary Language:** Code

*Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.*

## 2. Official Technical Reference & Guides (qishibo/qishibo.github.io)

## File: README.md

# Github Pages Blog

## [博客地址 qii404.me](https://qii404.me)

---

## File: _config.yml

paginate: 20
paginate_path: "page:num"
plugins:
    - jekyll-paginate
    - jekyll-sitemap

markdown: kramdown
highlighter: rouge

kramdown:
    input: GFM
    syntax_highlighter: rouge
    auto_ids: true

syntax_theme: github

comments: disqus
domain: https://qii404.me

name: qii404|齐士博博客
author:
    name: 齐士博

---

## File: about.md

---
layout: post
title: qii404 | 齐士博博客
comments: 1
keywords: 齐士博,qii404,php,javascript,qishibo,码农,前端,技术,IT
menu: about
---

### 我是谁？
> 不要管我是谁

---

### 我来自哪里？
> 不要管我来自哪里

---

### 我在哪工作？
> 也别管我在那里工作

---

### 我只告诉你：
>
  - 靠写码吃饭
  - PHP是我的好基友
  - Redis和我一个寝室
  - 我和鸟哥喝过酒
  - 前端也很6的哦

### 还有：
>
  - 没钱
  - 喜欢踢球
  - 喜欢骑车
  - 我很帅

---

### 动心了？
>
  -  [qii404#126.com](mailto:qii404@126.com)
  -  [微博 @齐士博](http://weibo.com/shiboooo?is_hot=1)
  -  [RSS](/feed.xml)

---

## File: bdunion.txt

747cd3efab198045eadabb84e3484597

---

## File: params.json

{
  "name": "Qishibo.GitHub.io",
  "tagline": "blog",
  "body": "### Welcome to GitHub Pages.\r\nThis automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here [using GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/), select a template crafted by a designer, and publish. After your page is generated, you can check out the new `gh-pages` branch locally. If you’re using GitHub Desktop, simply sync your repository and you’ll see the new branch.\r\n\r\n### Designer Templates\r\nWe’ve crafted some handsome templates for you to use. Go ahead and click 'Continue to layouts' to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved.\r\n\r\n### Creating pages manually\r\nIf you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.\r\n\r\n### Authors and Contributors\r\nYou can @mention a GitHub username to generate a link to their profile. The resulting `<a>` element will link to the contributor’s GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out our [documentation](https://help.github.com/pages) or [contact support](https://github.com/contact) and we’ll help you sort it out.\r\n",
  "note": "Don't delete this file! It's used internally to help with page regeneration."
}

---

## File: robots.txt

User-agent: *
Disallow: /google4a8e7f3e3fa175ee.html
Disallow: /baidu_verify_pmXEoAlnoR.html
Sitemap: https://qii404.me/sitemap.xml

---

## File: todolist.txt

https://segmentfault.com/q/1010000009752664 基于
phpredis的redis scan全库key遍历


/usr/lib/systemd/system/nginx.service  PrivateTmp=false tmp目录的坑

phpstorm快捷键设置 以及插件推荐

jenkins搭建 使用

cayley教程 基于 http://localhost:64210/docs/GremlinAPI  cayley mongo示例
    - ./cayley init -config cayley.cfg
    - ./cayley load -config cayley.cfg -quads data/testdata.nq
    - ./cayley http -config cayley.cfg


================================

console颜色 https://www.cnblogs.com/clover-toeic/p/4031618.html
https://www.jianshu.com/p/bba963125f1a

========================
参考 https://www.jianshu.com/p/ecd41f6986cc
scrapy scrapyd https://github.com/DormyMo/SpiderKeeper 使用 爬虫抓取

=======================
git 全局钩子 clone时自动把模板hook复制到.git文件夹中
https://www.liaohuqiu.net/cn/posts/git-global-hooks/

=======================
redis stream新数据格式
类似kafka
https://zhuanlan.zhihu.com/p/37591626
===================================
使用dnsmap加速Linux本地网络访问
/etc/dnsmasq.conf:
resolv-file=/etc/resolv.dnsmasq
listen-address=127.0.0.1 仅监听本地  多个ip逗号隔开


/etc/resolv.dnsmasq：
nameserver 223.5.5.5
nameserver 223.6.6.6
nameserver 8.8.8.8
nameserver 8.8.4.4

dnsmasq --test 检测

sudo service dnsmasq start

/etc/resolv.conf:
nameserver 127.0.0.1

​dnsmasq还是有一些功能，bind很难或者不能做到的。比如：

* 强制解析某些域名：
  address=/some.domain/1.2.3.4
  有一些​屏蔽广告的方案会这么用，将所有常见的广告域名都解析到127.0.0.1。如果用bind做，则需要为每一个域名配置一个zone文件，不仅配置麻烦，而且可能带来一些副作用。

* 指定上游DNS服务器使用哪个dns解析
​  server=/google.com/8.8.8.8
  许多防DNS污染的方案会这么用。例如在网关上配翻墙，国内流量走ISP出去，国外流量走VPN，8.8.8.8走VPN。一般DNS这一块会将可能被污染的域名都转发给8.8.8.8或者其他国外的DNS服务器​，而其他域名转发给ISP或者114.114.114.114这种国内的DNS，以提高网站访问速度。

* 屏蔽一些虚假解析：
  bogus-nxdomain=202.106.199.34
  许多运营商的DNS很流氓，会将一些不存在的域名解析到他们的广告服务器地址。用dnsmasq的这个选项，当dnsmasq发现某个域名返回的ip是指定的ip时，就对客户端返回“该域名不存在”的信息。

​此外，在使用dnsmasq时，有一些参数可以提高使用体验。例如：
＊no-negcache: 当由于某种原因，某个域名解析失败后，dnsmasq会默认缓存这个结果，也就是接下来的几分钟里客户端再请求这个域名都会得到name not resolved。打开这个选项后，dnsmasq不会缓存没有结果的解析，效果会好很多。
＊cache-size：这个值默认时150，有点小，调大一点，比如到1000也会好很多。


参考 http://blog.51cto.com/longlei/2065967 最有效的是直接在可视化界面配置中修改dns

另一种解决被重置的方法 https://askubuntu.com/questions/623940/network-manager-how-to-stop-nm-updating-etc-resolv-conf：
sudo vim /etc/NetworkManager/NetworkManager.conf
And add this to the [main] section:

dns=none

看/etc/resolv.conf里面注释是啥 决定用那种方法
```
# Generated by NetworkManager 用dns=none
```

dig qii404.me @127.0.0.1测试结果

Tip 本机只有dns=none方法成功了。。。

----：
deepin /etc/NetworkManager/system-connections/41.88 该文件相当于右侧设置栏中配置的数据 改了这个 每次就是新的了



有个坑

===============================
/etc/cron.d新增文件时不执行 需要重启cron才行。。。
另如果报错 Error: bad username; while reading /etc/cron.d/qii
需要指定执行用户才行 如10 10 * * * root /root/qii/credit.sh >> /root/qii/log_credit.log
=============================

phalcon 分表使用
$o = \Phalcon\Di::getDefault()->getModelsManager();
      $o->__destruct();
==================================
linux 文件同步
坚果云 客户端 每月限制上传1G 下载3G
dropbox 客户端 2G
apt install onedrive 命令行


onedrive会报错
● onedrive.service - OneDrive Free Client
   Loaded: loaded (/usr/lib/systemd/user/onedrive.service; disabled; vendor preset: enabled)
   Active: failed (Result: signal) since Wed 2018-12-19 10:48:04 CST; 6s ago
     Docs: https://github.com/skilion/onedrive
  Process: 27588 ExecStart=/usr/bin/onedrive -m (code=killed, signal=SEGV)
 Main PID: 27588 (code=killed, signal=SEGV)

12月 19 10:48:04 qii onedrive[27588]: 0x56314c3bfda3 ???
12月 19 10:48:04 qii onedrive[27588]:         ???:0
12月 19 10:48:04 qii onedrive[27588]: 0x7ff84f305a86 __libc_start_main
12月 19 10:48:04 qii onedrive[27588]:         ???:0
12月 19 10:48:04 qii onedrive[27588]: 0x56314c3b7b99 ???
12月 19 10:48:04 qii onedrive[27588]:         ???:0
12月 19 10:48:04 qii onedrive[27588]: 0xffffffffffffffff ???
12月 19 10:48:04 qii onedrive[27588]:         ???:0
12月 19 10:48:04 qii systemd[6274]: onedrive.service: Main process exited, code=killed, status=11/SEGV
12月 19 10:48:04 qii systemd[6274]: onedrive.service: Failed with result 'signal'.

因为vim编辑产生的～后缀文件导致，建议不要用vi编辑，编辑器产生变动即可
~/.config/onedrive/config为默认配置
或者在~/.config/onedrive/config 文件中写入如下即可
# Directory where the files will be synced
sync_dir = "~/OneDrive"
# Skip files and directories that match this pattern
skip_file = ".*|~*|*~"
==================================================
Neo4j 安装和使用

===============================================
nw.js  https://waylau.com/nwjs-quick-start/
实现个redis-cli客户端


============================================
git lfs 大文件支持
# gitlab_rails['lfs_enabled'] = true
# gitlab_rails['lfs_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/lfs-objects"

apt install git-lfs
git lfs install

git lfs track "*.png"
git add .gitattributes
such as '*.png filter=lfs diff=lfs merge=lfs -text'

git lfs ls-files

=================================================
PhpSpreadsheet读取excel日期字段格式时，返回的而是数字
1、$reader->setReadDataOnly(true); 不能时readonly模式
2、$sheet->getCell('I' . $i)->getFormattedValue() 而不是 $sheet->getCell('F' . $i)->getValue()

================================================
redis 可视化客户端 redis desktop manager收费之后的另一种选择
==============================================

---

## File: tools.md

---
layout: post
title: qii404 | 实用小工具
keywords: 在线小工具, 齐士博博客, qii404
menu: tools
---

### Yaml 文件在线解析

> [tools/yaml-parse](/tools/yaml-parse)

### Github Release 下载分析

> [github-release-download-statistics](/github-release-statistics/)

---

## File: _posts/2015-04-23-hello-world.md

---
layout: post
title: hello world
keywords: hello world, 第一份github-pages博客, jekyll
comments: 1
tags: [test]
---

第一篇基于github-pages的博客，jekyll构建，this is a test~

至于怎么用githug-pages构建博客，去搜吧，网上攻略很多的，比如阮一峰的那篇[搭建一个免费的，无限流量的Blog----github Pages和Jekyll入门](http://www.ruanyifeng.com/blog/2012/08/blogging_with_jekyll.html)

哈哈

---

## File: _posts/2015-05-01-gitconfig.md

---
layout: post
title: 一份好用的gitconfig
keywords: gitconfig, git, git高亮, git配置
comments: 1
code: 1
tags: [git]
---

推荐一份好用的gitconfig，我是直接在家目录下写了个.gitconfig文件，内容如下：

`不过要记住，user里面的name和email要换成你自己的啊.`

```ini
# git的alias设置，比如想提交的时候直接git ci -m'xxx'
[alias]
    co = checkout
    ci = commit
    br = branch
    st = status
    di = diff

# 默认的用户名和邮箱[改成你自己的啊...]
[user]
    name = qii404
    email = qii404@126.com

# 默认的高亮设置，比如git diff、status、branch 的时候会高亮现实
[color]
    status = auto
    branch = auto
    diff = auto
    interactive = auto

# 如果你用用户名密码的方式进行认证，可以加上这个，这样只会在第一次的时候输入密码，以后就会记住了
[credential]
    helper = store

```

---

## File: _posts/2015-08-25-date-recover.md

---
layout: post
title: DiskGenius | 数据恢复
comments: 1
keywords: DiskGenius, 数据恢复, 免费数据恢复
tags: [software]
---

内天，，我的磁盘废了。。

糟糕的一天，，，shit。。

给我的`windows`装了一个`ubuntu`，，，

结果，`tm`的不知道咋选的把我的盘给格式化了。。老子那么多照片啊。。

网上找啊找，找啊找，各种数据恢复的不是要钱就是`tm`的不好用。。

-----

不过终于找到了一个`DiskGenius`的破解版，**真正免费的破解版，，数据恢复，磁盘处理、分区，，简直是万能**。。




仅以此软件，贡献给那些数据曾经搞废了的人。。

哈哈我这是什么心态~

不唠叨了，贴上地址吧 [云盘](https://pan.baidu.com/s/1jHWXei6)

---

## File: _posts/2015-12-07-charles-regist.md

---
layout: post
title: charles 3.11 破解方法
comments: 1
keywords: charles3.11破解, charles, 破解方法
tags: [software, hacker]
---

Charles未破解版本，每次打开都要`relay 10秒钟`，重要的是，隔一段时间还会弹出一个提示框，说【*你还没注册，快点去买正版吧。。。*】

> 是不是很坑。。

给你一个文件 [charles.jar](https://pan.baidu.com/s/1nuOd0zn)

**Mac**用户放到`/Applications/Charles.app/Contents/Java/`目录下覆盖原来的charlse.jar即可，当然为了保险你需要先备份一下原来的文件。

**windows**放到安装目录`lib`文件夹下覆盖即可

看看效果图【哈哈，有没有暗爽~】

---

## File: _posts/2016-01-22-icdiff.md

[Binary file]

---

## File: _posts/2016-05-23-supervisor.md

---
layout: post
title: Supervisor 进程管理
keywords: supervisor, 进程管理， linux进程控制
comments: 1
code: 1
tags: [supervisor, opensource]
---

使用Linux、 Mac系统的同学们一定遇到过这样的问题：我要打开一个进程，或者说跑一个脚本，但是呢，这个脚本有些不稳定，时不时的抽一下风，自己可能就会挂掉了，然后我要手动重启。当然一次两次我手动重启还是可以接受的，但当我要启动的这个程序要作为Service服务运行的时候，那么我就必须得一直得ps看着他还在不在，不在了就得人工参与启动服务。

这样的日子过够了之后，一些聪明的程序猿可能就会想一些法子了，比如自己写一个daemon守护进程，一直ps盯着程序是否还在，如果不在就启动，否则不处理。类似下面的shell脚本，通过crontab的方式定时检测

```shell
exists=`ps ux |grep 'program.xxx'`

if [$exists = '' ];then
    echo 'not running'
    run program
else
    echo 'running, exit'
fi
```

上面的方法简单经济实惠，哼哼，感觉能满足大部分常规检测的需求，但今天我要说的是一个专业的进程守护工具， **Supervisor**，这货在基hub开源，由Python实现，地址 [https://github.com/Supervisor/supervisor](https://github.com/Supervisor/supervisor)

------

## 安装
4种方法

-   `pip install supervisor`  [常规python安装]
-   `apt-get install supervisor` [Linux系统 ubuntu]
-   `yum install supervisor` [Linux系统 cent]
-   `brew install supervisor` [Mac OS]

-----

## 使用
> 安装好了supervisor之后，系统主要会多出两个命令，`supervisord` `supervisorctl`，由于supervisor是类似CS的，`supervisord`其实就是服务端，真实的控制着程序运行，而`supervisorctl`就是客户端，可以连接服务端进行控制。


### 配置文件
很贴心，怕你不会写配置，程序帮你写好了一份样板，如下使用
`echo_supervisord_conf` 这个命令会输出配置样例，我们把输出写到文件中

```shell
echo_supervisord_conf > /usr/local/etc/supervisord.ini
```

之后`/usr/local/etc/supervisord.ini`文件中内容如下，忽略了一些不常用的配置项

```ini
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
;  - Shell expansion ("~" or "$HOME") is not supported.  Environment
;    variables can be expanded using this syntax: "%(ENV_HOME)s".
;  - Comments must have a leading space: "a=b ;comment" not "a=b;comment".

[unix_http_server]
file=/usr/local/var/run/supervisor.sock   ; (the path to the socket file)
;chmod=0700                 ; socket file mode (default 0700)
;chown=nobody:nogroup       ; socket file uid:gid owner
;username=user              ; (default is no username (open server))
;password=123               ; (default is no password (open server))

;[inet_http_server]         ; inet (TCP) server disabled by default
;port=127.0.0.1:9001        ; (ip_address:port specifier, *:port for all iface)
;username=user              ; (default is no username (open server))
;password=123               ; (default is no password (open server))

[supervisord]
logfile=/usr/local/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB        ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10           ; (num of main logfile rotation backups;default 10)
loglevel=info                ; (log level;default info; others: debug,warn,trace)
pidfile=/usr/local/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false               ; (start in foreground if true;default false)
minfds=1024                  ; (min. avail startup file descriptors;default 1024)
minprocs=200                 ; (min. avail process descriptors;default 200)
;umask=022                   ; (process file creation umask;default 022)
;user=chrism                 ; (default is current user, required if root)
;identifier=supervisor       ; (supervisord identifier, default is 'supervisor')
;directory=/tmp              ; (default is not to cd during start)
;nocleanup=true              ; (don't clean up tempfiles at start;default false)
;childlogdir=/tmp            ; ('AUTO' child log dir, default $TEMP)
;environment=KEY="value"     ; (key value pairs to add to environment)
;strip_ansi=false            ; (strip ansi escape codes in logs; def. false)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///usr/local/var/run/supervisor.sock ; use a unix:// URL  for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris              ; should be same as http_username if set
;password=123                ; should be same as http_password if set
;prompt=mysupervisor         ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history  ; use readline history if available

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

; ......

[include]
files = /usr/local/etc/supervisor.d/*.ini
```

我们要改的主要是如下部分：

`1、浏览器登录管理`

```ini
; http服务地址，我们可以在浏览器中进行进程管理
; 去掉注释改成下面的样子即可

[inet_http_server]         ; inet (TCP) server disabled by default
port=127.0.0.1:9001        ; (ip_address:port specifier, *:port for all iface) ; 服务器监听地址
username=user              ; (default is no username (open server)) ; 认证登录时的用户名
password=123               ; (default is no password (open server)) ; 认证登录时的密码
```

`2、运行程序配置`

可以看到主配置最后include了`supervisor.d`文件夹下的`*.ini`配置文件，这些被引入的配置其实就是你要启动和守护的程序配置，都要写在这个文件夹里，稍后还会详细说明

```ini
[include]
files = /usr/local/etc/supervisor.d/*.ini
```


### 运行Supervisor主程序

```shell
# -c 是指定了配置文件的位置
supervisord -c /usr/local/etc/supervisord.ini
```

> 如果控制台没有输出，并且ps有supervisor进程在，恭喜，你已经成功启动了！



### 加入你要守护的程序配置

在`/usr/local/etc/supervisor.d/`文件夹中增加你的程序配置，最好的是一个程序一个配置文件，易于管理，像这样

```ini
# filename: jenkins.ini 控制jenkins进程的配置

# 程序概述名
[program:jenkins-daemon]

# 程序运行时显示的名字 默认这样即可
process_name=%(program_name)s_%(process_num)02d

# 下面command配置是你的启动命令 如 /usr/bin/php /users/qii404/xxx.php ，绝对路径，不支持$HOME类似的变量
command=/usr/bin/java -jar /Users/qii404/jenkins/jenkins.war

# 该程序是否随着supervisor启动时自动启动
autostart=true

# 挂掉之后自动重新启动
autorestart=true

# 启动用户
user=baidu
numprocs=1
redirect_stderr=true

# 程序日志文件
stdout_logfile=/Users/qii404/jenkins_daemon.log
```



### 通过supervisorctl进入控制台

1. 执行`supervisorctl`，根据提示输入你在`/usr/local/etc/supervisord.ini`配置中的用户名和密码
2. 执行`help`可以找到你需要的命令列表
3. 此时，你新加入的jenkins配置还没剩生效，要手动引入，`reread`命令重新载入配置
4. `avail`可以看到你加入的程序和状态，如果看到了新加的jenkins，说明配置读取成功
5. `reload`重启程序，此时也会把你的jenkins启动
6. `status`可以看到目前正在运行的程序

```shell
supervisor> status
jenkins-daemon:jenkins-daemon_00                   RUNNING   pid 45418, uptime 0:10:23
```

如果看到上面输出，恭喜，你的jenkins已经被启动了~

如果想关闭，在控制台执行`stop jenkins-daemon:jenkins-daemon_00`即可



### 通过可视化界面控制
上面配置中还说了一个`port=127.0.0.1:9001 `的配置项起始就是为了浏览器控制台准备的，打开浏览器输入`127.0.0.1:9001`， 输入刚刚配置中的用户名和密码：





我们可以方便的进行重启、停止等操作，甚至还能监控程序的日志输出哦~

---

## File: _posts/2016-06-06-sendfile.md

[Binary file]

---

## File: _posts/2016-06-22-simple-html-dom.md

[Binary file]

---

## File: _posts/2016-07-22-wkhtmltopdf.md

---
layout: post
title: wkhtmltopdf 转换HTML为PDF时不显示中文
comments: 1
code: 1
keywords: wkhtmltopdf使用, wkhtmltopdf不显示中文, 中文乱码
tags: [opensource]
---

最近在使用开源工具 wkhtmltopdf 把 HTML 文件转换为 PDF，这货功能很强大，自带webkit浏览器内核，可以完全模拟浏览器特性，解析css样式、运行javascript进行dom渲染，从而完美的将HTML内容渲染为PDF，就像在浏览器上截屏一样，神奇的一笔

## 工具介绍

wkhtmltopdf

> 主页和wiki在官网 [http://wkhtmltopdf.org/](http://wkhtmltopdf.org/){:target="_blank"}，同时也在github上开源，是个星星大户
仓库地址 [https://github.com/wkhtmltopdf/wkhtmltopdf](https://github.com/wkhtmltopdf/wkhtmltopdf){:target="_blank"}

官网的宣传图



## 用法简介

基本用法很简单，只需要一个本地html文件

```shell
# 将本地 source.html 文件转换为 target.pdf
wkhtmltopdf source.html target.pdf

# 如有乱码需指定编码
wkhtmltopdf --encoding utf-8 source.html target.pdf
```

也可以直接根据 **url** 进行转换

```shell
# 将百度首页转换为 target.pdf
wkhtmltopdf http://www.baidu.com target.pdf

# 如有乱码需指定编码
wkhtmltopdf --encoding utf-8 http://www.baidu.com target.pdf
```



同时还有许多其他高级用法，细节参考`wkhtmltopdf -H` 或者官方wiki [http://wkhtmltopdf.org/usage/wkhtmltopdf.txt](http://wkhtmltopdf.org/usage/wkhtmltopdf.txt)

### 为pdf生成脚注

```shell
# 为pdf生成脚注
wkhtmltopdf --footer-left '这句话会在每页pdf的左下角' source.html target.pdf

# 为pdf生成脚注 同时设置字体
wkhtmltopdf --footer-left '这句话会在每页pdf的左下角' --footer-font-name 'Arial' source.html target.pdf
```

### 设置缩放级别

> Mac 下这个问题比较突出，有时候缩放的太小，加了 --disable-smart-shrinking 参数也不生效
Linux 下目前还没有缩放问题，所以可以忽略该项

```shell
# --zoom 指定缩放级别，默认1，自测 Mac 下 13.3 还算合适 打印出的 pdf 大小适中
wkhtmltopdf --zoom 13.3 source.html target.pdf
```

### 为pdf生成页码

```shell
# 为pdf在右下角生成页码  [page] 可以认为是程序会自动转换的特殊参数，他会转化为页码 footer-right让他右侧显示
wkhtmltopdf --footer-right [page] source.html target.pdf
```

> 上面实现页码时使用了`[page]`这个内建参数，凡是在头部、脚步需要该参数的地方，[page]参数都能被自动解析成页码，也就是说，凡是`--header-*` `--footer-*`的地方能可以用该参数，这样类似的参数还有很多，具体可以参考wiki

```shell
   # 我只把我用过的标注上，其他的没尝试过
   # 页码
   * [page]       Replaced by the number of the pages currently being printed
   # 日期
   * [date]       Replaced by the current date in system local format
   # 时间
   * [time]       Replaced by the current time in system local format
   # 文档标题
   * [title]      Replaced by the title of the of the current page object

   * [frompage]   Replaced by the number of the first page to be printed
   * [topage]     Replaced by the number of the last page to be printed
   * [webpage]    Replaced by the URL of the page being printed
   * [section]    Replaced by the name of the current section
   * [subsection] Replaced by the name of the current subsection
   * [isodate]    Replaced by the current date in ISO 8601 extended format
   * [doctitle]   Replaced by the title of the output document
   * [sitepage]   Replaced by the number of the page in the current site being converted
   * [sitepages]  Replaced by the number of pages in the current site being converted
```


### 自定义头部&脚注

> 如果要显示的头部尾部内容很多，或者逻辑很复杂，我们还可以将要显示的内容放进 footer.html 、 header.html 中用js去执行渲染结果，最终作为页面的页头或者页脚展示

```shell
# 脚注用一个html来实现 他会把 footer.html 进行dom渲染 然后放在脚注
wkhtmltopdf --footer-html './footer.html' source.html target.pdf

# 同理上面的也适用于头部注释，把参数改为header-xxx即可
wkhtmltopdf --header-html './header.html' source.html target.pdf

```

> footer.html 内容如下，原理也很简单，程序在生成每一页pdf的时候，会分配给每页一个唯一的url，并且在url中加上page time等参数，那么footer.html里的js就能获取url中的参数，进行html渲染，最终加进pdf的脚部

```html
<!--将下面的保存为footer.html 然后 wkhtmltopdf --footer-html './footer.html' source.html target.pdf-->
<!--原理很简单，程序再生成每一页pdf的时候，会自动给加上page time等参数，下面的js获取url中的参数进行html渲染，然后加进pdf的脚步即可-->

<html>
<head>
  <script>
  function subst() {
    var vars={};
    var x=window.location.search.substring(1).split('&');
    for (var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
    var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
    for (var i in x) {
      var y = document.getElementsByClassName(x[i]);
      for (var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
    }
  }
  </script></head><body style="border:0; margin: 0;" onload="subst()">
  <table style="border-bottom: 1px solid black; width: 100%">
    <tr>
      <td class="section"></td>
      <td>
        当前时间：<span class="time"></span>
      </td>
      <td style="text-align:right">
        第 <span class="page"></span>页 共 <span class="topage"></span>页
      </td>
    </tr>
  </table>
  </body>
</html>

```

## Tips:

wkhtmltopdf 的强大还不只这些，还支持缩放 ，`禁止渲染js`，`禁止图片渲染`，`cookie`，`dpi设置`，`svg渲染`，`代理`等等等等...简直就是一个原生浏览器啊...

---

## 问题解决

好了，说了这么半天功能，回归正题，说说我遇到的那个坑，在 Cent 环境下转换时，中文完全不显示！而在其他另外一台机器上，一样的语句去执行，中文就能正常显示！

> 后来查阅，原来程序在转换的时候需要对应的字体，以便用于文字渲染，但当你需要转换的html中包含本机环境没有的字体时，程序就会默认转为**宋体**来渲染，这其实应该是WebKit内核的设定，所以说，`你的机器上至少应该有宋体！！`，否则的话中文其实是没法显示的。

那么 Linux 下怎么安装宋体呢，也很简单

>  1. 下载宋体字体 对应文件名其实是`simsun.ttc` or `simsun.ttf`
2. 放到`/dev/share/fonts/`下，或者`~/.fonts/`下
3. 再进行 pdf 转换试试看~

---

## File: _posts/2016-07-29-ssh-clone-session.md

[Binary file]

---

## File: _posts/2016-09-26-php-exec.md

[Binary file]

---

## File: _posts/2016-12-30-hhvm-create-function.md

[Binary file]

---

## File: _posts/2017-02-02-redis-geohash.md

[Binary file]

---

## File: _posts/2017-04-11-python-write.md

---
layout: post
title: Python write 函数写文件失败
comments: 1
code: 1
keywords: Python Write Failed, python write, python文件写失败
tags: [python]
---

工作中涉及到了内容抓取的需求，用 Python 根据Url抓取对应的Html页面，并存储到本地文件，然后程序内继续将本地文件的路径作为参数，调用Node进行杂质过滤和内容提取。但怪的是在Python执行期间，已经完成了write操作，Node读取该文件的时候却得不到任何内容！这让我很尴尬


## 复现情景

#### 伪代码如下

```python
# filename: spider.py

# 要写入的html路径
html_file_path = 'xxx.html'

# request进行抓取
response = requests.get(url)
content = response.content

# 进行文件写入
html_handler = open(html_file_path, "w")
html_handler.write(content) # 注意这里完成了write操作

# 调用Node进行内容过滤，Node会读取刚刚写入的 Html 文件
shell = 'node filter.js --input ' + html_file_path
# 执行shell命令
process = subprocess.Popen(shell)
process.communicate()

```

其中filter.js中是这么读取输入文件的：

``` js
filename = 'xxx.html'
var sourceContent = fs.readFileSync(filename, {
    encoding: 'utf8'
});

console.log(sourceContent);
...

```

#### 开始执行
```shell
python spider.py

# 但却啥内容都没有打印出
```
执行的时候发现xxx.html文件是已经被创建了的，但Node在读取时总是空，手动打开xxx.html也是空文件！

#### 初步猜想

> 1. 文件编码问题，Node是按照utf-8读取文件的，而文件写入的非u8
> 2. 磁盘已满，文件写入失败


#### 验证猜想

1. 最好验证的是2，`df -h` 即可看到磁盘空间，然而并没有满
2. 编码问题，恩，常见问题之一，开始追查

> - 把程序断到write之后，打开文件查看编码，就是u8，并没有问题！
> - 那么继续怀疑难道是Node读取的时候出错了？
> - 写了个u8的文件让Node去读，也能正常读到！
> - 怪了，py生成的u8，Node读取也按照u8，为啥就是失败呢？

> 中间有一个有意思的事情，那就是本来程序先写入xxx.html, 然后执行shell让Node去读取的，后来我提前手写了这个xxx.html文件，然后在python里注释掉了*html_handler.write(content)*写文件的过程，然后继续shell调用Node，竟然读取成功了！！

------

**所以说，肯定是写文件的环节出了什么问题！在脚本运行期间，导致Python没有写入成功，所以Node读取时失败！**

-------

#### 问题发现和解决

> 无意间发现，write部分的代码好像有点别扭呢，是不是少了个`close()`呢！！
恩 ，发现bug就是这么随意...

然后在python的write步骤之后加上了下面的代码

```python
# 关闭文件句柄
html_handler.close()
```

恩，世界就圆满了！一切都正常了！


## 问题总结

> Python的write方法是`AIO [Asynchronous Input/Output]`  类型，即异步输入输出，类似于MySQL的落地机制，文件通过write方法写入文件时，并不是及时的写入系统磁盘，而是首先写入到自己的缓存区[内存]，当缓存区满了的时候才会异步写入到磁盘进行落地，否则永远不会写入真实磁盘文件

当然，有两种情况可以改变上述异步写入的行为：
> 1. 手动对文件句柄执行`close()`操作，这样解释器会立即刷新缓存到文件，立即写入
> 2. 程序终止时如`exit()`，解释器会自动进行内存清理、缓存区最终落地等操作，文件也会被写入

这和PHP的垃圾回收机制也很像嘛，默认在垃圾堆【根缓存区】满了的时候自动进行变量清除，或者你手动执行gc_collect_cycles()函数释放垃圾变量，强制立即回收内存。

### 回顾
再回顾之所以开始我用exit方法在write处打断点时即使没close文件也是被写入的，看来是命中了情况2。
后来加了close方法是命中了情景1，所以两种情况文件都被写入了。





恩，找bug找了大半天时间，结果就是因为简单的close函数，ಥ_ಥ

---

## File: _posts/2017-04-17-docker-gitlab.md

---
layout: post
title: 基于Docker的Gitlab服务器搭建
comments: 1
code: 1
keywords: Gitlab环境搭建, Gitlab on Docker, Gitlab, docker-compose
tags: [gitlab,docker,opensource]
---

是不是每个爱折腾的人都有搭建一套自己的Gitlab服务器的冲动~ 作为一个爱折腾的人，我曾经也搭建过内网Gitlab，装Redis，装PostgreSQL，装Ruby，装Gitlab，尤其是配置Ruby环境跑Gitlab的时候，不知遇到了多少坑！不过今天，我再次搭建Gitlab服务，用的是大神已经配置好的Gitlab Docker镜像，一条命令即可启动！

> Tips: Docker环境用的是Mac 下的[docker-machine](https://docs.docker.com/machine/)，其他环境如Linux、Windows也有对应的Docker一键环境，很方便的，这里我们重点讨论的不是如何安装Docker，所以这里略过。


## 概述
Gitlab的Docker镜像已经由印度大牛做好，地址 [https://github.com/sameersbn/docker-gitlab](https://github.com/sameersbn/docker-gitlab)， 其实里面已经说明了如何使用该环境，这里我相当于转述和完善一下而已。

> _该镜像做了四件事_：
>
> 1. 建立Redis环境 [sameersbn/redis]
> 2. 建立PostgreSQL环境 [sameersbn/postgresql]
> 3. 建立Ruby环境和Gitlab [sameersbn/gitlab]
> 4. 把各个镜像跑起来形成最终环境，开放端口供使用



## 实现步骤

### 一、完全手动安装执行

> Tips: 不推荐这么使用，因为每次启动之前都要手动启动redis、postgresql等，比较繁琐，容易出错

#### 1. 启动 Redis 容器

```shell
# 拉取镜像
docker pull sameersbn/redis:latest

# 启动容器
docker run --name gitlab-redis -d  \
  --publish 6379:6379 \
  --volume /srv/docker/redis:/var/lib/redis \
  sameersbn/redis:latest

# 此时访问运行docker的机器ip:6379端口应该正常连接redis
# 我的是 redis-cli -h 192.168.99.100 -p 6379
```

#### 2. 启动 Postresql 容器

```shell
# 拉取镜像
docker pull sameersbn/postgresql:latest

# 启动容器
docker run --name gitlab-postgresql -itd \
  --publish 5432:5432 \
  --env 'DB_USER=gitlab' \
  --env 'DB_PASS=password' \
  --env 'DB_NAME=gitlabhq_production' \
  --env 'DB_EXTENSION=pg_trgm' \
  --volume /srv/docker/postgresql:/var/lib/postgresql \
  sameersbn/postgresql:latest

  # 此时访问运行docker的机器ip:5432应该能连接上数据库
  # 我的是 pgcli -h 192.168.99.100 -p 5432 -U gitlab -d gitlabhq_production
```

#### 3. 启动 Gitlab 容器

```shell
# 镜像拉取
docker pull sameersbn/gitlab:latest

# 启动gitlab容器
docker run --name gitlab -d \
    --link gitlab-postgresql:postgresql --link gitlab-redis:redisio \
    --publish 10022:22 --publish 10080:80 \
    --env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' \
    --env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' \
    --env 'GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alpha-numeric-string' \
    --env 'GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alpha-numeric-string' \
    --volume /srv/docker/gitlab/gitlab:/home/git/data \
    sameersbn/gitlab:latest
```

#### 4. 访问10080端口

> 访问地址 [http://192.168.99.100:10080/](http://192.168.99.100:10080/) 理论上可以见到初始化更改密码的界面了，注意ip改成运行docker的机器ip

> **_`Tips`_**: 这里会有个坑，理论上启动了gitlab容器之后就能立即访问的，但实际上却是`502`响应，其实是 gitlab 正在执行初始化过程，需要一定的时间，稍等一两分钟再访问即可...

-----

### 二、使用docker-compose进行启动

> **强烈推荐**
docker-compose 类似于 node 使用 package.json， php 使用 composer.json 的方式，是通过配置文件管理依赖的方法。具体文档见 [https://docs.docker.com/compose/](https://docs.docker.com/compose/)，使用方法如下

```shell
# 根据 docker-compose.xml 启动docker 容器
# -c 指定启动的配置文件路径
docker-compose -f docker-compose.xml up
```

大神已经写好了docker-compose.xml启动文件，我们下载下来

```shell
wget https://raw.githubusercontent.com/sameersbn/docker-gitlab/master/docker-compose.yml
```

该文件内容如下，省略了部分不必要的变量，是能跑起来的最简设置：

```shell
version: '2'

services:
  redis:
    restart: always
    image: sameersbn/redis:latest
    # 如果拉取失败，呵呵，换用下面的阿里云源即可
    # image: registry.cn-hangzhou.aliyuncs.com/acs-sample/redis-sameersbn
    volumes:
    - /srv/docker/gitlab/redis:/var/lib/redis:Z

  postgresql:
    restart: always
    image: sameersbn/postgresql:latest
    # 如果拉取失败，呵呵，换用下面的阿里云源即可
    # image: registry.cn-hangzhou.aliyuncs.com/acs-sample/postgresql-sameersbn
    volumes:
    - /srv/docker/gitlab/postgresql:/var/lib/postgresql:Z
    environment:
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production
    - DB_EXTENSION=pg_trgm

  gitlab:
    restart: always
    image: sameersbn/gitlab:latest
    # 如果拉取失败，呵呵，换用下面的阿里云源即可
    # image: registry.cn-hangzhou.aliyuncs.com/acs-sample/gitlab-sameersbn
    depends_on:
    - redis
    - postgresql
    ports:
    - "10080:80"
    - "10022:22"
    volumes:
    - /srv/docker/gitlab/gitlab:/home/git/data:Z
    environment:
    # - DEBUG=false

    # postgresql 配置
    - DB_ADAPTER=postgresql
    - DB_HOST=postgresql
    - DB_PORT=5432
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production

    # redis 配置
    - REDIS_HOST=redis
    - REDIS_PORT=6379

    # 端口配置
    - GITLAB_PORT=10080
    - GITLAB_SSH_PORT=10022

    # CI 所使用的加密密钥
    - GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alphanumeric-string
    # Session 加密密钥
    - GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string
    # 数据库2FA密钥
    - GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alphanumeric-string

    # restart=always 配置容器退出后自动重启
```

#### 开始启动

```shell
# 启动配置中的所有依赖容器
docker-compose -f docker-compose.xml up

# 或者加上 -d 参数以后台 daemon 形式运行，但就看不到初始化过程了
# docker-compose -f docker-compose.xml up -d

# 顺利的话就开始执行初始化操作，步骤如下
# 1. postgresql
# 2. redis
# 3. gitlab
```

控制台会输出当前初始化过程，时间有点长，如果没完成就访问的话会报 `502` ，上面也提到过，等待1-2分钟即可

> **_`Tips`_**: 启动的时候很可能在镜像拉取的时候失败，原因你知道的，The Grate Wall ，解决方法也很简单，翻墙，或者，将上买呢配置文件中的 image 字段改为注释里写的阿里云源即可。


## 开始体验

> 访问你装有 Docker 的机器ip:10080端口，我的是[http://192.168.99.100:10080/](http://192.168.99.100:10080/)，注意不是127.0.0.1，因为你的Docker环境IP可能是独立的，看看发什么什么吧~







最后 : 放 Gitlab CE 源码地址 [https://github.com/gitlabhq/gitlabhq](https://github.com/gitlabhq/gitlabhq)， 有兴趣的同学可以不用Docker环境，完全自己搭建一套试一试 ಥ_ಥ

---

## File: _posts/2017-05-04-liboffice-ppt-to-image.md

---
layout: post
title: 命令行下 将PPT文件转换为图片
comments: 1
code: 1
keywords: ppt转图片, ppt to image, libreoffice
tags: [opensource, libreoffice, ppt]
---

之前做需求的时候有这么一个，输入是一个ppt文件，要求输出是每页ppt的图片格式，相当于把ppt分页转换为图片，然后图片再做后续的逻辑处理。如果在window下或者说有office的情况下，手动执行太简单，文件->导出为图片 即可实现，可视化操作，那么问题是我这个功能要作为服务运行，在非人工干预的情况下，所有操作都要由脚本实现，所以折腾了一下，这里记录下来。

## 使用工具
1. #### libreoffice
    > 官网 [https://zh-cn.libreoffice.org/](https://zh-cn.libreoffice.org/)
    > OpenOffice的现代衍生品，一个全平台下的office套件，支持命令行调用
2. #### convert
    > 官网 [http://www.imagemagick.org/script/](http://www.imagemagick.org/script/)
    > ImageMagick 下的工具之一
    > 其实ImageMagick这货强大的狠，各种图片转换、调整...绝对不止本文用到的这点
3. #### 思路
    > 首先用libreoffice将ppt转换为pdf格式，然后再用convert将pdf转换为图片

## 工具安装
1. #### libreoffice

    _Mac下_：
    1. App Store 搜索`libreoffice`安装。和下面的方法2任选其一
    2. `brew cask install libreoffice` 至于cask，请自行安装 [https://caskroom.github.io/](https://caskroom.github.io/)
    3. `brew install ghostscript` 这是Mac下额外要安装的gs库


    _Linux 下_：
    1. 下载rpm \| deb包 ，下载地址[https://zh-cn.libreoffice.org/download/](https://zh-cn.libreoffice.org/download/libreoffice-still/?version=5.2&lang=zh-CN#change)，自行选择对应版本
    2. Debian/Ubuntu系统 (.deb包):

        ```bash
        /*解压 如果版本号不同请自行更改文件名*/
        tar -zxvf LibreOffice_5.2.6_Linux_x86-64_deb.tar.gz
        /* 安装主安装程序的所有deb包 */
        $ sudo dpkg -i ./LibreOffice_5.2.6.2_Linux_x86-64_rpm/DEBS/*.deb
        ```
    3. Fedora/SUSE/Mandriva系统 (.rpm包):

        ```bash
        /*解压 如果版本号不同请自行更改文件名*/
        tar -zxvf LibreOffice_5.2.6_Linux_x86-64_rpm.tar.gz
        /* 安装主安装程序的所有rpm包 */
        $ sudo yum install ./LibreOffice_5.2.6.2_Linux_x86-64_rpm/RPMS/*.rpm
        ```

2. #### convert

    _Mac下_：

    ```bash
    # 安装 imagemagick
    brew install imagemagick
    ```

    _Linux下：_

    ```bash
    # 安装 imagemagick CentOS
    sudo yum install ImageMagick

    # 如果是ubuntu
    sudo apt-get install ImageMagick
    ```

3. #### 安装验证
    > Tips: `soffice`命令在Linux下安装后可能没有被放到 /usr/local/bin 中，这时候应该执行如下操作

    ```shell
    # 手动更新locate数据库
    sudo updatedb

    # 查找soffice命令安装位置 我的是在 /opt/libreoffice5.2/program/下
    locate soffice

    # 在/usr/local/bin中建立软链，便于使用
    ln -s /opt/libreoffice5.2/program/soffice /usr/local/bin/soffice
    ```

    开始验证

    ```bash
    $ soffice -h

    # 看到如下输出即可
    LibreOffice 5.2.6.2 a3100ed2409ebf1c212f5048fbe377c281438fdc

    Usage: soffice [options] [documents...]

    $ convert -h

    # 看到如下输出即可
    Version: ImageMagick 6.7.2-7 2017-03-22 Q16 http://www.imagemagick.org
    Copyright: Copyright (C) 1999-2011 ImageMagick Studio LLC
    Features: OpenMP

    Usage: convert [options ...] file [ [options ...] file ...] [options ...] file
    ```


## 开始转换

### 1. ppt -> pdf

```shell
# 将ppt转换为pdf格式，理论上会再当前文件夹下生成同名sample.pdf
soffice --convert-to pdf:writer_pdf_Export sample.ppt
```

### 2. pdf -> jpg

```shell
# 将pdf文件的每一页存为jpg图片，会在当前文件夹下生成 sample-0.jpg sample-1.jpg ...
# 图片后缀的数字是ppt的页码索引
convert sample.pdf sample.jpg
```

---

## File: _posts/2017-05-15-microsoft-wannacry.md

---
layout: post
title: Windows勒索病毒WannaCry防护方法 | Windows关闭445端口
comments: 1
keywords: MS17-010, Windows关闭445端口, wannacry, windows 445
tags: [windows, wanna cry]
---

5月12日侵袭全世界的“蠕虫式”勒索软件 WanaCry (也称作WannaCry或WanaCry0r 2.0)，已致中国部分行业企业内网、教育网大规模感染。这种病毒依靠美国国家安全局开发的核弹级侵入工具-深蓝  EternalBlue 在局域网内疯狂传播，即使没有连接外网也难以避免。也就是说，即使你没有点击病毒软件，只要和你一个局域网络的任何一个小白误点了病毒，那么整个网络内都会惨遭毒手。





## 病毒传播原理

Windows下默认会打开`445`端口用于文件共享相关功能，但该端口一直存在漏洞，不幸的是微软官方之前也一直没有发现该漏洞。而核弹级蠕虫病毒-深蓝 正是通过该漏洞进行传播，它会在局域网内扫描445端口发现局域网内所有机器，并通过漏洞远程执行病毒程序，比如现在大家都看到过的文件加密勒索程序。

### 漏洞名称：

> Microsoft Windows SMB远程任意代码执行漏洞 (MS17-010)

### 漏洞描述：

> SMBv1 server 是其中的一个服务器协议组件。
Microsoft Windows 中的SMBv1服务器存在远程代码执行漏洞。
远程攻击者可借助特制的数据包利用该漏洞执行任意代码。


----

## 解决方法

立刻安装补丁

> Windows 于3月份其实已经发布了应对该漏洞的补丁，即使是已经宣布停止更新的XP系统也针对性的提供了补丁，所以一直打开自动更新的用户其实并没有什么危险的，危险的是一直关闭自动更新的那些用户，现在赶紧安装！

### Windows XP

Windows XP SP2 x64 :  [https://www.microsoft.com/en-us/download/details.aspx?id=55250](https://www.microsoft.com/en-us/download/details.aspx?id=55250)

Windows XP SP3 x86 : [https://www.microsoft.com/zh-CN/download/details.aspx?id=55245](https://www.microsoft.com/zh-CN/download/details.aspx?id=55245)

### Windows 2003

Windows Server 2003 SP2 x64 : [https://www.microsoft.com/zh-CN/download/details.aspx?id=55244](https://www.microsoft.com/zh-CN/download/details.aspx?id=55244)

Windows Server 2003 SP2 x86: [https://www.microsoft.com/zh-CN/download/details.aspx?id=55248](https://www.microsoft.com/zh-CN/download/details.aspx?id=55248)

### 其他

> 由于其他产品微软还支持补丁更新，所以打开更新程序手动更新即可
或者手动去补丁下载页面自行下载安装，需要选择对应的Windows版本
官方地址 [https://technet.microsoft.com/zh-cn/library/security/MS17-010](https://technet.microsoft.com/zh-cn/library/security/MS17-010)

------

## 彻底解决

> 安装上述补丁之后如果还不放心，那你就需要彻底解决该问题，怎么办呢？关闭漏洞依赖的对应端口即可，危险端口包括`445` `135 ` `137` `138` `139`，并`关闭文件共享`

### 关闭危险端口

#### 1. 左下角搜索框里输入“防火墙”，打开防火墙设置


#### 2. 首先保证防火墙是启用的


#### 3. 然后点击“高级设置”


#### 4. 在“入站规则”上右键-“新建规则”


#### 5. 选择“端口”


#### 6. 选择“TCP”， “特定端口”输入 445,135,137,138,139


#### 7. 选择“阻止连接”


#### 8. 默认选择三个域


#### 9. 随便输入名称即可


### 关闭 SMB 文件共享

#### 1. 左下角搜索 启用或关闭Windows功能


#### 2. 取消`SMB 1.0/CIFS文件共享支持`
> 需要重启

---

## File: _posts/2017-10-14-nginx-php-empty.md

---
layout: post
title: nginx php文件显示空白
comments: 1
code: 1
keywords: nginx, php-fpm, php文件空白
tags: [nginx, fpm, wsl]
---

今天在bash on windows鼓捣php环境的时候偶然遇到一个坑，就是nginx启动以后，访问index.html可以正常显示，但访问index.php的时候页面一片空白，啥都没有！根据以往在linux下的经验，我的配置完全正确，肯定可行，但在wsl下却跑不通，摸索过程记录如下。

## 复现情景

### nginx

正常启动，配置如下（其实就是wsl下apt安装的nginx默认配置），

> Tips: 贴上在wsl下启动nginx的坑：

> - fastcig_pass不能像linux下一样使用套接字：`unix:/var/run/php5-fpm.sock`，所以只能让fpm监听端口，然后fastcig_pass监听端口即可
> - 不能使用master模式（master模式即nginx会fork出n多salve进程处理请求，master只做管理），这根wsl下的fork限制有关，所以必须在nginx.conf最外面关闭，可以在`worker_processes`后面加上这句 `master_process off;`

```ini

location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
        # Uncomment to enable naxsi on this location
        # include /etc/nginx/naxsi.rules
}

location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
#       # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
#
#       # With php5-cgi alone:
        fastcgi_pass 127.0.0.1:9000;
#       # With php5-fpm:
        #fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
}

```

### fpm

正常启动，监听9000端口

```ini
; 不使用sock
;listen = /var/run/php5-fpm.sock
listen = 127.0.0.1:9000
```

### index.php

随便写了个php文件

```php
<?php

phpinfo();
```

结果就是访问index.html文件正常，但访问index.php的时候页面空白，没有返回，查看access日志也是显示200，并没有错误。但如果查看fpm错误日志的话，会得到`ERROR: failed to retrieve TCP_INFO for socket: Invalid argument (22)`参数错误的提示。

## 解决方法

在nginx配置中`location ~ \.php$`模块里增加如下参数

```ini

# 主要增加如下两行
# 1、引入fastcgi相关参数，用于请求实现了fastcgi协议的守护进程，如fpm
include fastcgi_params;
# 2、重新定义 fastcgi的 SCRIPT_FILENAME参数， 因为如果要实现fastcgi协议，必须指定 SCRIPT_FILENAME 参数
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

# 更改之后为：

location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
#       # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
#
#       # With php5-cgi alone:
        fastcgi_pass 127.0.0.1:9000;
#       # With php5-fpm:
        #fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

```

重启nginx即可

## 问题探究

在nginx的fastcgi协议模式中，是需要指定运行文件位置的，即`SCRIPT_FILENAME`变量，这样fastcgi接口才能知道到底要去执行哪个文件，才能将这个文件路径作为参数正确请求fpm进行处理。然后wsl下默认的nginx配置是没有这句配置的，所以nginx不知道拿哪个文件去请求fpm，这样fpm就会报参数错误,`ERROR: failed to retrieve TCP_INFO for socket: Invalid argument (22)`，最终返回给nginx的内容为空白。

`SCRIPT_FILENAME` 如果不指定，默认值配置在`/etc/nginx/fastcgi_params`中，为`$request_filename`。我们在配置中使用`include fastcgi_params`这其实就是引入fastcgi实现的相关参数，后续nginx通过这些参数才可以去请求实现了fastcgi接口的守护进程，如fpm等。内容如下：

```conf
fastcgi_param   QUERY_STRING            $query_string;
fastcgi_param   REQUEST_METHOD          $request_method;
fastcgi_param   CONTENT_TYPE            $content_type;
fastcgi_param   CONTENT_LENGTH          $content_length;

# 这句指定了默认 $request_filename 如 index.php
fastcgi_param   SCRIPT_FILENAME         $request_filename;
fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
fastcgi_param   REQUEST_URI             $request_uri;
fastcgi_param   DOCUMENT_URI            $document_uri;
# 这句指定了 $document_root 如 /var/share/nginx/html/
fastcgi_param   DOCUMENT_ROOT           $document_root;
fastcgi_param   SERVER_PROTOCOL         $server_protocol;

fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;

fastcgi_param   REMOTE_ADDR             $remote_addr;
fastcgi_param   REMOTE_PORT             $remote_port;
fastcgi_param   SERVER_ADDR             $server_addr;
fastcgi_param   SERVER_PORT             $server_port;
fastcgi_param   SERVER_NAME             $server_name;

fastcgi_param   HTTPS                   $https if_not_empty;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param   REDIRECT_STATUS         200;
```

所以在不覆盖`SCRIPT_FILENAME`参数情况下默认值是`$request_filename`，即请求资源的路径，形如 index.php，这类似于相对路径，某些系统上nginx是找不到对应文件的；
然后我们加入的下面这句配置就是手动指定了实现fastcgi协议的 `SCRIPT_FILENAME` 参数，并且指定为了绝对路径

```conf
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
```

即指定为`$document_root$fastcgi_script_name`，最终会被解析为`/usr/share/nginx/html/index.php`，这样nginx就会拿着正确的参数去请求fpm，fpm也就能正确返回了。

-----

总结起来就是解决很简单，贵在探究，嗯。

---

## File: _posts/2017-12-05-https-your-site.md

---
layout: post
title: 如何用免费的方式让网站支持HTTPS
comments: 1
code: 1
keywords: HTTPS, Let's Encrypt, 免费https证书, 升级https
tags: [https, nginx]
---

前两天,用4G访问自己的博客的时候,发现右下角出现了那个可恶的"流量球",相信大家肯定有见过的,显而易见,这是运营商劫持了我的流量,并且给网站注入了一些内容,以此来推销他们的套餐. 作为一个有理想的咸鱼,怎么能让运营商如此肆意妄为?于是乎,开始了我的博客https改造之路.


## 实现原理
使用 [Let's Encrypt](https://letsencrypt.org/) 提供的免费证书, 放到自己博客服务器中, 并且在nginx配置好证书路径, 这样使用浏览器访问的时候就会见到熟悉的绿色小锁头了. 需要注意证书必须颁发给某个域名, 所以ip地址无效.
> Tips: 如果是 [GitHub Pages](https://pages.github.com/) 搭建的博客, 由于Pages的服务器我们无法控制, 所以只能通过其他方式实现, 文章结尾会专门说明.

## 实现步骤

### 1. 证书申请

1.1 安装工具certbot, 实际上就是一个bash文件, 他会帮你执行复杂的操作

```bash
git clone https://github.com/certbot/certbot
cd certbot
chmod +x certbot-auto

# certbot-auto 即为自动化脚本工具, 他会判断你的服务是nginx还是apache, 然后执行对应逻辑
./certbot-auto --help
```

1.2 生成证书

> 由于我的服务器是在运行中, 所以我只需要生成证书, 然后手动配置即可


```bash
# webroot代表webroot根目录模式, certonly代表只生成证书 邮箱亲测没啥大用, 域名一定要和自己要申请证书的域名一致
./certbot-auto certonly --webroot --agree-tos -v -t --email 你的邮箱 -w 服务器根目录 -d 你要申请的域名

# 实际如下 用 qii404.me 域名做测试, 实际需换为你的域名
./certbot-auto certonly --webroot --agree-tos -v -t --email qii404@126.com -w /usr/share/nginx/html/ -d qii404.me
```

然后会在`/etc/letsencrypt/`目录下生成如下目录结构

```
/etc/letsencrypt/
├── accounts
│   └── acme-v01.api.letsencrypt.org
│       └── directory
├── archive
│   └── qii04.me
│       ├── cert1.pem
│       ├── chain1.pem
│       ├── fullchain1.pem
│       └── privkey1.pem
├── csr
│   └── 0000_csr-certbot.pem
├── keys
│   └── 0000_key-certbot.pem
├── live
│   └── qii04.me
│       ├── cert.pem -> ../../archive/qii04.me/cert1.pem
│       ├── chain.pem -> ../../archive/qii04.me/chain1.pem
│       ├── dhparams.pem
│       ├── fullchain.pem -> ../../archive/qii04.me/fullchain1.pem
│       ├── privkey.pem -> ../../archive/qii04.me/privkey1.pem
│       └── README
├── renewal
│   └── qii04.me.conf
└── renewal-hooks
    ├── deploy
    ├── post
    └── pre
```

你所需要的证书其实是在`/etc/letsencrypt/live/qii404.me/`目录中的

`fullchain.pem`可以看作是证书公钥, `privkey.pem`是证书私钥, 是我们下面需要使用到的两个文件.

### 2. 证书配置

需要配置nginx, 使其支持https即443端口的访问, 同时需要把证书路径配置进去, 才能生效.

> 编辑`/etc/nginx/sites-enabled/default`配置文件, 当然路径可能和我的不一样，也可能直接写在了`/etc/nginx/nginx.conf`里，将下面的443 server部分追加到配置中即可（和80端口的server并列的位置）。当然环境不同，直接复制可能会导致`location`部分有问题，最简单的办法是复制你自己80端口的server中`location`配置一份，替换下面443中的location配置即可。

```conf
# https 443端口配置
server{
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    server_name _;

    # 此处需要改为你的代码root目录 和80端口root一致即可
    root /usr/share/nginx/html;
    index index.php index.html index.htm

    # 打开ssl传输
    ssl on;
    # 配置公钥路径
    ssl_certificate "/etc/letsencrypt/live/qii404.me/fullchain.pem";
    # 配置私钥路径
    ssl_certificate_key "/etc/letsencrypt/live/qii404.me/privkey.pem";

    # 下面因人而异，直接把80端口的所有location配置复制到此处即可
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass 127.0.0.1:9000;
    }
}
```

80和443共存结构如下

```conf
# 80端口配置
server{
    listen 80;
    root xxx;

    location / {
        xxx;
    }
}

# https 443端口配置
server{
    listen 443;
    root xxx;

    ssl on;
    # 配置公钥路径
    ssl_certificate "/etc/letsencrypt/live/qii404.me/fullchain.pem";
    # 配置私钥路径
    ssl_certificate_key "/etc/letsencrypt/live/qii404.me/privkey.pem";

    location / {
        xxx;
    }
}
```


### 3. 重启 nginx

```bash
sudo service nginx restart
```

## HTTPS验证

重启nginx之后, 再次访问你的网站地址, https://你的域名, 如 https://qii404.me, 看看会不会出现小绿锁头吧~



> Tips: 如果完成上述操作后浏览器还是没有绿色锁头, 可能是因为你的网站中有一些资源如css、img、js引用的地址是http方式的, 这种情况下即使你的网站地址是https, 但只要有一个标签src属性是非https方式, 浏览器就会提示不安全, 绿色小锁头就不会出现. 所以, 必须得保证你的站点及站点引用的所有资源必须为https方式才行.

## 关于使用GithubPages搭建的个人博客

&nbsp;&nbsp;&nbsp;&nbsp;其实我的博客就是搭建在 [Pages](https://pages.github.com/)上的, 地址[https://github.com/qishibo/qishibo.github.io](https://github.com/qishibo/qishibo.github.io) , 但是添加证书这种操作我们是没有办法在人家的服务器上进行的, 所以不能直接复述上述操作.

间接方法是使用CDN, 然后添加自己域名的CNAME到CDN域名, 实际上就是访问你的域名时, 会转到CDN域名进行访问, 也就是访问CDN域名等价于在访问你的域名, 这时候CDN上如果配置了证书的话, 一样会出现绿色的小锁头.

目前用的是 [又拍云](https://www.upyun.com/products/cdn)的免费cdn服务, PS 这真不是一个广告, 哈哈, 新用户送一张61块钱的代金券, 够使用好长时间了, 然后再在上面使用 [证书服务](https://www.upyun.com/products/ssl) 可以申请一个免费的 Let's Encrypt 证书, 然后cdn服务配合ssl证书就实现了https访问.



但目前的**坑**是又拍云的cdn有效期只能有三个月, 三个月后免费的这个就失效了, 即使你花钱续费, 续费的有效期也是三个月而已, 所以需要一直关注, 比较蛋疼; 并且,,,,,重要的一点, 他的cdn服务需要域名备案...


## 写在最后

[Let's Encrypt](https://letsencrypt.org/) 提供了各个版本系统、各个web server的申请方式, 如果有兴趣, 可以自己实现更多定制化的需求, 官网 [https://certbot.eff.org/](https://certbot.eff.org/), 官方说明如下图所示,

---

## File: _posts/2018-01-06-minukube.md

---
layout: post
title: 使用minikube在本机搭建kubernetes集群
comments: 1
code: 1
keywords: minikube, kubernetes, k8s
tags: [minikube, kubernetes]
---

Kubernetes（k8s）是自动化容器操作的开源平台，基于这个平台，你可以进行容器部署，资源调度和集群扩容等操作。如果你曾经用过Docker部署容器，那么可以将Docker看成Kubernetes底层使用的组件，Kubernetes是Docker的上层封装，通过它可以很方便的进行Docker集群的管理。今天我们使用minikube在单机上进行Kubernetes集群的部署，目的是让我们对k8s有个初步的认识。


## 安装docker

首先安装docker环境，不详细说明了，网上资料一大堆，可以参考官方安装文档

Mac: [https://docs.docker.com/docker-for-mac/install/](https://docs.docker.com/docker-for-mac/install/)

Ubuntu: [https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/)

CentOS: [https://docs.docker.com/engine/installation/linux/docker-ce/centos/](https://docs.docker.com/engine/installation/linux/docker-ce/centos/)

当然，如果上面所有方法你都失败了，也可以尝试直接下载binary可执行文件，然后启动docker即可
[https://docs.docker.com/engine/installation/linux/docker-ce/binaries/](https://docs.docker.com/engine/installation/linux/docker-ce/binaries/)


## 安装Minikube

### Mac

```bash
# 如未安装cask，自行搜索 brew安装cask
brew cask install minikube

minikube -h
```

### Linux

```bash
# 下载v0.24.1版本
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.24.1/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

# 也可以下载最新版，但可能和本文执行环境不一致，会有坑
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

minikube -h
```


## 安装Kubectl

>kubectl即kubernetes的客户端，通过他可以进行类似docker run等容器管理操作

```bash
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/

kubectl -h
```

------------------

## 启动程序

### 启动minikube

```bash
sudo minikube start
```

首次启动会下载localkube，下载过程可能会失败，会有如下提示，重试几次即可

```
Starting local Kubernetes v1.8.0 cluster...
Starting VM...
Downloading Minikube ISO
 64.70 MB / 140.01 MB [====================>-----------------------]  46.21% 14s
E0105 14:06:03.884826   10434 start.go:150] Error starting host: Error attempting to cache minikube ISO from URL: Error downloading Minikube ISO: failed to download: failed to download to temp file: failed to copy contents: read tcp 10.0.2.15:47048->172.217.24.16:443: read: connection reset by peer.

================================================================================
An error has occurred. Would you like to opt in to sending anonymized crash
information to minikube to help prevent future errors?
To opt out of these messages, run the command:
    minikube config set WantReportErrorPrompt false
================================================================================
Please enter your response [Y/n]:
```

如果下载成功，但是报了诸如`VBoxManage not found`这样的错误，如下

```
Starting local Kubernetes v1.8.0 cluster...
Starting VM...
Downloading Minikube ISO
 140.01 MB / 140.01 MB [============================================] 100.00% 0s
E0105 14:10:00.035369   10474 start.go:150] Error starting host: Error creating host: Error executing step: Running precreate checks.
: VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path.

 Retrying.
E0105 14:10:00.035780   10474 start.go:156] Error starting host:  Error creating host: Error executing step: Running precreate checks.
: VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path
================================================================================
An error has occurred. Would you like to opt in to sending anonymized crash
information to minikube to help prevent future errors?
To opt out of these messages, run the command:
    minikube config set WantReportErrorPrompt false
================================================================================
Please enter your response [Y/n]:
```

解决办法是安装 `VirtualBox`【对于windows或者mac】 再重新启动；当然如果你是Linux，也可以执行如下命令启动minikube，此时就不需要安装VirtualBox了。

因为minikube默认需要虚拟机来初始化kunernetes环境，但Linux是个例外，可以追加`--vm-driver=none`参数来使用自己的环境，说明见[https://github.com/kubernetes/minikube#quickstart](https://github.com/kubernetes/minikube#quickstart)


```bash
# linux 下独有，不依赖虚拟机启动
sudo minikube start --vm-driver=none

# 如果是Mac or Windows，安装VirtualBox后再重新start即可
sudo minikube start
```

如果安装了虚拟机，或者使用了--vm-driver=none参数，并且下载完毕，会有如下提示运行成功

```
Starting local Kubernetes v1.8.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Downloading localkube binary
 148.25 MB / 148.25 MB [============================================] 100.00% 0s
 0 B / 65 B [----------------------------------------------------------]   0.00%
 65 B / 65 B [======================================================] 100.00% 0sSetting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
===================
WARNING: IT IS RECOMMENDED NOT TO RUN THE NONE DRIVER ON PERSONAL WORKSTATIONS
    The 'none' driver will run an insecure kubernetes apiserver as root that may leave the host vulnerable to CSRF attacks

When using the none driver, the kubectl config and credentials generated will be root owned and will appear in the root home directory.
You will need to move the files to the appropriate location and then set the correct permissions.  An example of this is below:

    sudo mv /root/.kube $HOME/.kube # this will write over any previous configuration
    sudo chown -R $USER $HOME/.kube
    sudo chgrp -R $USER $HOME/.kube

    sudo mv /root/.minikube $HOME/.minikube # this will write over any previous configuration
    sudo chown -R $USER $HOME/.minikube
    sudo chgrp -R $USER $HOME/.minikube

This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true
Loading cached images from config file.

```

### 启动一个容器服务

```bash
# kube-nginx999 是要定义的容器名称 nginx:latest表明要用nginx镜像 --port=80表明容器对外暴露80端口
sudo kubectl run kube-nginx999 --image=nginx:latest --port=80

> deployment "kube-nginx999" created
```

### 查看状态

```bash
sudo kubectl get pods

NAME                             READY     STATUS              RESTARTS   AGE
nginx999-55f47cb99-46nm8         1/1       containerCreating   0          38s

```

稍等一分钟左右，如果你的服务一直是`containerCreating`状态，没有变化，那就是创建实例出现问题，如下方法查看log

```bash
sudo minikube logs
```

日志中出现 `failed pulling image...` 则是因为镜像拉取失败导致服务创建失败，原因？GFW嘛！服务在拉取自身需要的`gcr.io/google_containers/pause-amd64:3.0`镜像时失败了，如下报错。

```bash
Jan 05 03:52:58 minikube localkube[3624]: E0105 03:52:58.952990    3624 kuberuntime_manager.go:632] createPodSandbox for pod "nginx666-864b85987c-kvdpb_default(b0cc687d-f1cb-11e7-ba05-080027e170dd)" failed: rpc error: code = Unknown desc = failed pulling image "gcr.io/google_containers/pause-amd64:3.0": Error response from daemon: Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
```

### 解决方法：用本地镜像替代

> 原理就是使用阿里云的镜像下载到本地，然后命名为minikube使用的gcr.io的同名镜像，替代远端镜像即可

```bash
# 下载阿里云镜像
docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0

# 本地命名为 gcr.io/google_containers/pause-amd64:3.0
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0
```

### 重新启动服务

> 增加 `--image-pull-policy=IfNotPresent` 参数，表明优先使用本地镜像，不从远端拉取

```bash
sudo kubectl run kube-nginx999 --image=nginx:latest --port=80 --image-pull-policy=IfNotPresent
```

如果提示已经存在，换个名字重新执行即可。这时候查看服务状态应该是如下Running状态代表创建成功，但此时还不能访问容器

```bash
sudo kubectl get pods

NAMESPACE     NAME                             READY     STATUS             RESTARTS   AGE
default       kube-nginx999-77867567f5-48czx   1/1       Running            2          16h
```

### 发布服务

```bash
sudo kubectl expose deployment kube-nginx999 --type=NodePort

> service "kube-nginx999" exposed
```

### 查看服务地址

```bash
sudo minikube service kube-nginx999 --url

> http://127.0.0.1:30690
```

上面命令展示的地址即启动的nginx容器服务地址，访问 [http://127.0.0.1:30690](http://127.0.0.1:30690) 即可出现nginx首页，服务成功启动！

> PS: 访问[http://localhost:30690](http://localhost:30690)是不可以的。


----------------------

## dashboard 管理后台

> dashboard是kubernetes提供的容器服务管理后台，可视化界面，用来进行机器负载，集群管理，镜像扩容，配置数据等相关操作

### 启动dashboard

```bash
# 会打印出管理后台地址
sudo minikube dashboard --url

# 或者用下面写法，会自动打开默认浏览器，但我的一直失败，没有打开默认浏览器，没关系，执行后自己打开也行
sudo minikube dashboard
```

但初次会报下面的两种错误之一

```bash
# 1
Could not find finalized endpoint being pointed to by kubernetes-dashboard: Error validating service: Error getting service kubernetes-dashboard: services "kubernetes-dashboard" not found

# 2
Waiting, endpoint for service is not ready yet...
Waiting, endpoint for service is not ready yet...
```

如果查看log的话，会找到和pause一样的错误，即在镜像拉取的时候失败，解决方法如下，将所有kubernetes需要的镜像全部用阿里源下载到本地，然后命名为gcr.io...，来让minikube不从远端下载

> 如果不确定应该将tag重命名为什么的话，可以执行`sudo grep 'image:' -R /etc/kubernetes`看到默认情况下需要的镜像名以及版本号，对应去 [https://dev.aliyun.com/search.html](https://dev.aliyun.com/search.html) 搜索下载，然后命名为上面配置中定义的tag即可，当然，你可以在阿里云下载1.1然后重命名为1.2也没关系，差几个小版本不会有太大影响。

```bash
docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.7.1
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.7.1 gcr.io/google_containers/kubernetes-dashboard-amd64:v1.8.0

docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-addon-manager:v6.4-beta.2
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-addon-manager:v6.4-beta.2 gcr.io/google-containers/kube-addon-manager:v6.4-beta.2

docker pull registry.cn-shenzhen.aliyuncs.com/gcrio/k8s-dns-kube-dns-amd64:latest
docker tag registry.cn-shenzhen.aliyuncs.com/gcrio/k8s-dns-kube-dns-amd64:latest gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.5

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5 gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/k8s-dns-sidecar-amd64:1.14.5
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/k8s-dns-sidecar-amd64:1.14.5 gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.5

docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/storage-provisioner:v1.8.1
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/storage-provisioner:v1.8.1 gcr.io/k8s-minikube/storage-provisioner:v1.8.1
```

然后重启minikube

```bash
sudo minikube stop
sudo minikube start [--vm-driver=none] # linux没装virtualbox的情况下需要加上后面的参数
```

再次执行

```bash
sudo minikube dashboard --url

> http://127.0.0.1:30000/
```

访问 [http://127.0.0.1:30000/](http://127.0.0.1:30000/) 即可看到操作后台




### 写在最后

如果你下载工具时提示下载错误，基本上就是因为GFW，所以如果你有本地ss能够科学上网的话，可以在终端里执行下面命令，让 curl wget等命令也会走代理，加快下载

```bash
export http_proxy='socks5:127.0.0.1:1080'
```

有个坑，执行完以后访问 127.0.0.1 也是会走代理，这时候当然要换一个tab访问即可。

---

## File: _posts/2018-02-02-kafka-cluster.md

---
layout: post
title: Kafka集群搭建过程和简单使用
comments: 1
code: 1
keywords: kafka, 卡夫卡
tags: [kafka, cluster]
---

Kafka是由Apache软件基金会开发的一个开源流处理平台，由Scala和Java编写，是一种高吞吐量的分布式发布订阅消息系统。由于负载能力高，存储容量大，通常被我们用在大规模的日志收集和处理上，下面就来说说如何构建Kafka集群。当然，你也可以同时参考官网的教程 [http://kafka.apache.org/](http://kafka.apache.org/)。


## 环境依赖

>本教程中Kafka是官方最新稳定版 1.0.0，即 kafka_2.11-1.0.0，所以要求JDK版本为**1.8**, 低版本无法运行。
本教程以Ubuntu 14.04为基础环境，其他环境类似
集群实现用三台虚拟机完成，ip为 10.20.1.153, 10.20.1.154, 10.20.1.155

 - jdk 1.8
 - zookeeper
 - scala
 - kafka 1.0.0

### 1. JDK安装

```bash
# 安装jdk的ppa源，因为默认的源可能没有1.8
sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update

# 安装jdk1.8
apt-get install openjdk-8-jdk

# 如果安装了多版本jdk，执行该命令进行版本切换
update-alternatives --config java

# 验证安装
java -version

openjdk version "1.8.0_141"
OpenJDK Runtime Environment (build 1.8.0_141-8u141-b15-3~14.04-b15)
OpenJDK 64-Bit Server VM (build 25.141-b15, mixed mode)
```

### 2. Zookeeper安装和集群配置

```bash
# 安装zookeeper
sudo apt-get install zookeeper
```

完成后配置文件位于`/etc/zookeeper/conf`中，编辑 zoo.cfn 增加如下集群配置，三台机器该配置文件**完全一致**

```conf
# 假设三台机器要部署zk集群，需要将三台机器全部写入
# 格式为 server.[唯一id，一般用ip后三位]=[ip]:[follower和leader交互端口]:[master选举端口]
# 端口按照如下默认即可

server.153=10.20.1.153:2888:3888
server.154=10.20.1.154:2888:3888
server.155=10.20.1.155:2888:3888
```

编辑`myid`，注意三台机器该配置文件应该不一致，该id表明每个节点唯一身份id，也用ip后三位即可；如果不用ip需要注意要和上面`server.[id]=`中的id对应

```conf
# 10.20.2.153 /etc/zookeeper/conf/myid
153

# 10.20.2.154 /etc/zookeeper/conf/myid
154

# 10.20.2.155 /etc/zookeeper/conf/myid
155
```

在每台节点上执行下面命令，启动zookeeper集群

```bash
# zkServer.sh 命令位置可能会因操作系统而不同，默认如下
/usr/share/zookeeper/bin/zkServer.sh start
```

验证zk是否成功，在任意节点上执行下面命令

```bash
/usr/share/zookeeper/bin/zkCli.sh

# 正常的话会有如下显示
Connecting to localhost:2181
Welcome to ZooKeeper!
JLine support is enabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0]

# 输入help得到常用命令

# 输入 'ls /' 应该会有如下输出
> [zookeeper]

```

### 3. Scala安装

```bash
# 安装scala
apt-get install scala

# 验证安装
scala -version
> Scala code runner version 2.9.2 -- Copyright 2002-2011, LAMP/EPFL
```

### 4. Kafka安装和集群配置

去官网下载最新release版本，地址 [http://kafka.apache.org/downloads](http://kafka.apache.org/downloads)，或者直接wget获取

```bash
# 下载
wget http://apache.javapipe.com/kafka/1.0.0/kafka_2.12-1.0.0.tgz

# 解压
tar -zxvf kafka_2.12-1.0.0.tgz

cd kafka_2.12-1.0.0
```

配置文件在`config`文件夹中，分别编辑每个节点的`server.properties`服务配置文件，注意下面是三段，拆开写到三个节点的配置中去，而不是直接复制到一个配置中！

```conf
# 设置broker id，可以认为是每个节点的唯一id，并且三台机器该值不能相同
# listeners 为监听ip和端口
# zookeepers.connect 为zk集群ip port

# 第一段 写到 10.20.1.153上server.properties配置
broker.id=0
listeners=PLAINTEXT://10.20.1.153:9092
zookeeper.connect=10.20.1.153:2181,10.20.1.154:2181,10.20.1.155:2181

# 第二段 写到 10.20.1.154上server.properties配置
broker.id=0
listeners=PLAINTEXT://10.20.1.154:9092
zookeeper.connect=10.20.1.153:2181,10.20.1.154:2181,10.20.1.155:2181

# 第三段 写到 10.20.1.155上server.properties配置
broker.id=0
listeners=PLAINTEXT://10.20.1.155:9092
zookeeper.connect=10.20.1.153:2181,10.20.1.154:2181,10.20.1.155:2181
```


每台机器分别执行下面命令启动集群

```bash
# 保证你在kafka_2.12-1.0.0文件夹下
bin/kafka-server-start.sh config/server.properties
```

启动过程可能会报错

```
Exception in thread "main" java.lang.UnsupportedClassVersionError: kafka/Kafka : Unsupported major.minor version 52.0
```

这是由于最新版本的Kafka需要至少`JDK1.8`的支持，升级或者重新安装即可，上面已经说过了，如下即可

```bash
# 安装jdk的ppa源，因为默认的源可能没有1.8
sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update

# 安装jdk1.8
apt-get install openjdk-8-jdk

# 如果安装了多版本jdk，执行该命令进行版本切换
update-alternatives --config java
```

版本对应如下

```
J2SE 9 = 53
J2SE 8 = 52
J2SE 7 = 51
J2SE 6.0 = 50
J2SE 5.0 = 49
JDK 1.4 = 48
JDK 1.3 = 47
JDK 1.2 = 46
JDK 1.1 = 45
```

还有一种情景可能会报如下错误

```
# 实际上 kafka-master 会是你的主机名
java.net.UnknownHostException: [kafka-master]: [kafka-master]: Name or service not known
```

这是由于你所使用的机器很有可能是虚拟机，然后主机名无法被解析成ip，编辑`/etc/hosts`增加域名解析即可，正常情况下我们增加了`listeners`参不会出现这个错误，所以也不需要执行下面操作。

```conf
# /etc/hosts
# 编辑时注意把后面换成你的主机名，就是执行 hostname 的输出

# 10.20.1.153
10.20.1.153 kafka-master

# 10.20.1.154
10.20.1.154 kafka-node

# 10.20.1.155
10.20.1.155 kafka-node2
```

### 开始使用

创建 Topic，在任意节点执行均可

```bash
# --zookeeper 为zk集群地址，使用任意一个节点都行
# --replication-factor 为复制的份数，Kafka实际上会将一个消息复制多份存储，保证不丢失
# --partitions 分区，真实的物理节点

bin/kafka-topics.sh --create --zookeeper 10.20.1.153:2181 --replication-factor 3 --partitions 1 --topic my-test
```

查看Topic

```bash
# --zookeeper 为zk集群地址，使用任意一个节点都行
bin/kafka-topics.sh --describe --zookeeper 10.20.1.153:2181
```

查看Topic 列表

```bash
bin/kafka-topics.sh --list --zookeeper 10.20.1.153:2181
```

创建生产者，在任意节点执行均可

```bash
# --broker-list broker 节点列表，即三台服务器节点列表，实际上写成其中一个、两个或者三个均可，中间逗号隔开

bin/kafka-console-producer.sh --broker-list 10.20.1.155:9092,10.20.1.154:9092,10.20.1.153:9092 --topic my-test

# 输入111111测试发送一条消息
[2018-02-02 08:21:53,580] INFO Updated PartitionLeaderEpoch. New: {epoch:5, offset:32}, Current: {epoch:4, offset20} for Partition: my-replicated-topic-0. Cache now contains 3 entries. (kafka.server.epoch.LeaderEpochFileCache)

>111111

```

创建消费者，在任意节点执行均可

```bash
# --broker-server broker 节点列表，类似于上面的 --broker-list
# 即三台服务器节点列表，实际上写成其中一个、两个或者三个均可，中间逗号隔开
# --from-beginning 表明从头获取，而不是从接入时间获取之后的消息

bin/kafka-console-consumer.sh --bootstrap-server 10.20.1.155:9092,10.20.1.154:9092,10.20.1.153:9092 --from-beginning --topic my-test

# 正常会打出生产者发送的1111
> 111111
```

### 基于OpenStack虚拟机部署时的坑

该情况下的虚拟机`对外ip`[暴露的ip]和`真实ip`[ifconfig显示的ip]可能只是映射关系，用户访问对外ip时，OpenStack会转发到对应的真实ip实现访问。但此时如果 Kafka `server.properties`配置中的`listeners=PLAINTEXT://10.20.1.153:9092`中的ip配置为[对外ip]的时候无法启动，因为socket无法绑定监听，报如下错误

```
kafka.common.KafkaException: Socket server failed to bind to 10.20.1.154:9092: Cannot assign requested address
```

解决方法也很简单，`listeners=PLAINTEXT://10.20.1.153:9092`中的ip改为真实ip[ifconfig中显示的ip]即可，其他使用时正常使用对外ip即可，跟真实ip就没有关系了。

---

## File: _posts/2018-02-08-charles-https-problem.md

---
layout: post
title: Windows下Charles抓取Https时移动设备访问chls.pro/ssl无法安装证书，页面无响应
comments: 1
code: 1
keywords: charles http
tags: [charles, https]
---

情景是使用Charles抓取手机端Https请求，至于详细步骤，网上教程一大堆，请自行搜索。Mac下无此问题，本文适用的是Windows下移动设备无法安装证书的情景。

**需要注意的是**

1. 手机和PC要在同一wifi下
2. 抓包时Charles不能关闭
3. 手机wifi代理要设置为PC端ip

实际上，移动设备在安装Charles证书时，访问 [chls.pro/ssl](http://chls.pro/ssl) 无响应是因为PC端在装根证书时出现了错误，重新安装根证书即可，如下操作

1.PC端安装根证书



2.选择证书存储时一定注意要选择 **受信任的根证书颁发机构** 这个才行



3.手机链接PC代理后，Safari访问 [chls.pro/ssl](http://chls.pro/ssl) 安装证书即可

> 注意，如果是较新版本的ios，证书安装完毕后，还需要二次确认的过程， `设置`->`通用`->`关于本机`->`证书信任设置`->`打开 Charles Proxy CA 证书开关`

---

## File: _posts/2018-03-21-git-php-check.md

---
layout: post
title: Git提交时自动检测PHP文件是否有语法错误
comments: 1
code: 1
keywords: php check on git commit
tags: [githook, php]
---

老话题了，不过最近在公司普及[Gitlab](https://qii404.xyz/2017/04/17/docker-gitlab.html)的时候又提到了，索性就记录下来。原理是使用`php -l phpfile`对PHP文件进行语法检测，如果不通过的话，则无法继续Commit操作。

### 实现流程

> 只是为了避免低级错误提交到仓库


1. 在本地仓库中编辑`.git/hooks/pre-commit`文件，没有则建立，内容如下

    ```bash
    #!/bin/sh

    changed_files=`git diff-index --cached --name-only HEAD --`
    for f in $changed_files ;do
        ext=${f##*.}
        if test "$ext" = "php" ;then
            if test -e "$f";then
                php -l $f
            fi
        fi
    done

    ```
2. 更改权限`chmod +x .git/hooks/pre-commit`


故意提交一个错误语法试试，会见到报错信息，提交失败，修改后重新提交即可。

```bash
PHP Parse error:  syntax error, unexpected 'fsdafasdf' (T_STRING), expecting function (T_FUNCTION) in app/controllers/IndexController.php on line 21
Errors parsing app/controllers/IndexController.php
```

---

## File: _posts/2018-04-07-expect-timeout.md

[Binary file]

---

## File: _posts/2018-04-22-zephir-build-phpext.md

[Binary file]

---

## File: _posts/2018-05-16-phalcon-db-master-slave.md

---
layout: post
title: Phalcon框架数据库读写分离的实现方法
comments: 1
code: 1
keywords: phalcon mysql master-slave
tags: [phalcon, php]
---

[Phalcon](https://github.com/phalcon/cphalcon)框架和[Yaf](https://github.com/laruence/yaf)类似，是一款用C实现的拓展级别的框架，不过其功能实现更加丰富，设计思路基于依赖注入、容器等方式，更符合现代框架思想。本文主要针对Phalcon框架数据库层的读写分离进行说明，权当记录。

## 前提准备

既然需要主从分离，那么数据库连接至少得有两个，即将主库和从库分别作为服务注册到di容器，如下

```php?start_inline=1

// app/config/services.php
use Phalcon\Db\Adapter\Pdo\Mysql;

// 设置主库
$di->setShared('dbMaster', function () {
    $params = [
        'host'     => '127.0.0.1',
        'username' => 'root',
        'password' => 'root',
        'dbname'   => 'phalcon_test',
        'charset'  => 'utf8',
        'port'     => 3306,
    ];

    return new Mysql($params);
});

// 设置从库 本地测试 和主库用一个mysql实例即可
$di->setShared('dbSlave', function () {
    $params = [
        'host'     => '127.0.0.1',
        'username' => 'root',
        'password' => 'root',
        'dbname'   => 'phalcon_test',
        'charset'  => 'utf8',
        'port'     => 3306,
    ];

    return new Mysql($params);
});

```

## 实现方法一

> 基于Model初始化时的读写分离设置

Model配置如下，例子是写在每个Model的`initialize`方法中，当然你也可以统一写到BaseModel中，然后每个Model继承即可。

```php?start_inline=1

// app/models/Users.php
use Phalcon\Mvc\Model;

class Users extends Model
{
    /**
     * 设置Model主从切换
     */
    public function initialize()
    {
        // 写操作 使用dbMaster连接
        $this->setWriteConnectionService('dbMaster');
        // 读操作 使用dbSlave连接
        $this->setReadConnectionService('dbSlave');
    }

    /**
     * 表名
     */
    public function getSource()
    {
        return 'table_xxx';
    }
}

```

## 实现方法二

> 基于modelsManager实现的读写分离

1、首先需要自定义modelsManager实现，用于替换框架自带的Manager

```php?start_inline=1

// app/library/ModelsManager.php
use Phalcon\Mvc\Model\Manager;

class ModelsManager extends Manager
{
    /**
     * 读操作 返回dbSlave
     *
     * @param \Phalcon\Mvc\ModelInterface $model
     *
     * @return \Phalcon\Db\Adapter\Pdo\Mysql
     */
    public function getReadConnection(\Phalcon\Mvc\ModelInterface $model)
    {
        return $this->getDI()->get('dbSlave');
    }

    /**
     * 写操作 返回dbMaster
     *
     * @param \Phalcon\Mvc\ModelInterface $model
     *
     * @return \Phalcon\Db\Adapter\Pdo\Mysql
     */
    public function getWriteConnection(\Phalcon\Mvc\ModelInterface $model)
    {
        return $this->getDI()->get('dbMaster');
    }
}

```

2、将自定义modelsManage注册到di容器

```php?start_inline=1
// app/config/services.php

use app/library/ModelsManager;

/**
 * 设置自定义modelsManager
 */
$di->setShared(
    'modelsManager',
    new ModelsManager()
);
```



## 实现效果

> 按照上述方法实现后，你所用到的Phalcon实现的SQL查询均可自动实现主从切换，包括`Query`、`QueryBuilder`、`AR`、`PHQL`等。

当然，如果你某些情境下需要强制指定主库或者从库操作怎么办，如下即可：

```php?start_inline=1

// di容器
$di = \Phalcon\Di\FactoryDefault::getDefault();

// 获取主库链接直接操作
$di->get('dbMaster')->execute('update table_xxx set age = 100 where id = 1');

// 获取从库链接直接操作
$di->get('dbSlave')->query('select * from table_xxx');
```

## 如何验证

> 空口无凭，我怎么知道所有的读操作都发生在了从库上，所有的写操作都发生在了主库上；别担心，每次查询的时候写个日志记录一下不就完了。

需要更改最上面注册`dbMaster`和`dbSlave`时的逻辑，如下：

```php?start_inline=1
// app/config/services.php

use Phalcon\Db\Adapter\Pdo\Mysql;

// 设置主库
$di->setShared('dbMaster', function () {
    $params = [
        'host'     => '127.0.0.1',
        'username' => 'root',
        'password' => 'root',
        'dbname'   => 'phalcon_test',
        'charset'  => 'utf8',
        'port'     => 3306,
    ];

    $connection = new Mysql($params);

    // 记录每次sql查询日志
    $logger        = new \Phalcon\Logger\Adapter\File('/tmp/phalcon_sqls.log');
    $eventsManager = new \Phalcon\Events\Manager();

    // 注册记录sql的event
    $eventsManager->attach('db', function ($event, $connection) use ($logger) {
        ($event->getType() == 'beforeQuery') && $logger->log('Master: ' . $connection->getRealSQLStatement());
    });

    // 设置事件管理器
    $connection->setEventsManager($eventsManager);

    return $connection;
});

// 设置从库 本地测试 和主库用一个mysql实例即可
$di->setShared('dbSlave', function () {
    $params = [
            'host'     => '127.0.0.1',
            'username' => 'root',
            'password' => 'root',
            'dbname'   => 'phalcon_test',
            'charset'  => 'utf8',
            'port'     => 3306,
        ];

        $connection = new Mysql($params);

        // 记录每次sql查询日志
        $logger        = new \Phalcon\Logger\Adapter\File('/tmp/phalcon_sqls.log');
        $eventsManager = new \Phalcon\Events\Manager();

        // 注册记录sql的event
        $eventsManager->attach('db', function ($event, $connection) use ($logger) {
            ($event->getType() == 'beforeQuery') && $logger->log('Slave: ' . $connection->getRealSQLStatement());
        });

        // 设置事件管理器
        $connection->setEventsManager($eventsManager);

        return $connection;
});
```

完成之后查看`/tmp/phalcon_sqls.log`日志，如下，即代表成功进行了分离

```
[Wed, 16 May 18 11:18:43 +0800][DEBUG] Slave: SELECT `table_xxx`.`id` FROM `table_xxx`
[Wed, 16 May 18 11:18:43 +0800][DEBUG] Master: UPDATE `table_xxx` SET `name` = 'xxx'
```

## 优劣比较

> 个人觉得两种方法实现各有优劣，需要结合自己的业务进行选取.

- 使用方法一时，可以单独设置每个Model使用的db链接，适用于多个库中同名表操作的不同情境，更加灵活。
- 使用方法二时比较固定，直接使用Manager返回固定db链接，不再做其他处理逻辑，比较死板，但性能会高于方法一。

建议如果只是为了使用主从切换功能的话，方法二会更适合，性能会更高，毕竟实际业务可能没有那么多切换库的逻辑。

## 写在最后

上述两种方法可以同时存在，但优先级是方法二 > 方法一，即`modelsManager`的优先级高于`Model`中定义。本质原因是因为Model中执行SQL查询时，也会向modelsManager索取db实例，框架源代码文件如下

```php?start_inline=1
// phalcon/mvc/model.zep

/**
 * Gets the connection used to read data for the model
 */
public function getReadConnection() -> <AdapterInterface>
{
    var transaction;

    let transaction = <TransactionInterface> this->_transaction;
    if typeof transaction == "object" {
        return transaction->getConnection();
    }

    return (<ManagerInterface> this->_modelsManager)->getReadConnection(this);
}

/**
 * Gets the connection used to write data to the model
 */
public function getWriteConnection() -> <AdapterInterface>
{
    var transaction;

    let transaction = <TransactionInterface> this->_transaction;
    if typeof transaction == "object" {
        return transaction->getConnection();
    }

    return (<ManagerInterface> this->_modelsManager)->getWriteConnection(this);
}
```

可以看出读写Model进行查询时db实例的获取会通过modelsManager的`getReadConnection`和`getWriteConnection`方法实现，默认情况下Manager会返回Model注册时的实例，即方法一的实现，但如果同时使用了方法二，会直接覆盖原本Manager的实现，不会再使用Model中注册的逻辑。

---

## File: _posts/2018-06-21-gitlab-runner.md

---
layout: post
title: Gitalb Runner和Gitlab CI 集成设置
comments: 1
code: 1
keywords: gitlab-runner php-check
tags: [gitalb, runner, gitlab-ci]
---

为了代码规范，最近在公司私有Gitlab仓库中增加了CI集成，用于代码风格检测和单元测试，如果不符合某些硬性条件，那么你是没有办法创建MergeRequest的，这样就能对代码质量有一定的保障，也能让代码风格保持相对一致。


## 下载可执行文件

> 具体也可以参考官网教程 [https://docs.gitlab.com/runner/#install-gitlab-runner](https://docs.gitlab.com/runner/#install-gitlab-runner)，里面介绍了如何在Linux, Mac, Wundows, Docker, Kuberbetes中进行安装。

```bash
# Linux x86-64 64位系统选择此项
sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

# Linux x86  32位系统选择此项
sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386

# 增加文件执行权限
sudo chmod +x /usr/local/bin/gitlab-runner
```

执行下面命令验证安装结果

```bash
gitlab-runner -h
```

## 增加CI用户用于执行任务

```bash
# 增加名为gitlab-runner的用户 会同时创建家目录 /home/gitlab-runner
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
```

## 安装和启动

```bash
# 指定运行用户和运行文件夹
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
# 开始runner
sudo gitlab-runner start
```

## 向Gitlab注册该Runner

> 各个系统注册方式可能稍有不同，也可以参考官方文档 [https://docs.gitlab.com/runner/register/](https://docs.gitlab.com/runner/register/)，里面有关于Linux，Mac，Windows的相关操作。

1、执行该步骤之前，要从Gitlab中获取相关认证信息，以管理员身份登陆，跳到后台设置页面



2、Runner设置，找到下图中的注册URL和注册令牌



3、开始注册流程

```bash
# 进行注册
sudo gitlab-runner register
```

执行完上面命令后，会交互式的让你输入相关信息，如下操作

```bash
Running in system-mode.

#  1、输入上图中的注册URL
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://10.20.99.111/

# 2、输入上图中的注册令牌
Please enter the gitlab-ci token for this runner:
xxxxxxxxxx

# 3、输入描述 如 another runner
Please enter the gitlab-ci description for this runner:
[gitlab]:another runner

# 4、输入Runner的tag 多个的话逗号隔开
Please enter the gitlab-ci tags for this runner (comma separated):
my-tag

# 5、未设置tag的能否运行 直接Enter，默认false即可
Whether to run untagged builds [true/false]:
[false]:

# 6、是否锁定Runner到当前项目 直接Enter 默认true即可
Whether to lock the Runner to current project [true/false]:
[true]:

# 7、选择执行类型 输入shell即可
Registering runner... succeeded                     runner=E7XAAAAA
Please enter the executor: docker+machine, docker-ssh, parallels, shell, ssh, virtualbox, docker-ssh+machine, kubernetes, docker:
shell


Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
```

4、完成注册之后，刷新刚才的Runner设置页面，即可看到新注册的Runner已经准备就绪了。




## 设置Gitlab CI集成

> gitlab CI默认已经被打开，在你的代码库根目录增加名为`.gitlab-ci.yml`的文件即可。该文件中可以指定运行时各种操作，详情可以参考官方文档 [https://docs.gitlab.com/ce/ci/quick_start/README.html](https://docs.gitlab.com/ce/ci/quick_start/README.html)。注意一点，该文件执行时的所在目录可以看作是**代码根目录**。


以最简单的`PHP语法检测`为例，对应`.gitlab-ci.yml`文件如下：

```yml
# 开始运行之前的操作
before_script:
  - echo 'runner begin'

# 增加名为php-syn-check的任务
php-syn-check:
  tags: # 指定使用有 my-tag 标签的runner运行该任务
    - my-tag
  script: # 任务运行的命令，原理是遍历所有php文件，依次执行 php -l进行语法检测
  - for i in `find ./ -type f -name '*.php'|grep -v vendor`;do php -l $i;error_code=$?;if [ $error_code != 0 ]; then;exit $error_code;fi;done;

```

`.gitlab-ci.yml`文件中保留的关键字列表，这些是不能被设置成任务名的。

|关键字| 是否必须|    描述|
|----|----|----|
|image|   否|   用于docker镜像|
|services|    否|   用于docker服务|
|stages|  否|   定义构建阶段|
|types|   否|   stages的别名(已废除)|
|before_script|   否|   定义在每个job之前运行的命令（如环境初始化）|
|after_script|    否|   定义在每个job之后运行的命令（如临时文件的删除）|
|variable|    否|   定义构建变量|
|cache|   否|   定义一组文件列表，可在后续运行中使用|



## 查看CI集成结果

> 将上述文件提交到版本库，在提交页面即可看到CI运行状态，`初次`运行的时候可能会`不通过`，原因是因为运行Runner的机器没有安装PHP，所以无法执行`php -l`命令，解决方法是在Runner所在机器上安装php即可，即`sudo apt install -y php7.0-cli`：

1、未通过CI集成时，点击方框部分进入详情



点击任务名



即可看到任务失败详情，是由于某个php文件有语法错误导致的



修复有语法问题的php文件后，再次提交，可以看到CI通过



点击详情查看，看到php语法检测已通过，会有`Job succeeded`字样



至此，最基本的CI集成过程已经完成，如果想编写适合自己开发情景的配置文件，请参考官网文档 [https://docs.gitlab.com/ce/ci/yaml/README.html](https://docs.gitlab.com/ce/ci/yaml/README.html), 会更加具体的介绍如何编写`.gitlab-ci.yml`文件

---

## File: _posts/2018-08-30-php-image-server.md

[Binary file]

---

## File: _posts/2018-10-31-php-excel.md

[Binary file]

---

## File: _posts/2018-11-22-elastic-search.md

[Binary file]

---

## File: _posts/2019-01-07-nwjs-wrap-html.md

---
layout: post
title: 使用nw.js封装网页，打包成独立应用 | nwjs入门指南
comments: 1
code: 1
keywords: nw.js 网页封装 独立应用 nwjs入门指南
tags: [nwjs]
---

[nw.js](https://github.com/nwjs/nw.js)原名node-webkit，它通过Chromium内核，可以在node环境下模拟浏览器运行时，因此可以将js封装成独立应用如.deb, .exe, .app等，从而实现跨平台运行。此外，他还直接兼容Chrome打包程序(Packaged Apps)，尤其适合在Chrome商店2016年停止上架新的打包程序之后，可以让原程序直接运行。

## nw.js 安装

1、官网地址[https://nwjs.io/](https://nwjs.io/)，选择对应你自己的系统，选择`Normal`下的最新安装包下载到本地，我的是Ubuntu64位，所以下载文件是[nwjs-v0.35.4-linux-x64.tar.gz](https://dl.nwjs.io/v0.35.4/nwjs-v0.35.4-linux-x64.tar.gz)；

或者直接打开[http://dl.nwjs.io/](http://dl.nwjs.io/)选择版本、平台进行下载，个人感觉速度会快

2、解压进入

```bash
tar -zxvf nwjs-v0.35.4-linux-x64.tar.gz
cd nwjs-v0.35.4-linux-x64
```

3、检测能否正常运行，如果出现下图窗口，证明运行成功

```
./nw
```



## 封装网页

> 1.微信网页版地址为[https://wx.qq.com/](https://wx.qq.com/)
2.下面所有操作均在刚才解压的nwjs目录中！！！如 `nwjs-v0.35.4-linux-x64`

1、创建`wechat.html`用于包裹网页的html文件

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>WeChat</title>
  </head>
  <body>
    <iframe src='https://wx.qq.com/' height=760px; width=900px;></iframe>
  </body>
</html>
```

2、创建`package.json`清单文件，其中`main`字段指定加载上面创建的网页文件，具体解释见[下文](#packagejson文件配置项详解)

```json
{
  "name":"WeChat",
  "main":"wechat.html",
  "window":{
        "id": "wechat",
        "title":"WeChat",
        "toolbar":false,
        "frame":true,
        "position":"center",
        "always-on-top":true,
        "width":1000,
        "height":728,
        "as_desktop": true,
        "show_in_taskbar": true
    },
    "domain": "wechat_app",
    "version": "1.0",
    "dependencies": {}
}
```


3、运行应用

```bash
# 在nwjs的解压目录中执行
./nw .
```

正常的话会看到熟悉的网页微信登录界面，扫码登录即可



## 各个系统平台打包

### Windows打包

1. 下载好nwjs模板文件，如[nwjs-v0.35.4-win-x64.zip](http://dl.nwjs.io/v0.35.4/nwjs-v0.35.4-win-x64.zip)，解压缩，**进入**到里面的`nwjs-v0.35.4-win-x64`文件夹
2. 新建上文中的`package.json`和`wechat.html`
3. 右键选中`package.json`和`wechat.html`这俩文件，添加到zip文件，生成`wechat.zip`，必须保证zip文件打开直接就是这俩文件，里面没有嵌套文件夹
4. 打开cmd，同样进入到`nwjs-v0.35.4-win-x64`文件夹，执行`copy /b nw.exe+wechat.zip wechat.exe`
5. 上述步骤会生成`wechat.exe`应用程序，双击打开即可

Tips: 如果报错如下：

```
The program can't start because nw_elf.dll is missing from your computer. Try reinstalling the program to fix this program.
```

原因是你生成的`wechat.exe`不是在上文的`nwjs-v0.35.4-win-x64`模板目录中，很可能是因为移动位置导致了目录错误，因为生成的exe文件不能脱离模板目录独立运行。解决方法也很简单，将exe文件重新放回nwjs模板目录中，双击即可运行。即如果需要移动目录安装位置或者给别人使用，需要将`nwjs-v0.35.4-win-x64`作为整体移动。

### Linux && Mac打包

1. 下载好nwjs模板文件，如[nwjs-v0.35.4-linux-x64.tar.gz](https://dl.nwjs.io/v0.35.4/nwjs-v0.35.4-linux-x64.tar.gz)
2. 解压，里面有`nwjs-v0.35.4-linux-x64`文件夹，并**进入**文件夹
4. 新建上文中提到的`package.json`和`wechat.html`
5. 将`package.json`和`wechat.html`俩文件添加到`wechat.zip`压缩包中，`zip wechat.zip package.json wechat.html`
6. 将`nw`和`wecaht.zip`叠加到`wechat`中，`cat nw wechat.zip > wechat`
7. 赋予执行权限`chmod +x wechat`
8. `./wechat`打开应用

Tips: 如果报错如下

```
/home/qii/wechat: error while loading shared libraries: libnw.so: cannot open shared object file: No such file or directory
```

跟windows下原因一致，因为生成的可执行文件不能脱离nwjs模板目录运行，将执行文件放回到`nwjs-v0.35.4-linux-x64`再执行即可。如果安装目录需要移动或者给别人使用，需要将`nwjs-v0.35.4-linux-x64`整体移动。


### Linux下桌面图标生成

新建`/usr/share/applications/wechatweb.desktop`桌面文件如下，下次桌面直接双击即可运行

```shell
[Desktop Entry]
# 如果未生成wechat可执行文件，用下面的这句exec 替换成你自己的绝对路径
#Exec=/home/qii/Desktop/nwjs-v0.35.4-linux-x64/nw /home/qii/Desktop/nwjs-v0.35.4-linux-x64
# 如果生成了wecaht可执行文件，用下面的这句exec 绝对路径
Exec=/home/qii/Desktop/nwjs-v0.35.4-linux-x64/wechat
Name=WeChatWeb
StartupNotify=false
Terminal=false
Type=Application
# 应用图标 自己替换即可
Icon=/home/qii/Desktop/nwjs-v0.35.4-linux-x64/wechat.jpg
```

## package.json文件配置项详解

> 详情可参考官网地址[http://docs.nwjs.io/en/latest/References/Manifest%20Format/](http://docs.nwjs.io/en/latest/References/Manifest%20Format/)

|配置项|说明
|--|--|
|name|应用名称|
|main|主加载文件，如index.html或者app.js|
|domain|chrome拓展唯一id，设置为xxx，最终url地址为`chrome-extension://xxx`|
|bg-script|后台脚本，应用加载时运行|
|user-agent|自定义ua|
|crash_report_url|应用crash是报告地址|
|inject_js_start|程序开始时注入的js脚本|
|inject_js_end|程序结束时注入的js脚本|
|`window`|应用窗口设置，子配置项如下|
|id|程序唯一id，同一id程序相关操作会被记录，如上次打开的窗口位置等|
|title|默认窗口名称|
|width|窗口宽度|
|height|窗口高度|
|icon|应用icon|
|frame|边框模式，false时窗口无边框包裹|
|position|打开窗口时的位置，可选项 null center mouse|
|min_width|最小宽度|
|min_height|最小高度|
|max_width|最大宽度|
|max_height|最大高度|
|resizable|窗口是否可以拖动调整尺寸|
|as_desktop|linux专用，官方解释  show as desktop background window under X11 environment|
|show|应用打开时是否隐藏应用|


## Chrome打包程序兼容

> Chrome开发者平台通告：自2016年11月21日起，所有新发布的封装应用或托管应用都仅限 Chrome 操作系统用户使用（Windows、Mac 或 Linux 用户均无法使用这些应用）。
我们将于 2017 年 12 月中旬从 Chrome 网上应用店的搜索和浏览功能中移除所有封装应用和托管应用。现有的应用均继续适用于各大平台，并会继续收到更新。

所以2016.11.21之后的打包应用只能用于Chrome OS，如果想在Mac Linux Windwos继续使用的话，可以通过nwjs本地实现，操作如下：

1、直接进入PackagedApps项目文件夹，如果你本地没有，用下面[便签应用](https://github.com/qishibo/MinimalistNote)例子

```bash
# github clone下来
git clone https://github.com/qishibo/MinimalistNote.git
# 进入应用目录
cd MinimalistNote
```

2、运行应用

```bash
# 运行命令，其中nw执行路径要为绝对路径
# 有个坑 nwjs-v0.35.4-linux-x64目录下需要删除掉刚才创建的package.json文件，否则nw优先找到自己所在目录的package.json，而不是以当前目录为数据目录
/home/qii/Desktop/nwjs-v0.35.4-linux-x64/nw .
```

便签应用效果如下



## 遇到的一些坑

1. `/path_to_nw/nw .`运行应用时，其实是以`nw可执行文件`所在目录(即nw安装目录`nwjs-v0.35.4-linux-x64`)为根目录，找到里面的`package.json`运行，而不会以当前执行命令所在目录执行！所以在当前目录无论怎么修改`package.json`都不会生效。表面现象就是无论怎么修改文件，nwjs应用永远不会刷新，不会更改

2. nwjs生成的网页封装应用会同正常浏览器一样缓存Cookie数据，LocalStorage数据等，如果需要强制删除，其实缓存文件都在`~/.config/WeChat`下，一个应用一个文件夹，删除文件夹即可

3. 如果报错`清单文件缺失或不可读取`，则表明nw没有找到对应的`package.json`文件，确保你的`which nw`目录或者`nw可执行文件`所在目录存在`package.json`文件
```
无法加载以下来源的扩展程序： /home/qii/Desktop/nwjs-v0.35.4-linux-x64. 清单文件缺失或不可读取
```

---

## File: _posts/2019-07-10-electron.md

---
layout: post
title: Electron入门指南 | 一篇文章看懂Electron封装网页并打包应用
comments: 1
code: 1
keywords: electron electron网页封装 electron打包
tags: [electron]
---

用[Electron](https://electronjs.org/)（原名为Atom Shell）有一段时间了，周围总有人问相关的问题，今天就来总结一下。Electron的本质是提供了一个浏览器环境，并且在这个浏览器环境里，可以直接使用nodejs方法处理数据，然后渲染到dom中，就凭这一点，给开发者自由发挥的空间就相当巨大了，既可以用html来实现界面，又可以使用node相关模块调用丰富的原生（操作系统）API，相当于既是server又是client，岂不爽哉？


## Electron安装

### 1、安装依赖

开发环境下Electron依赖Node环境，所以需要先安装nodejs，具体参考[https://nodejs.org/](https://nodejs.org/)，安装完成后执行如下命令确保正常展示版本号即可，本文不再赘述

```bash
node -v
npm -v
```

### 2、全局安装

全局安装和下面的局部安装二选一，推荐全局安装

```bash
# 墙内的话安装过程中会下载失败，需要首先设置electron的源为淘宝源即可
npm config set ELECTRON_MIRROR http://npm.taobao.org/mirrors/electron/

# 全局安装 需要的话追加上 --registry='http://registry.npm.taobao.org' 使用淘宝npm源安装
sudo npm install electron -g --allow-root -unsafe-perm=true
# Windows使用下面语句 64位32位机器都是--win32
npm install electron -g --platform=win32

# 验证安装
electron -v
```

### 3、局部安装

建议`新建`个文件夹，然后进入到该文件夹进行操作

```bash
# 墙内的话安装过程中会下载失败，需要首先设置electron的源为淘宝源即可
npm config set ELECTRON_MIRROR http://npm.taobao.org/mirrors/electron/

# 局部安装 需要的话追加上 --registry='http://registry.npm.taobao.org' 使用淘宝npm源安装
npm install electron --save-dev
# Windows使用下面语句 64位32位机器都是--win32
npm install electron --save-dev --platform=win32

# 验证安装
node_modules/.bin/electron -v
```

### 4、安装下载进度

正常的话,安装下载进度如下直至完成

```bash
> electron@5.0.6 postinstall /home/qii/electron/eee/node_modules/electron
> node install.js

Downloading tmp-26643-1-SHASUMS256.txt-5.0.6
[============================================>] 100.0% of 4.74 kB (4.74 kB/s)
npm notice created a lockfile as package-lock.json. You should commit this file.

+ electron@5.0.6
updated 1 package and audited 201 packages in 499.235s
found 0 vulnerabilities
```


## 文件准备

> 新建文件夹并进入，如果上面是局部安装时话已经建好了文件夹则直接进入即可，并在文件夹下新建如下文件。

### 1、package.json

Electron会首先加载的配置文件，如果局部安装该文件已经存在的话，补全缺少的字段即可

```json
{
  "name": "my-app",
  "version": "0.1.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^5.0.6"
  }
}
```

必填字段含义如下

|字段|含义|
|name|应用名称，会显示在窗口顶部|
|version|应用版本号|
|main|应用入口点，没有的话会默认尝试加载index.js文件|

### 2、main.js

package.json中指定的入口点文件，即程序主文件

```javascript
const {app, BrowserWindow} = require('electron')

let mainWindow

// 创建主窗口，设置了宽高等信息
function createWindow () {
  mainWindow = new BrowserWindow({
    width: 1000,
    height: 600,
    webPreferences: {
      // node集成，即是否注入node能力
      nodeIntegration: true
    }
  })

  // 加载主页面内容 index.html
  mainWindow.loadFile('index.html')

  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

app.on('ready', createWindow)
```

### 3、index.html

main.js中加载的主窗口文件，即主布局文件

```html
<!DOCTYPE html>
<html>
<head>
    <!-- 此title会覆盖package.json中设置的name，作为应用顶部名称 -->
    <title>my-app</title>
</head>
<body>
    Hello World!
</body>
</html>
```


## 启动应用

### 1、全局安装时

```bash
# 进入到包含 package.json 文件的目录，别忽略了后面的点
electron .
```

### 2、局部安装时

```bash
# 进入到包含 package.json 文件的目录
npm start

# 或者直接使用脚本启动，别忽略了后面的点
node_modules/.bin/electron .
```

### 3、验证生效

正常启动的话会出现如下界面代表成功，页面内容显示的就是index.html定义的页面




## 封装微信网页版

### 1、修改main.js

也很简单，需要将`main.js`中加载的主布局文件`index.html`改成`https://wx.qq.com/`

```javascript
const {app, BrowserWindow} = require('electron')

let mainWindow

function createWindow () {
  mainWindow = new BrowserWindow({
    width: 1000,
    height: 770,
    webPreferences: {
        // nodeIntegration: true
    }
  })

  // mainWindow.loadFile('index.html')
  // 改为使用loadURL加载微信网页版url地址
  mainWindow.loadURL('https://wx.qq.com/')

  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

app.on('ready', createWindow)
```

### 2、重新运行

重新运行Electron，即可看到微信登录扫码界面

```bash
# 全局安装
electron .

# 局部安装
node_modules/.bin/electron .
# or
npm start
```




## 程序打包成应用

以上所述都是在本地开发环境下使用，但如果要给被人使用，就必须得将Electron打包成一个程序包。幸运的是我们可以通过[electron-builder](https://github.com/electron-userland/electron-builder)将程序打包成Win、Linux、Mac平台都兼容的版本，交给别人之后直接安装即可。


### 1、安装electron-builder

```bash
npm install --save-dev electron-builder

# 验证安装
node_modules/.bin/electron-builder -h
```

### 2、开始打包

注意生成各自平台的程序包最好在对应平台上执行，否则可能打包失败

```bash
# Linux打包成AppImage文件
# 在Linux环境上执行
node_modules/.bin/electron-builder -l AppImage

# Windows打包成exe安装文件
# 在Windows环境下执行
node_modules/.bin/electron-builder -w nsis
# 如果在非Windows上打包win程序，也可以借助docker 如下
# docker run --rm -it -v ${PWD}:/project electronuserland/builder:wine sh -c "node_modules/.bin/electron-builder -w nsis"

# Mac打包成dmg文件
# 在Mac环境下执行
node_modules/.bin/electron-builder -m dmg
```

打包过程中由于package.json中没有设置repository字段可能会报错，但不影响文件生成，忽略即可

```
  • electron-builder version=20.44.4
  • description is missed in the package.json appPackageFile=/home/qii/electron/eee/package.json
  • author is missed in the package.json appPackageFile=/home/qii/electron/eee/package.json
  • writing effective config file=dist/builder-effective-config.yaml
  • no native production dependencies
  • packaging       platform=linux arch=x64 electron=5.0.6 appOutDir=dist/linux-unpacked
  • building        target=AppImage arch=x64 file=dist/my-app 0.1.0.AppImage
  • application Linux category is set to default "Utility" reason=linux.category is not set and cannot map from macOS docs=https://www.electron.build/configuration/linux
  • Cannot detect repository by .git/config. Please specify "repository" in the package.json (https://docs.npmjs.com/files/package.json#repository).
Please see https://electron.build/configuration/publish
  • default Electron icon is used reason=application icon is not set
  • Cannot detect repository by .git/config. Please specify "repository" in the package.json (https://docs.npmjs.com/files/package.json#repository).
Please see https://electron.build/configuration/publish
TypeError: Cannot read property 'provider' of null
```

### 3、最终文件

打包好的程序在当前目录`dist`文件夹下

Linux生成`my-app 0.1.0.AppImage`文件，双击执行或者`./my-app 0.1.0.AppImage`执行即可
Windows生成`my-app 0.1.0.exe`文件，双击安装即可
Mac生成`my-app 0.1.0.dmg`，双击拖拽安装即可


### 4、利用配置文件打包

> 上面1-3方法相当于临时打包，每次运行命令的时候都要指定打包命令，虽然很直接，但是有点不太方便，下面用另一种方法来打包，将打包配置写到package.json中
，方便使用。

**a. 修改package.json文件**

Tips: 样例已经增加了`repository`字段，防止出现上面直接打包时的报错，对应的值是仓库地址，如果你的代码是托管在github上的，就写成`github:username/repository`，如果是本地代码，可以随便写一个如`github:xxx/xxx`，但必须要有 [这是electron-builder强制的]

```json
{
  "name": "my-app",
  "version": "0.1.0",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "build:linux": "node_modules/.bin/electron-builder -l",
    "build:windows": "node_modules/.bin/electron-builder -w",
    "build:mac": "node_modules/.bin/electron-builder -m"
  },
  "devDependencies": {
    "electron": "^5.0.6",
    "electron-builder": "^20.44.4"
  },
  "author": "qii404",
  "repository": "github:xxx/xxx",
  "build": {
    "win": {
      "icon": "icon.png",
      "target": [
        "nsis"
      ]
    },
    "nsis": {
      "allowToChangeInstallationDirectory": true,
      "oneClick": false,
      "menuCategory": true,
      "allowElevation": false
    },
    "linux": {
      "icon": "icon.png",
      "category": "Utility",
      "target": [
        "AppImage"
      ]
    },
    "mac": {
      "icon": "icon.png",
      "type": "development",
      "category": "public.app-category.developer-tools",
      "target": [
        "dmg"
      ]
    }
  }
}
```

相关字段含义如下

|字段|含义|
|scripts|npm运行的脚本别名，如执行`npm:build:linux`等价于执行`node_modules/.bin/electron-builder -l`|
|author|作者名称|
|repository|打包时代码托管地址，必填，没有的话可以随意写成如`github:xxx/xxx`|
|build|打包配置|
|icon|应用生成的图标|
|target|打包成何种应用，如windows打包成exe、tar等，Linux打包成AppImage、snap等|
|category|应用分类，固定即可，Linux、Mac上要求的|
|nsis|windows下打包nsis时的特有安装参数，如指定可以更改安装目录等|

**b. 构建应用**

分别在各自平台执行打包，如下

```bash
# linux
node_modules/.bin/electron-builder -l
# or
npm run build:linux

# windows
node_modules/.bin/electron-builder -w
# or
npm run build:windows

# mac
node_modules/.bin/electron-builder -m
# or
npm run build:mac
```


正常的话会如下提示，表明配置已经从package.json中的`build`字段读取，最终生成的AppImage(Linux)、exe(windows)、dmg(mac)文件就是应用程序，别人就能直接使用了。

```bash
> my-app@0.1.0 build:linux /home/qii/electron/eee
> electron-builder -l

  • electron-builder version=20.44.4
  • loaded configuration file=package.json ("build" field)
  • description is missed in the package.json appPackageFile=/home/qii/electron/eee/package.json
  • writing effective config file=dist/builder-effective-config.yaml
  • no native production dependencies
  • packaging       platform=linux arch=x64 electron=5.0.6 appOutDir=dist/linux-unpacked
  • building        target=AppImage arch=x64 file=dist/my-app 0.1.0.AppImage
```

## 项目关联推荐

[Another Redis Desktop Manager](https://github.com/qishibo/AnotherRedisDesktopManager/) 是一款现代的，基于Electron和ElementUI实现的跨平台Redis桌面客户端，兼容Windows、Linux、Mac平台，小巧优雅，基本构建和实现和本文一致，如果想要研究Electron更深层次应用的话，可以参考该开源项目。毕竟RDM收费了，作为一个替代品，AnotherRedisDesktopManager还是一个很好的选择的 [手动狗头]

---

## File: _posts/2021-07-03-ubuntu-install-nvidia-driver.md

---
layout: post
title: Ubuntu安装Nvidia英伟达显卡驱动，安装Cuda和Cudnn配置机器学习环境
comments: 1
code: 1
keywords: ubuntu机器学习 nvidia驱动 cuda cudnn
tags: [machine-learning]
---

平时跑模型的话，要在Ubuntu上安装机器学习环境，装过几次了，可是每次都需要查查找找，故记录下来备用。


## 显卡驱动安装


### 1、下载对应型号显卡驱动

首先查看自己机器显卡型号

```bash
lspci | grep -i nvidia
```

得到如下输出，其中`GeForce GTX 1080`就是型号

```
01:00.0 VGA compatible controller: NVIDIA Corporation GP104 [GeForce GTX 1080] (rev a1)
01:00.1 Audio device: NVIDIA Corporation GP104 High Definition Audio Controller (rev a1)
```

然后去[https://www.nvidia.cn/Download/index.aspx](https://www.nvidia.cn/Download/index.aspx)官网选择型号下载，得到类似`NVIDIA-Linux-x86_64-450.80.02.run`这样的执行文件




### 2、删除可能存在的Nvidia驱动

```bash
sudo apt-get remove --purge nvidia*
```

### 3、禁用原有开源显卡驱动

```bash
sudo vi /etc/modprobe.d/blacklist-nouveau.conf
```

然后将下面内容添加进去[存在则追加]，即将nouveau驱动拉黑

```
blacklist nouveau
options nouveau modeset=0
```

保存后，执行如下命令更新改动
```
sudo update-initramfs -u
```

执行`sudo reboot`**重启**电脑，nouveau驱动应该已被禁用，执行下面命令没有输出才对

```bash
lsmod | grep nouveau
```

### 4、切换至控制台

按下`Ctrl+Alt+f1`切换到tty1控制台，输入用户名密码进行登录。

PS: 如果是**server**版本的系统[无可视化桌面系统]，或者是**远程登录**到本机，请忽略该步骤

### 5、关闭桌面系统

桌面系统即`X Server`，用于在Linux下显示桌面和其他相关可视化环境。安装显卡驱动时必须要将目前的桌面服务关闭才能成功，同理如果是server版本则无需此操作。

```bash
sudo service lightdm stop
```

### 6、安装驱动

进入之前下载驱动文件的目录，执行如下命令

```bash
# 换成自己下载的文件名
sudo ./NVIDIA-Linux-x86_64-450.80.02.run
```

每次安装都会出现如下提示，实际上pre-install固定会失败的，目的就是为了让你知道你自己在干嘛，选择`Continue installation`
```
 The distribution-provided pre-install script failed!  Are you sure you want to continue?
```



如下提示是否需要32位兼容，不需要，`no`即可
```
Install NVIDIA's 32-bit compatibility libraries?
```

或者有时会出现如下提示，直接`ok`忽略即可，32位兼容的问题
```
Unable to find a suitable destination to install 32-bit compatibility libraries. Your system may not be set up for 32-bit compatibility. 32-bit compatibility files will not be installed; if you wish to install them, re-run the installation and set a valid directory with the --compat32-libdir option.
```


DKMS注册内核模块，直接`no`不需要
```
Would you like to register the kernel module sources with DKMS? This will allow DKMS to automatically build a new module, if you install a different kernel later
```



然后会有如下过程提示



是否运行Nvidia-xconfig来配置X configuration文件，选择`yes`
```
Would you like to run the nvidia-xconfig utility to automatically update your X configuration file so that the
 NVIDIA X driver will be used when you restart X?  Any pre-existing X configuration file will be backed up.
```




Tips: 如果提示这个Error，说明Xserver还没关，重新执行上面的第5步关闭Xserver
```
ERROR: You appear to be running an X server; please exit X before installing.  For further     
details, please see the section INSTALLING THE NVIDIA DRIVER in the README available on 
the Linux driver download page at www.nvidia.com.
```



### 6、验证安装

执行`nvidia-smi`命令能看到显卡相关信息即可，其中的`CUDA Version: 11.1`为最高能支持到的cuda版本，并非当前系统安装的cuda版本







## Cuda安装

### 1、从官网下载

下载地址为[https://developer.nvidia.com/cuda-downloads](https://developer.nvidia.com/cuda-downloads)，选择自己的版本，按照命令执行即可，`Installer Type`一般选择`runfile`，然后执行最下面提示的两句命令



### 2、按照1步骤的提示下载并执行

1步骤中的两句命令一般如下
```bash
# 下载文件
wget https://developer.download.nvidia.com/compute/cuda/xxx/local_installers/cuda_xxx_linux.run
# 进行安装
sudo sh cuda_xxx_linux.runfile
```

一开始会让你阅读一大堆说明，直接连续按**空格**到最下面即可，会有如下提示：

输入`accept`接受协议

```
Do you accept the previously read EULA?
accept/decline/quit: 
```   

是否安装显卡驱动，由于上面我们自己安装过了，所以这里选择输入`n`

```
Install NVIDIA Accelerated Graphics Driver for Linux-x86_64 410.48?
(y)es/(n)o/(q)uit:
```

是否安装toolkit，输入`y`

```
Install the CUDA 10.0 Toolkit?
(y)es/(n)o/(q)uit:
```

输入toolkit安装目录，默认即可，按`enter`

```
Enter Toolkit Location
 [ default is /usr/local/cuda-10.0 ]:
```

是否创建cuda软连接，输入`y`，这里一定要是y，会创建`/usr/local/cuda/`软链指向真正的cuda目录
```
Do you want to install a symbolic link at /usr/local/cuda?
(y)es/(n)o/(q)uit:
```

是否安装cuda样例，如果有测试需求可以`y`，当然`n`问题也不大，选择y的话下一步会确认安装目录，直接enter即可
```
Install the CUDA 10.0 Samples?
(y)es/(n)o/(q)uit: 
```

### 3、添加环境变量

执行`vi ~/.bashrc`，在文件尾部追加如下内容
```bash
export PATH=$PATH:$PATH:/usr/local/cuda/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64
export CUDA_HOME=$CUDA_HOME:/usr/local/cuda
```

保存后执行`source ~/.bashrc`使其生效

### 4、验证安装

执行下面命令会看到cuda版本

```bash
nvcc --version

# 如下输出
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2018 NVIDIA Corporation
Built on Sat_Aug_25_21:08:01_CDT_2018
Cuda compilation tools, release 10.0, V10.0.130
```



## Cudnn安装

> Cudnn安装即下载文件复制到Cuda目录的过程，故实际上并未真正安装软件


### 1、下载文件

下载地址为[https://developer.nvidia.com/cudnn](https://developer.nvidia.com/cudnn)，不过需要注册才能下载，点击Download


勾选 `I Agree` 那一行之后，根据自己上面安装的Cuda版本选择对应的Cudnn版本下载，这里选择的是`cuDNN Library for Linux (x86)`，下载后得到类似`cudnn-10.0-linux-x64-v7.6.5.32.tgz`的压缩文件


执行如下命令解压，会自动解压到cuda文件夹中

```bash
# 换成你下载的文件
tar -zxvf cudnn-10.0-linux-x64-v7.6.5.32.tgz
```

执行如下操作将头文件和so文件拷贝到cuda目录下即完成安装

```bash
# 注意下面的cuda-10.0目录换成上面安装的版本号
sudo cp cuda/include/cudnn.h /usr/local/cuda-10.0/include
sudo cp cuda/lib64/libcudnn* /usr/local/cuda-10.0/lib64

# 增加读取权限
sudo chmod a+r /usr/local/cuda-10.0/include/cudnn.h 
sudo chmod a+r /usr/local/cuda-10.0/lib64/libcudnn*
```



## Cuda版本切换

如果机器上安装了多个版本的cuda，则会在`/usr/local/`中存在多个cuda-xx的文件夹，如下：


其中`/usr/local/cuda`文件夹是个软链接，链接到目前的cuda版本目录，所以如果要切换版本的话，只需要将原来cuda软链删除，重新建立指向另一个cuda-xx目录即可

## 关于NVIDIA-SMI失效

有时候执行`nvidia-smi`命令时会报错`NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running`，这种可能是由于最近升级内核导致的新内核无法启动驱动，如下处理：

1.查看之前安装的nvidia驱动版本

```bash
ls /usr/src | grep nvidia

# 得到如下输出
nvidia-srv-510.47.03
```

2.使用dkms重新安装

```bash
# 有些机器需要安装dkms，如果已安装则忽略
sudo apt-get install dkms

# 使用dkms重新编译
# 注意 -v 后面的版本号，就是第一步中 nvidia- 后面的内容，如果上面输出是 nvidia-510.47 ，那就得 -v 510.47
sudo dkms install -m nvidia -v srv-510.47.03
```

3.重启电脑，再执行`nvidia-smi`就恢复正常了


## 其他工作

剩下的就是去装pytorch和tensorflow等机器学习库了，也不难，本文不再赘述

pytorch: [https://pytorch.org/get-started/locally/](https://pytorch.org/get-started/locally/)
tensorflow: [https://tensorflow.google.cn/install/gpu](https://tensorflow.google.cn/install/gpu)

---

## File: _posts/2023-05-27-mapreduce-java.md

---
layout: post
title: MapReduce实现WordCount词数统计入门教程
comments: 1
code: 1
keywords: MapReduce HADOOP WordCount
tags: [MapReduce, HADOOP]
---

WordCount单词统计是MapReduce的入门程序，用于统计文本文件中每个单词出现的个数，该文章用于记录实现过程。


## 新建WordCount.java文件

```java
import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordCount {

  public static class TokenizerMapper
       extends Mapper<Object, Text, Text, IntWritable>{

    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

    public void map(Object key, Text value, Context context
                    ) throws IOException, InterruptedException {
      StringTokenizer itr = new StringTokenizer(value.toString());
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        context.write(word, one);
      }
    }
  }

  public static class IntSumReducer
       extends Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable<IntWritable> values,
                       Context context
                       ) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
  }

  public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    Job job = Job.getInstance(conf, "word count");
    job.setJarByClass(WordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(args[0]));
    FileOutputFormat.setOutputPath(job, new Path(args[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

```

## 编译java文件

> 编译前请确保环境变量已成功配置`HADOOP_HOME`和`CLASSPATH`，否则编译会报错

```bash
javac WordCount.java

# 会生成如下class类文件
# 'WordCount$IntSumReducer.class'  'WordCount$TokenizerMapper.class'   WordCount.class

# 确保环境变量存在，一般都配置好了，该步骤可忽略
# export HADOOP_HOME="/opt/hadoop-2.9.2/"
# export CLASSPATH="$($HADOOP_HOME/bin/hadoop classpath):$CLASSPATH"
```

## 把class文件打包成jar

```bash
jar -cvf WordCount.jar WordCount*.class

# 会生成WordCount.jar文件，输出如下
# added manifest
# adding: WordCount$IntSumReducer.class(in = 1739) (out= 739)(deflated 57%)
# adding: WordCount$TokenizerMapper.class(in = 1736) (out= 754)(deflated 56%)
# adding: WordCount.class(in = 1491) (out= 814)(deflated 45%)
```

## 上传数据文件到HDFS

```bash
# words.txt在master节点或者slave节点均可，内容为任意一段话或者一篇英文文章
hadoop fs -copyFromLocal ./words.txt /data/words.txt
```

## 使用HADOOP运行jar包

```bash
# WordCount为主类名称，前面的java文件中定义的，/data/words.txt为hadf上需要统计的文本文件，/output/result指定输出目录
hadoop jar WordCount.jar WordCount /data/words.txt /output/result

# 正常执行输出如下
# INFO client.RMProxy: Connecting to ResourceManager at master/192.168.36.130:8032
# WARN mapreduce.JobResourceUploader: Hadoop command-line option parsing not performed. Implement the Tool interface and execute your application with ToolRunner to remedy this.
# INFO input.FileInputFormat: Total input files to process : 1
# INFO mapreduce.JobSubmitter: number of splits:1
# INFO Configuration.deprecation: yarn.resourcemanager.system-metrics-publisher.enabled is deprecated. Instead, use yarn.system-metrics-publisher.enabled
# INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1683783058362_0016
# INFO impl.YarnClientImpl: Submitted application application_1683783058362_0016
# INFO mapreduce.Job: The url to track the job: http://master:8088/proxy/application_1683783058362_0016/
# INFO mapreduce.Job: Running job: job_1683783058362_0016
# INFO mapreduce.Job: Job job_1683783058362_0016 running in uber mode : false
# INFO mapreduce.Job:  map 0% reduce 0%
# INFO mapreduce.Job:  map 100% reduce 0%
# INFO mapreduce.Job:  map 100% reduce 100%
# INFO mapreduce.Job: Job job_1683783058362_0016 completed successfully
# INFO mapreduce.Job: Counters: 49

# File System Counters...
# Job Counters...
# Map-Reduce Framework...
# Shuffle Errors...
# File Input Format Counters...
# File Output Format Counters...
```

## 查看结果

```bash
hadoop dfs -cat /output/result/part-r-00000

# 如下内容
#qii     1
#this    1
#tool    1
#world   1
#aha     2
#hello   1
#yes     2
```

## 报错说明

### 如果报错权限问题如下

```
Caused by: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.AccessControlException): Permission denied: user=xx, access=EXECUTE, inode="/tmp":root:supergroup:drwx------
        at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.check(FSPermissionChecker.java:350)
        at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkTraverse(FSPermissionChecker.java:311)
        at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkPermission(FSPermissionChecker.java:238)
        at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkPermission(FSPermissionChecker.java:189)
        at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkTraverse(FSPermissionChecker.java:539)
```

则说明HFDFS目录之前为root用户创建，当前用户为非root，没有权限，切换到root用户再执行即可。`sudo`执行可能会存在环境变量读取不到的问题，需要执行`sudo -s`切换root再进行操作。

### 如果编译时报错不存在

```
WordCount.java:4: error: package org.apache.hadoop.conf does not exist
import org.apache.hadoop.conf.Configuration;
                             ^
WordCount.java:5: error: package org.apache.hadoop.fs does not exist
import org.apache.hadoop.fs.Path;
                           ^
WordCount.java:6: error: package org.apache.hadoop.io does not exist
import org.apache.hadoop.io.IntWritable;

```

说明hadoop相关类名没有找到，需要配置class环境变量，可以手动执行命令，或者将其复制到`~/.bashrc`中

```bash
export HADOOP_HOME="/opt/hadoop-2.9.2/" # 改为实际目录
export CLASSPATH="$($HADOOP_HOME/bin/hadoop classpath):$CLASSPATH"
```

---

## File: _posts/2023-05-28-hadoop-setup.md

---
layout: post
title: Hadoop搭建及相关配套组件sqoop\flume\hive)说明
comments: 1
code: 1
keywords: HADOOP
tags: [HADOOP,sqoop,flume,hive]
---

本文介绍Hadoop搭建以及周边组件使用，如sqoop、flume、hive的安装等，配置为能跑起来的最简配置，主要演示Hadoop运行过程。

## 运行版本概述

```
/opt
├── apache-flume-1.8.0-bin
├── apache-hive-3.1.2-bin
├── hadoop-2.9.2
├── jdk1.8.0_371
└── sqoop-1.4.7.bin__hadoop-2.6.0
```

## Hadoop核心配置文件

> 详细配置项可参考https://blog.51cto.com/u_13836096/2532831 ，默认端口配置参考https://blog.csdn.net/Hu_wen/article/details/73481296

### core-site.xml

> 所有节点的defaultFS均为master，指定为同一个

```xml
<configuration>
        <property>
                <name>fs.defaultFS</name>
                <value>hdfs://master:9000</value>
        </property>
        <property>
                <name>hadoop.tmp.dir</name>
                <value>/opt/hadoop-2.9.2/data/tmp</value>
        </property>
</configuration>

```

### hadoop-env.sh

> 只需要配置JAVA_HOME即可，可以通过读取环境变量的方式，也可以显式声明

```bash
# export JAVA_HOME=${JAVA_HOME}
export JAVA_HOME=/opt/jdk1.8.0_371/
```

### hdfs-site.xml

> replication配置为节点数即可，`namenode.secondary.http-address`配置为第二个节点

```xml
<configuration>
<property>
        <name>dfs.replication</name>
        <value>3</value>
</property>
<property>
        <name>namenode.secondary.http-address</name>
        <value>slave2:50090</value>
</property>
</configuration>

```

### mapred-env.sh

> 只需要配置JAVA_HOME即可，可以通过读取环境变量的方式，也可以显式声明

```bash
# export JAVA_HOME=${JAVA_HOME}
export JAVA_HOME=/opt/jdk1.8.0_371/
```

### mapred-site.xml

> 更改framework为yarn服务

```xml
<configuration>
<property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
</property>
</configuration>

```

### yarn-env.sh

> 只需要配置JAVA_HOME即可，可以通过读取环境变量的方式，也可以显式声明

```bash
# export JAVA_HOME=${JAVA_HOME}
export JAVA_HOME=/opt/jdk1.8.0_371/
```

### yarn-site.xml

> 指定resourcemanager的节点，master即可，测试中slave1 slave2启动均失败，只能启动resourcemanager，无法启动nodemanager，需要指定hostname为master且在master启动【或者将该配置文件configuration中内容全部删除后再在master启动】

```xml
<configuration>
<!-- Site specific YARN configuration properties -->
    <property>
            <name>yarn.resourcemanager.hostname</name>
            <value>master</value>
    </property>
    <property>
            <name>yarn.nodemanager.aux-services</name>
            <value>mapreduce_shuffle</value>
    </property>
</configuration>

```

### masters

> 指定的master节点，只填写为master即可，所有节点均需要配置此项

```
master
```

### slaves

> 指定的slave节点，填写为slave1 slave2，注意只有master节点才需要填写，slave节点不需要更改此配置

```
slave1
slave2
```

## 启动Hadoop

```bash
# 只在master节点执行，通过master启动其他slave，前提是配置好ssh免登录
start-hdfs.sh
```

## 启动Yarn

```bash
# 需要在ResourceManager节点启动Yarn，实际上只在master执行即可，其他节点执行会有问题
start-yarn.sh
```

## 可视化访问

HDFS主页面 http://master:50070
Yarn管理页面 http://master:8088


## 其他相关命令

### sqoop导入hdfs

> 保证mysql可以通过非localhost远程访问，使用下面命令导入. 注意mysql后面的地址为其他节点可访问的地址，不可用localhost

```bash
sqoop import --connect "jdbc:mysql://master:3306/ttt" --delete-target-dir --username root --password 123 --table staff -m 1 --target-dir /ttt/sqoop_import
```

### Flume

监听端口

```bash
flume-ng agent --conf conf/ --conf-file conf/netcat-logger.conf --name a1 -Dflume.root.logger=INFO,console
```

```ini
# netcat-logger.conf
# 示例配置方案: 单节点 Flume 配置

#为agent各组件命名
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# 描述并配置sources组件（数据类型、采集数据源的应用地址）
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444

# 描述并配置sinks组件(采集后数据流出的类型)
a1.sinks.k1.type = logger

# 描述并配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 将source 和 sink 通过同一个channel连接绑定
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
```

监听新增文件，并传入到hdfs

```bash
flume-ng agent -c conf -f conf/hdfs.conf -n agent1 -Dflume.root.logger=INFO,console
```

```ini
# hdfs.conf
#定义三大组件的名称
agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1

# 配置source组件
agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /opt/apache-flume-1.8.0-bin/mylogs/
agent1.sources.source1.fileHeader = false

# 配置拦截器
agent1.sources.source1.interceptors = i1
agent1.sources.source1.interceptors.i1.type = host
agent1.sources.source1.interceptors.i1.hostHeader = hostname

# 配置sink组件
agent1.sinks.sink1.type = hdfs
agent1.sinks.sink1.hdfs.path =hdfs://master:9000/weblog/%Y%m%d-%H-%M
agent1.sinks.sink1.hdfs.filePrefix = access_log
agent1.sinks.sink1.hdfs.maxOpenFiles = 5000
agent1.sinks.sink1.hdfs.batchSize= 100
agent1.sinks.sink1.hdfs.fileType = DataStream
agent1.sinks.sink1.hdfs.writeFormat =Text
agent1.sinks.sink1.hdfs.rollSize = 102400
agent1.sinks.sink1.hdfs.rollCount = 1000000
agent1.sinks.sink1.hdfs.rollInterval = 60
#agent1.sinks.sink1.hdfs.round = true
#agent1.sinks.sink1.hdfs.roundValue = 10
#agent1.sinks.sink1.hdfs.roundUnit = minute
agent1.sinks.sink1.hdfs.useLocalTimeStamp = true

# Use a channel which buffers events in memory
agent1.channels.channel1.type = memory
agent1.channels.channel1.keep-alive = 120
agent1.channels.channel1.capacity = 500000
agent1.channels.channel1.transactionCapacity = 600

# Bind the source and sink to the channel
agent1.sources.source1.channels = channel1
agent1.sinks.sink1.channel = channel1
```

### Hive

下载Hive

```bash
wget https://mirrors.tuna.tsinghua.edu.cn/apache/hive/hive-3.1.2/apache-hive-3.1.2-bin.tar.gz
```

1. 编辑`conf/hive-env.sh`文件（没有则从template文件复制），增加如下两项
	```ini
	# Set HADOOP_HOME to point to a specific hadoop install directory
	HADOOP_HOME=/opt/hadoop-2.9.2/
	# Hive Configuration Directory can be controlled by:
	export HIVE_CONF_DIR=/opt/apache-hive-3.1.2-bin/conf
	```

1. 执行`schematool -dbType derby -initSchema`进行数据库初始化，会在当前目录生成`metastore_db`文件夹，即metadata数据库

1. 执行hive命令即可

---

## File: _posts/aa-2015-07-03-zhangman-birthday.md

---
layout: post
title: 今天女朋友生日，不能忘哈哈
keywords: 张漫, 我女朋友
comments: 1
tags: [other]
---

咳咳，今天，07月03号，我女朋友张漫的阳历生日，在一起那么久了，可以算是老夫老妻了都


```ini

.___  .__
|   | |  |   _______  __ ____    ___.__. ____  __ __
|   | |  |  /  _ \  \/ // __ \  <   |  |/  _ \|  |  \
|   | |  |_(  <_> )   /\  ___/   \___  (  <_> )  |  /
|___| |____/\____/ \_/  \___  >  / ____|\____/|____/
                            \/   \/
       .__
_______|  |__ _____    ____    ____     _____ _____    ____
\___   /  |  \\__  \  /    \  / ___\   /     \\__  \  /    \
 /    /|   Y  \/ __ \|   |  \/ /_/  > |  Y Y  \/ __ \|   |  \
/_____ \___|  (____  /___|  /\___  /  |__|_|  (____  /___|  /
      \/    \/     \/     \//_____/         \/     \/     \/
       .__.__   _____  _______      _____
  _____|__|__| /  |  | \   _  \    /  |  |
 / ____/  |  |/   |  |_/  /_\  \  /   |  |_
< <_|  |  |  /    ^   /\  \_/   \/    ^   /
 \__   |__|__\____   |  \_____  /\____   |
    |__|          |__|        \/      |__|

```


谁说码农没有情调的，不服来辩~
@张漫

---

## File: _posts/removed-2015-11-09-goagent.md

removed