Server饭-用API发微信通知或用微信控制你的服务器

大家好,这是又一个向微信发通知的服务。这样的服务其实不少,一定是有新的特色我才好意思来介绍。 Server饭不仅可以给微信发通知,还能反过来用微信和服务器产生交互。大概像下面这样:

Intro

好了,还是先介绍基础功能吧。

主动发送通知

给自己发消息是最常用的功能。
拉到文末扫码关注服务号,或者在微信搜索 “LetServerRun” 这个服务号关注。
在服务号发送 token 命令查看自己的用户 token 。
使用用户 token,您就可以用 API 向公众号发警告消息了:

curl "https://api.letserver.run/message/info?token=YOUR-TOKEN&msg=hello"

这个 GET 接口是为了调试和轻量使用场景的,在程序中使用的话有 一系列接口和SDK。

反向控制服务器

Server饭的特色功能是用微信控制服务器做简单的事情。就像一开始的图里那样。 放心,不需要你提供ssh密钥,为了安全,命令能做什么完全由你定义。

实现的原理是在服务号中你发的命令会被存储在云端, Agent 每分钟向云端发起请求检查一次,如果有命令则拉回来执行它。 执行完成之后可以返回成功或者失败的结果,你就会在微信服务号上看到。

Agent 哪里来呢?有这么几种选择:

  • 最自由:调用我们的API自己写
  • 省事点:调用 SDK 自己写
  • 够用就好:直接用我们几个开源的方案

这里我们先使用一个 开源的通用 Agent来上手。
它可以帮你在服务器执行特定的命令。后面我们可以根据需求,自己通过 API 或 SDK,集成 Agent 或自己编写。

假设你的服务器是 Debian/Ubuntu ,如果是别的请参考 安装通用Agent
如果您本身就是 root 用户,麻烦去掉所有命令中的 sudo

# 注册仓库
curl -1sLf \
  'https://dl.cloudsmith.io/public/hackfan/skadi/setup.deb.sh' \
  | sudo -E bash
# 更新
apt update
# 安装
apt install skadi

在安装后,因为还没有 Token,所以并没有自动启动。
Token 哪来的呢?在服务号输入命令: agent add 名字 (名字是要你给它取个简单的名字,以后每次都要用它发命令)
然后将得到的 Token 写入配置文件,像下面这样。 你也可以编辑 /etc/skadi/skadi.yml 这个文件自己写入。

# 写入 Token
sudo skadi AGENT-TOKEN
# 启动服务
sudo systemctl start

只有第一次需要配置Token后手动启动服务,服务器重启它是会依靠systemd自己启动的。

然后就可以试用这个 Agent 了。

在公众号输入 名字 help ,看看 Agent 自己的帮助。
这个官方的通用 Agent 功能由你部署它的服务器上的 /etc/skadi/skadi.yml 这个配置文件定义。

然后你可以顺次输入名字 date,名字 lsroot,名字 free -m,名字 Hi Fool, 去试用,接下来,更改配置文件就可以完成重启服务,查看状态等简单的动作了。

只是想现在看看的话,也可以看看 代码仓库的版本

更多功能

因为目前只是提供了一个舞台,更多的功能在持续的发掘中,我们会陆续更新文档中的 cookbook:

  • 在 CI 服务中通知微信,甚至直接给 Agent 发任务进行持续部署
  • Agent 之间链式发送任务配合完成工作
  • 集成在业务系统中当作一个简单的控制台,比如清除缓存,封禁用户等操作,微信上就搞定了。
  • 当成一个 延迟队列使用
  • 控制家里的电脑
  • 控制路由器或者 NAS
  • 更多用法等待你开发脑洞~

最后,二维码在这里哦,暂时用不到也可以扫扫留着备用,只有重大更新才会推送通知,平时0骚扰哦。

LetServerRun

使用 Go 1.16 的 signal.NotifyContext 让你的服务重启更优雅

在 Go 1.16 的更新中,signal包增加了一个函数 NotifyContext, 这让我们优雅的重启服务(Graceful Restart)可以写的更加优雅。

一个服务想要优雅的重启主要包含两个方面:

  • 退出的旧服务需要 Graceful Shutdown,不强制杀进程,不泄漏系统资源。
  • 在一个集群内轮流重启服务实例,保证服务不中断。

第二个问题跟部署方式相关,改天专门写一篇讨论,今天我们主要谈怎么样优雅的退出。

首先在代码里,用了外部资源,一定要使用defer去调用Close()方法关闭。 然后我们就要拦截系统的中断信号,保证程序收到中断信号之后,主动有序退出,这样所有的 defer 才会被执行。

在以前,大概是这么写:

func everLoop(ctx context.Context) {
LOOP:
    for {
        select {
        case <-ctx.Done():
            // 收到信号退出无限循环
            break LOOP
        default:
            // 用一个 sleep 模拟业务逻辑
            time.Sleep(time.Second * 10)
        }
    }
}

func main() {
    // 建立一个可以手动取消的 Context
    ctx, cancel := context.WithCancel(context.Background())

    // 监控系统信号,这里只监控了 SIGINT(Ctrl+c),SIGTERM
    // 在 systemd 和 docker 中,都是先发 SIGTERM,过一段时间没退出再发 SIGKILL
    // 所以这里没捕获 SIGKILL
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sig
        cancel()
    }()

    // 开始无限循环,收到信号就会退出
    everLoop(ctx)
    fmt.Println("graceful shuwdown")
}

现在有了新的函数,这一段变得更简单了:

func main() {
    // 监控系统信号和创建 Context 现在一步搞定
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    // 在收到信号的时候,会自动触发 ctx 的 Done ,这个 stop 是不再捕获注册的信号的意思,算是一种释放资源。
    defer stop()

    // 开始无限循环,收到信号就会退出
    everLoop(ctx)
    fmt.Println("graceful shuwdown")
}

最后,还有一个值得注意的问题:如果有多个 goroutine 利用 context 做取消信号,我们需要保证全部退出了才能退出主进程。

目前还没有特别优雅的办法,需要为每个 goroutine 传递一个 sync.WaitGroup 的指针,最后在退出前 Wait 一下。

感谢 Golang ,当年用别的语言需要写一大堆代码的功能,现在几行就可以轻松实现了。 让它成为你服务程序的标配吧。

最后,我是写最新的项目 Server饭的时候,发现这种最新的写法的。 Server饭 可以让你把微信公众号当作随身的 Terminal 控制你的服务端。 在它的 Agent 的 main 函数中就有上述用法的示例,欢迎参考。

附上 Server饭 的服务号二维码,感兴趣的同学可以关注一下:

LetServerRun