机械境

这里只是我的后花园,随性而写

缘起

虽然我日常的工作都是在 MacBook Pro (13-inch, 2018, Four Thunderbolt 3 Ports) 上完成的,也用得还算比较顺手。但是还是有偶尔需要 Windows Only 的需求,比如小米的解锁 Bootloader 的工具就只有 Win 版。本来还有一台 DELL XPS13 作为备用机,由于某些原因主板进水了,在官方修了一次,回来没用几次之后又再次挂了,懒得再修了,就一直在吃灰。鉴于我之前有个 256 G 的 Windows To Go 的 USB 盘,想这再废物利用一下。

主要诉求如下,把原来的 WTG 中的 Windows 10 升级到最新的版本可以用 WLS2,可以作为单独的 WTG 运行,也可以通过虚拟机的方式挂载到 macOS 下应个急。思路如下,在 Windows 中安装 Boot Camp 驱动,在 macOS 中可以 VMware Fusion 中可以作为 Boot Camp 分区引导。

准备

Windows 10 镜像

可以在 MSDN I tell you 下载,这个是完全是合法的,不涉及任何盗版的问题。

VMware Fusion

  • 注册 VMware Fusion,个人免费使用
  • brew install --cask vmware-fusion 安装 VMware Fusion
  • 用刚注册的序列号激活 VMware Fusion

Boot Camp

  • Make sure that your Mac is connected to the Internet.
  • Open Boot Camp Assistant, which is in the Utilities folder of your Applications folder.
  • From the menu bar at the top of your screen, choose Action > Download Windows Support Software, then choose your USB flash drive as the save destination. When the download completes, quit Boot Camp Assistant.

制作 WTG

  • Windows

在 Windows 下制作 WTG 有一堆工具了,WinToUSB 算是比较傻瓜式的。安装之后,按照向导即可。

  • macOS

可以通过 VMware Fusion 安装一个 Windows 虚拟机,然后在虚拟机里面安装 WinToUSB 来制作 WTG 的盘。

安装

WTG 制作完成后,只安装 Boot Camp 下载的驱动。如果出现 “Boot Camp is unsupported on this computer model” 错误,可参考 How to fix the ‘Boot Camp is unsupported on this computer model’ error on Mac 解决。

在 macOS 中启动 WTG

  • 在 System Preferences->Security & Privacy->Full Disk Access 中启用 com.vmware.DiskHelper

  • 在 VMware Fusion 中启动,具体步骤可以参考 Launching your Boot Camp partition in VMware Fusion

  • 启用 CPU 虚拟化

Tips

  • WTG 不能升级?
    • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\PortableOperatingSystem 的值修改为 0
    • 在虚拟机中启动 WTG,就可以升级

参考链接

  • Download and install Windows support software on your Mac
  • [教程] Windows To Go 系统升级(2018.8.27 更新)

---EOF---

以太坊 Parity 客户端是除 Geth 之外使用量最高的一款以太坊客户端,使用的是 Rust 语言,现在改名为 openethereum 了。

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
38
version: "3"

services:
openethereum:
image: openethereum/openethereum:v3.0.1
container_name: openethereum
restart: unless-stopped
ports:
- "30303:30303"
- "30303:30303/udp"
- "127.0.0.1:8545:8545"
- "127.0.0.1:8546:8546"
user: openethereum
volumes:
- ./chaindata:/chaindata
stop_signal: SIGINT
stop_grace_period: 2m
command:
- --no-download
- --base-path=/chaindata
- --jsonrpc-interface=all
- --jsonrpc-cors=all
- --ws-interface=all
- --ws-origins=all
- --no-ipc
- --scale-verifiers
- --num-verifiers=4
- --pruning=fast
- --pruning-memory=512
- --cache-size-db=1024
- --cache-size-blocks=128
- --cache-size-queue=512
- --cache-size-state=256
logging:
driver: "json-file"
options:
max-size: "2m"
max-file: "10"

Read more »

缘起

在 GitHub 建一个和用户名同名的仓库,仓库中 README.md 的内容就会显示在账户首页。如果 README.md 中的内容是动态更新的,那么首页的内容也就是动态更新的。

根据此思路,只要通过 GitHub Actions 去动态更新 README.md 即可。

配置

这里以我仓库的内容为例,自动同步博客的内容;统计 GitHub 的贡献记录。

  • 同步博客标题,只需要抓取 RSS 转换成 README.md 即可
  • GitHub 的统计,可以通过 anuraghazra/github-readme-stats 完成,这个本身就是动态的了,配置一下即可

更新脚本

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
#!/usr/bin/python
# -*- coding: utf-8 -*-

import requests
import xml.etree.ElementTree as ET

feed = requests.get('https://gythialy.github.io/atom.xml').text
root = ET.fromstring(feed)
nsfeed = {'nsfeed': 'http://www.w3.org/2005/Atom'}
with open('README.md', 'w') as f:
f.write(r'''
## Hi there 👋

- 🔭 I’m currently working on [@QLC Chain](https://github.com/qlcchain)
- 🌱 I’m currently learning [Rust](https://github.com/rust-lang/rust)

## Latest blog posts
''')
for entry in root.findall('nsfeed:entry', nsfeed)[:5]:
text = entry.find('nsfeed:title', nsfeed).text
url = entry.find('nsfeed:link', nsfeed).attrib['href']
published = entry.find('nsfeed:published', nsfeed).text[:10]
f.write('- {} [{}]({})\n'.format(published, text, url))

f.write('''
[>>> More blog posts](https://gythialy.github.io/)
## Statistics
![Goren's github stats](https://github-readme-stats.vercel.app/api?username=gythialy&count_private=true&show_icons=true)
''')

GitHub Actions

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
38
39
40
name: Build README

on:
push:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
name: Configure pip caching
with:
path: ~/.cache/pip
key: ${{runner.os}}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{runner.os}}-pip-
- name: Install Python dependencies
run: |
python -m pip install -r requirements.txt
- name: Update README
run: |-
python build.py
cat README.md
- name: Commit and push if changed
run: |-
git diff
git config --global user.email "readme-bot@example.com"
git config --global user.name "README-bot"
git add -A
git commit -m "🤖 Updated README.md" || exit 0
git push

参考链接

  • Building a self-updating profile README for GitHub
  • simonw

---EOF---

背景

由于 WebAssembly Interface Types 还处于草案阶段,目前 WASM 只支持四种基本类型,本质上来说没什么大用。

在实际的开发中,数据结构肯定是远比 i32,i64,f32,f64 这四种基本类型来得复杂的,至少可能是一个复杂的结构体。比如,我需要把一组 key/value 存起来,然后可以通过 key 查询到对应的 value。按照目前的 WASM 的规范,这个实现都是比较麻烦的。key/value 可能是字符串或者其他结构,但是不管什么类型,在内存都是按照字节序的方式存储,所以只需要把类型编码成字节序放到内存中,这样这个结构就可以转化成内存的起始地址(指针)和数据长度,这两个值就是 i32/i64,并且都是 WASM 支持的类型。

函数声明

  • 对外 API

    为了对调用者友好,调用这不需要处理 slice 的地址

1
2
3
4
5
  // 把 key/value 写入存储
pub fn storage_write(key: &[u8], val: &[u8])

// 根据 key 查询对应的 value
pub fn storage_read(key: &[u8]) -> Option<Vec<u8>>
  • 内部接口

在虚拟机中注入这两个 host function 来实现具体的逻辑

1
2
3
4
5
6
7
8
9
10
extern "C" {
pub fn qlcchain_storage_read(
key: *const u8,
klen: u32,
val: *mut u8,
vlen: u32,
offset: u32,
) -> u32;
pub fn qlcchain_storage_write(key: *const u8, klen: u32, val: *const u8, vlen: u32);
}

Read more »

为了能够让人类阅读和编辑 WebAssembly,wasm 二进制格式提供了相应的文本表示。这是一种用来在文本编辑器、浏览器开发者工具等工具中显示的中间形式。本文用基本语法的方式解释了这种文本表示是如何工作的,以及它是如何与它表示的底层字节码,及在 JavaScript 中表示 wasm 的封装对象关联起来的。

本质上,这种文本形式更类似于处理器的汇编指令。

S - 表达式

不论是二进制还是文本格式,WebAssembly 代码中的基本单元是一个模块。在文本格式中,一个模块被表示为一个大的 S - 表达式。

S - 表达式是一个非常古老和非常简单的用来表示树的文本格式。因此,我们可以把一个模块想象为一棵由描述了模块结构和代码的节点组成的树。不过,与一门编程语言的抽象语法树不同的是,WebAssembly 的树是相当平的,也就是大部分包含了指令列表。

首先,让我们看下 S - 表达式长什么样。树上的每个一个节点都有一对括号 ——(…)—— 包围。括号内的第一个标签告诉你该节点的类型,其后跟随的是由空格分隔的属性或孩子节点列表。

S - 表达式如下:

1
(module (memory 1) (func))

这条表达式,表示一棵根节点为 “模块(module)” 的树,该树有两个孩子节点,分别是 属性为 1 的 “内存(memory)” 节点 和 一个 “函数(func)” 节点。

Read more »

WebAssembly 是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如 C、C++ 和 Rust 等低级源语言提供一个高效的编译目标。对于网络平台而言,这具有巨大的意义 —— 为客户端 app 提供了一种在网络平台以接近本地速度的方式运行多种语言编写的代码的方式。JavaScript 框架不但可以使用 WebAssembly 获得巨大性能优势和新特性,而且还能使得各种功能保持对网络开发者的易用性。简而言之,WebAssembly 可以为 Javascript 提供由 C/C++ 等更高效的语言编写的库。

前世今生

问题

业务需求越来越复杂,前端的开发逻辑越来越复杂,相应的代码量随之变的越来越多,整个项目的起步的时间越来越长。在性能不好的电脑上,启动一个前端的项目甚至要花上十多秒。除了逻辑复杂、代码量大,还有另一个原因是 JavaScript 这门语言本身的缺陷,JavaScript 没有静态变量类型。这门解释型编程语言的作者 Brendan Eich,仓促的创造了这门如果被广泛使用的语言,以至于 JavaScript 的发展史甚至在某种层面上变成了填坑史。

asm.js

为了解决这个问题,WebAssembly 的前身,asm.js 诞生了。asm.js 是一个 Javascript 的严格子集,合理合法的 asm.js 代码一定是合理合法的 JavaScript 代码,但是反之就不成立。同 WebAssembly 一样,asm.js 不是用来给各位用手一行一行撸的代码,asm.js 是一个编译目标。它的可读性、可读性虽然比 WebAssembly 好,但是对于开发者来说,仍然是无法接受的。

无论 asm.js 对静态类型的问题做的再好,它始终逃不过要经过 Parser,要经过 ByteCode Compiler,而这两步是 JavaScript 代码在引擎执行过程当中消耗时间最多的两步。而 WebAssembly 不用经过这两步。这就是 WebAssembly 比 asm.js 更快的原因。

WebAssembly 横空出世

在 2015 年,我们迎来了 WebAssembly,是经过编译器编译之后的代码,体积小、起步快。在语法上完全脱离 JavaScript,同时具有沙盒化的执行环境。WebAssembly 同样的强制静态类型,是 C/C++/Rust 的编译目标。

2019 年 12 月 5 日 — 万维网联盟(W3C)宣布 WebAssembly 核心规范 成为正式标准

工具链

  • AssemblyScript
    支持直接将 TypeScript 编译成 WebAssembly
  • Emscripten
    将其 C/C++ 编译成 WebAssembly
  • WABT
    将 WebAssembly 在字节码和文本格式相互转换的一个工具,方便开发者去理解这个 wasm 到底是在做什么事。
  • WebAssembly Studio
    在线 IDE,支持 C/C++/Rust

关键概念

模块

表示一个已经被浏览器编译为可执行机器码的 WebAssembly 二进制代码。一个模块是无状态的,并且像一个二进制大对象(Blob)一样能够被缓存到 IndexedDB 中或者在 windows 和 workers 之间进行共享(通过 postMessage() 函数)。一个模块能够像一个 ES2015 的模块一样声明导入和导出。

内存

ArrayBuffer (浏览器 WASM 虚拟机实现),大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作。

表格

带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用(为了安全和可移植性的原因)。

实例

一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。一个实例就像一个已经被加载到一个拥有一组特定导入的特定的全局变量的 ES2015 模块。

生命周期

wasm lifecycle

  1. 通过工具链把其他语言 (C++、Go、 Rust) 编译成 WebAssembly 汇编格式 .wasm 文件
  2. 在网页中使用 fetchXMLHttpRequest 等获取 wasm 文件
  3. .wasm 编译为模块,编译过程中进行合法性检查
  4. 实例化,初始化导入对象,创建模块的实例
  5. 执行实例的导出函数,完成所需操作

虚拟机体系

wasm vm system

WebAssembly 模块在运行时由以下几部分组成,如上图所示

  1. 一个全局类型数,与很多语言不同,在 WebAssembly 中 “类型” 指的并非数据类型,而是函数签名,函数签名定义了函数的参数个数参数类型返回值类型;某个函 数签名在类型数组中的下标 (或者说位置) 称为类型索引
  2. 一个全局函数数组,其中容纳了所有的函数,包括导入的函数以及模块内部定 义的函数,某个函数在函数数组中的下标称为函数索引
  3. 一个全局变量数组,其中容纳了所有的全局变量包括导入的全局变量以及 模块内部定义的全局变量,某个全局变量在全局变量数组的下标称为全局变量索引
  4. 一个全局表格对象,表格也是一个数组,其中存储了元素 (目前元素类型只能 为函数) 的引用,某个元素在表格中的下标称为元素索引。
  5. 一个全局内存对象
  6. 一个运行时栈
  7. 函数执行时可以访问一个局部变量数组,其中容纳了函数所有的局部变量,某 个局部变量在局部变量数组中的下标称为局部变量索引

在 WebAssembly 中,操作某个具体的对象(比如读写某个全局变量)都是通过其索引完成。在当前版本中,所有的索引都是 32 位整形数。

参考资料

  • WebAssembly Concepts
  • WebAssembly 标准入门

---EOF---

介绍

之前在 traefik 基于 Treafik 部署 Bitwarden RS 中简单介绍了如何基于 Traefik 部署 Bitwarden RS。Treafik 的官方文档一如既往地混乱,甚至有些地方前后不一致,让人看得云里雾里的。

Traefik v2 的新特性

  • Providers
    是指你正在使用的集群技术 (Kubernetes, Docker, Consul, Mesos 等)
  • Entrypoints
    是最基本的配置,指监听请求的端口.
  • Services
    是在你的基础设施上的运行的软件,通过 services 配置如何路由到真正的应用程序
  • Routers
    将传入的请求和你的 service 连接起来。他们持有的 rules 决定哪个 service 将处理对应的请求
  • Middleware
    中间件是可以在请求被 service 处理之前对其进行更新的组件。Traefik 提供了一些开箱即用的中间件来处理 认证、速率限制、断路器、白名单、缓冲等等

Traefik v2 配置

configuration

配置分为静态配置和动态配置:

  • 静态配置:启动的时候加载。包括 Entrypoints、Provider 连接信息
  • 动态配置:运行时可动态读取改变的。包括 Routes、Services、Middlewares、Certificates

Read more »

缘起

在 Dockerfile 中,如果我们不显式指明用户,进行权限处理,那么默认的 Dockerfile 生成的镜像,在运行时会以 root 身份进入 ENTRYPOINT,进而执行 CMD。因此,以 root 身份启动 container 是一件很危险的事情。尽管,docker 容器内的 root 与宿主 host 本身的 root 并不一定具有一样的权限,但是在容器内部的 root 拥有和宿主机一样的 UID (UID 0)。如果以 priviledge 的方式运行 container,那么两者将会一样,从而产生巨大的安全隐患。

简而言之,出于安全的考虑,需要在 Dockerfile 的 ENTRYPOINT 之前,也就是运行 application 之前,进行用户切换。

示例

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
# Build gqlc in a stock Go builder container
FROM qlcchain/go-qlc-builder:latest as builder

ARG BUILD_ACT=build

COPY . /qlcchain/go-qlc
RUN cd /qlcchain/go-qlc && make clean ${BUILD_ACT}

# Pull gqlc into a second stage deploy alpine container
FROM alpine:3.11.3
LABEL maintainer="developers@qlink.mobi"

ENV QLCHOME /qlcchain

RUN apk --no-cache add ca-certificates && \
addgroup qlcchain && \
adduser -S -G qlcchain qlcchain -s /bin/sh -h "$QLCHOME" && \
chown -R qlcchain:qlcchain "$QLCHOME"

USER qlcchain

WORKDIR $QLCHOME

COPY --from=builder /qlcchain/go-qlc/build/gqlc /usr/local/bin/gqlc

EXPOSE 9734 9735 9736

ENTRYPOINT ["gqlc"]

VOLUME ["$QLCHOME"]

主要思路如下:

  • 一阶段构建需要执行的 app 可执行程序
  • 二阶段创建用户及用户组,拷贝一阶段的编译出来的文件
  • 切换到刚创建出来的用户,执行 ENTRYPOINTCMD

---EOF---

0%