一直被忽视的tap方法

ruby的任何object,都带有一个奇怪的method,:tap

tap接受一个block,把自身作为参数传递到block中,并执行block中的内容,最后返回自身。

引用文档的说明
:tap 就像轻拍一下,试探以下该对象的反应,做一些操作,然后仍然返回该object。

如果不使用:tap,我们需要输入数组内容时候,有以下做法。

1
2
3
4
arr = "hello".bytes.to_a
p arr
arr = arr.collect {|byte| byte.to_s(16) }
p arr

有了:tap,我们就可以写成长到像一口老痰的一句话形式。

1
2
"hello".bytes.to_a.tap {|arr| p arr }
.collect {|byte| byte.to_s(16) }.tap {|arr| p arr }

算了吧,我还是喜欢不用:tap的形式。至少看到文档中的这个例子我是这么想的。

不过今天总算是看到利用tab可以写的略简单明了的地方了。

我以前常犯这个错误。

1
user = User.new(name: "gyorou", age: 26).save

你们猜user结果是啥? 嗯。结果是 true。 因为:save返回的结果是true或者false。

那我们怎么获取user?

1
2
user = User.new(name: "gyorou", age: 26)
user.save

但是写成两行的话这里就比较难受了。利用:tap,我们可以写成一行。

1
user = User.new(name: "gyorou", age: 26).tap(&:save)

时间瞬间变得清爽。可见,:tap也是有存在的道理,只不过文档没能让人信服而已罢了。

以上。

可以玩好久的WEB解密向游戏

近期有V2坛友做了一个WEB解密的网站。游戏规则很简单,在当前网页中找到通往下一关的password就行。
于是周末花了点时间挑战一下,顺便消耗一点百无聊赖的时间吧。

挑战地址

下面是我个人的提示。喜欢挑战的童鞋可以直接略过。实在抓脑想不出可以看一下参考参考。

stage1

直接点链接进入第二关。

stage2

右键点击查看页面源码或者直接打开chrome的debug console,找到被css式样隐藏的链接即可。

stage3

发现url赫然写着thethirdstage.html,试着把third改成fourth进入第四关。

stage4

查看页面源码,发现注释里面有下一关的页面名称。输入页面名称进入第5关。

stage5

总之先谷歌一下吧。把谷歌到到答案输入进去,进入第六关。

stage6

谷歌图像检索。顺利找到该图女主名称。

stage7

略难。首先进入页面debug模式看到有个图像的tag是’ing’,修改成’img’之后发现是白图一张。很显然,上面肯定有我们要的线索。
这种一般用不显眼的颜色写了什么在图上,谷歌一下online ocr,比如这个网站,把刚刚的图片扔进去,顺利获取下一关的信息。

stage8

发现是一张jpg的图。之前都是png的图,唯独这张是jpg,可以想象一下应该是利用了jpg的特性隐藏了什么东西。以前有把torrent藏在jpg里面的猥琐做法,这次比较简单,我们直接用文本编辑器打开该jpg即可。比如vim question.jpg,移动到最下端,赫然发现下一关的信息就在下面。

stage9

比较简单,打开console把隐藏的input field的数字从0改成9就行了。

stage10.5

直接一下子跳到了第10.5关?提示我们得先回到第十关。还好我的chrome插件提醒我从第九关跳过来的时候有302跳转。查看一下跳转信息就可以扒到第十关的url了。
直接在浏览器里输入第十关的url会立即又跳到第10.5关。我们可以直接用curl获取页面源码来查看。

stage11

md5值啊。直接谷歌好了。一般来说一些彩虹表会提供很多常用字符串的md5值。这一关我们直接谷歌就是了。

stage12

乱码。但是不用怕,我们用curl就行了。lol。
然后看到问题提示,再次通过谷歌,当然这个问题的答案对于理工科的同学来说应该是常识才对。

stage13

翻看源码发现snowa.png。这个是什么啊?搜一下雪花图相关的图片加密,并没有什么通用方法。手贱看看有snowa估计有snowb,嗯,这样我们得到两张雪花图片。
乍看是一模一样的图片,diff一下呗。最方便的当然是用imagemagick啦。没有的童鞋请自己安装。

magick compare snowa.png snowb.png snowdiff.png 打开 snowdiff.png一看,答案写得真够大的。

stage14

两个提示,第一个谷歌意思意思是让你倒过来看。我们发现这个是一家知名的公司名字。第二个提示,问你仅仅是公司名吗?显然把公司名字直接输入进去是错误的。我们可以谷歌一下该单词还有无别的意思。最后在wikipedia可以找到我们期待的答案。

stage15

扫视源码发现有PNGsNot.png的图片。提示文件头有问题。我们试着用vim打开该png文件,发现开头是PNG98a字样,谷歌搜索png98a什么狗屁都没有,倒是我们发现gif里面有GIF89a的式样。我们试着把PNG改成GIF,然后把文件名后缀改成PNGsNot.gif,打开文件,顺利获取答案。

今天就暂时到这里。

Web

vue v-for 的一个暗坑

v-for 的坑网上随便一搜该有一大堆了。这次遇到的比较奇葩。

比如有下面的template。

1
2
3
4
5
6
7
8
9
<template>
...

<md-layout v-for="reply in replies">
<p>{{reply.content}}</p>
</md-layout>

...
</template>

replies通过API获取。过程中发现,当api返回[]的时候,我们通过api添加reply,并把顺利添加的结果添加到replies里面,可这个时候,view并没有重新render。而分明replies里面已经有一个元素。

刷新浏览器,这个时候api返回已经不为空,继续往replies添加元素,view 顺利render。

为什么就第一个元素的时候不行? 我也不知道。但是确保添加第一个元素的时候重新render,经过试验发现必须降v-for的这个block包裹在一个container里面。

比如如下就没问题了。

1
2
3
4
5
<md-layout md-column>
<md-layout v-for="reply in replies">
<p>{{reply.content}}</p>
</md-layout>
</md-layout>

以上。

vue

segwit 到底是什么


近两天在一些BTC交易所的板块中出现了有人疯喊”距离segwit 100% 还有 xxx block, HODL”, 之类的话。那么segwit到底是什么东西? segwit会如何影响比特币呢?

TL;NR

Segwit是一种见证隔离机制。目的主要是把签名从transaction的input中移动到一个独立的数据段中,以减小transaction的大小。其另外一个好处是防止了txid的篡改。其实现协议为BIP141。

为什么现在的txid不安全?

现在的txid生成的时候会带入签名,然而,签名中可以允许有冗余的部分,于是修改签名的冗余部分,签名依然合法,而txid则会改变,这样导致发出原始transaction的节点生成的txid可以被其他节点修改。如果原始的节点利用txid来追踪交易,那就会和MtGox一样被人偷破产。

segwit 生效的几个过程

  1. BIP91 lock in(7.21)
  2. BIP91 激活 BIP 9(7.23 bit4 超过80%)
  3. BIP9 激活 BIP 141(bit1 超过 95%)

因为每个步骤都是按period执行,所以目前bit1虽然超过了95%,但激活是在下一个period。每个period为2016个block。

BIP9 BIP91 BIP141 BIP148 这些是什么?

BIP141 是segwit的本体协议。
BIP9,BIP91,BIP48是segwit的部署方式。

BIP9 规定所有支持segwit的节点在制作block的时候都装入bit1作为统计的flag,当含有bit1的block超过95%的时候就会激活segwit。

由于BIP9风险性比较大,所以。作为缓冲,提出了先通过BIP91激活BIP9,然后激活BIP141的方法。另外一种方法是通过BIP148激活BIP9,然后激活BIP141。

BIP148的做法是规定在8月1号强制所有不含有bit1的block违法来实现。由于在7月23号BIP91已经激活了BIP9,所以这个暴力的方法被避免了。

注意容易混淆的是BIP148和实际上8月1号发生的bitcoin cash的是没有什么关系的。bitcoin cash目的是通过硬分叉解决transaction容量的问题。而上述几个协议都是软分叉的协议。

为什么segwit 导致价格上涨?

Segwit使得比特币更加安全,投资家的信心随之上涨, 结果就是价格上涨了。

segwit 2x 又是什么

segwit2x的2x指的是segwit之后可用空间变成两倍的意思。

Rails 5.1 导入 Vue

Vue太棒了。相比react的virtual dom大法,我们确确实实是在写html和js以及css。
Rails 5.1 加入了对各种JS前端框架的支持以及自带webpack, 所以,我们可以很方便就使用vue啦。

导入 vue

新建项目。

1
$ rails new myapp --webpacke=vue

我们会发现安装完成之后会多出来

1
2
3
4
app/javascript/packs                                                             
├── app.vue
├── application.js
└── hello_vue.js

在view中嵌入hello_vue,就可以显示定义在app.vue模版中的内容了。

1
2
3
# app/views/layouts/application.html.erb

<%= javascript_pack_tag 'hello_vue' %>

为了实现SPA,我们需要添加vue-router。

1
$ yarn add vue-router

然后建一些components。在这些components里面建一些模版。
大概像下面这个样子。

1
2
3
4
5
6
7
8
9
app/javascript/packs                                                             
├── app.vue
├── application.js
├── components
│   ├── home
│   │   └── index.vue
│   └── layout
│   └── footer.vue
└── hello_vue.js

最后,就是加上路由啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// app/javascript/packs/hello_vue.js

import Vue from 'vue/dist/vue.esm.js'
import VueRouter from 'vue-router'



import App from './app.vue'
import Footer from './components/layout/footer.vue'
import Home from './components/home/index.vue'


Vue.use(VueRouter)


Vue.component('my-footer', Footer)

const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
component: Home
}
]
})

document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#app',
template: App,
router
})
})

注意点

  1. vue需要动态编译template的话,需要把importvue.esm而不是vue

  2. 不要混用require和import。这里只能用import。

以上。

用iptables隐藏docker映射到host上的端口

docker可以让我们很方便地安装本地服务。但同时,默认的docker的设置使这些端口可以很轻松地从remote访问。
理想的做法是利用nginx把docker 默认打开的端口反向代理到别的端口,然后对新的端口进行用户验证保护。

屏蔽外部端口访问我们很自然而然就想到了使用iptables。

目的

我们在运行某个container的时候,使用了端口映射,例如

1
$ docker docker run --name myservice -p 5000:5000 some_image_name

我们想要仅允许在服务器上通过localhost:5000访问docker container的服务,而屏蔽掉外部通过myhostname:5000来访问服务的request。

错误的尝试

1
$ sudo iptables -A INPUT -p tcp -m tcp --dport 5000 -j ACCEPT

尝试在浏览器输入myhostname:5000,发现依然可以访问到5000端口。

这个是为什么呢?

选择正确的chain

第一次通过简单搜索谷歌,复制粘贴的方法失败了。看来还得静下来看一下iptables这个东西。

这篇文章
很好解释了docker中iptables的用法。

然而iptables的概念太多。我们把范围缩小到filter这个table中。

简要来说,我们通常需要处理的是三条chain(INPUT, FORWARD, OUTPUT)。所有的package 通过相应的chain,chain中的规则进行match。如果有match的规则,则执行规则规定的动作,否则继续向后访问规则,最后最后如果没有匹配的规则,则使用chain的默认的policy来处理。 默认的Policy可以是 ACCEPT 或者 DROP。分别表示接受和丢弃该Package。 被Drop的表现通常是,在浏览器上,显示load中但是总是无法出来结果。

当我们启动docker deamon,挂上docker container的时候,docker会在FORWARD chain中追加叫DOCKER和DOCKER-ISOLATION的自定义CHAIN。

由此可见。我们要追加的规则应该追加在FORWARD chain而不是在INPUT chain。

正确的做法是

1
$ sudo iptables -I FORWARD -p tcp -m tcp --dport 5000 -j ACCEPT

但是注意,docker每次重启之后都会把自定义的DOCKER chain 插到FORWARD chain的第一个。所以,我们不如把这个规则写到DOCKER chain中。

1
$ sudo iptables -I DOCKER -p tcp -m tcp --dport 5000 -j ACCEPT

其他疑惑

  1. 为什么iptables知道特定的外部reqeust需要走的是FORWARD而不是INPUT的chain?

这个是因为在filter table之前由NAT table替换了走向docker的request的destination。并不是所有到本机的请求都是走INPUT chain的,想象一下如果本机是NAT的gateway,那么很显然,大部分package需要转发到下面去。

  1. 为什么经常有两条一摸一样的规则?

光用iptables -L的话会看到几乎完全一样的两行,我们查看具体内容需要iptables -v,这样可以看到in out两个参数,这两个参数分别为in的网卡端口,和out的网卡端口。

使用“apple watch complication”显示当前比特币价格

有时候我们想要扫一眼手表就可以看到当前的比特币价格。这个时候,我们可以考虑把价格显示到apple watch的complication这个widget中。

complication 是什么

所谓的complication其实就是显示在自定义表盘上的这些模块了。其中有大有小。比如下面的例子,除了时间以外,
有表示日期,温度,运动量的小模块,也有表示日程安排的大模块。

那么为了表示我们需要的比特币价格的模块,有哪些方法呢?

方法1:通过WatchConnectivity

WatchConnectivity 这个Framework用来实现ios和watchos的通信。

这个时候很显然我们要自己建一个ios的application,然后通过这个application把当前的比特币价格传送给apple watch,然后表示在表盘的complications上面。 用xcode新建watch application然后勾选上complications的选项这个就不说了。

ios获取数据

我们看到bitflyer提供了realtime的PubNub的subscribe key。利用这个Key我们可以连续获得ticker。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//AppDelegate.swift

import PubNub

// 需要 delegate PNObjectEventListener

// ...省略

let configuration = PNConfiguration(publishKey: "demo", subscribeKey: "sub-c-52a9ab50-291b-11e5-baaa-0619f8945a4f")
self.client = PubNub.clientWithConfiguration(configuration)
self.client.addListener(self)
self.client.subscribeToChannels(["lightning_ticker_BTC_JPY"], withPresence: false)

// 添加实现delegate的fucntion

func client(_ client: PubNub, didReceiveMessage message: PNMessageResult) {

let bicoinInfo = message.data.message as! NSDictionary

// bitcoinInfo是一个词典类型,保存了诸如`best_ask`, `best_bid`, `ltp` 之类的情报。
// bitcoinInfo["ltp"] => 299900
// 我们需要把这个东西传给apple watch。
}

ios传送数据

导入 WatchConnectivity, 实现WCSessionDelegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 导入
import WatchConnectivity

// 初始化
if WCSession.isSupported() {
self.session.delegate = self
self.session.activate()
}

// 实现delegate
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
// 将当前的tick保存在lastestTick中。
// 获取 lastTick
self.session.transferUserInfo(["tick": lastestTick])
}

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}

watchos extension接受数据

watchos 需要接受ios传来的数据并刷新当前的complications来表示最新的价格。

这里注意一点是complications的更新,在watchos里面会有一个计数,一天更新超过一定的次数就无法更新了。
在上面的ios的实现中,我们通过didReceiveMessage 来触发传输给watchos的信息,所以我们在这里要主动发送一个空的信息,来让ios把信息发过来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 同样需要导入WatchConnectivity

import WatchConnectivity

// 初始化

func applicationDidFinishLaunching() {
if WCSession.isSupported() {
self.session.delegate = self
self.session.activate()
}
// Perform any final initialization of your application.
}

// 实现delegate

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {

}
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
let lastTick = userInfo["tick"] as! NSDictionary
// 更新持有的tick数据
TickData.setTick(tick: lastTick)
let complicationServer = CLKComplicationServer.sharedInstance()
guard let activeComplications = complicationServer.activeComplications else {
return
}

for complication in activeComplications {
complicationServer.reloadTimeline(for: complication)
}

}

注意我们这里主要实现didReceiveUserInfo,这个函数实现对通过transferUserInfo传过来的数据进行处理。

在具体的处理实现中,我们获取传来的lastTick, 然后通过complicationServer.reloadTimeline来刷新complication。

watchos complication 模版和渲染

ComplicationController.swift,我们可以看到xcode已经帮我们delegate了CLKComplicationDataSource

首先我们得决定当我们没有任何数据的时候,我们的complication表示什么内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MARK: - Placeholder Templates

func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
let template = CLKComplicationTemplateModularLargeStandardBody()
let image = UIImage(named: "bitcoin")

template.headerImageProvider =
CLKImageProvider(onePieceImage: image!)

template.headerTextProvider =
CLKSimpleTextProvider(text: "BTC/JPY")
template.body1TextProvider =
CLKSimpleTextProvider(text: "0")

handler(template)
}

CLKComplicationTemplateModularLargeStandardBody()用来新建一个ModularLarge尺寸的模版,这个模版的就是我们上图看到的日程安排那块的大小。

接着我们实现getCurrentTimelineEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// MARK: - Timeline Population

func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {

// Call the handler with the current timeline entry
let lastTick = TickData.getTick()
let ltp = String(describing: lastTick["ltp"] as! Int)
let template = CLKComplicationTemplateModularLargeStandardBody()
let image = UIImage(named: "bitcoin")

template.headerImageProvider =
CLKImageProvider(onePieceImage: image!)

template.headerTextProvider =
CLKSimpleTextProvider(text: "BTC/JPY")
template.body1TextProvider =
CLKSimpleTextProvider(text: ltp)

let entry = CLKComplicationTimelineEntry.init(date: Date(), complicationTemplate: template)

handler(entry)

}

在这里做的就是通过lastTick这个Datasource把数据获取数据,然后建立一个模版,和时间一起封装成一个CLKComplicationTimelineEntry的实例然后交给handler。

到这里,我们已经可以在complication上面显示比特币的价格了。

手表截图

方法2 使用PushOver的Glances API

上面的实装要是了解原理也不是那么复杂,但是等我们真正build这个application,上传,审核,发布,那可是很长一段岁月。
更何况往往我们并没有那么多耐心去折腾。

很多时候,稍微花一点钱,我们的问题很快就可以迎刃而解。比如我们可以使用PushOver的glances api来实现在complication上表示自定义信息的需求。

文档已经很详细就不多说了,唯一需要注意的,还是complication不接受太频繁的更新这一点。

手表截图2

总结

技术往往是达成目的的手段。当我们一味追求技术的时候,往往会忽视一些最根本的问题。
有些东西,如果是花钱就能解决的问题,那么不妨考虑一下花这个钱好了。

比特币自动交易策略入门

上周五开始入坑比特币交易。
由于各个交易所提供了丰富的API,基本上利用API来协助交易变得非常简单。
比如结合itchat这个工具我们可以实现使用微信来进行交易。

三天下来有一点感受就是,总是盯着价格容易精神恍惚,所以得早日转战自动交易。
gekko就是一个非常适合用来做自动交易的框架。

要自动交易自然了解交易策略是非常重要的。下面就gekko默认自带的几个交易策略做一下备忘。

DEMA

这个方法使用(EMA)Exponential Moving Average crossovers来预测当前市场的走向。所谓的EMA可以参考wikipedia,总的来说,可以把它理解成一个K线图的加权平均,所以自带一个参数α,数值介乎0至1。α也可用天数N来代表,这个时候(α=1/(N+1))。在gekko这个框架里面,涉及用到EMA的策略,我们通常可以看到short和long这两个参数。分别代表短期N和长期的N。
DEMA中,使用短期平均和长期平均曲线的交错点来判断市场走向。下面的down和up阈值就分别代表了其上下的幅度。

1
2
3
4
5
6
7
8
# EMA weight
# the higher the weight, the more smooth (and delayed) the line
short = 10
long = 21

[thresholds]
down = -0.025
up = 0.025

MACD

基本和DEMA相同,不同点是引入了一个叫signal的参数,这个时候会有第三条EMA曲线,是用short和long EMA的查生成的。signal是其期间长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
# EMA weight (α)
# the higher the weight, the more smooth (and delayed) the line
short = 10
long = 21
signal = 9

# the difference between the EMAs (to act as triggers)
[thresholds]
down = -0.025
up = 0.025
# How many candle intervals should a trend persist
# before we consider it real?
persistence = 1

PRO

基本和MACD相同,具体区别参考这里

RSI

Relative Strength Index。主要考察过卖,过买点来分析市场走向。

1
2
3
4
5
6
7
8
interval = 14

[thresholds]
low = 30
high = 70
# How many candle intervals should a trend persist
# before we consider it real?
persistence = 2

low 和 high两个参数代表RSI数值的上值和下值,超过上值就有可能发生超买,低于下值就有可能发生超卖。

StochRSI

从名字上可以看出和RSI类似,参数也相同,就不多说了。

CCI

Commodity Channel Index。计算当前价和均价的相关性。也是判断超买和超卖的指标,前提是假设这些走向具有周期性。

1
2
3
4
5
6
7
8
9
10
# constant multiplier. 0.015 gets to around 70% fit
constant = 0.015

# history size, make same or smaller than history
history = 90

[thresholds]
up = 100
down = -100
persistence = 0

talib-macd

talib的MACD实现。基本原理和参数和上面的MACD一致。

Byobu太卡,改投Tmux

之前觉得按键映射记起来麻烦,一直没敢用Tmux。直到最近,我自我觉得配置简单,用起来非常省心的Byobu越来越卡,终于还是忍受不了了。
秉着有可能被鄙视的态度,开始逐渐转用Tmux。原本准备硬着头皮去看配置,熟悉按键,结果发现其实耐心看来,非常简单。

抄个配置

Tmux的按键规则其实也就是<prefix>+<key>这种形式了。默认的<prefix>由于是<C-b>, 一个手根本握不来,所以我们有必要改掉默认的<prefix>
考虑到vim的panel切换用到<C-w>,我们这里使用<C-q>来代替Tmux默认的前缀。

下面我们抄个配置,保存到~/.tmux.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 将前缀变更为C-q
set -g prefix C-q

# 解除默认前缀
unbind C-b

# 用vim的键盘bind在panel切换
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# v用vim的键盘bind变更panel的size
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# | 用来纵向分割panel
bind | split-window -h

# - 用来横向分割panel
bind - split-window -v

# 窗口号从1开始
set-option -g base-index 1

# 支持使用鼠标
setw -g mouse on

# 状态栏颜色
set -g status-fg white
set -g status-bg black

# 设定copy mode
## 使用vi的key bind
setw -g mode-keys vi

这里的bind key operation默认将 key 绑定到prefix 后面。如果不需要绑定到prefix后面,则使用option -T。而这里的option -r 表示在按了一次prefix之后,不需要重复按prefix,直接重复按key即可。

更多配置解释可以参考这里

实际上的一些问题

  • 如何screen scroll?

prefix+[进入copy-mode之后就可以用vi的key-bind各种滚动了。

  • to be added..

Mail Server 的转移备忘

最近对着信用卡上账单发呆的时候,发现一些毫无用处的月费还在傻傻地续着。
比如49.212.212.212这台服务器上仅仅是挂了我的邮箱而已。一个邮箱的费用价值居然有1080日元/月?
太说不过去了,migrate到其它服务器之后解约。

基本步骤

  1. 在target server 上安装和配置 postfix 以及 dovecot。 smtp通过SASL验证

  2. 切换DNS,将绑定在老服务器上的域名指向新服务器

  3. 在新服务器上的添加SSL证书

  4. rsync老服务器上的邮件目录下的所有邮件

收获

在实际migrate的过程中遇到了很多坑。主要原因还是对mail server的原理不熟悉造成的。

postfix 和 dovecot

通常我们看到网上的大多数教程都教我们安装配置postfix和dovecot这两个东西。
首先postfix是一个邮件中转器,所谓的中转就是,当我们收到邮件的时候,看一下邮件的目标地址是否在我们服务器的配置上?如果是的话,就停止中转,这样在我们的邮箱里,这封邮件就躺了下来。而dovecot的作用是,我们需要查看邮件的时候,不可能每次都登陆服务器找到邮箱目录然后读取,而是通过imap或者pop协议,在客户端(pc,手机application)上读取。
所以,postfix主要对应的是smtp,而devecot主要是对应imap和pop协议。

smtp接受外部的submission来发送邮件

默认的配置我们是无法通过外部的mail client来发送邮件的。这个时候需要配置postfix的master.cf, 添加

1
2
3
4
5
6
7
8
9
10
submission inet n       -       n       -       -       smtpd
# -o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
# -o smtpd_reject_unlisted_recipient=no
# -o smtpd_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
# -o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject

netstat -tlnp 确认587端口打开的话,就可以从外部的mail client上发送邮件了。

参考资料

postfix dovecot 配置

587,465, 25端口的区别