参考官方文档: https://support.atlassian.com/bitbucket-cloud/docs/runners/
首先禁用 Linux 系统的 swap 功能。若不禁用 swap,在 runner 进行 builder 时会使用 swap 内存,可能会出现内存不足的错误。
1 | sudo swapoff -sv |
编辑 /etc/sysctl.conf
添加 vm.swappiness = 1
再重启系统。
接着在 Bitbucket 的设置界面添加 Runner,然后执行界面给出的命令。
然后在目标机器上执行命令:
1 | docker run-it -d \ |
在 bitbucket.pipelines.yml
中的相关步骤配置 runs-on
,如:1
2
3
4
5
6
7pipelines:
default:
- step:
runs-on:
- self.hosted
- linux.shell
script:
因为 runner 是以 linux-shell 的方式运行的,所以会有一些限制: https://support.atlassian.com/bitbucket-cloud/docs/set-up-runners-for-linux-shell/
]]>因为 whenever 是使用系统的 cron 来实现的定时任务,所以直接就在 docker image 内安装一个 cron 即可: apt-get install -y cron
。
然后尝试执行命令: whenever -i; crontab -l
看看定时任务都有设置正确。最后在容器启动的时候使用命令 service cron start
一并将 cron 也启动。
至此感觉事情很简单,一切都很顺利。然而过了两天客户反馈说是这两天的定时任务都没有执行。
定时任务没执行,那就是 cron 有问题。现在来开始排查问题。
先再安装 syslog: apt-get install -y rsyslog
,将 cron 的日志保存下来,方便查找错误。
然后往 crontab 中随便添加一条任务,每分钟执行一次命令: date >> /tmp/date.log
。
结果1分钟之后 cron 的日志显示有报如下错误:1
FAILED to open PAM security session (Cannot make/remove an entry for the specified session)
经过搜索知道在 docker 内是没有 session 的,所以 PAM set_loginuid 会失败。需要将 set_loginuid
这行注释掉:1
sed -i '/^session\s\+required\s\+pam_loginuid.so/c\#session required pam_loginuid.so' /etc/pam.d/cron
重启 cron 之后,date >> /tmp/date.log
定时任务也有正常执行了。
到此感觉问题应该是解决了。然而又过了一天客户还是说定时任务没有执行。
看来还是存在问题的,还得接着排查。
whenever 的任务是定时执行一些 rake 任务,然而执行的结果没有任何的日志。
于是我就新添加了一条 crontab 任务,将 rake 命令的 stdout 和 stderr 重定向到日志文件中。
然后就发现其实是因为在执行 rake 命令时提示 rake 命令不存在,从而导致执行失败的。
看来就因为 PATH 环境变量的问题。
在启动 container 时是有设置了一些环境变量的,然而 cron 这个进程并没有继承这些变量。
所以现在就需要手动为 cron 再配置环境变量。在启动 cron 之前先执行命令:1
2
3
4rm /etc/environment
for variable_value in $(cat /proc/1/environ | sed 's/\x00/\n/g'); do
echo $variable_value >> /etc/environment
done
现在再测试一切就正常了。
至此问题终于是解决了,于是就记录一下方便以后查阅。
关于添加自己的机器,可参考 Gitlab 官方文档: https://docs.gitlab.com/runner/register/index.html
同样的,为了方便,我们使用 docker 来一键部署。首先到 gitlab 的 CI/CD 项目设置页面,然后查看当前项目的 runner 注册 token。
然后执行如下命令进行 runner 注册。
1 | docker run --rm -it -v $PWD/tmp/gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:alpine register |
注意这里的 PROJECT_REGISTRATION_TOKEN
需要替换为对应的 token;--run-untagged
要设为 true,才能运行所有的 job。否则就只会执行打了 tag 的 job 。
如果需要多个项目共享该 runner,那么 --locked
就设为 false;
注册成功之后会生成 ./tmp/gitlab-runner-config/config.toml 文件: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
26concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "runner-docker-01"
url = "https://gitlab.com/"
token = "xxxxxxxxxxxxxxxxxxxx"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
host = "unix:///var/run/docker.sock"
tls_verify = false
image = "alpine"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
shm_size = 0
如果更换服务器了,但是还想要继续使用该 runner 的配置,只需要保留该配置文件即可。
以上是 runner 注册成功,接下来就使用该配置文件来启动 runner 服务吧。
同样也是为了安全起见,我们使用 docker-in-docker 的方式来部署。
创建一个 docker-compose.yml 文件:
1 | version: "2" |
然后执行命令 docker-compose up -d
启动服务即可。
关于添加自己的机器,可参考 Github 官方文档: https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/adding-self-hosted-runners
在 github 中 runner 分为三个级别: repository, organization, enterprise。
其中 repository 级别的 runner 就只能在这个代码仓库下使用;
organization 和 enterprise 级别的 runner 可以在这个 organization 或者 enterprise 下的所有代码仓库中共享。
由于我是免费用户,这里我就以 organization 为例吧。另外为了方便我们就直接通过 docker 来运行。
参考官方文档,首先进入到 organization 的 actions 设置页面,然后选择添加一个 runner。没有 organization 的可以自己创建一个。
接着在自己的 server 上执行命令:
1 | docker run -d \ |
其中 GH_REPOSITORY 就是设置页面上显示的 url 参数,GH_RUNNER_TOKEN 就是 token 参数。GH_RUNNER_LABELS 则根据自己的需要来为该 runner 设置标签。
至此,自己的 runner 就已经配置好了。不过这里我们是直接在这个 runerr 的 container 中操作 server 上的 docker 服务。这样其实是非常危险的,尤其是对于公有仓库来说。
为了安全起见,我们可以再运行一个 Docker-in-Docker 的服务,然后这个就只需要连接到该服务即可。
创建一个 docker-compose.yml 文件:
1 | version: "2" |
然后执行命令 docker-compose up -d
启动服务即可。
如果之后 actions 任务太多处理不过来,可以将 runner 扩容,多增加几个 runner 实例: docker-compose scale github-runner=3
至此,我们自己的 runner 就配置好了。如果想要在 actions 中使用我们自己的 runner,则需要修改之前的配置文件 .github/workflows/main.yml
。将 runs-on: ubuntu-latest
改为 runs-on: self-hosted
。
首先用 Cheat Engine 来修改资源,以及查找资源的内存地址。注意这里的数据都要使用浮点数类型。
如图找到的食物内存地址是 0F2B93B0
通过逐步分析得到以下指标的内存地址为:
木材地址 = 食物地址 + 4
石头地址 = 食物地址 + 8
黄金地址 = 食物地址 + 12
黄金地址 = 食物地址 + 12
人口上限地址 = 食物地址 + 16
当前人口地址 = 食物地址 + 44
如果想要实际无限人口,只需要修改 人口上限 和 当前人口 这两个数据即可。
这些地址都找到之后,然后就开始写代码,使用 Python 调用 WriteProcessMemory
来修改游戏的内存。
然而在打完这一局之后开启新的游戏时却发现这些地址全都变了。
每次开启新的游戏时都需要重新查找一遍地址内存,这个就有点麻烦了。
通过分析发现这些地址都全是动态地址,每次游戏时都不一样。根据仅记得的一点汇编知识知道,这些动态地址是通过基址加偏移地址得到的。所以现在一劳永逸的方法是找到基址地址。而基址是静态的。然后接下来逐步进行分析。
首先刚刚食物地址是0F2B93B0。游戏程序如果要从这个地址获取食物数据,那么就得在某个地方储存这个地址值。这里我们就先将这个地址称为食物地址指针
。接着用 CE 查找一下 0F2B93B0 这个值,然后就找到了食物地址指针
地址为 206221B8。如图:
注意这里要用 16 进制的方式来查找这个地址值。如果用 C 语言代码的描述相当于:1
2
3int food_point = 0x206221B8 // 食物地址指针
int food_address = *food_point; // 食物地址指针 指向食物地址 0x0F2B93B0
int food = *food_point; // 从食物地址 0x0F2B93B0 得到食物的值
经过测试发现这个食物地址指针
地址也是动态的。没办法了,只能继续往下找。现在来看看都有谁访问了这个地址。如图:
这里我们又得到了一个地址: 20622110 。再来看看是哪里记录了这个地址呢。如图:
这里我们又找到了 007A5FEC 这个地址,运气不错。在 CE 中显示为绿色,表示它是一个静态地址。这表示它就是我们要找的基址了。
现在来总结一下。使用 C 语言代码描述如下:1
2
3
4int base_addr = 0x007A5FEC; // 游戏基址
int food_point = *base_addr + 0xA8; // 基址记录的值0x20622110 + 偏移地址0xA8 = 食物地址指针
int food_address = *food_point; // 得到食物地址
int food = *food_address; // 从食物地址 0x0F2B93B0 得到食物的值
最终修改的结果如图:
上面修改器的完整源代码,如有需要可通过以下链接获取:
https://github.com/wusuopu/cheat_engine_age2
需要先安装以下程序:
因为是在本地学习 k8s,因此为了方便我就使用 Rancher 的 k3s 来进行安装。
下载 Vagrant 配置文件: https://github.com/wusuopu/kubernetes-vagrant-alpine
然后执行命令:1
vagrant up --no-parallel --provision
这里启动 k3s 服务,并将 master node 的 /etc/rancher/k3s/k3s.yaml 文件内容复制到 host 系统上来,这样就可以直接在 host 系统中用 kubectl 来操作集群。
1 | -> % k get node -o wide |
这里集群的各个 node 都是一个 docker container。1
2
3
4
5-> % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a91d13e2cca0 wusuopu/vagrant:k3s-alpine "dockerd-entrypoint.…" About an hour ago Up 35 minutes 2375-2376/tcp, 6443/tcp, 127.0.0.1:2201->22/tcp kubernetes-vagrant-alpine_alpine-03_1585887389
cce93873b666 wusuopu/vagrant:k3s-alpine "dockerd-entrypoint.…" About an hour ago Up 35 minutes 2375-2376/tcp, 6443/tcp, 127.0.0.1:2200->22/tcp kubernetes-vagrant-alpine_alpine-02_1585887382
d4844843ca3e wusuopu/vagrant:k3s-alpine "dockerd-entrypoint.…" About an hour ago Up 36 minutes 2375-2376/tcp, 0.0.0.0:6443->6443/tcp, 127.0.0.1:2222->22/tcp kubernetes-vagrant-alpine_alpine-01_1585887365
以下通过一个例子来说下 Github Actions 的用法。
在项目根目录下创建文件 .github/workflows/main.yml
1 | name: CI |
以上的配置执行的操作是,当有新的 ‘v’ 开头的 tag push 时,则 build docker image 并 push 到 cloud.canister.io 的私有 docker registry 上。
这里 canister 的用户名和密码是通过 secrets 来配置的,在 github 的 repository -> Settings -> Secrets 页面。
]]>目前 Vagrant (https://www.vagrantup.com/downloads.html) 最新为 v2.2.6,Kubernetes 为 v1.16
下载最新版的 kubectl:
1 | `curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl` |
需要下载 Vagrant 所兼容的版本( https://www.vagrantup.com/docs/virtualbox/ )。我是直接用 apt-get 安装 5.2 的版本。
1 | apt-get install virtualbox virtualbox-guest-additions-iso |
创建 Vagrantfile 配置文件,这里在 virtualbox 内运行的是 Ubuntu 18.04。
若 Vagrant 的 Box 文件下载太慢,可先提前下载好:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20wget https://app.vagrantup.com/ubuntu/boxes/bionic64/versions/20191125.0.0/providers/virtualbox.box
cat > virtualbox.json <EOF
{
"name": "ubuntu/bionic64",
"versions": [
{
"version": "20191125.0.0",
"providers": [
{
"name": "virtualbox",
"url": "virtualbox.box"
}
]
}
]
}
EOF
vagrant box add ubuntu/bionic64 virtualbox.json
1 | # -*- mode: ruby -*- |
接着启动 ubuntu
1 | vagrant up |
登录 ubuntu 实例
1 | vagrant ssh |
至此 ubuntu 就是运行起来了。然后却只有一个 实例,但是在实际应用中可能是多个实例组成的集群。
先停止刚刚的实例
1 | vagrant halt |
然后修改 Vagrantfile 文件,将 $num_instances 变量改为3。这里我就启动3个实例来组成集群。
接着启动这3个实例
1 | vagrant up |
查看实例状态
1 | -> % vagrant status |
然后登录到其中的某个实例
1 | vagrant ssh ubuntu-02 |
目前的3个 ubuntu 实例如下:
1 | 172.17.8.101 (ubuntu-01) master |
因为是在本地学习 k8s,因此为了方便我就使用 Rancher 的 k3s 来进行安装。
先分别在各个实例中安装 docker,参考: https://docs.docker.com/install/linux/docker-ce/ubuntu/
先安装 master node
1 | vagrant ssh ubuntu-01 |
接着分别进入另外两个实例内安装 worker node,其中 K3S_TOKEN 的值来自 /var/lib/rancher/k3s/server/node-token 文件。
1 | vagrant ssh ubuntu-02 |
最后可以将 master node 的 /etc/rancher/k3s/k3s.yaml 文件内容复制到 host 系统上来,这样就可以直接在 host 系统中用 kubectl 来操作集群。
1 | -> % k get nodes -o wide |
当前安装的 k3s 是 v1.0.0,k8s 是 v1.16.3
最后我将完整的配置已经上传到 github 上了。大家有需要可以直接执行如下命令即可:
1 | git clone https://github.com/wusuopu/kubernetes-vagrant-ubuntu |
1 | yarn init |
1 | yarn add express |
1 | yarn add --dev typescript |
这是最麻烦的一步。先安装 webpack:
1 | yarn add --dev webpack webpack-cli webpack-node-externals ts-loader |
添加 webpack.config.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
34
35
36
37
38
39const path = require('path');
const nodeExternals = require('webpack-node-externals');
const WebpackShellPlugin = require('webpack-shell-plugin');
const { NODE_ENV = 'production' } = process.env;
const rootPath = path.resolve(__dirname)
let plugins = []
if (NODE_ENV === 'development') {
plugins.push(new WebpackShellPlugin({
onBuildEnd: ['yarn run:dev']
}))
}
module.exports = {
entry: path.resolve(rootPath, './src/index.ts'),
mode: NODE_ENV,
target: 'node',
watch: NODE_ENV === 'development',
externals: [ nodeExternals() ],
output: {
path: path.resolve(rootPath, 'build', NODE_ENV === 'development' ? 'dev' : 'prod'),
filename: 'index.js'
},
resolve: {
extensions: ['.ts', '.js'],
},
plugins: plugins,
module: {
rules: [
{
test: /\.ts$/,
use: [
'ts-loader',
]
}
]
},
}
添加 tsconfig.json
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": true,
"outDir": "build"
},
"exclude": [
"node_modules",
"build",
"config",
"release"
]
}
在 package.json
中添加相关命令:1
2
3
4
5
6"scripts": {
"start": "node build/prod/index.js",
"start:dev": "NODE_ENV=development node node_modules/webpack/bin/webpack.js --config webpack.config.js",
"build": "NODE_ENV=production node node_modules/webpack/bin/webpack.js --config webpack.config.js",
"run:dev": "NODE_ENV=development nodemon build/dev/index.js",
}
这样在开发过程只需要执行 yarn run start:dev
启动开发服务器即可,在代码有改动之后会自己重新 build 并重启服务。
开发完成之后执行 yarn build
进行打包发布。
安装 jest:1
yarn add -D jest @types/jest ts-jest supertest
添加 jest.config.js
:1
2
3
4
5
6module.exports = {
testEnvironment: 'node',
transform: {
"^.+\\.ts$": "ts-jest"
},
};
相应的测试文件的文件名以 .test.ts
结尾。
然后在 package.json
中添加相关命令:1
2
3"scripts": {
"test": "jest"
}
最终项目目录结构如下:1
2
3
4
5
6
7
8
9
10.
├── jest.config.js
├── package.json
├── src
│ └── index.ts
├── tests
│ └── index.test.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
现在这台 MacBook Air 差不多用了4年,因此打算换台新的电脑。目前需要一台有独显的电脑,一来是跑一些程序,二来是方便打游戏。
另外由于 macOS 已经用习惯了,不想再换其他系统。因此才考虑的安装黑苹果。通过两周的调研,别人给的经验是最好选择已经有人安装成功过的电脑型号,免得有问题自己折腾。
于是乎就选择了性价比比较高的神舟。最后安装完成之后,系统还有些小问题,不过基本也能用。对于要求比较高的建议还是白苹果吧。
因为我打算安装3个系统,我的硬盘分区如下:1
2
3
4
5
6
7
8
9
10
11
12128G SSD:
ESP 600M 用于EFI引导
EXT4 127G 用于安装ubuntu
1TB机械硬盘:
ESP 200M 用于EFI引导
MSR 128M
NTFS 100G Windows C
NTFS 100G Windows D
SWAP 16G Linux交换分区
EXFAT32 320G 用于多个系统之间分享文件
APFS 440G 安装黑苹果
在安装过程中需要用苹果的硬盘工具进行分区,如果硬盘上有文件的话需要先进行备份。
安装过程我是参考的黑果小兵博客: https://blog.daliansky.net/Lenovo-Xiaoxin-Air-13-macOS-Mojave-installation-tutorial.html
下载镜像 ( https://blog.daliansky.net/tags/%E9%95%9C%E5%83%8F/ ) 然后使用镜像烧写工具将镜像烧写到8G的U盘。这里我用的是 Etcher 这个工具。
参考别人的说法,10.14 的系统没有 Nvidia 的显卡驱动,10.13 的系统才有。不过我还是下载的 10.14.4 的系统镜像。
下载 Z7M-KP7GZ 的相关驱动: https://github.com/kirainmoe/hasee-z7-kp7gz-macos
启动电脑进入 BIOS 禁用 Secure Boot。然后通过U盘启动进入WinPE,先按照上面的硬盘分区计划对硬盘进行分区。如果你是整块硬盘只用于安装 macOS,则可以忽略这上步。
然后重启换另一个U盘启动进入 macOS 的安装界面。进入 Clover 后按 o 键在 Boot 项后面添加 nv_disable=1 参数。如图:
然后回车开始引导 macOS。等几分钟之后就进入了安装程序,依次选择语言、同意用户协议、选择安装硬盘。
这里可以打开硬盘工具,然后进行分区。建议使用 APFS 格式的分区。注意,如果原来硬盘上有数据的记得先备份。如图:
安装过程中会重启,重启之后进入 Clover 再选择使用对应的硬盘分区,选择”Boot macOS install from Mac”启动项,不要选择Preboot的启动项。
如果出现Kernel Panic错误,重启再来试一遍吧。
安装过程中啥都干不了,只能等了。差不多等半个小时就安装完成。接下来进入设置向导,选择地区、添加几号等操作。这个过程中触摸板用不了,所以需要外接鼠标。
最后安装完成,进入系统。
系统安装完成之后,接下来就是安装EFI。因为现在EFI文件还是U盘里,否则就只能每次启动时都需要通过U盘来引导了。
在前面使用硬盘工具进行分区时,它会在当前硬盘的开始位置创建一个200M的ESP分区。我们只需要将U盘中的EFI复制到这个分区里即可。
打开终端执行命令:1
2
3
4
5
6# 查找分区情况
diskutil list
# 分别挂载本地硬盘的EFI分区各u盘的EFI分区
sudo diskutil mount disk1s1
sudo diskutil mount disk3s1
然后将u盘中EFI目录下的所有文件复制到硬盘中的EFI目录。如果之前已经有安装过其他的系统,那么只需要将u盘中的 EFI/CLOVER 复制到硬盘中的 EFI 目录下即可
现在系统安装好了,EFI也安装好了。应该就可以正常地进入系统了。不过目前还有这些问题:
然后我们再接着安装驱动文件。先挂载 ESP 分区,备份 EFI 目录,以免出错之后进不了系统时可以恢复。
删掉 /EFI/CLOVER 文件夹中的所有文件,然后将刚刚下载的所有驱动文件复制到 /EFI/CLOVER 目录下。
重启电脑然后执行里面的 optimize.sh 脚本,注意不要启动HiDPI。驱动安装好之后触摸板应该就能用了。
如果声卡不能使用的话,执行命令 kextstat -list-only | grep AppleALC 看看AppleALC.kext 驱动已正确加载。如果没有正确加载,请将其安装在 /Library/Extensions 下后执行命令 sudo kextcache -i / 重建缓存并重启。
接下来搞定无线网卡,如果不想折腾直接用有线了行。Z7M-KP7GZ用的是Intel的无线网卡,这个在 macOS 下是无解的。
其他折腾过的网友建议是换Broadcom的。如果要使用AirDrop或者Handoff,要求有蓝牙4.0LE,需要特定型号的博通网卡,比如博通BCM94322HMP(有带蓝牙和不带蓝牙版本的,注意挑选)。
于是我就在淘宝上买了块 BCM94350 WiFi、蓝牙二合一。然后拆机将网卡换上去。网卡在风扇的旁边。
结果一开机系统就崩溃了,进入不了登录界面。后来联系卖家,他们说这个只有 macOS 10.13 的驱动,没有 10.14 的。而我安装主正是 10.14 的系统。
欲哭无泪啊,折腾了两天,BCM94350网卡的驱动实在搞不定。最后只得再买了个支持黑苹果的USB无线网卡。
另外显卡驱动目前在 10.14 下也是无解的。如果要外接显示器的话,可能需要再购买带显卡的 type-C 设备。
参考 https://facebook.github.io/create-react-app/docs/adding-typescript
react-scripts从2.1.0版本开始就支持typescript了,这里我们先将项目的react-scripts依赖升级到最新版本:1
yarn add --exact react-scripts@2.1.8
然后再添加typescript依赖:1
yarn add typescript @types/node @types/react @types/react-dom @types/jest
然后将 src/index.js
更名为 src/index.tsx
接着创建 tsconfig.json
文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22{
"compilerOptions": {
"module": "esnext",
"target": "es5",
"lib": ["es5", "es6", "es7", "es2017", "dom"],
"sourceMap": true,
"allowJs": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"skipLibCheck": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src/**/*"
]
}
参考 https://storybook.js.org/docs/configurations/typescript-config/
同样也先将storybook升级到最新版:1
yarn add --dev --exact @storybook/addons@5.0.1 @storybook/react@5.0.1
然后再添加依赖:1
yarn add --dev awesome-typescript-loader @types/storybook__react @storybook/addon-info react-docgen-typescript-webpack-plugin ts-jest
接着修改webpack配置,对应.storybook/webpack.config.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
29const path = require("path");
const antdTheme = {
'@primary-color': '#846bc1',
}
module.exports = ({ config, mode }) => {
config.module.rules.push({
test: /\.less$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader",
options: {
modifyVars: antdTheme, // 如果要自定义主题样式
javascriptEnabled: true
}
}]
});
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve('awesome-typescript-loader'),
options: { configFileName: path.resolve(__dirname, './tsconfig.json') }
});
config.resolve.extensions.push('.ts', '.tsx');
return config;
};
以及babel配置,对应.storybook/babelrc.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
31module.exports = function(api) {
api.cache.forever();
return {
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}
],
"@babel/preset-react"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}
}
同时修改storybook配置.storybook/config.js
,让其支持.tsx文件:
将之前的:1
const req = require.context('../src/components', true, /\.stories\.js$/)
修改为:1
const req = require.context('../src/components', true, /\.stories\.[jt]sx?$/)
最后再创建 .storybook/tsconfig.json
文件:1
2
3
4
5
6
7
8{
"extends": "../tsconfig",
"compilerOptions": {
"jsx": "react",
"isolatedModules": false,
"noEmit": false
}
}
至此一切都完成了。
运行storybook时若出现错误: Error: Cannot find module '@emotion/core/package.json'
,则手动安装一下: yarn add --dev @emotion/core
1.先下载 android6 的树莓派镜像: https://pan.baidu.com/s/1YHrmjN3be7UaLAdBJr-YhQ
2.下载完成后解压,然后开始执行写入操作: sudo dd if=andrpi3-20160626.img of=/dev/disk3 bs=4096000
这里我用的读卡器,被识别为 disk3。文件比较大,请耐心等待。我写入差不多花了一个小时的时间。
将SD卡插回到树莓派中,并接上鼠标、键盘、显示器,然后开机。
这里稍微注意一下,树莓派3需要 5V/2A 的电源才能工作。
刚开始我试了一下开不了机,结果才发现是电源电压不足。
运行效果如图:
连接上网络之后使用 adb 进行操作: adb connect 192.168.0.101
这里我的树莓派的ip为 192.168.0.101
项目是使用 create-react-app
创建的,项目目录结构如下:1
2
3
4
5
6
7
8
9
10
11
12
13├── .storybook/
├── README.md
├── antd-theme.js
├── node_modules
├── package.json
├── public
└── src
├── App.js
├── components
│ ├── button.js
│ └── button.stories.js
├── index.js
└── registerServiceWorker.js
这里创建了一个 button
组件,内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// src/components/button.js
import React, { Component, Fragment } from 'react';
import { Button } from 'antd';
class ButtonGhost extends Component {
render() {
return (
<Fragment>
<h3 className="ex-title">Ghost Button</h3>
<div style={{ background: 'rgb(47, 45, 165)', padding: '26px 16px 16px' }}>
<Button type="primary">Primary</Button>
<Button className="ml20" ghost>Default</Button>
<Button className="ml20" type="dashed" ghost>Dashed</Button>
<Button className="ml20" type="danger" ghost>danger</Button>
</div>
</Fragment>
);
}
}
export default ButtonGhost;
对应的 storybook 案例如下:1
2
3
4
5
6
7// src/components/button.stories.js
import React from 'react';
import { storiesOf } from '@storybook/react';
import Button from './button';
storiesOf('General', module)
.add('Button', () => <Button />)
然后 storybook 配置如下:1
2
3
4
5
6
7
8
9
10
11
12// .storybook/config.js
import React from 'react'
import { configure } from '@storybook/react'
import { ThemeProvider } from 'styled-components'
const req = require.context('../src/components', true, /\.stories\.js$/)
function loadStories() {
req.keys().forEach((filename) => req(filename))
}
configure(loadStories, module)
然后运行 storybook : start-storybook -p 6006 -c .storybook
,效果如下:
这是由于 antd 的 css 没有加载,因此所有按钮的样式都没有。
参考 https://ant.design/docs/react/use-with-create-react-app-cn 的说明,修改 babel 和 webpack 的配置。
参考 https://storybook.js.org/configurations/custom-webpack-config 的说明,修改 storybook 的 webpack 配置。
创建文件 .storybook/.babelrc
: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{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}
],
"react",
"stage-3"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}
创建文件 .storybook/webpack.config.js
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader",
options: {
//modifyVars: antdTheme, // 如果要自定义主题样式
javascriptEnabled: true
}
}]
}
]
}
};
然后在 .storybook/config.js
文件添加 import 'antd/dist/antd.less';
。
最后的效果如下:
这几个都是C++的函数,在Python中可以通过ctypes来直接调用。
然后接下来介绍一下基本的操作流程。
1.通过 任务管理器 或者其他方式得到需要修改的游戏进程。然后通过 OpenProcess
注入该进程。1
2
3
4
5
6
7
8
9PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
hProcess = ctypes.windll.kernel32.OpenProcess(
PROCESS_QUERY_INFORMATION|PROCESS_VM_READ|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,
False, pid
)
2.然后可以通过 ReadProcessMemory
来扫描游戏的内存找到需要修改的数值项的内存地址。1
2
3
4
5
6
7
8
9buf = ctypes.c_int32()
nread = ctypes.c_size_t()
ret = ctypes.windll.kernel32.ReadProcessMemory(
hProcess,
base_addr,
ctypes.byref(buf),
ctypes.sizeof(buf),
ctypes.byref(nread)
)
这里是读取 base_addr
地址之后的4个字节的内容。可以通过循环来遍历游戏的内存,找到需要修改的地址。
当然,为了方便也可以直接使用 Cheat Engine 之类的软件来查找,然后把找到的内存地址记录下来即可。
3.得到需要修改的内存地址之后,就可以 WriteProcessMemory
来修改该地址保存的值。1
2
3
4
5
6
7
8
9buf = ctypes.c_int32(value)
nread = ctypes.c_size_t()
ret = ctypes.windll.kernel32.WriteProcessMemory(
hProcess,
base_addr,
ctypes.byref(buf),
ctypes.sizeof(buf),
ctypes.byref(nwrite)
)
这里是往 base_addr
这个地址写入值为 value
的4字节内容。
4.最后如果不再需要修改了的话,就通过 CloseHandle
关闭该注入操作。1
ctypes.windll.kernel32.CloseHandle(hProcess)
以上都是针对 Windows 系统的,对于 Linux 系统的话 可以通过 ptrace
(http://man7.org/linux/man-pages/man2/ptrace.2.html) 操作实现。由于我没有 Linux 的游戏就没有研究了。
上面修改器的完整源代码,如有需要可通过以下链接获取:
https://github.com/wusuopu/cheat_engine_caesar3
下面通过一个例子来介绍如何使用 pytest 对 flask 应用进行单元测试。
首先新建一个 flask 应用,并针对根路径创建一条路由。代码如下:
1 | # server.py |
然后针对首页编写单元测试,代码如下:
1 | # tests/test_app.py |
然后执行命令运行该测试用例: pytest -s tests/test_app.py
在 pytest 中编写测试用例就只需要新建一个以 test_
开头的函数即可。
以上是针对flask路由作的最基本测试。接下来编写一个新的路由,该页面只有用户登录之后才能访问。代码如下:
1 | # server.py |
要对该路由进行测试,则需要先创建一个用户。1
2
3
4
5
6
7
8
9# tests/test_app.py
def setup_module(module):
App.testing = True
fixture.setup()
def teardown_module(module):
"""
"""
上面的 setup_module
和 teardown_module
函数分别是在所有的测试用例执行之前与执行之后执行。在这里我们通过 setup_module
在执行测试之前先创建一个用户。然后再创建一个 pytest 的 fixture:1
2
3
4
5
6
7
8# tests/conftest.py
def auth_client(client):
with client.session_transaction() as sess:
sess['user_id'] = str(fixture.users[0].id)
yield client
这里创建了一个 auth_client
fixture,之后所有以 auth_client
发起的请求都是登录状态的。
最后再针对 /member
路由编写两个测试用例,分别是未登录状态与登录状态下的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def test_member_page_without_login(client):
"""
没有登录则跳转到登录页面
"""
rv = client.get('/member')
assert rv.headers['Location'] == 'http://localhost/login?next=%2Fmember'
assert rv.status_code == 302
def test_member_page_with_login(auth_client):
"""
已经登录则返回当前用户id
"""
rv = auth_client.get('/member')
assert rv.status_code == 200
assert rv.data.decode('utf8') == str(fixture.users[0].id)
以上就是一个简单的 flask 应用了。但是有时一个稍微复杂一点的应用会用到一些第三方的api。这时针对这种情况编写测试用例时就需要用到 mock 功能了。再编写一个新的路由页面:1
2
3
4
5
6
7
8# server.py
def movies():
data = utils.fetch_movies()
if not data:
return '', 500
return flask.jsonify(data)
1 | # utils.py |
请求该路由会返回豆瓣top250的电影信息。然后再编写两个测试用例分别模拟api调用成功与失败的情况。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# tests/test_app.py
def test_movies_api(client):
"""
调用豆瓣api成功的情况
"""
fetch_movies_patch = mock.patch('utils.fetch_movies')
func = fetch_movies_patch.start()
func.return_value = {'start': 0, 'count': 0, 'subjects': []}
rv = client.get('/movies')
assert rv.status_code == 200
assert func.called
fetch_movies_patch.stop()
def test_movies_api_with_error(client):
"""
调用豆瓣api出错的情况
"""
fetch_movies_patch = mock.patch('utils.fetch_movies')
func = fetch_movies_patch.start()
func.return_value = None
rv = client.get('/movies')
assert rv.status_code == 500
assert func.called
fetch_movies_patch.stop()
这里使用 python 的 mock 模块来模拟让某个函数返回固定的结果。
]]>1 | upstream app_server { |
这样在 uwsgi 的应用程序中只需要读取 http headers 中的 X-Forwarded-For 字段即可。
但是最近由于运维架构的是采用 haproxy + nginx + uwsgi 是形式,导致了在 uwsgi 应用程序中获取到的 ip 都是 haproxy 的。
为了要获取到真实的ip地址,需要由 haproxy 将 ip 传给 nginx,再由 nginx 传给 uwsgi。
在网上搜索了半天 haproxy 的相关配置,感觉太复杂了。因此还是决定从 nginx 入手。
经过实验将 nginx 的配置改为如下即可:
1 | proxy_set_header X-Forwarded-For $http_x_forwarded_for; |
docker run -ti --rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix chrome
如果是mac OS系统的话,相对麻烦一些。
1.安装所需的软件:
1 | brew install socat |
2.依次运行刚刚安装的两个程序:
1 | socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\" |
3.设置X11, XQuartz
-> Preference
-> Security
-> Allow connections from network clients
4.docker run --rm -e DISPLAY=
ifconfig | grep “inet\ “ | tail -1 | cut -d “ “ -f 2:0 chrome
对于已经启动了的container,可以在container内执行命令 export DISPLAY=<ip>:0
来设置 DISPLAY 从而使用本机的 X11 服务。
倘若当前你的 mac OS 没有连接网络,那么可能就没有ip地址供container内访问。
此时也许可以执行命令: sudo ifconfig lo0 alias 10.200.10.1/24
来手动设置一个ip。
然后在container内再设置 export DISPLAY=10.200.10.1:0
。
决定要进行迁移主要是两个原因:
1.使用 octpress 来生成页面感觉越来越慢了;
2.octpress 的页面样式表不知怎么的突然就坏掉了,整个页面显示都不正常了。这个是最主要的原因。
迁移的工作还算是比较顺利,只是体验了一下 hexo 感觉 bug 也不少。只得自己写些 patch,然后将就着用吧。
]]>1.首先需要一根 USB 转串口的线,如果没有的话可以去某宝上买一根吧,反正也不贵。我选的是 PL2303。
再根据系统以及芯片的不同而下载安装不同的驱动程序。 对于 mac OS 用户执行命令: ls /dev/ | grep tty.usb
,
如果驱动都安装正确的话应该是会有输出结果的。
2.然后在 pi 的系统上启用 serial。我安装的是 debian 系统,执行命令: sudo raspi-config
,
选择 advanced options
-> serial
进行启用 serial。
3.串口连接
对于 Raspberry Pi3 的 GPIO 引脚如下:
串口线与 pi 的连接方式为: GND -> GND, RXD -> TXD, TXD -> RXD, 如图:
最后在电脑上使用串口连接软件进行连接,对应的串口设置为 115200 8N1,如图:
首先注册一个账号并创建一个 CodePush 的应用:
1 | npm install -g code-push-cli |
按照说明 https://github.com/Microsoft/react-native-code-push#getting-started,使用 rnpm 进行安装即可:
1 | npm install --save react-native-code-push |
安装完成之后还需要再进行一些配置,对于 iOS 需要将 AppDelegate.m
文件中的 jsCodeLocation
修改为: jsCodeLocation = [CodePush bundleURL];
。
同时再在 Info.plist
文件中添加一项 CodePushDeploymentKey
,其值为 CodePush 应用的 Deployment Key。
对于 android 需要在 MainActivity
类的 getPackages
方法中设置 Deployment Key。同时根据 ReactNative 的版本不同而使用不同的方法来设置 getJSBundleFile
,
参考: https://github.com/Microsoft/react-native-code-push#android-setup
。
在安装、配置完成之后,即可以使用CodePush进行程序的更新操作了。
根据官方的说明只需要调用 CodePush.sync()
即可完成自动更新操作。
我针对自己的情况再进行封装了一下:
1 | function syncCodePush() { |
以上函数是保证只有在wifi的网络环境下才进行更新操作,同时由于 CodePush.sync()
返回的是一个 Promise
对象,
在这里我就遇到了由于网络异常而下载出错,从而导致 app 崩溃。因此需要处理 reject
的情况。
有时在程序更新之后的首次运行时可能会需要作一些迁移的操作,这里可以使用 getUpdateMetadata
来检查程序是不是首次运行。
1 | Codepush.getUpdateMetadata().then( |
在 app 发布安装包发布出去之后,已经有用户下载安装了。此时如果再有 js 代码更新或者图片文件的改动的话,可以使用 CodePush 进行发布。
进入 ReactNative 的项目根目录,使用 code-push 命令进行发布更新。例如:
1 | code-push release-react DemoApp ios -m -d Staging --des "更新描述" -t "~2.0.0" |
以上命令是发布一个紧急更新到 Staging ,只有 ios appp 的版本为 2.0~3.0 的才会下载该更新包。
由于是紧急更新,app在下载安装完成之后会自动重启应用该更新包。否则的话就需要用户下次手动启动app时该更新包才会生效。
在 CodePush 中针对 ios 和 android 可以共用一个应用,只是我个人感觉这样在管理 deployment history 时不太方便。
因此我通常会创建两个应用,例如: DemoApp-iOS、DemoApp-Android 这样的。
需要注意的是,由于 CodePush 的 server 是在国外,因此下载的速度会比较慢。
最后我自己使用 Electron + Vue.js 开发了一个 CodePush 的简易管理工具,https://github.com/wusuopu/code-push-gui 。
可以对 CodePush 的 app 跟 deployment 进行简单的管理。