organicCola's House

Organic cola is good for your happiness

0%

Pre

读书做笔记,通常有以下办法
  1. 直接在支持标注的pdf文件里插入标注,这种标注会跟嵌入在pdf文件内,用任何pdf阅读软件打开都能看到。
  2. 另外新建一个文件记录笔记。
  3. 使用一些读书笔记软件。

    经典的有类似文献管理软件Zotero,较新的有像Obsidian这样的专注笔记的软件等。他们也都可以安装插件,实现各种联动和笔记的管理:像是平台间同步,生成跳转到pdf文件笔记处的url链接等。

古早使用Zotero来做笔记的困惑和思索 Zotero Using: Annotation organization think

zotero自带的add notes from annotation可以实现导出笔记功能,除了page外甚至还会带上annotaion的具体位置。
另外最近摸索了下pdf中annotaion管理的问题:

  1. 如果在zotero里标注,标注只能在zotero里查看,是没有写入到pdf文件里的,而是储存在zotero自己的数据库里;
  2. 如果是系统的预览app或一些其他pdf阅读器标注是会写入到pdf文件里的,任何阅读器都能查看到;
  3. 将标注嵌在pdf文档中的缺点是其文件大小肉眼可见地变大了,一两个标注就能增加上100kb+,但其实有效信息也就几十个字而已。
  4. 利用zotero来写标注是实现了pdf文件和标注分离,但是信息又嵌在了zotero中,你需要维护zotero的整个数据库保证标注安全
  5. 如果要将标注剥离出来成为单独的文件那么就可以用生成md/txt文件的方法,
  6. 剥离md文件是很好的方案,但是也带来了一些问题:md文件中的标注可以通过点击里面的链接来跳转到zotero,但是反过来很难实现在zotero里阅读的同时让md也实时地滚动到对应处的笔记
在说我的痛点之前,先总结一下:
  1. 首先将标注直接写在pdf文件中似乎是个不错的选择,兼容性好,对于各种支持pdf标准标注的软件来说都可以看到。但是pdf二进制文件的性质使得其不方便进行变更的同步,一处改动则需要同步更新这个文件才行。我需要一种可以解耦笔记与原pdf文件本身的方案
  2. 另外新建一个笔记的方法则进行了彻底地pdf文件与笔记的解耦,读书笔记与pdf本身没有直接的联系,我只需要管理笔记文件本身的更新。这是正确的方向。但是解耦的代价是笔记本身失去了与pdf本身的关联,笔记的内容与pdf文件本身的关联全靠文字提示与大脑记忆。于是诞生了需要在笔记中插入pdf内容标记索引的需求,形式一般是一定格式的文字,例如第几行第几页;也可以是一个url。目的是可以快速跳转到对应的pdf位置,帮助回忆。可以说这种思路没有问题。
  3. 事实上,三方软件及其插件基本上都是第二点末尾提到的思路。比如Zotero将标注的数据存在了自己的数据库里,可以在自己软件内进行平台间的同步,但是这时的笔记还只是标注,与pdf密不可分,距离笔记的形态还是有距离,所以一般还是会通过导出笔记到专门的笔记软件如Obsidian中进行管理。Obsidian内也可以通过插件将pdf读书集成到应用中,实现一边读书一边做笔记并生成笔记链接url。
下面说说我的痛点:

总体上我准备采用第三种方案,在单独的文件中做笔记,并通过软件将pdf标注生成url方便索引。

  1. 但是看了下现有的一些方案,基本上这种生成的索引都是单向的,即可以从url跳转到pdf文件处,但是无法在pdf读到那个地方时候跳到笔记文件对应处。
  2. 如果在pdf文件中同步直接标注,则可以直接在阅读pdf文件时候同步看到当前对应的高亮和标注。但是这又引入我上面说到的pdf直接标注的缺点了,你需要同时管理其中的pdf二进制文件,工作量又增加了。
  3. 也有些Obsidian三方可能会采取这样一种策略,将高亮标注信息保存在插件内部数据库:当你在通过这个插件阅读pdf时,标注一条数据会在生成的url索引中,插入‘插件内部数据库关于这条高亮的信息’,这样就既不用维护pdf文件,又能在pdf中显示高亮数据了。(Zotero进行标注时采用的就是这种方法,将数据全部存于Zotero本地的数据库内。)但是要注意,不用维护pdf文件是好处,带来的问题是你相当于需要维护这个插件的数据库,数据库丢失了或者数据出问题了,标注信息也就丢失了。当然维护数据自然是记笔记需要付出的代价,但是相比与一个human-unreadable数据库,我更愿意维护一个human-readable单个笔记文件。可读是很重要的,即使在最恶劣的情况下,系统坏掉,但是由于肉眼可读,只要可以打开这个笔记文本文件本身,那你的数据就可以恢复。当然对于大型系统来说数据库系统无疑是高效于文件系统的,只是在我的笔记系统里,这是是不适合的而已。

Solution:

那么首先归纳我的需求
  1. 笔记文件为中心,笔记存于pdf文件之外,一切标注数据存于单个文本笔记文件中;
  2. 在阅读的时候可以自动同步展示笔记文件中对应笔记。当然在笔记文件中也可以同步展示pdf中对应的引用处。
  3. (附加)从笔记文件中获取数据,并在在pdf中展示高亮。
解决方案

几经查找发现了一个比较合适的方案。
EMacs下的pdf-tools + org-noter, 满足了需求12中annotation剥离和同步阅读的问题。
所有我需要的信息保存在一个单独的org文件中,阅读PDF时,笔记文件中的标注会自动同步滚动到对应处。

缺点 Cons:
  1. 是标注不能在pdf文件中高亮显示。
  2. Emacs是桌面全平台,但是没有iOS端
缺点改进:
  1. 缺点1的问题是可以解决的,为之增加了根据笔记文件中的高亮信息一键在pdf中嵌入高亮和标注的功能。
    org-noter-embed-all-org-note-to-pdf
    这个方法可以将一个没有任何标注的pdf,嵌入笔记文件中的高亮。当然你无需维护这个pdf,因为所有信息都在笔记文件中,你随时可以生成标注。我一般做法是阅读时生成标注信息,而不把标注真正的保存进pdf中。
  2. 缺点2的问题目前只能workaround
    虽然不能完全解决,但是因为在移动平台上使用得不多,可以在桌面端将标注嵌入pdf文件,因为pdf文件的标注的兼容性,在iOS上自然是可以查看的。不过,想要进行标注的修改更新的话还是要到笔记文件中更新。
    其实想要实现更新在pdf文件中更新来同步更新标注,可以像缺点1那样,修改org-noter从pdf导出标注的方法,将其导出数据格式适配为包含高亮region数据的格式。这样更新了pdf数据后,将其导出到笔记文件,实现同步。这样就实现双向无缝同步了。
    但是习惯上因为很少在移动端阅读,所以这个需求不是很迫切,适配工作会延后。

Demo

org-noter_demo
demo content:

  1. sync pdf display to current heading note
  2. sync pdf display and notes between heading notes
  3. sync notes display to current pdf view page automatically
  4. embed notes from notes file to pdf file (personal impelmentation)
graph TD;
A(pdf) --- O
B(pdf) --- O
C(pdf) --- O
subgraph E[org-noter in Emacs]
O(org-file) 
end
O --- |sync| G[(Git repository)]
Oa --- |sync| G[(Git repository)]
subgraph Ea[org-noter in Emacs]
Oa(org-file) 
end
Oa --- H(pdf)
Oa --- I(pdf)
Oa --- J(pdf)
(Optional) 从pdf导出标注

org-noter也支持从pdf中导出其中outline/链接/标注信息到笔记文件的功能。
不过导出的高亮标注的文本格式我还没有与我新添加的org-noter-embed-all-org-note-to-pdf功能做适配器,导出的文本不能使用我添加的embed功能。
其实以现在既然以笔记文件为中心,所有信息都应该是在笔记中的,从pdf中导出标注的功能可以说一般是不需要的,只有两种场景会用到:

  1. 从旧pdf进行迁移,导入数据
  2. 从pdf中导出outline
  3. 在移动平台上直接对pdf进行了更新,于是从pdf中导出数据到笔记文件,进行数据更新。
    鉴于目前这方面需求不是很迫切,适配工作会延后。
(Optional) org-pdftools

这是另一个用于补充功能的Package,可以生成一个url来进行跳转。可能大家更为熟悉这种方式,不过对我来说比较鸡肋,org-noter本身已具有同步显示功能

关于pdf-tools + org-noter 的具体使用,可以看另一篇
Using-pdf-tools-org-noter-in-Emacs

Pre

想要在blog中提供一个文件下载连接,通常做法是将文件储存在一个三方平台,然后在blog中插入这个连接
但是如果文件本身不大,有时候我们也是希望将文件本身存在hexo的文件库里然后来引用的。

Solution

其实可以参考引用本地图片的方法

在source文件夹下存在一个imgs文件夹,里面又一个pic.png 我想引用它就这么写
![pic](../imgs/pic.png)

于是我在source下再新建一个files文件夹,并在里面新建一个test.txt
这样引用
[test](../files/test.txt)
click to preview text file
注意: 如果文件可读的话会直接在网页中进入预览,如果是不是文字类或者可预览的文件,比如没有后缀才会弹出下载
click to download non-text file

注意,有时候会提示找不到资源,可以在路径最前面加上”/“
[test](/../files/test.txt)
click to preview text file
click to download non-text file

Pre

Charles 除了经典的通过http执行代理debug之外还可以使用sock,两种方式的区别可以看Charles官方的说明,而实际使用中明显的区别就是有些http下遇到的无法抓包情况socks下可以正常执行。

Solution

1. 使用iOS自带的代理

使用iOS wifi 自带的代理设置一般只能设置http类型
如果想要socks类型可以使用自动代理,可以利用自动代理模式

1. 1. 自建Pac文件
1
2
charles_socks.pac
function FindProxyForURL(url, host) { return 'SOCKS 192.168.199.105:8881; SOCKS5 192.168.199.105:8881'; }
1. 2. 本地启动一个服务器来提供pac文件
1
2
cd pathToPacFile
python -m SimpleHTTPServer
1. 3. 在wifi代理里面选半自动代理,填入pac文件地址

http://127.0.0.1:8000/pathToPacFile
但是经测试如果这个socks使用时仍有问题,他跟http一样会在某些时候卡住无法继续,也许是iOS自带的socks支持程度与charles的不同。

2. 使用Socks Proxy Client

目前发现的能提供正常可用的sock5连接iOS客户端有以下几个,当然这些国区AppStore都没有。

1
2
3
ShadowRocket(paid)
Surge(paid)
Brook(a free & open-source one)

Pre

一个魔性三消微信小程序游戏。由于游戏第二关是真随机,导致很难通关。既然制作者不讲武德也别怪玩家不客气。

Solution

通过抓包可以看到为数不多的请求里有个很扎眼的map_id,是关卡地图,于是可以从rewrite request入手,把所有关卡的地图全变成第一关最简单的地图。

Several solution

Solution 1 Desktop + iOS device
Desktop DebuggingProxyTool(Charles/Fidder…)
iOS device ProxyTool supporting Socks(Brook/Surge/ShadowRocket/Quantumult…)

or

Solution 2 iOS Traffic Sniffer App with rewrite function
iOS device Http Cacher/Storm Sniffer/Surge/Thor…

安卓没有试,但理论上方法一样。安装证书Charles证书之后,通过wifi连接Charles,有可能会遇到SSL Pinning导致https无法抓取,解决办法是Android上安装Xposed+JustTrustMe来绕过SSL Pinning

Cost

Mac Proxy App Price
Charles 不注册也可以免费试用, 有免费注册方法,sniff,rewirte全功能可用

proxy app 国区没有幸存者

iOS Proxy App Price
Brook freeCharge(国区没有)
Surge freeDownload,抓包免费 proxy rewrite内购,(国区没有)
ShadowRocket paid(国区没有)
Quantumult paid(国区没有)
iOS on-deivce Sniffer App Price
HttpCacher freeDownload,http抓包免费,https抓包内购,rewrite内购
StormSniffer freeDownload,http抓包免费,https抓包内购,rewrite 需要内购
Surge freeDownload,抓包免费,proxy rewrite内购(国区没有)
Thor paid,(似乎现在appStore搜不到)

推荐 Charles + Brook 免费组合
我用的Charles + ShadowRocket 因为我刚好有

Step

  1. 使用Charles在桌面端开启代理注意要使用SocksProxy (菜单栏 -> Proxy -> ProxySetting)
    Charles_proxy

  2. 按照Charles的提示安装CA证书在你的手机上。(菜单栏 Help -> SSL Proxying -> Install Charles Root Certification on a Mobile Device or Remote Browser)

  3. iOS使用ShadowRocket添加Charles的代理,注意类型填socks类型,这里ip如果不知道填什么可以在Charles里查看(菜单栏 -> Help -> Local IP Address)。代理要设置为全局代理。
    ShadowRocket_add_SocksProxy

  4. 打开Charles的rewrite功能,url’s parameter, key map_id 的 value 不用填,自动全部替换统一改为 80001 (菜单栏 -> Tools -> rewrite)
    Charles_rewrite_setting

  5. (Optional)接下来按顺序操作,验证是否操作成功,不过此时如果顺利直接进游戏也可以。

    1. 删除微信里羊了个羊
    2. 打开Charles的Recoring (菜单栏 -> Proxy -> Start Recoring)
    3. Charles中点击Clear Session(菜单栏 -> File -> Clear Session)
    4. 再进入羊了个羊,一直进入到第一关
    5. 检查左侧是否出现了红框里的数据,如果所有的id都是80001,恭喜你,成功了。
      如果不行可能是检查步骤出错,回到第一步重新操作;如果多次不行则前面设置代理有问题,返回检查。
      Charles_verify_result
  6. 进游戏过了第一关之后来到第二关,但是第二关也变成了第一关的模样,15秒速通。

Pre

现在可能还有人写日记。。。或者不算日记,但是是一些私密的东西,不想让人看的东西。

Issue

不想让人看很简单,你只写在本地就行了。
但是一般你不会只在一个设备上记录,有可能还会在别的设备上记录或查看,于是涉及到了同步。
隐私内容的同步是个问题,不过无非几种方式:

  • 云上中心化的存储方式:各大云盘,各大app,搭建服务器存储。
    缺点是传到网上内容就由不得你了,尽管所有笔记都声称自己加密了用户数据,也保证不会偷窥用户隐私,但是这种保障等于空气。至于搭建的服务器,机器是服务商提供的,仍然没有办法保证服务商不偷窥我们的内容。即使没有偷窥问题,如果服务器被攻击了隐私就会暴露。
  • 本地文件同步方案。由于没有上云,隐私安全性有所提高。

    p2p: 通过Syncthing一类的同步软件,进行同步。不过本地p2p需要一个常驻的在线设备。不过p2p的粗暴有可能会遇到多设备同时修改造成冲突的问题。
    git: 本地自建git server,可以很好地解决多设备修改冲突的问题。不过也同样需要git server常驻在线。
    如果不采用随时地可以同步的方法,就可以不需要常驻在线设备,只在需要同步的时候进行同步。不过这样需要不能随时随地备份最新的数据,总是感觉有点不放心。

Solution

其实对比两种方案,本质的区别也就是同步范围的区别。一个是通过更多人能接触的中心服务器,一个是限制范围只在本地小范围内同步。如果可以结合云上同步的便捷与本地同步的隐私就好了。

压缩加密文件

其实早就有了一种简单朴素的保护文件隐私的办法,那就是文件加密,相信其实有不少人都是这么做的。
我想这个方法虽然简单,但是其实经过上面的分析后却会发现这其实是一个很好的,简单又实用的方法。
于是文件的隐私通过对文件加密折中实现。
一种经典方法便是压缩文件并加上密码保护, zip/rar等。
于是,将文件加密并上传至云,并在各个设备间同步,似乎是一个不错的选择。

使用非对称加密

zip等压缩加密办法使用的是对称加密,通常面临暴力破解的问题,当然你也可以通过增加密码的复杂度来提升安全性。除了对称加密外还有非对称加密可以选择,ecc/rsa都可以。目前来看理论上只要你的密钥不外泄,加密文件是十分安全的。

Tools

推荐一个工具 GnuPG
其实加解密只是GPG的一个用途,其主要用途是用于用于身份认证,并提供了密钥管理。

An introduction to GnuPG

使用流程

将文件压缩,并对其进行加密,上传至云。同步至设备时再反向操作解密解压文件。
不过有个问题要注意,就是因为加密的缘故,git对它也不适用了,因为会被当作二进制文件。针对这个问题可以在文件夹内部建一个内部git用于控制,压缩加密时连同.git文件夹一起打包加密,这样就既可以使用git控制版本,又不影响加密了。
如此就可以完美和之前的笔记方法融合在一起。

一种朴素的几乎纯文本笔记和GTD方案 - A basic enough solution to organize GTD and dailyNotes

Diagram

flowchart LR;

subgraph A[Client A]
  F((Privacy File with Built-in Git)) --> |Zip| Z(zipped file);
  subgraph GPG[GnuPG]
    Z --> E[[Encrypted File]]
    P(Public key) --> |encrypt with Public Key| E
  end
end

subgraph B[Client B]
  subgraph GPGa[GnuPG]
    Ea[[Encrypted File]] --> Za
    S(Secret key) --> |decrypt with Secret Key| Za;
  end
  Za(zipped file) --> |Unzip| Fa((Privacy File with Built-in Git))
end

A <-.-> |Sync| G;
G[(Git repository)];
G <-.-> |Sync| B;

G(nu)pg

PGP vs GPG
PGP 是最早先的加密商业软件
一般现在提起PGP是指PGP协议
G(nu)pg is for Gnu Privacy Guard
GPG是GNU下的PGP实现

GPG采用主私钥 + 若干子密钥设计

平时一般使用子密钥来进行具体操作。主密钥只用来进行签发新的子密钥,或对密钥进行修改。可以认为主密钥是你在网上的主要身份凭证,如果因此要保护好,一般因为泄漏或者丢失而不得不对其进行revoke将会使你失去身份。所以在所有设置都正常的情况下,日常使用不需要用到Primary key,应该将其保存在一个安全的地方,隔离与电脑的设备,甚至是纸张。

一些常见缩写含义

密钥能力
Abb Ability
[C] Certifitating 认证,用于签发子密钥
[S] Signing 签名
[E] Encrypting 加密
[A] Authenticating 身份验证
密钥缩写含义
Abb Explain
pub Public Key
sec Secret Key
sub Public SubKey
ssb Secret Subkey

一些命令

生成主密钥

gpg --gen-key

本地密钥保护密码

生成密钥时会同时让你设定一个用于保护本地密钥的密码,每个主密钥的保护密码是相互独立的,在不同设备上密码是独立,也就是说这个密钥是gpg的本地设置,与钥匙串无关,所以在你修改了密码手动save的话,系统会告诉你钥匙串没有更新,没有save的必要。

1
2
gpg --edit-key userId
> passwd
list keys
1
2
3
4
gpg --list-keys (简写 -k)
gpg --list-serect-keys (简写 -K)
gpg -k --keyid-format long (显示sub keyid)
gpg -k --fingerprint (指纹)
edit key 撤销密钥/修改密钥有效期/删除密钥
1
2
3
4
5
6
7
gpg --edit-key --keyid-format long uid (进入edit交互)
> key 1 选择第(1)个subkey(不选择key会默认对整个primary key所包含的所有key进行操作)
> key 2 选择第(2)个subkey
> expire 将会对key1 / key2 (进行过期日期设置)
> revkey 吊销key1 / key2 (已经revoke的key仍然可以加解密,他只是一个标记,告诉你使用这个的人已经不会再继续使用它)
> delkey (删除key1 / key2)
> save (不保存修改无法生效)
uid编辑

修改uid,uid只作为你的信息,对加密没有影响。uid只能添加,和删除,不能修改

1
2
3
4
gpg --edit-key
> adduid 新增按提示操作
> uid 1 (与key类似,uid 1 表示选中了uid 1
> deluid 删除uid
设置信任
1
2
3
4
gpg --edit-key uid
> trust
> 0…5(选择信任度)
> save(其实不需要保存,信任是本地gpg的设置,并没有改变此uid的任何属性)
导出密钥
1
2
gpg -ao public-key.txt --export <UserId>
gpg -ao encrypt-subkey --export-secret-subkeys <keyId>

注意,在keyId后面加上!可以强指定此id而不会全部导出子密钥。

导入密钥
1
2
3
# you can cp the file from the source to local using scp, then import key from the file.
scp @192.168.199.105:/Users/Alex/encrypt-subkey .
gpg --import encrypt-subkey
删除密钥 按userID删除key(不进入交互)
1
2
gpg --delete-secret-keys <UserId>  # 删除私钥,UID 也可以替换成子密钥ID, 主密钥Key ID
gpg --delete-keys <UserId> # 删除公钥
加解密

对称加解密,不带密码缓存,每次都输入的方法
gpg -c --no-symkey-cache test.txt.gpg
非对称加密
-r 参数 指定接受者id 会使用这个id下的一个公钥来加密,需要对方有这个公钥对应的密钥才能解密。
gpg -e -r uesrId test.txt

修改私钥读取提示需要密码的时间(秒)
1
2
3
~/.gnupg/gpg-agent.conf
default-cache-ttl 600
max-cache-ttl 7200

一些解释

revoke和delte的区别

revoke相当于打上吊销标记,delete才是删除。
revoke还有个作用就是通过send-key到key-server,让keyserver同步状态来声明revoke

Expire

已经过期的key仍然可以解密。但是不能进行新的加密,会给你过期的提示。

1
2
> gpg: CF12434DC!: skipped: Unusable public key
> gpg: test.txt: encryption failed: Unusable public key

但是我们随时可以设置密钥的有效期,所以即使过期了想继续用的话重新设置有效期即可。其实过期日期主要还是声名给通讯的对方来知晓的,如果只是单机使用直接设个永不过期也无所谓。

对于revoke 解释

由于pgp是针对传输的加密,对于密钥丢失/泄漏的情况,一般流程是,用户revoke密钥后作出声名,上传revoke过后的key到key server,大家更新之后之后就会知道此key已经revoke,接下来不会再继续使用。但是你不能控制用户是否去更新你的key设置。如果用户没有去更新,然后删掉这个公钥,那么他手上的公钥依然可以解密已revoke的key之前加密过的文件。

关于密钥指定

在GPG中密钥一般是由系统自动选择的,比如根据你填写的UserId或者当前要进行的动作(Sign/Ecrypt)来自动选择名下最新的一个公钥来进行加密。不过也可以在keyId后加上!来强制指定,当你有多个key时这个做法很有效。
gpg -e -r CF12434DC! test.txt

Appendix

一些想法

其实非对称加密比较适用于通信加密,只要文件在远离密钥的地方,目前来说几乎是不可解密的;但是假如攻击者获取了存有密钥的设备,对于本地文件的加密,就只剩下本地对于密钥的对称加密保护这一道防线了。这时候就需要一个强密码保护,但是强密码保护很难用人脑记住,肯定存放于某个保存密钥的app甚至是文件内,而这个app的保护密码设定又是一个难题,肯定是人脑能记住的,相对容易输入的,相对不太复杂,这种密码被攻破的概率也很大。
如果能随时更改这个密码的话就好了,于是想到一个方案:在一个云端存放子密钥,每端在想要加解密gpg文件前下载gpg子密钥,使用完之后删除。这样,如果设备遗失了第一时间删除网络上的子密钥,设备上的内容就无法读取了。前提是攻击者不会提前知道这个网址,不过提前知道的概率不大,因为只有进入了设备才能知道,网址的存在,但是一旦设备遗失我们已经将网址内的密钥清除了。
其实除了算法加密,通过改变加密流程,加密思路,乃至社工的办法,都是算是一种加密,只要能保护文件内容的方法就是好方法。

现在人不写日记了(正经人谁写日记,你写吗?)
但是好记性不如烂笔头, 工作生活免不了要写写笔记备忘录之类的。

Pre

现状 Situation:

很长一段时间我的笔记都处在一种自由生长的状态,电脑和手机都会用到,使用不同软件不同方法,各成体系,之间几乎不能同步。

  • 电脑端:一组txt文本记录Notes,处在git控制之下,使用vim编辑;todo list也都以PlainText形式记录在这些文本中,做完手动更改状态。
  • 手机端:使用iOS自带的备忘录,做todo list,几乎不会写笔记,有也是塞进todo list里面。
存在问题 Issue:
  • 一个早就有的痛点:不同平台无法同步,同步只能通过Apple的备忘录手动复制粘贴。
  • 有数据丢失风险,有时候网络不佳,刚写完的备忘录会在iCloud上同步失败,一条todo直接消失不见。
  • 手机上todo和电脑端的数据格式不同,这是也无法同步的原因之一,另一个是缺少同步手段。

Solution

分析 Analysis:

  • 不能同步的原因是不同平台间的内容储存和管理方式不同
  • 手机端用的是iOS自带的软件,数据被限制在app中,iCloud平台也不够通用,而且限制在Apple生态中
  • 电脑端用的是文本文件。
  • 数据稳定性受网络影响,并且手机端同步的状态只能通过肉眼查看内容来确认状态,没有一个显式的状态。

大纲 Requirments:

整理一下问题,现在可以大体罗列一下需求。

  • 需要最大程度解决兼容性问题,使用文本文件为中心 txt/md
    • 不能寻求一个All in one的app,以防锁死在其中,失去支持,不能导出数据,或者导出的数据很难处理与其他应用不兼容。
    • 不应该以任何App为中心来进行构建,因为你无法保证每个app之间能良好通信,共享数据。
    • 应该以一种最为基础的内容为中心。
    • 需要一种最基础简单的数据形式: 文本,最简单的即是TXT文本文件,相信它的兼容性是无可比拟的。笔记的本质是信息,而文字是一种高效率信息记录方式,小小的1M都蕴含着海量信息;图表为辅。
    • txt文件的兼容性可以保证最大程度上在各种设备、应用中储存,打开,阅读。
  • 需要一种最安全的储存/同步方式:git/syncthing(p2p)
    • 需要可以在本地编辑。
    • 需要可以云端储存
    • 最好可以进行版本控制
  • 对文件进行编辑,基于文本文件使用各种工具增强体验 各种工具和应用
    • 使用趁手的编辑器/应用
    • 最好是可以支持md
    • 最好是可以用Vim
    • 最好是有一种基于简单格式文本的但有方便互动操作的GTD方案

RelationShip Diagram

flowchart TB;

subgraph Mac[Mac local]
  F([Txt/Md/Org Files on Mac]) --- T;
  subgraph R[Review/Edit]
    V(Vim on Alacritty)
    E(Emacs: todo-list)
  end
  R --- T; 
  T(Alacritty)
  G((Git in Alacritty))
  T --- G;
end
G <-.->|Sync| Gc;
Gc <-.->|Sync| Gi;
Gc[(Git remote repository)]

subgraph iOS[iOS local]
  A([Txt/Md/Org Files in iOS Files]) --- Fi;
  Fi(iOS Files app) --- I;
  I(iSH)
  subgraph Ri[Review/Edit]
    Vi(iVim on iOS) 
    B(beOrg: todo-list)
    O(Obsidian on iOS)
  end
  Ri --- Fi;
  I --- Gi;
  Gi((Git in iSH))
end

工具 Tools

这些工具都是免费的或免费版也已经够用的,大多数是开源的

  • 电脑端:

    • 环境:Cli Alacritty
    • 同步:git
    • 编辑:Vim/Emacs(GTD/公式/制图/文学编程)
  • 移动端(iOS)

    • 同步:git(in iSH)
      iOS端因为封闭的文件系统要在app间共享文件和使用git比较麻烦,iOS上的git app最知名的有Working Copy,不过想要连接Working Copy以外其他App的文件夹和全部git功能都需要Pro订阅,弃之。
      然后发现了一个神器iSH。iSH内运行alpine linux,可以安装一个完整的git,和电脑端的别无二至。因为直接是linux环境,运行脚本,修改alias这些不在话下,用起来甚至比GUI的WorkingCopy更方便。
      对于连接iSH文件夹以外的位置,iSH可以通过mount命令做到。
      mount -t ios-unsafe . /root/mountFolder/
      注意这里最好写ios-unsafe,否则使用git的时候可能会经常进行index并且非常慢。
      并且注意因为alpine是单线程的,在iOS下使用可能会有问题,解决方法是git.gitconfig文件里加上
      1
      2
      [pack]
      threads = 1
  • 编辑/阅读:iVim/Obsidian/beOrg(用于todo-list)
    iVim:在键盘上方添加了一些常用符号,操作起来虽然没有真键盘方便但是比原键盘舒服多了。
    Obsidian:移动端屏幕比较小,直接在终端里操作并不方便,还是需要Obsidian这种能够渲染md文件的app来进行阅读比较舒适,其实也有别的md渲染app,为什么选Obsidian?并不是因为他有双向连接什么的,只是紫色黑曜石的icon比较好看~ 事实上电脑上我也并不会使用Obsidian,笔记/todo/代码 终端一把梭。
    beOrg:可以识别你的Emacs Org文件,todo-list,也可以使用agenda,因为没有键盘,在移动端还是这种GUI更方便,点选就可以改变state、deadline和新建todo。文本格式和Emacs兼容的还行,偶尔会有多少一两行空行啥的,问题不大。

Summary

  1. 以文本文件为内容核心

    使用md文件和org文件作为memo和todo list。特点是完全text-based,md和org的特点决定了他们即使不通过任何特定工具,任何平台上都可以以文本文件的形式查看,并且很直观,没有复杂的格式,但是又具有扩展性,可以做更复杂的展示、图表统计等。

  2. 通过git/syncthing等同步工具做全平台同步,

    而git的同步储存、版本管理能力无需多言,通过git自然而然地又满足了多备份异地备份,抗灾能力强,还可以通过自建本地私有git服务来实现完全信息私有。

  3. 外围可以使用工具来增强体验。

    使用beorg、obsidia之类的工具增强阅读和交互编辑体验在手机上简单的点选todi-list,obsidian可以建关系图。同时文件text-based的特性让你可以用你自己最喜欢的编辑器来编辑,比如vim,typora,emacs,vscode等。

缺点
要说简单肯定是不如notion/飞书这种一站式方便,也有一些工具上的使用成本。
优点
总的来说文本文件系统是最通用基础的管理方式,能保证极大的兼容性,与各种特定的软件之间解耦,但又能广泛地通过软件加强其能力。长远看可以把控其中细节,其中的细节可以拆分,便于拓展替换,与特定软件解耦。
试想当你所有笔记都在All-in在app中,某天你发现某个功能无法满足你的要求需要使用其他软件来配合,却发现该软件很难与其他app互动,因为他们数据格式不能互通。。。又或者某天他忽然挂了,网络卡住,或软件本身卡住,你却无法查看其中内容,或者你想转移数据却发现只有付费Pro版才能这么做,转出来了却发现内容根本不可读。。。又或者他数据根本不提供本地导出,你所有的数据都以一种你无法读取无法触及的方式存在一个遥远的服务器上,然后陷入深深的绝望。。。

这时候你才想起来,你本可以对自己的数据更有掌握权的。

Pre

一般平时的环境可以直连国际万维网,所以没有什么问题。但某天在家中使用git时,虽然开着ClashX,却发现速度还是龟速,忍不了了,遂着手解决一下。

TL;DR

Solution🚀

TroubleShooting

首先是看一下,检查一下全局的代理值

1
2
$ echo $https_proxy
http://127.0.0.1:7890

心中一惊,这意味着全局的代理是正常的,然而速度还是慢,说明原因还没找到,need to go deeper…

检查.gitconfig文件

git config --global -e

1
2
3
4
5
6
7
[http]
sslBackend = openssl
proxy = http://127.0.0.1:7890
[https]
proxy = http://127.0.0.1:7890
[socks5]
proxy = socks5://127.0.0.1:7890

所有的设置也都正常,按理说只要全局设置了git自动就会走代理,但是现在两个都设置了还不行。
忽然想到会不会问题不在git设置,而是在clash?

查看ClashXConnections

打开ClashX dashboard 查看 Connections 一栏。当我使用git clone 来进行测试时,发现里面居然找不到git的相关连接。
到这里为止可算是找到了一点蛛丝马迹,但这只是表象,具体问题现在还不知道,不过猜测可能是网站不在Clash的规则里面?于是查找了规则名单,一些大型的git托管网站都在里面,所以问题不在这里。

ssh的锅

搞来搞去还是不行,想吐。但这时候我忽然想起来,会不会是ssh的锅?
于是git clone一个https形式的repository。发现Clash Connections中出现了连接,到这里算是确定了问题所在。

Solution

首先想到的是我直接使用https形式的地址不就行了吗,但是忽然发现bitbucket似乎不支持https的地址,至少是需要支持ssh形式的。于是放弃奇技淫巧,还是走正道。本来https也不够方便,还有可能要时不时输个密码。
于是网上搜索一番,ssh走代理的方式:

1
2
3
4
5
6
~/.ssh/config

Host bitbucket.org
Hostname ssh.bitbucket.org
Port 22
ProxyCommand nc -v -x 127.0.0.1:7890 %h %p

nc 也就是网上所说的“瑞士军刀”netcat
再次执行git clone 测试,丝滑~

1
2
3
$ git clone **************************
Connection to ssh.bitbucket.com port 22 [tcp/ssh] succeeded!
...

Appendix

注意终端里各个程序不一定都会走全局代理,这个ssh就是典型,需要看具体应用的代理方式是如何设置的。

iOS project Editor

一般对于iOS开发者来说Xcode是进行开发的首选工具,这也是苹果官方的开发工具。
如果是非专职iOS开发者,那么也有极大的概率使用着其他开发工具。比如写flutter或者rn这种非native app,则可能会直接使用VSCode;对于native app除了vscode也有其他选择,比如jetBrains家的App Code,已经做的非常好,甚至集成了CocoaPods,不过AppCode貌似是收费的。
如果你是Vim爱好者,那么很容易在Xcode以外的这些工具中找到Vim插件,体验不错,常用的功能都有。
对于Xcode官方也已经在Xcode13中提供了Vim keybind支持,不过很基础,一些常用的功能体面没有提供,使用使用体验与原生Vim存在差距。

Language Server Protocol

其实现阶段在类似sublime、vscode、Emacs中跨行编辑代码并不是难事,多亏了微软主导的Language Server Protocol,每个语言的开发者据此协议提供自己语言服务器以对外提供接口,然后编辑器只需要遵循这个协议即可轻松实现对各种语言的对接,编辑器只需要做好自己的使用体验,界面,输入优化等。

graph TD;
S(Swift) --> D;
A(Java) --> D;
B(JavaScript) --> D;
C(C/C++) --> D;
D(Language Server Protocol) --> E(Vim);
D --> F(Sublime);
D --> G(VSCode);
D --> H(Emacs);

Use Vim for iOS project development

而本文要讨论的则是一个更加朴素的环境,终端中的Vim,在终端中使用Vim来进行iOS工程的编写。
虽说是终端编写,但并不是说不需要Xcode了,Vim中做的只是编辑代码,仍需要使用Xcode来进行编译和运行以及Debug。
最终达成的效果是,用Xcode新建一个工程,编译,生成编译日志并将其解析为可供lsp-server使用的数据,在Vim中调用lsp-server提供自动补全、定义跳转等支持,在Vim中进行代码编写。

graph TD;
A(XcodeCompile/Debug)--> |compile| B;

subgraph prepare info for Language Server
B(compile log)-.-> |Objective-C| X;
B(compile log)--> |Swift| Xa;
X(xcpretty/XCCompilationDB process) -.-> |compile_commands.json| L;
Xa(Xcode Build Server process) --> |.compile| L;
end

subgraph Vim-lsp<=>Language server Interactive
L((Vim-lsp)) -.- |json-compilation-database| Cl(Clangd);
L --- |Build Server Protocol| S(Sourcekit);
end

L === |LSP support: autoCompleteion/goToDefinition...| C;
C((Coding In Vim));

A simple Screencast