looking a wood sprite in the forest

Pour Over Coffee Makers


办公室日常用品

  • hario v60 2人份玻璃滤杯 1
  • hario v60 600ml 云朵分享壶 2
  • comandante c40 手摇磨豆机 3
  • kalita 700ml 细嘴壶 4
  • rivers seek 随手杯 5

  • 2020-0615 新购入 Mr.clever 聪明杯 6

  • 2020-0622 新购入 hario drip kettle air 7, 用了两次手感偏轻水流控制得不稳 T_T

  • 2020-1027 新购入 fellow ode 电动磨豆机 8

  • 2020-1109 新购入 fellow stagg ekg 温控壶 9

  • 2023-half 新购入 hoop 澡盆滤杯 10,类似聪明杯但会拉长萃取时间

每天一早 中午 到公司开始烧水,然后吭呲吭呲摇 40g 豆子兑 600ml 左右的水

手法就比较 随意 业余啦,主要参考下面两个视频,出品大概率自己都还挺喜欢的 按比例加注水量翻倍

“4:6 method” by Tetsu Kasuya 11

“The Ultimate V60 Technique” by James Hoffmann

家庭版

  • baratza encore 电动磨豆器 12
  • kinto OCT 2人份陶瓷滤杯 13
  • ikea vardagen 500ml 玻璃量杯 14
  • muji 万古烧马克杯 15

  • 2020-0615 新购入 hario filter-in 冷萃瓶 已碎,21年夏再次购入 16

刚开始的时候装备上交了不少学费,比如:电动砍豆机廉价手磨aeropress, 虽然不能一概而论,实际上很大程度还是遵循一分钱一分货的原则,性能越好则价格呈指数上涨 条件允许的话尽量选择好一点的设备,毕竟也是天天要用的家伙事

Read more ⟶

Mastodon


Mastodon 长毛象1 – 基于 rubyonrails/reactjs/nodejs 开发的分布式 & 去中心化 twiter clone。利用空闲时间在 aws lightsail 上开了个实例把服务跑了起来

一开始走了些弯路,因为选机房和省钱的缘故,重建了若干次操作系统,最后的选择是 tokyo+cloudflare,没错我又套了 cdn,实在是海外线路到北京联通不稳定

安装步骤没有使用 docker 而是参考文档从源码安装2,原因和解决方案如下:

  • 机器用 $3.5/mo 512mem 最便宜的那档消费降级(512M 内存重启会拉垮弱鸡,服务已迁移到 oraclecloud),出于 net/io 性能考虑就不使 docker 啦

  • 内存问题,RAILS_ENV=production bundle exec rake mastodon:setup 这一步骤执行到 rails assets:precompile, 不管是在 docker 里跑还是直接运行都会报 swap 分区不足,找到两个方案来解决:

# create swapfile <https://linuxize.com/post/create-a-linux-swap-file/>
$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

# verify active
$ sudo swapon --show
# optional: low value is better for production
$ sudo sysctl vm.swappiness=10

邮件配置

# By default the memory limit in Node. js is 512 mb, 
# use command —- max-old-space-size to increase the memory limit
$ NODE_OPTIONS="--max-old-space-size=4096" RAILS_ENV=production bundle exec rails assets:precompile

直接配置 localhost 发送邮件无效,先用 sendgrid 免费版顶着, 一天 100 封邮件目前够用

Read more ⟶

Gitlab Repo


gitlab 里面已经有十来个 group,几百个 project,虽然不一定会每个项目都参与, 但是为了快速同步代码,又捡起了 android 开发常用的 repo 命令来管理多项目代码。 这里使用了 gitlab graphql 接口来遍历项目生成分组的 manifest 文件

#!/usr/bin/env python

import argparse
import json
import os
import urllib2

GITLAB_HOST = "gitlab.mydomain.com"
GITLAB_SSH_URL = "ssh://git@" + GITLAB_HOST
GITLAB_GRAPHQL_URL = "https://" + GITLAB_HOST + "/api/graphql"
GITLAB_TOKEN = ""
GITLAB_GROUP = [""]
BLACK_LIST = ("",)

# 以上配置修改为自己的设定

template = """<?xml version="1.0" encoding="UTF-8"?>
<!-- autogen by gen.py, do not edit this file -->
<manifest>
  <remote  name="origin" fetch="{ssh_url}" />
  <default revision="master" remote="origin" sync-c="true" sync-j="4" />
{content}
</manifest>"""



def write_file(content, filename="default.xml"):
    _file = os.path.join(os.path.dirname(__file__), filename)
    with open(_file, "w") as f:
        f.write(template.format(content=content, ssh_url=GITLAB_SSH_URL))


def fetch_project_of_group(group, write=True):
    data = '{ group(fullPath: "%s") { projects { nodes { fullPath } } }}' % group
    req = urllib2.Request(
        GITLAB_GRAPHQL_URL,
        data=json.dumps(dict(query=data)),
        headers={
            "Authorization": "Bearer " + GITLAB_TOKEN,
            "Content-Type": "application/json",
        },
    )
    resp = json.load(urllib2.urlopen(req))
    content = "\n".join(
        [
            '<project path="{d}" name="{d}"/>'.format(d=p["fullPath"])
            for p in resp["data"]["group"]["projects"]["nodes"]
            if p["fullPath"] not in BLACK_LIST
        ]
    )

    if write:
        write_file(content, filename=group.replace("/", "-") + "-group.xml")
    else:
        return content


def main():
    parser = argparse.ArgumentParser(description="Process some projects.")
    parser.add_argument(
        "-G",
        "--group",
        help="for group/subgroup or all",
        type=str,
        choices=ns + ["all", "default"],
    )
    arg = parser.parse_args()

    if arg.group == "all":
        for x in GITLAB_GROUP:
            fetch_project_of_group(x)
        d = [fetch_project_of_group(x, write=False) for x in GITLAB_GROUP]
        write_file("\n".join(d))
    elif arg.group == "default":
        d = [fetch_project_of_group(x, write=False) for x in GITLAB_GROUP]
        write_file("\n".join(d))
    else:
        fetch_project_of_group(arg.group)


if __name__ == "__main__":
    main()

USAGE: 创建完 manifest 文件之后提交到仓库就可以愉快的玩耍啦

Read more ⟶

Dash Replacement with tmux


通过 tmux 快捷集成替换 dash.app 查询开发文档

$ brew install dasht

$ dasht-docsets | tr 'A-Z' 'a-z'
go
javascript
python_3
rust
tornado
# tmux.conf quick start
bind -n S-up command-prompt -p 'docset:' "splitw -h -fb -l 80 dasht '%%'"
Read more ⟶

Cloudflare Gost


从清明节开始,稳定运行好几年的 ss 服务器终于阵亡了,所有端口全挂。一直蹭公司的 vpn 查资料也挺到了五一,实在拖延够够的就再另外开了一台机器中转过去迁移数据,不过嘛年纪大了又开始犯懒,企图拯救获得资格认证的机器,通过一番网上冲浪学习到了目前(实测可用)能满足我需求的方案。简单来说就是:cloudflare[后文简称为 cf] + websockets over gost,实际的客户端通过 cdn 代理再接入服务

有几个需要注意的地方:

  • gost 启动时绑定的 localhost 不直接对外访问,走了 caddy 的转发,而这一步和 cf 的 ssl 证书配置会造成不停的重定向跳转,需要将 cf 加密模式配置为 flexible

  • 然后修改 caddy 的域名配置为 http://domain.com https://domain.com { … } 阻止 cf 和 caddy 之间的 http -> https

  • gost 服务端监听 ws 协议,本地的 gost 客户端转发 wss 协议连接 cf_domain:443

  • 需要鉴权的方案使用 socks5+wss://username:password@domain:port

  • android 客户端的设置,因为使用了 ws 协议,所以需要将域名写入到插件的配置里,直接用域名变量无法解析

具体配置参考官方文档,一切浪费的时间都是源于没认真仔细看文档

update 2021-03-01 由于 shawdowsocks 的 android 客户端升级导致插件不可用,另外部署了 brook wsserver 给手机使用

Read more ⟶

webpack resolve local module


最近的项目刚开始,设计的目录层级有点深
经常会在好几层本地路径之间互相引用

import Image from '../../../../components/image'

这层层叠叠的路径写起来实在丑陋
不由得想起 Python 从项目根目录引用模块
然后研究了一下 Node.js 里的几种简易实现

干脆利落的软连接: ln -s node_modules src

修改环境变量: NODE_PATH=. node app

从本地目录安装:

// package.json 
// 需要运行 npm install
{
  "name": "baz",
  "dependencies": {
    "foo": "file: ./src",
  }
}

另外还有些修改 global,或者引入其他 require 实现的方法就不再一一列出了
最终选择的是修改 webpack 配置

// webpack.config.js
resolve: {
  modulesDirectories: [__dirname, 'node_modules'],
}

https://gist.github.com/branneman/8048520 http://stackoverflow.com/questions/10860244/how-to-make-the-require-in-node-js-to-be-always-relative-to-the-root-folder-of-t/41078266#41078266 https://webpack.github.io/docs/configuration.html#resolve-modulesdirectories

Read more ⟶

Leonard Cohen - you want it darker


年度最佳

you want it darker

you want it darker

循环听了好几天

干净低沉的嗓音加配乐

愈发担心以后听不到了怎么办 😢~

Read more ⟶

web audio


照文档撸了一下 AudioContext 可视化音频
桌面浏览器上 Safari 9, Chrome stable 绘制正常
移动端只有微信的 webview 能工作, 纯玩票叻 wave

ref: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <title>AV</title>
  </head>
  <body>
    <canvas id="vis"></canvas>
    <audio id="av" src="YOUR_AUDIO_FILE"></audio>
    <script src="index.js"></script>
  </body>
</html>
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;

window.onload = function(){

  var canvas = document.getElementById('vis');
  var canvasCtx = canvas.getContext('2d');

  var isPlaying = true;
  var audio = document.getElementById('av');
  var audioCtx = new AudioContext();
  var analyser = audioCtx.createAnalyser();
  var audioSrc = audioCtx.createMediaElementSource(audio);
  audioSrc.connect(analyser);
  analyser.connect(audioCtx.destination);

  analyser.fftSize = 2048;
  var bufferLength = analyser.fftSize;
  dataArray = new Uint8Array(bufferLength);
  analyser.getByteTimeDomainData(dataArray);

  WIDTH = 500;
  HEIGHT = 150;
  function draw() {
    requestAnimationFrame(draw);
    if (!isPlaying) return;

    analyser.getByteTimeDomainData(dataArray);

    canvasCtx.fillStyle = 'rgb(200, 200, 200)';
    canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
    canvasCtx.lineWidth = 2;
    canvasCtx.strokeStyle = 'rgb(0, 0, 0)';
    canvasCtx.beginPath();

    var sliceWidth = WIDTH * 1.0 / bufferLength;
    var x = 0;

    for(var i = 0; i < bufferLength; i++) {

      var v = dataArray[i] / 128.0;
      var y = v * HEIGHT/2;

      if(i === 0) {
        canvasCtx.moveTo(x, y);
      } else {
        canvasCtx.lineTo(x, y);
      }

      x += sliceWidth;
    }

    canvasCtx.lineTo(canvas.width, canvas.height/2);
    canvasCtx.stroke();
  };

  draw();
  audio.play();

  document.body.onclick = function() {
    console.log(audio, isPlaying);
    if (isPlaying) {
      audio.pause();
      isPlaying = false;
    } else {
      audio.play();
      isPlaying = true;
    }
  }
}
Read more ⟶

move to caddy


去年用 Hexo 搭建的日志已经好几个月没更新了,最近休假有点空闲就继续更新吧。
先从 Github 迁移回自己的 Linode,然后安装一个 Caddyserver1 来渲染 markdown

Caddy is a unique web server with a modern feature set. Think nginx or Apache, but written in Go. With Caddy, you can serve your websites over HTTP/2. It can act as a reverse proxy and load balancer. Front your PHP apps with it. You can even deploy your site with git push. Cool, right?2

Download and install systemd

wget -O 'caddy.tar.gz' 'https://caddyserver.com/download/build?os=linux&arch=amd64&features=git%2Cipfilter'
tar zxf caddy.tar.gz
mkdir /opt/caddy
mkdir /var/run/caddy/
chown caddy:caddy -R /opt/caddy
chown caddy:caddy -R /var/run/caddy
mv caddy /opt/caddy/
useradd -d /var/run/caddy --system caddy

chmod 644 /etc/systemd/system/caddy.service
systemctl enable caddy.service
systemctl start caddy.service
systemctl status caddy.service

https://github.com/caddyserver/examples/blob/master/systemd%2Fcaddy.service

Read more ⟶

mongodb backup


有台机器准备2月份下架 记一个 mongodb 备份小脚本 :)

#!/bin/bash
# vim: set et sw=2 ts=2 sts=2 ff=unix fenc=utf8:

MONGO_DATABASE="_name_"

MONGO_HOST="_ip_"
MONGO_PORT="_prot_"
TIMESTAMP=`date +%Y-%m-%dT%H:%M:%S`
MONGODUMP_PATH="/usr/bin/mongodump"
BACKUPS_DIR="/data/dumps/"
BACKUP_NAME="$MONGO_DATABASE-$TIMESTAMP"

while test $# -gt 0
do
  case "$1" in
    -m) echo "backup mongthly and clear week_dir"
      #rm $BACKUPS_DIR"week/*"
      find $BACKUPS_DIR"week" -type f -name '*.tgz' -delete
      tar -czPf $BACKUPS_DIR"month/"$BACKUP_NAME.tgz $BACKUPS_DIR$MONGO_DATABASE
      ;;
    -w) echo "backup weekly"
      echo "tar -czPf $BACKUPS_DIR"week/"$BACKUP_NAME.tgz $BACKUPS_DIR$MONGO_DATABASE"
      ;;
    -d) echo "just dump"
      $MONGODUMP_PATH -d $MONGO_DATABASE --out $BACKUPS_DIR
      ;;
    *) echo "do nothing"
      ;;
  esac
  shift
done
# crontab -e
10 3 * * * /bin/bash $HOME/bin/mongobackup.sh -d > /dev/null 2>&1
20 3 * * 1 /bin/bash $HOME/bin/mongobackup.sh -w > /dev/null 2>&1
30 3 1 * * /bin/bash $HOME/bin/mongobackup.sh -m > /dev/null 2>&1

每天早上3点按天/周/月分别保存

Read more ⟶