Archive for 未分类

freenas 安装 syncthing 并配置权限

freenas 安装 syncthing 并配置权限

pw userdel syncthing
pw groupdel syncthing

pw groupadd -n gdata -g 1001
pw useradd -n syncthing -u 1002 -d /vat/tem/syncthing -s /usr/sbin/nologin -g gdata

freenas本身
添加 syncthing 用户,uid为 983

执行下列命令,保证数据目录所属组为 gdata,并为所有目录增加 SGID 权限。
find /some/dir -type d -exec chgrp www {} +
find /some/dir -type d -exec chmod g+s {} +

syncthing@jails@freenas 内执行下面的命令。创建 dgata 用户组,并将 syncthing 加到组里面。

chown -R 账号名:组名 文件或目录
pw groupadd -n gdata -g 1001
pw groupmod gdata -m syncthing

新建文件所属组设置
https://blog.csdn.net/furzoom/article/details/77737344

枫竹梦 2017-08-31 10:54:29

问题场景1:在Linux进行开发Web程序,Web root目录下某些目录需要被www用户进行写操作,而开发过程中使用的用户假设为mn,新建的目录和文件默认用户/组为mn:mn,这对www用户来讲是不可写的。最简单的办法是使用chown -R mn:www xxx设置用户/组为mn:www。这样每次有新的文件都需要进行设置,十分不方便。

问题场景2:对于运维人员来讲,需要在dirA目录下新建的文件为groupA组,在dirB目录下新建的文件为groupB组。

解决办法
使用Linux下的特殊权限位即可以实现。例如对于问题场景2中的情况,将dirA目录的用户组设置为groupA,然后添加SGID权限,即:

chgrp groupA dirA/
chmod g+s dirA/

这样得到dirA的目录权限为

drwxrwsr-x

以后在dirA下新的目录具有相同的权限,且用户组为groupA;新建的文件用户组同样为groupA。

如果是已经存在的目录需要同样的设置,使用如下命令:

find /some/dir -type d -exec chgrp www {} +
find /some/dir -type d -exec chmod g+s {} +

NB: 上述命令的 {} + 表示将find查找到的所有目录添加到命令后进行执行,而不是对每一个查找到的目录执行一次。

特殊权限知识
Linux下的文件权限除了用户、组、其他的可读(r)、可写(w)、可执行(x)之外,还有三个特殊的权限位,它们是SUID、SGID、SBIT(粘滞位)。

SUID
s出现在文件所有者的x权限位上。
SUID用于可执行文件,使得该文件在执行时具有该文件所有者的权限。
对于没有可执行权限的文件设置SUID,该文件不会具体可执行权限。
chmod u+s somefile

如/usr/bin/passwd的文件。
SGID
s出现在文件所属组的x权限上。
SGID用于文件和目录。

对于目录,设置SGID,在该目录下新建的目录同样有SGID权限和所属组。
对于文件,设置SGID,使用该文件执行时具有所属组的权限。
chmod g+s somefile
SBIT
t出现在文件其他的x权限上。
SBIT用于目录。

设置了SBIT的目录,下新建的文件,只有自己和root可以进行删除。

如/tmp目录。

No comment »

4线风扇温度调速+WEB显示

 

-- D6 接 ds18b20 中  左为地 右VCC
-- D7 接风扇调速(蓝线(最边上))。 4线风扇依次为:地、VCC、测速(1圈2方波)、PWN调速

ds18b20=require("ds18b20")

ds18b20.setup(6)

pwm.setup(7, 1000, 512)
pwm.start(7)

-- 温度越高转速越高
-- minT 度是起点    
minT = 20
-- maxT 度是风扇最高速度
maxT = 80

temperature = nil
speed = nil
duty = 1023

function t()
    temperature = ds18b20.readNumber(nil,ds18b20.C)
    if (temperature == nil) then
        duty = 1023
    else
        if (temperature<=minT) then
            duty = 0
        elseif (temperature>=maxT) then
            duty = 1023
        else
            duty = (temperature-minT)*1023/(maxT-minT)
        end
    end    
    pwm.setduty(7,duty)    
    print("temperature:",temperature," duty:",duty,"/1023")
    tmr.softwd(-1)
    tmr.softwd(5)
end

tmr.alarm(1,1000,tmr.ALARM_AUTO,t)

tmr.softwd(5)

wifi.setmode(wifi.STATION)
wifi.sta.config("XXX", "XXXXXXXXXX", 1)
wifi.sta.sethostname("NodeMCU")
cfg={}
cfg.ip="192.168.101.6"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.101.1"
wifi.sta.setip(cfg)

srv=net.createServer(net.TCP) 
srv:listen(80,function(conn) 
    conn:on("receive",function(conn,payload) 
        local html = string.format("HTTP/1.0 200 OK\r\n"
        .."Content-Type: text/html\r\n"
        .."Connection: Close\r\n\r\n"
        .."Temperature:%f
duty:%d/1023",temperature,duty) conn:send(html,function(sent) conn:close() end) end) end)

No comment »

修正中文 gitbook pdf 书籍字体大小不一的问题

第二次用了,记录下来,下次就不用在查资料了。

gitbook 网站生成的 pdf 中文字体非常难看,字体大小不一。

解决办法是手工指定中文字体,指定 pdf 文字大小。

book.json 例子:

 
{
  "gitbook": "2.x.x",
  "title": "Go语言圣经",
  "description": "中文版",
  "language": "zh",
  "structure": {
    "readme": "preface.md"
  },
  "pluginsConfig": {
    "fontSettings": {
      "theme": "white",
      "family": "msyh",
      "size": 2
    },
    "plugins": [
      "yahei",
      "katex",
      "-search"
    ]
  },
  "pdf": {
    "pageNumbers": true,
    "fontSize": 18,
    "paperSize": "a4",
    "margin": {
      "right": 30,
      "left": 30,
      "top": 30,
      "bottom": 50
    }
  }
}

例子使用的 微软雅黑 字体,默认linux下没有这个字体,需要手工将字体拷贝到 /usr/share/fonts/truetype 目录。

可以通过 travis 自动生成 pdf 文件,.travis.yml 例子是:

 
language: node_js

node_js:
  - "stable"

script:
  - echo pass

before_deploy:
  - sudo apt-get update -qq
  # install Chinese fonts
  - sudo apt-get install -y fonts-arphic-gbsn00lp golang
  # Install Gitbook
  - npm install gitbook-cli -g
  - npm install svgexport -g
  - npm install gitbook-plugin-yahei
  # Clone the repository
  - sudo -v && wget --no-check-certificate -nv -O- https://raw.githubusercontent.com/kovidgoyal/calibre/master/setup/linux-installer.py | sudo python -c "import sys; main=lambda:sys.stderr.write('Download failed\n'); exec(sys.stdin.read()); main()"

  - sudo cp tools/*.ttc /usr/share/fonts/truetype

  - go run zh2tw.go . .md$ tw2zh

  - gitbook install
  - gitbook build ./ --format=json
  - gitbook build ./
  - gitbook pdf ./ ./gopl-ch.pdf
  - gitbook epub ./ ./gopl-ch.epub
  - gitbook mobi ./ ./gopl-ch.mobi


deploy:
  provider: releases
  api_key: $CI_USER_TOKEN
  skip_cleanup: true
  file:
    - gopl-ch.pdf
    - gopl-ch.epub
    - gopl-ch.mobi
  on:
    tags: true
    all_branches: true

这个例子会在每次 git tag 时自动生成 pdf 文件,并发布到 github 。需要将 github token 设在到 CI_USER_TOKEN 环境变量。

Comments (2) »

Docker 小抄

之前学习docker时整理的,现在有的命令忘记了。打印出来,方便随时查找。

web 版本格式没了,[PDF]Docker 小抄 有正确的格式。

镜像部分

docker pull ubuntu:14.04
获得 ubuntu 标签为 14.04的镜像,如果省略14.04则下载 latest 标签的镜像,ubuntu默认为最新的长期支持版本。相当于:docker pull registry.hub.docker.com/ubuntu:14.04 。

docker images
显示本地已有镜像。镜像id表示了唯一的镜像,有些的镜像可能存在多个 TAG ,但是id一致表示实际是一个镜像。

docker commit -m “提交说明” -a “提交用户” 容器id 用户名/仓库名:TAG
提交镜像,只是提交到本地,并没有上传到 docker hub 。

sudo cat ubuntu-14.04-x86_64-minimal.tar.gz |docker import – ubuntu:14.04
从本地导入镜像。源可以是 openvz 模板,地址:http://openvz.org/Download/templates/precreated

docker push ouruser/sinatra
上传镜像到 docker hub

docker save -o ubuntu14.04.tar ubuntu:14.04
导出镜像到文件

docker load –input ubuntu14.04.tar 或 docker load < ubuntu14.04.tar 导入镜像,这将导入镜像及相关元数据(包括标签等信息)。 docker rmi aaaaa/bbbb 删除镜像,注意 docker rm 是删除容器。无法删除使用中的镜像。 docker rmi $(docker images -q -f "dangling=true") 移除未打标签的中间镜像。 容器 docker run ubuntu:14.04 /bin/echo 'hello word' 使用 ubuntu:14.04 镜像启动一个新的容器并执行 /bin/echo 'hello word' 命令。 默认容器停止后还会继续保留,可以通过 start 命令重新启动,可以通过 --rm 参数实现容器停止自动删除的功能。--restart=always选项用来保证Docker守护进程在容器出错或者重启后自动启动容器。 docker run -t -i ubuntu:14.04 /bin/bash 启动容器并执行 /bin/bash 命令。 -t 是分配伪终端并绑定到容器的标准输入上面。-i 则是保持标准输入打开状态。 docker start 容器id 启动已停止的容器,将继续执行创建时指定的命令。通过增加 -i 参数可以进入交互模式。 docker stop 容器id 停止容器。并不会丢失对容器所做的修改!!通过 start 重新启动容器后可以发现之前的修改都保存着! docker restart 容器id 重启容器。同样不会丢失所做的修改! docker run -d 镜像名称 命令 后台启动容器,就是不要将标准输出在当前终端下。注意:容器是否会长期运行是和指定的命令有关,而和 -d 参数没有关系。 docker log 容器id 查询容器日志,就是标准输出记录。 docker attach 容器id 附加到指定容器,个人理解就是将容器标准输出连接到当前终端,将当前中断的标准输入连接到容器。注意,多个附加操作是显示一样的内容。实际操作容器时建议通过新命令 docker exec 大体。退出,一定不要用ctrl+c,那样就是让docker容器停止了。要用如下快捷键:先按,ctrl+p再按,ctrl+q 。使用CTRL-c或CTRL-d退出容器,将向容器发送SIGKILL,导致容器停止。使用 CTRL-\退出容器时,将会输出docker客户端的堆栈信息。 docker run -t -i → can be detached with ^P^Q and reattached with docker attach docker run -i → cannot be detached with ^P^Q; will disrupt stdin docker run → cannot be detached with ^P^Q; can SIGKILL client; can reattach with docker attach docker exec 容器id 命令 在指定的容器内执行命令。执行/bin/bash命令需要通过 -ti 选项打开伪终端及标准输入。已经关闭的容器无法执行命令。 nsenter 老的在指定容器执行命令的方案。 docker export 容器id > ubuntu.tar
导出容器快照到文件。

cat ubuntu.tar | sudo docker import -test/ubuntu:v1.0
导入容器快照为镜像。容器快照不保存历史纪录及元数据,体积小;load 导入的镜像储存包含历史纪录及元数据(标签等),体积比较大。

docker import http://www.aaa.com/bbb.tgz
通过网络导入容器快照。

docker rm 容器id
删除容器。-v 参数表示同时删除数据卷,否则即使容器被删除数据卷也会永久保留。

docker rm -v $(docker ps -a -q)
删除所有未运行的容器,-v 参数使得会同时删除数据卷。

docker inspect 容器id
审计容器,显示容器的详细信息。

数据管理

sudo docker run -ti -v /user_data ubuntu /bin/bash
启动一个容器,并且挂载了一个 /user_data 的数据卷。数据卷绕过了 UFS ,对数据卷的更新不会影响镜像,默认会一直存在,不会随着容器的删除而删除。实际实现是见一个目录挂载到了容器指定的目录,这里是 /user_data 。未指定数据卷源时源是在主机的 /var/lib/docker/volumes 目录下,docker inspect 容器id 输出的容器详细信息里面有数据卷的详细信息。
“Volumes”: {
“/udata”: “/var/lib/docker/volumes/1328a35ef12eb9cfa97210f567a13d70ccf157856c2d79f922a858ef4da1fad8/_data”
},
“VolumesRW”: {
“/udata”: true
},
可以看到 /user_data 数据卷实际是主机的/var/lib/docker/volumes/1328a35ef12eb9cfa97210f567a13d70ccf157856c2d79f922a858ef4da1fad8/_data目录,对这个目录的修改容器的/user_data 会实时的反应出来。
不指定数据卷源时会自动创建一个新的目录作为数据卷的源,默认每个容器的数据卷不相关。

docker run -ti -v /user_data:~/user_data:ro ubuntu /bin/bash
使用主机的 ~/user_data 目录作为源为容器创建一个路径是 /user_data 的只读数据卷,ro 表示只读。
也可以挂载单个文件作为数据卷,但是 vi 等文本编辑工具会造成文件 inode 改变,会使得docker报告错误。

docker rm -v 容器id
删除容器并删除数据卷。资料显示目前 docker 默认删除容器时不会删除容器卷,同时不存在删除无主地数据卷的功能,是个大坑…

docker run -d -v /dbdata –name dbdata training/postgres echo Data-only container for postgres
创建一个名叫 dbdata 的容器,它有一个数据卷 /dbdata ,它的启动命令是 echo … 表示启动就输出一句话就结束容器(数据卷容器不需要启动即可使用)。

docker run -d –volumes-from dbdata -ti –name db1 ubuntu bash
启动一个新容器,它挂载数据卷容器 dbdata 。也就是它将完全以和 dbdata 容器 相同的容器路径挂载相同的主机目录。也就是本容器 /dbdata 和 容器 dbdata 下的 /dbdata 都是挂载的相同的主机目录。可以通过多个 –volumes-from 参数来从多个数据卷容器挂载数据,一个数据卷容器可以同时被多个容器挂载。
同样删除容器及数据卷容器默认不会实际删除数据,必须在删除最后一个容器时通过 docker rm -v 增加 -v 参数来实际删除数据。

docker run –volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
备份数据卷容器的数据。命令实际执行的操作:容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。

docker run -v /dbdata –name dbdata2 ubuntu /bin/bash
创建一个空数据卷容器
docker run –volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf
/backup/backup.tar
将当前目录的backup.tar 内的数据恢复到 数据卷容器 dbdata2 。命令实际执行的操作:创建一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。
docker run –volumes-from dbdata2 busybox /bin/ls /dbdata
检查恢复是否成功。

网络功能

docker run -d -P training/webapp python app.py
启动一个容器, 使用 -P 参数时,docker 将会随机的映射一个 49000-49900 的端口到容器开放的网络端口。

$ sudo docker ps -l
]CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
532192b4328c training/webapp “python app.py” About a minute ago Up About a minute 0.0.0.0:32768->5000/tcp suspicious_lovelace
可以参看映射的端口号。

$ sudo docker port 5321
5000/tcp -> 0.0.0.0:32768
可以查看指定容器的端口映射。

docker run -d -p 5000:5000 training/webapp python app.py
小写的 p 映射制定的端口。可选的格式是:ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort 。

docker run -d -p 5000:5000/udp training/webapp python app.py
增加 /udp 标记表示映射 udp 端口。

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER all — anywhere anywhere
ACCEPT all — anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT all — anywhere anywhere
ACCEPT all — anywhere anywhere

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp — anywhere 172.17.0.24 tcp dpt:5000
可以看到端口映射是通过 iptables 实现的。

docker run -d –name db -e AAA=A1 -e bbb=b1 training/postgres
docker run -d -P –name web –link db:db training/webapp python app.py
建立一个名为 db 的容器,然后创建一个名为 web 的容器,并连接到 db 容器。注意:即使不执行连接操作默认所有容器也都在同一个虚拟局域网里面,是可以互相通信的,只不过不知道docker为容器分配的ip地址而已。可以通过 docker 服务的 –icc=false 参数禁止默认容器之间的互联,同时 –iptables=true 允许 docker 修改 iptables 时,link 操作会自动添加 iptables 规则允许相互访问开放的端口。

$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3113cef6d6b0 training/webapp “python app.py” 2 minutes ago Up About a minute 0.0.0.0:32770->5000/tcp web
b2fbf68028c3 training/postgres “su postgres -c ‘/us 4 minutes ago Up 4 minutes 5432/tcp db
文档上面说 ps 命令在NAMES字段会显示连接关系,但是实测没有…

root@vps11:~# docker exec -ti web bash
root@5a48dc56a38e:/opt/webapp# env
HOSTNAME=5a48dc56a38e
DB_NAME=/web/db
DB_PORT_5432_TCP_ADDR=172.17.0.5
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
LS_COLORS=
DB_ENV_bbb=b1
DB_ENV_AAA=A1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/opt/webapp
DB_PORT_5432_TCP_PORT=5432
SHLVL=1
HOME=/root
DB_PORT_5432_TCP_PROTO=tcp
LESSOPEN=| /usr/bin/lesspipe %s
DB_ENV_PG_VERSION=9.3
LESSCLOSE=/usr/bin/lesspipe %s %s
_=/usr/bin/env
root@5a48dc56a38e:/opt/webapp# cat /etc/hosts
172.17.0.6 5a48dc56a38e
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.5 db 2f115673d7cd
可以看到 docker 通过环境变量及hosts文件为web容器公开了连接信息(并没有为 db 容器公开web 的信息),DB_的前缀是连接别名。同时 link 还会为web容器公开了db容器 -e 设置的环境变量(环境变量前面增加大写的被连接容器名字,小写的环境变量也会同样小写转发过去。) inspect web 能看到 link 了 db,但是看不到 db 的环境变量,只能看到 db -e 设置的自身的环境变量。

Dockerfile

# This dockerfile uses the ubuntu image
# VERSION 2 – EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# 基础镜像
FROM ubuntu

# Maintainer: 维护者信息
MAINTAINER docker_user [email protected]

# Commands to update the image
RUN echo “deb http://archive.ubuntu.com/ubuntu/ raring main universe” >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo “\ndaemon off;” >> /etc/nginx/nginx.conf

# 启动容器时运行的命令,如果指定多个,只执行最后一个。如果 run 时指定了命令会覆盖当前设置。
CMD /usr/sbin/nginx

FROM
格式为 FROM 或FROM :
第一条指令必须为 FROM 指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。
MAINTAINER
格式为 MAINTAINER ,指定维护者信息。
RUN
格式为 RUN  或 RUN [“executable”, “param1”, “param2”]。
前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN [“/bin/bash”, “-c”, “echo hello”]。
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\ 来换行。
CMD
支持三种格式
• CMD [“executable”,”param1″,”param2″] 使用 exec 执行,推荐方式;
• CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
• CMD [“param1″,”param2”] 提供给 ENTRYPOINT 的默认参数;
指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。
EXPOSE
格式为 EXPOSE […]。
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。
ENV
格式为 ENV 。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
例如
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD
格式为 ADD
该命令将复制指定的  到容器中的 。 其中  可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
COPY
格式为 COPY
复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 
当使用本地目录为源目录时,推荐使用 COPY。
ENTRYPOINT
两种格式:
• ENTRYPOINT [“executable”, “param1”, “param2”]
• ENTRYPOINT command param1 param2(shell中执行)。
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。
VOLUME
格式为 VOLUME [“/data”]。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。相当于 docker -v 参数,为了可移植性考虑,不允许指定主机路径,只能指定容器内的路径。
USER
格式为 USER daemon。
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用 gosu,而不推荐 sudo。
WORKDIR
格式为 WORKDIR /path/to/workdir。
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c。
ONBUILD
格式为 ONBUILD [INSTRUCTION]。
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
[…]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build –dir /app/src
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。

来自

创建镜像

编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像。

基本的格式为 docker build [选项] 路径,该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下所有内容发送给 Docker 服务端,由服务端来创建镜像。因此一般建议放置 Dockerfile 的目录为空目录。也可以通过 .dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略路径下的目录和文件。

要指定镜像的标签信息,可以通过 -t 选项,例如

$ sudo docker build -t myrepo/myapp /tmp/test1/

No comment »

golang recover() 的一个坑,记录一下。

今天碰到了一个 recover() 的一个坑。

有一个操作预期可能出现 panic 而且不需要关心它是否panic.就建立了一个匿名函数将操作包裹起来并 追加了 defer recover。 原来都是 defer func(){_=recover()}(),这次省掉了匿名函数,直接 defer recover() 。

 
package main

func main(){
    defer recover()
    panic("123")
}
/*

panic: 123

goroutine 1 [running]:
main.main()
        D:/golang/src/github.com/gamexg/go-test/鍩烘湰璇硶/recover娴嬭瘯/1/1.g         
o:5 +0x97
exit status 2
*/

结果如上,直接挂了。recover 无效…

查看了 recover 的文档:recover 只能放到 defer 函数里面,不能放到子函数。实测直接 defer recover() 也不行。recover 是内置函数,源码需要到go的实现里面查,懒得去查了。

go 的很多设计感觉都是黑魔法,就像这里,如果 panic 是一个协程本地变量保存的,那么在什么地方调用 recover 都行。但是看情况不是,恐怕和函数调用搅到一起了。

v:=dict[key]、 v,ok:=dict[key] 这个设计也是使用的黑魔法,感觉是一开始只设计了第一种实现,但是由于需要获得是否存在key,就另加了第二个实现,很生硬的感觉。

chan 实现也比较奇怪,连接之类的关闭都是 conn.Close() 实现关闭,chan 却是 close(chan),设计并不统一,很别扭。

Comments (1) »

完整的golang 多协程+信道 任务处理示例

有几个地方需要注意:for i + 协程时如果协程使用 i ,那么需要增加 i:=i 来防止多协程冲突;实际执行任务时需要用一个函数包起来,防止单个任务panic造成整个程序崩溃。

 
package main
import (
    "sync"
    "fmt"
)

/*
一个标准的协程+信道实现

*/

func main() {

    taskChan := make(chan int)
    TCount := 10
    var wg sync.WaitGroup //创建一个sync.WaitGroup

    // 产生任务
    go func() {
        for i := 0; i < 1000; i++ {
            taskChan <- i
        }
        // 全部任务都输入后关闭信道,告诉工作者进程没有新任务了。
        close(taskChan)
    }()

    // 告诉 WaitGroup 有 TCount 个执行者。
    wg.Add(TCount)
    // 启动 TCount 个协程执行任务
    for i := 0; i < TCount; i++ {

        // 注意:如果协程内使用了 i,必须有这一步,或者选择通过参数传递进协程。
        // 否则 i 会被 for 所在的协程修改,协程实际使用时值并不确定。
        i := i

        go func() {

            // 协程结束时报告当前协程执行完毕。
            defer func() { wg.Done() }()

            fmt.Printf("工作者 %v 启动...\r\n", i)

            for task := range taskChan {

                // 建立匿名函数执行任务的目的是为了捕获单个任务崩溃,防止造成整个工作者、系统崩溃。
                func() {

                    defer func() {
                        err := recover()
                        if err != nil {
                            fmt.Printf("任务失败:工作者i=%v, task=%v, err=%v\r\n", i, task, err)
                        }
                    }()

                    // 故意崩溃,看看是不是会造成整个系统崩溃。
                    if task%100==0{
                        panic("故意崩溃啦")
                    }

                    // 这里的 task 并不需要通过参数传递进来。
                    // 原因是这里是同步执行的,并不会被其它协程修改。
                    fmt.Printf("任务结果=%v ,工作者id=%v, task=%v\r\n",task*task,i,task)
                }()
            }

            fmt.Printf("工作者 %v 结束。\r\n", i)
        }()
    }

    //等待所有任务完成
    wg.Wait()
    print("全部任务结束")
}

Comments (2) »

shadowsocks-windows 修改版

修改socks5协议连接建立成功回复发送时间

原始的是socks5收到tcp代理请求立刻回复已连接到远程主机。
现在改为只有建立了到远程ss的连接后才回复链接已建立。

shadowsocks-windows 修改版

No comment »

shadowsocks-csharp 源码分析

坑死啊,之前使用 shadowsocks-csharp 作为上层代理就发现有些不对劲,这次仔细研究下代码,发现被坑了…
shadowsocks-csharp 并没有按标准 socks5 协议实现…

socks5 接受新连接是在这里:
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/Listener.cs#L83

// Start an asynchronous socket to listen for connections.
Console.WriteLine("Shadowsocks started");
_tcpSocket.BeginAccept(
   new AsyncCallback(AcceptCallback),
        _tcpSocket);
UDPState udpState = new UDPState();
_udpSocket.BeginReceiveFrom(udpState.buffer, 0, udpState.buffer.Length, 0, ref udpState.remoteEndPoint, new AsyncCallback(RecvFromCallback), udpState);

开始第一次数据接收,这里有个坑,第一次会接受 buf.Length (4096) 直接的数据,但是后面时却只处理了前几个字节,后面的内容被抛弃了。我上次为了降低延迟鉴定及请求命令一起发过来,结果只收到了鉴定回应,现在知道原因了,ss 只处理了鉴定,连接请求被丢弃了…
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/Listener.cs#L159

        public void AcceptCallback(IAsyncResult ar)
        {
            Socket listener = (Socket)ar.AsyncState;
            try
            {
                Socket conn = listener.EndAccept(ar);

                byte[] buf = new byte[4096];
                object[] state = new object[] {
                    conn,
                    buf
                };

                conn.BeginReceive(buf, 0, buf.Length, 0,
                    new AsyncCallback(ReceiveCallback), state);
            }
            catch (ObjectDisposedException)

这里遍历自身支持的服务,确定是用什么服务来处理。
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/Listener.cs#L198

                foreach (Service service in _services)
                {
                    if (service.Handle(buf, bytesRead, conn, null))
                    {
                        return;
                    }
                }

TCPRelay 为每个连接建立一个 Handler 来处理请求。
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/TCPRelay.cs#L22

        public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
        {
            if (socket.ProtocolType != ProtocolType.Tcp)
            {
                return false;
            }
            if (length < 2 || firstPacket[0] != 5)
            {
                return false;
            }
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
            Handler handler = new Handler();
            handler.connection = socket;
            handler.controller = _controller;

            handler.Start(firstPacket, length);
            return true;
        }

将第一次收到的数据保存到内部变量,HandshakeReceive里面去处理。
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/TCPRelay.cs#L94

        public void Start(byte[] firstPacket, int length)
        {
            this._firstPacket = firstPacket;
            this._firstPacketLength = length;
            this.HandshakeReceive();
        }

这里就是有问题的地方,对第一次收到的数据只检查了下头就把数据抛弃了…
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/TCPRelay.cs#L175

        private void HandshakeReceive()
        {
            if (closed)
            {
                return;
            }
            try
            {
                int bytesRead = _firstPacketLength;

                if (bytesRead > 1)
                {
                    byte[] response = { 5, 0 };
                    if (_firstPacket[0] != 5)
                    {
                        // reject socks 4
                        response = new byte[] { 0, 91 };
                        Console.WriteLine("socks 5 protocol error");
                    }
                    connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(HandshakeSendCallback), null);
                }
                else
                {
                    this.Close();
                }
            }
            catch (Exception e)
            {
                Logging.LogUsefulException(e);
                this.Close();
            }
        }

这里对收到的代理请求直接回复连接成功,而没有管是不是成功。这又是一个坑啊…造成测速失败…
https://github.com/shadowsocks/shadowsocks-windows/blob/master/shadowsocks-csharp/Controller/Service/TCPRelay.cs#L232

                    command = connetionRecvBuffer[1];
                    if (command == 1)
                    {
                        byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
                        connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(ResponseCallback), null);
                    }
                    else if (command == 3)
                    {
                        HandleUDPAssociate();
                    }

No comment »

修改android 中兴天机(ZTE S291)开机第一屏(第二屏)画面

接上一篇,android 本身提供了2个简单的办法修改第一屏画面,但是我这居然都不能用。

第一个办法是通过 fastboot flash splash1 写入新开机画面,但是我这里发现中兴是将图片保存在splash分区,并且是多个画面保存在同一个分区。直接将单个图片刷入 splash ,直接把手机变成转了,由于 FTM 开机画面也保存在 splash ,造成连 FTM 都进不去,无奈只能返厂,现在还没到…手里的这个s291还得用,等返厂回来有空闲的机器在测试。

 
del splash1.bmp.nb
nbimg -F splash1.bmp -w 480 h 800
del splash1.img
rename splash1.bmp.nb splash1.img
adb reboot bootloader
# 等待手机重启进入 fastboot
fastboot flash splash1 splash1.img
fastboot reboot

另一个办法是通过 load_oemlogo 命令修改,但是手机上没有这个命令,拷贝上去又怕出兼容问题,没用这个办法。

最后通过对比不同开机图案刷机包的 splash.mbn 文件,发现图片是以 24位 BMP 位图的形式保存在 splash.mbn 里面的。文件前面填充0,从1024开始是第一个图片,所有图片连续存放,有些包是2个图片,有些是3个图片。

写了个工具,可以自动导出、导入 splash.mbn 文件内的图片,地址是:https://github.com/GameXG/SImage 。导出、修改、导入、fastboot 刷入即可实现更改开机画面。

附件:
nbimg-1.1win32

参考:
http://android.tgbus.com/lab/break/201207/444432.shtml
http://www.programgo.com/article/73812043212/

Comments (2) »

android 刷机包 boot.img 格式

boot.img 存放了 boot 分区的内容,boot分区负责存放系统正常启动时linux zImage内核及 ramdisk 临时根文件系统。

可以使用mkbootimg、unpackbootimg 打包、解包。

https://github.com/osm0sis/mkbootimg 有mkbootimg、unpackbootimg 的源码,git 取出狗make即可生成可执行文件。
我这里make出错,手工去掉 makefile 文件内的 Werror 既可。

 
mkdir out
unpackbootimg -i boot.img -o .\out

即可解包,注意需要预先创建 out 目录,否则解包失败…

通过下面的命令可以把 ramdisk 解压出来。

 
gamexg@gamexg-VirtualBox:~/mkbootimg/out$ cp boot.img-ramdisk.gz ramdisk.cpio.gz
gamexg@gamexg-VirtualBox:~/mkbootimg/out$ gzip -d ramdisk.cpio.gz
gamexg@gamexg-VirtualBox:~/mkbootimg/out$ mkdir ramdisk
gamexg@gamexg-VirtualBox:~/mkbootimg/out$ cd ramdisk
gamexg@gamexg-VirtualBox:~/mkbootimg/out/ramdisk$ cpio -i -F ../ramdisk.cpio

打包就没再操作,本来计划修改 boot.img 把开机画面改掉,结果发现这里保存的画面是android原版的,证明真正的开机画面还是没在这里保存…
fastboot flash splash splash1.img 刷完后开机画面还是老样子,实在头疼啊…
不过能看到fstab ,也算有点作用吧(虽然mount也可查出来)。

参考:
http://blog.csdn.net/wh_19910525/article/details/8200372
http://blog.csdn.net/ttxgz/article/details/7742696

No comment »