用 Wireguard 随时随地”回家”

这两天外出,希望能随时无缝访问家里内网,试了一下效果确实不错,记录一下,基本上参考了这两篇文章:

https://fedoramagazine.org/configure-wireguard-vpns-with-networkmanager/

https://www.wireguard.com/#conceptual-overview

我的家里的局域网设置如下:

主路由器内网IP:192.168.0.1/24

一个Raspberry Pi(Peer A)专门作Wireguard的Gateway,内网 IP:192.168.0.4/24,有IPv6外网地址。

准备连接的笔记本电脑(Peer B),连接在外网上。

首先确保Peer A和Peer B都安装了wireguard-tools,此时应有/etc/wireguard这个文件夹,权限700。

然后通过如下方法在Peer A和Peer B上生成各自的Private Key和Public Key:

cd /etc/wireguard; wg genkey | tee privatekey | wg pubkey > publickey

先在Peer A上创建/etc/wireguard/wg0.conf。已有的内网子网是192.168.0.0/24,于是新划192.168.16.0/24作为Wireguard内部网络网段。监听端口这里设置为10000,可以更改。同时由于联通4G网络的最大MTU是1400,Wireguard自己的是1420,于是用1400减去80得到MTU 1320。这个MTU可以应该可以兼容国内的大部分移动网络,wg0.conf内容如下:

[Interface]
Address = 192.168.16.1/24
SaveConfig = true
ListenPort = 10000
PrivateKey = <Peer A's Private Key>
MTU = 1320

[Peer]
PublicKey = <Peer B's Public Key>
AllowedIPs = 192.168.16.2/24

然后通过Network Manager创建端口,允许转发路由包,并且使用FirewallD配置规则:

# 创建wg0
nmcli con import type wireguard file /etc/wireguard/wg0.conf

# 允许转发
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-ip-forward.conf
sysctl --system

# 创建监听Wireguard端口防火墙规则
firewall-cmd --permanent --add-port 10000/udp

# 创建允许转发路由的防火墙规则
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -d 192.168.16.0/24 -o wg0 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i wg0 -j ACCEPT

# 重新读取规则
firewall-cmd --reload

这里需要在路由器上加一条规则,以便内网到Wireguard网段的包可以正确路由:

ip route add 192.168.16.0/24 via 192.168.0.4 

然后就只只需要在Peer B上配置了,同上,创建/etc/wireguard/wg0.conf。这里AllowedIPs加入内网网段,允许通过Wireguard路由到内网。

[Interface]
Address = 192.168.16.2/24
SaveConfig = true
PrivateKey = <Peer B's Private Key>
MTU = 1320

[Peer]
PublicKey = <Peer A's Public Key>
AllowedIPs = 192.168.0.0/24, 192.168.16.0/24
Endpoint = <Peer A's address>:60000

之后建立wg0:

nmcli con import type wireguard file /etc/wireguard/wg0.conf

这样就应该可以ping通了。可以通过wg命令查看接口状态,还可以给Peer A配上DDNS,即时更新IPv6地址,将Endpoint改为域名即可。

使用 memstrack 分析 shmem 的使用

最近笔记本上经常内存使用率激增,然而 ps_mem, top 等工具毫无线索, /proc/meminfo 可以看出是 shmem 激增。不过 ipcs 也没有什么有用信息,各个 tmpfs 使用也是正常的。

于是试了一下前阵子写的 memstrack,开机先启动然后正常使用一段时间,得到如下结果:

看到大概 1.3G 的 shmem 是被 chrome -> i915 driver 给用掉了,而且稳步上升中。禁止 chrome 的硬件加速之后一切正常了。

memstrack 还不能 track userspace stack,不清楚具体是什么东西 leak 了还是别的问题,不知道如果加一下 userspace 支持的话,会不会得到更有用的信息。

网站跑在运行Fedora 32 + K8s + CRIO的树莓派集群上

成功把整个网站放到自己的树莓派集群上了,现在有数据在自己手上(物理的)的感觉了。

树莓派4-4G X 4的集群

自己动手搞了个架子,然后弄了几个开关(下方四个小开关)方便断路,并给每个开关加了个自恢复保险防止手滑短路,并且可以增加仪式感。启动集群的时候一个开关一个开关地打开感觉自己在启动核反应堆😁,不过这个24小时常开的,基本上用不太到开关功能。自恢复保险确实起了一次作用,在面包版插元件不小心短路了,直接断开。

搭建基本参照K8s官网,用的Kubeadm启动集群,Calico作为CNI。然后左上方路由器跑的HAproxy通过autossh穿透到外网,然后反代到Nginx Ingress,CEPH作为存储(另有一个现成的CEPH集群),(恩,在那个CEPH集群上再跑K8s更合理一些毕竟性能好,但我就是觉得树莓派在日常工作的桌子旁边跑更好玩…)。

内核需要自己编译,然后配置引导,参考树莓派官网,uboot形式暂时走不通。Gentoo上有很好的关于如何boot一个64bit kernel的说明。其他也遇到不少坑,其中就包括Fedora里的K8s太过陈旧,正在帮maintainer更新包发个新Release,PR已提,快了™。

在那之前我自己弄了个COPR:https://copr.fedorainfracloud.org/coprs/kasong/kubernetes/

其他大部分坑都包含在这个playbook里,可以参考,请不要直接用,IP地址域名都不通用:

- - hosts: all
  tasks:
  - name: Enable crio 1.17 dnf module
    command: "dnf module enable cri-o:1.17/default -y"

  - name: Basic K8s package
    yum:
      name: "{{ packages }}"
      state: installed
    vars:
      packages:
      - kubernetes-node
      - kubernetes-client
      - kubernetes-kubeadm
      - kubernetes-master
      - cri-tools
      - crio

  - name: Ensure overlayfs, br_netfilter is loaded
    lineinfile:
      path: "/etc/modules-load.d/kubernetes-crio.conf"
      line: "{{ item }}"
      create: yes
    with_items:
      - br_netfilter
      - overlay

  - name: Enable Kluster firewall zone
    firewalld:
      zone: pi-cluster
      state: present
      permanent: yes

  - name: Enable 192.168.2.0/24 to Kluster zone
    firewalld:
      source: 192.168.2.0/24
      zone: pi-cluster
      state: enabled
      permanent: yes

  - name: Enable Kluster firewall zone
    firewalld:
      zone: intra-intra-net
      state: present
      permanent: yes

  - name: Enable 192.168.2.0/24 to Intra zone
    firewalld:
      source: 192.168.12.0/24
      zone: intra-intra-net
      state: enabled
      permanent: yes

  - name: Enable 192.168.2.0/24 to Intra zone
    firewalld:
      source: 192.168.0.0/24
      zone: intra-intra-net
      state: enabled
      permanent: yes

  - name: Enable K8s API port to Intra
    firewalld:
      port: 6443/tcp
      permanent: yes
      zone: intra-intra-net
      state: enabled

  - name: Enable K8s API port to Intra
    firewalld:
      service: ssh
      permanent: yes
      zone: intra-intra-net
      state: enabled

  - name: Enable NodePort Services to Intra
    firewalld:
      port: 30000-32767/tcp
      permanent: yes
      zone: intra-intra-net
      state: enabled

  - name: Enable crio
    service:
      name: crio
      enabled: yes
      state: started

  - name: Enable kubelet
    service:
      name: kubelet
      enabled: yes

#  - name: Disable ZRAM
#    service:
#      name: zram-swap
#      enabled: no
#      state: stopped

  - name: Enable K8s API port
    firewalld:
      port: 6443/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable etcd ports
    firewalld:
      port: 2379-2380/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable mdns port
    firewalld:
      service: mdns
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable kubelet port
    firewalld:
      port: 10250/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable kube-scheduler port
    firewalld:
      port: 10251/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable kube-controller-manager port
    firewalld:
      port: 10252/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable NodePort Services port
    firewalld:
      port: 30000-32767/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: Enable BGP ports
    firewalld:
      port: 179/tcp
      permanent: yes
      zone: pi-cluster
      state: enabled

  - name: QUIRK Firewalld disable nft
    lineinfile:
      dest: "/etc/firewalld/firewalld.conf"
      regexp: '^FirewallBackend=.*'
      line: 'FirewallBackend=iptables'

  - name: Start/Restart Firewalld
    service:
      name: firewalld
      state: restarted

  - name: QUIRK Remove invalid Kubelet config option
    replace:
      path: /etc/systemd/system/kubelet.service.d/kubeadm.conf
      regexp: '^(.+)--allow-privileged=([^ "]*)(.*)'
      replace: '\1\3'

  - name: QUIRK Kubelet don't depend on docker
    lineinfile:
      dest: "/usr/lib/systemd/system/kubelet.service"
      regexp: '^(Requires=docker.service)$'
      line: '# \1'
      backrefs: yes

  - name: Tell NetworkManager not to control calico interface
    blockinfile:
      create: yes
      path: /etc/NetworkManager/conf.d/calico.conf
      block: |
        [keyfile]
        unmanaged-devices=interface-name:cali*;interface-name:tunl*

  - name: Start/Restart NetworkManager
    service:
      name: NetworkManager
      state: restarted

  - name: K8s kernel modules
    blockinfile:
      create: yes
      path: /etc/modules-load.d/kubernetes-crio.conf
      block: |
        br_netfilter
        overlay

  - name: K8s kernel sysctl
    blockinfile:
      create: yes
      path: /etc/sysctl.d/99-calico.conf
      block: |
        net.ipv4.conf.all.rp_filter = 1

  - name: K8s kernel sysctl
    blockinfile:
      create: yes
      path: /etc/sysctl.d/99-kubernetes-cri.conf
      block: |
        net.bridge.bridge-nf-call-iptables  = 1
        net.ipv4.ip_forward                 = 1
        net.bridge.bridge-nf-call-ip6tables = 1

  - name: Update Kubelet config
    lineinfile:
      dest: "/etc/systemd/system/kubelet.service.d/kubeadm.conf"
      line: 'Environment="KUBELET_CRIO_ARGS=--container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock"'
      insertbefore: "ExecStart=.*"

  - name: Update Kubelet config
    lineinfile:
      dest: "/etc/systemd/system/kubelet.service.d/kubeadm.conf"
      regexp: '^ExecStart=(((?!\$KUBELET_CRIO_ARGS).)+)$'
      line: 'ExecStart=\1 $KUBELET_CRIO_ARGS'
      backrefs: yes

  - name: Update CRIO config for insecure repo
    ini_file:
      dest: "/etc/crio/crio.conf"
      section: "crio.image"
      option: 'insecure_registries'
      value: '[ "registry.intra.intra-net.com" ]'

  - name: Update common container config for insecure repo
    ini_file:
      dest: "/etc/containers/registries.conf"
      section: "registries.insecure"
      option: 'registries'
      value: '[ "registry.intra.intra-net.com" ]'

  - name: Ensure /opt/cni exists for symlink creation in next step
    file:
      path: "/opt/cni"
      state: directory

  - name: Create symbolic link for CNI Plugin
    file:
      src: "/usr/libexec/cni"
      dest: "/opt/cni/bin"
      state: link

  - name: "Now run 'sudo kubeadm init --cri-socket /var/run/crio/crio.sock --pod-network-cidr=10.24.0.0/16'"
    debug:

# Misc TODO: Apply a Docker mirror
#
# Misc TODO: Apply following config for calico yaml
#  - name: IP
#    value: "autodetect"
#  - name: IP_AUTODETECTION_METHOD
#    value: "interface=eth.*"
#  - name: IP6_AUTODETECTION_METHOD
#    value: "interface=eth.*"
#
# Misc TODO: Black list vc4 if kernel is failing
# echo blacklist vc4 > /etc/modprobe.d/blacklist-vc4.conf
#
# Misc TODO: Still need old fashion cgroup
# cgroup_enable=memory systemd.unified_cgroup_hierarchy=0
#
# Misc TODO: Ensure following kernel configs are enabled
# CONFIG_NETFILTER_XT_MATCH_CGROUP
# CONFIG_F2FS_FS_SECURITY
# CONFIG_CFS_BANDWIDTH
# CONFIG_BLK_DEV_RBD
# CONFIG_BRIDGE_NETFILTER
#
# Misc TODO: Tune etcd
# - name: ETCD_MAX_WALS
#   value: "5"
# - name: ETCD_HEARTBEAT_INTERVAL
#   value: "500"
# - name: ETCD_ELECTION_TIMEOUT
#   value: "10000"
# - name: ETCD_SNAPSHOT_COUNT
#   value: "5000"
# - name: ETCD_LOG_LEVEL
#   value: "error"

正如所见,还有一堆TODO没写成playbook形式,回头需要再搭一遍的时候再说了XD。很多坑我会尝试在打包环节修一下,playbook中的一部分就不再需要了。

优雅地让docker,nginx,acme.sh一起工作

前阵子买了个配置不错的阿里云,先搭了个Minecraft上去,Minecraft也跑在Docker里,并加载了DynMap显示实时地图。为了日后好扩展,方便上一些比较好看的页面,先用一个nginx反代一下,顺便挂上HTTPS,证书当然要从Letsencrypt搞。

看了下现有的方案,感觉都不太好看,都需要独立维护一个Dockerfile,不能直接用Nginx mainline,刷新证书的时候需要先关闭服务器,而且acme.sh和nginx跑在同一个容器里。想了一下,打算让acme.sh和nginx分开跑,nginx把challenge内容转发给acme.sh,这样应该就妥了,惟一的不好的地方是需要写个crontab,每隔2个月重启一下nginx的容器。不过其他方案也有downtime,也算可以接受。

目录结构和配置文件:

/
    acme.sh  -  SSL证书存放和acme帐号信息,配置信息
    nginx  -  自定义nginx配置存放的地方
        conf.d  -  一些nginx全局配置
        sites-enabled - 各个domain设置
        sites-conf  -  domain之间共用的设置

nignx/conf.d/acme.sh.conf

upstream acme-sh {
  server acme-sh:80;
}

nignx/conf.d/enabled-sites.conf

include /etc/nginx/sites-enabled/*.conf;

nginx/sites-conf/acme.sh.conf

location ~ "^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$" {
    default_type text/plain;
    proxy_read_timeout    60;
    proxy_connect_timeout 60;
    proxy_redirect        off;
    proxy_pass http://acme-sh;

    proxy_set_header      X-Real-IP $remote_addr;
    proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header      X-Forwarded-Proto $scheme;
    proxy_set_header      Host $host;
}

nginx/sites-enabled/example.com.conf

server {
    server_name example.com;

    listen 0.0.0.0:443 ssl;
    listen [::]:443 ssl;

    ssl on;
    ssl_certificate /etc/ssl/letsencrypt/example.com/fullchain.cer;
    ssl_certificate_key /etc/ssl/letsencrypt/example.com/example.com.key;
    include sites-conf/acme.sh.conf;

    ...
}

server {
    server_name example.com;

    listen 0.0.0.0:80;
    listen [::]:80;

    include sites-conf/acme.sh.conf;

    location / {
        return 301 https://example.com$request_uri;
    }
}

第一次要先获取一个有效证书:

docker run --rm  -it  \
  -v "$(pwd)/acme.sh":/acme.sh  \
  --net=host \
  neilpang/acme.sh  --issue -d example.com  --standalone

使用docker-compose做容器管理,yml内容如下:

some-other-service:
    ...

nginx-srv:
    image: nginx:mainline
    volumes:
        - ./nginx/conf.d:/etc/nginx/conf.d
        - ./nginx/sites-conf:/etc/nginx/sites-conf
        - ./nginx/sites-enabled:/etc/nginx/sites-enabled
        - ./acme.sh:/etc/ssl/letsencrypt
    links:
        - some-other-service:upstream
        - acme-sh:acme-sh
    ports:
        - "443:443/tcp"
        - "80:80/tcp"

acme-sh:
    image: neilpang/acme.sh
    command: daemon
    volumes:
        - ./acme.sh:/acme.sh

调 docker-compose up就可以了。

最后加一个cronjob让nginx定期重新读取证书:

/var/spool/cron/root:

cd /path/to/docker-compose.yaml && /usr/bin/docker-compose exec nginx-srv nginx -s reload

 

记一次openwrt上折腾使用xkcptun,openvpn,ipset的策略路由

工作需求,需要加速外网访问,因此一个国外VPS服务器是必须的。由于外网条件限制,常常会有丢包,因此使用KCP进行加速可以极大的改善高丢包网络下的通讯速度,副作用是会重复发包放大流量,不过一般流量管够随便用,不需要担心。

首先要安装xkcptun:

基于Golang的kcptun无法很好的运行在MIPS环境上,因此使用C编写的xkcptun。

服务端:

我在repo中加入了可以build使用的Dockerfile,直接clone:

git clone https://github.com/ryncsn/xkcptun

之后直接进入目录,修改一下Dockerfile里面的参数,比如密码,端口地址等等,可以直接build了。这些参数应该单独拿出来,不过一时省事先不管了。

docker build -t xkcptun .

我一般习惯用docker-compose来管理:

xkcptun-ovpn:
  image: xkcptun
  net: "host"
  command: xkcp_server -c /etc/xkcptun.json -f
  restart: always
Openwrt(客户端):

直接从这里下载包安装:

https://github.com/gigibox/openwrt-xkcptun/releases

之后修改/etc/config/xkcptun,之后执行

/etc/init.d/xkcptun_client start
/etc/init.d/xkcptun_client enable

没研究UI,我的WNDR4300上只安装openwrt_xkcptun_0.4.409-1_ar71xx.ipk就够了。

此处挖坑

Openwrt-xkcptun release的build编译的有问题,用原repo的编译出来client和server无法连接,经过各种抓包和调试,发现是竟然是大小端的问题。应该重新编译一下修复大小端问题,考虑到Openwrt编译又要好久,就先在自己的repo里dirty hack了一下来兼容老的ar71xx build,因此这个repo编译出的xkcptun与其他的不兼容。

Openvpn配置:

直接使用docker-openvpn进行配置:

https://github.com/kylemanna/docker-openvpn

其中udp://VPN.SERVERNAME.COM换成vpn的地址,并且换成tcp协议,方便kcp进行包装。可以加上一个不存在的子域名前缀,方便之后添加hosts项来让kcp充当代理的作用。

CLIENTNAME是要链接的client name。

生成ovpn文件之后,将其拷到Openwrt设备上,并且在/etc/hosts中添加127.0.0.1 VPN.SERVERNAME.COM,让xkcp监听本地的1194转发到远端的1194,这样就可以让openvpn跑在kcp上面了。

策略路由:

为了完成策略路由,要先安装相关包:

opkg install iptables-mod-nat-extra ipset

opkg remove dnsmasq && opkg install dnsmasq-full

修改/etc/dnsmasq.conf,加入confdir=/etc/dnsmasq.d

建立/etc/dnsmasq.d,使用下面的脚本生成一个配置文件,命名dnsmasq_flist_ipset.conf,扔到/etc/dnsmasq.d/文件夹中:

https://github.com/cokebar/gfwlist2dnsmasq/blob/master/gfwlist2dnsmasq.sh

使用如下命令建立一个ipset list:

ipset -N flist iphash

防止DNS解析问题,先加入两个DNS服务器:

ipset add flist 8.8.8.8
ipset add flist 8.8.4.4

使用iptables做一下标记和允许转发,让标记包走特定路由表:

# iptables mark package matches the ipset
iptables -t mangle -A OUTPUT -j fwmark
iptables -t mangle -A fwmark -m set --match-set flist dst -j MARK --set-mark 0xffff

# Masquirade the package as routing is done before iptables mangle
iptables -t nat -A POSTROUTING -m mark --mark 0xffff -j MASQUERADE

# Route all package using ovpn table
ip rule add fwmark 0xffff table ovpn

# And allow forwarding
iptables -I FORWARD 1 -o tun0 -j ACCEPT

在之前的ovpn文件中加入,实现自动恢复:

script-security 2

up '/bin/sh -c "ip route add default dev tun0 table ovpn"'

这样便可以实现vpn连接自动添加路由和允许iptables转发的功能了,大功告成。

将iptables和ipset相关命令放入/etc/firewall.user,iproute2相关放入/etc/rc.local即可完成自动恢复。

Python Hybrid Method

这两天有一个特殊需求,在一个Python class上实现一个method,可以被当作Class method调用,也可以被当作Instance的Method调用。看起来不是很Pythonic,一个函数有两个不同的行为,不过在特殊情况下确实很实用。

尝试对性能进行了一下优化,并没有用到太复杂的优化方法,所以只是相对性能可以接受。调用消耗大概是普通调用的一倍。

from functools import wraps
class hybridmethod(object):
    """
    High performance hybrid function wrapper
    """
    __slot__ = ['context', 'method', 'clsmethod']

    def __init__(self, func):
        self.clsmethod = self.method = wraps(func)(lambda *a, **kw: func(self.context, *a, **kw))

    def classmethod(self, func):
        """
        Function to call when calling with class
        """
        self.clsmethod = wraps(func)(lambda *a, **kw: func(self.context, *a, **kw))
        return self

    def __get__(self, instance, cls):
        if instance is None:
            self.context = cls
            return self.clsmethod
        self.context = instance
        return self.method

自己用到的场景:

class Model(object):
    @hybridmethod
    def from_dict(self, dict_):
        """
        Inflate the instance with dict
        """
        for k, v in dict_.items():
            setattr(self, k, v)
        return self

    @from_dict.classmethod
    def from_dict(cls, dict_):
        """
        Inflate the instance with dict
        """
        instance = cls()
        for k, v in dict_.items():
            setattr(instance, k, v)
        return instance

新技能GET – 修热水器

家里的燃气热水器坏了一阵子,本来打算打算趁五一电商活动买个新的换上。不过,本着电器不用到彻底报废就不打算坏的原则,决定自己修一下。

打开盖子前后还是有点犹豫,毕竟涉及天然气,水路和电路,万一搞出差错出问题就不好办了。不过一番七手八脚折腾打开之后,经过仔细端详感觉原理也挺简单。感觉主要是温度反馈控制和可靠性设计这两天比较考验厂家实力。

好了不装逼了,经过大致判断应该是水流传感器坏了。依据是:打开水龙头由于有旧的存水,所以随着新水源进入温度变化看显示面板是有变化的,不过就是不打火。而且是完全没有打火的痕迹,比如风扇预启动,打火装置充电之类的都没发生。

水流传感器样子看起来是一个管子上多了一个方形盒子的感觉。淘宝上20一个,快递要好几天才能到,那就愉快地拆传感器吧。不过家里正好水阀坏了,ORZ。但拆都拆了,决定带着水压尝试拆一下电路部分。(似乎有的水流传感器电路部分和密封的盖子是一起固定的,拆开就崩。而家里的水流传感器可能是分离设计的或者锈地太厉害结果密封部分粘住了)

打开之后发现果然,电路部分整个浸水了,而且都锈了。再一看发现这个水流传感器似乎安装有问题,塑料外壳上有两个突起用来卡住电路,但直接怼在的电路板上了,导致密封不严实。猜测这应该也是进水的原因。直接把电路拆下来之后对着传感器剩下部分观察了一下,看起来大概原理是密封部分里面一个水轮,末端是磁铁,正对着电路部分的一个霍尔元件,水流导致水轮转动就有信号了。霍尔元件直接三个引脚接出来。其中两个脚分别是+5V和GND,另一个是信号脚,+5V处串一个5K电阻。其中电阻锈地彻底废了,但还好霍尔原件完好。

尝试修复比较简单就不做更多监测了,用飞线大法重连接了一下电路板短路的地方,朽烂了的电阻从其他报废电器上拆一个过来。装回热水器,安全起见先关闭燃起阀门测试一下水流,热水器立马嗡嗡地跑起来了,稳如狗。

Gentoo Zsh 缺少 curses.so 的问题

长期以来一直快乐的使用着 oh-my-zsh 里的 zsh-navigation-tools plugin 来进行模糊查询,不知道哪次 Gentoo 升级之后这个插件不能用了,报错:
command not found: zcurses
网上简单查询发现这个是个 zsh 内置模块的指令,应该是编译的时候 curses.so 被跳过了。
检查一下ebuild,没有看到有相关的USE flag,尝试调整ncurse和zsh包的版本和USE flag均无果。
检查一下 zsh 代码中的 configure.ac,发现$LIB中没有加入 ncurses 相关的 lib,zsh 会编译的时候会跳过 ncurses 相关模块的编译。

临时解决方法:
1、本地建立一个 Portage Overlay – https://forums.gentoo.org/viewtopic-t-827407-start-0.html,按需求 copy & paste 相关 ebuild
2、src_configure 中加入一行 myconf += ( –enable-libs=-lncursesw )
3、emerge zsh

zsh-navigation-tools plugin 又欢乐的跑起来了。

自己的Node.JS + Arduino的家居控制系统

很久以前的课设时实现了自己的一个想法,无线控制屋里的灯,再也不用每次都要站起来去手动关灯了。为了能通用些,写了成了一个小的框架,每个控制功能作为一个“驱动/模块“,然后在这个基础上把温湿度传感,红外/空调遥控之类的都做了出来。之后借来的Intel Gailieo还回去了,开始磨磨蹭蹭地把系统放到了树梅派上,然后改进了一下Arduino端的结构,也尝试写了一个比较通用的框架。

使用Node.js作为服务器提供Web端控制,通过不同的通讯模块(现在有NRF24L01,网络和WIFI)处理请求和传感器发回的信息等,将用户请求发送到下位机(比如Arduino),服务器处理各种传感器和控制器的具体任务。

Arduino上尝试实现了一个库,提供一个框架,方便实现硬件逻辑,可以将一个操作绑定到一个函数上,实现不同操作的处理。

服务器端对应的灯的“驱动”目前看起来是这样的:

var light = {};
var plat = undefined;

light.decorate = function (thing, desc){
  thing.turnOff = function(){
    var op = {
      'op':'turnOff',
    }
    thing.send(op);
  }
  thing.turnOn = function(){
    var op = {
      'op':'turnOn',
    }
    thing.send(op);
  }
  thing.powerOff = thing.turnOff;
  thing.powerOn = thing.turnOn;
  thing.receives.add(function(data){
    thing.status = data.status;
  })
  thing.interfaces['Power On'] = {
    type:'switch',
    name:'Power On',
    desc:'开灯',
    action:{
      1:'turnOn',
      0:'turnOff'
    },
    value:'power'
  };
}

light.init = function (base){
  plat = base;
}


module.exports = light;

Arduino上用自己的这个框架的话代码也比较干净利索,目前看起来是这样:

#define RF24_ADDR 0xFFFF000001
//#define SOME_DEBUG
//#define SOME_ERROR
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <SPI.h>
#include "printf.h"
#include "Some.h"

Thing<link_RF24> me(0,
                    "{\"name\":\"智能灯\",\"groups\":{\"power\":{\"hasBattery\":false},\"smart\":{\"alwaysOn\":true,\"heartBeat\":false},\"light\":{}}}");


void turnOn(void) {
  Serial.println("turnOn");
  digitalWrite(6, LOW);
  me.send("{\"power\":1}");
}

void turnOff(void) {
  Serial.println("turnOff");
  digitalWrite(6, HIGH);
  me.send("{\"power\":0}");
}

void setup() {
//  Serial.begin(115200);
//  printf_begin();
  pinMode(6, OUTPUT);
  digitalWrite(6, LOW); //Turn the light on as soon as possible.
  me.setup();
  me.poke();
  me.on("turnOn", turnOn);
  me.on("turnOff", turnOff);
  turnOn();
}

void loop(void) {
  delay(50);
  me.refresh();
}

不少地方需要跟进,比如使用Arduino设备的自动发现,给每个设备分配更加独立可识别的ID,加密和安全性等,服务器中对事件的处理等等。

目前服务器跑在树梅派上,使用Arduino改造了自己的屋顶灯,做了几个传感器,跑得很欢。

持续更新中…

Github:

Server:https://github.com/ryncsn/some

Arduino:https://github.com/ryncsn/some-arduino

Gentoo Tips

记录一下一些有用不好找的小知识:

忽略一些package自带的pre emerge check failed:

export I_KNOW_WHAT_I_AM_DOING=yes

 

建立Hardened Desktop Profile:

Gentoo 以前是有 Hardened Desktop 的Profile的,后来取消了。

使用的话可以自己建一个

mkdir -p  /etc/portage/make.profile && cd /etc/portage/make.profile

echo 5 > eapi

echo “gentoo:hardened/linux/amd64
gentoo:targets/desktop” > parent

echo “-abi_x86_32” > use.mask (启用multilib,不用的话跳过)

 

建立 Package Set:

将一堆包归为一个Set,方便安装和管理:

mkdir /etc/portage/sets && cd /etc/portage/sets

echo “<packages>” > <set name>

比如建立Android编译环境的包:

echo “media-gfx/pngcrush
app-arch/lz4
dev-python/lz4
sys-devel/bison
dev-python/buildutils
net-misc/curl
sys-devel/flex
dev-vcs/git
app-crypt/gnupg
dev-util/gperf
media-libs/audiofile
media-libs/alsa-lib
app-arch/unzip
dev-util/valgrind” > android

emerge -av @android

 

分步编译安装Package:

ebuild myebuild fetch (if you don’t have it in distfiles)

ebuild myebuild unpack (unpacked to /var/tmp/portage/packagename/something)

ebuild myebuild compil

ebuild myebuild install

ebuild myebuild qmerge

 

将package.use,package.keywords等建立为目录:

可以将这些文件用同名目录替代,portage会遍历目录下文件

 

忽略emerge过程中部分失败的包

emerge –keep-going