CardDAV Sync

CardDAV Sync

第一次使用 CardDAV 用來同步聯絡人,以 Xandikos 作為伺服器,用 vdirsyncer 進行主要同步,再配合 Thunder bird Tbsync 套件,堪稱完美。

Xandikos

Xandikos 是一個輕量級但完整的 CalDAV 和 CardDAV 伺服器。

安裝;

apt install xandikos

第一次啟動

xandikos --defaults --autocreate  -d $HOME/dav -p 8888

即可馬上進行測試與驗證,如果覺得不錯,想要持續使用,可以放到systemd user mode ; 因為 目前 xandikos Multi user 沒有很好用。

設定 systemd user mode 自動啟動:

mkdir .config/systemd/user/xandikos.service 

xandikos.service

[Unit]
Description=Xandikos CalDAV/CardDAV server
After=network.target

[Service]
ExecStart=/usr/bin/xandikos \
  --directory $YOUR/DAV/PATH//dav \
  --listen-address 127.0.0.1 \
  --route-prefix=/dav \
  --port 8888 \
  --defaults
Restart=on-failure
KillSignal=SIGQUIT
Type=simple
NotifyAccess=all

[Install]
WantedBy=multi-user.target
systemctl --user daemon-reload
systemctl --user restart xandikos.service

同時有一個建議設定,因為系統已經有 apache2, 所以順便設定 recerse proxy

       <Location "/dav/">
           ProxyPreserveHost On
           ProxyPass http://127.0.0.1:8888/dav/
           ProxyPassReverse http://127.0.0.1:8888/dav/
           AuthType Basic
           AuthName DAV-Z
           AuthUserFile /xxxx/.htpasswd
           Require valid-user
       </Location> 

如此一來,我的DAV服務就是 https://xxx.thomas.org/dav/ 就會由 xandikos 進行。

csv2vcf

我有固定去爬一些通訊錄檔成 csv 檔案,這時候要用 csv2vcf,but vsf 3.0 thunderbird 不適應,所以魔改了一些

def convert_to_vcard(input_file, single_output, input_file_format):
                                          
    FN = input_file_format['name']-1 if 'name' in input_file_format else None
    NICKNAME = input_file_format['nickname']-1 if 'nickname' in input_file_format else None
    ORG = input_file_format['org']-1 if 'org' in input_file_format else None
    DEP = input_file_format['dep']-1 if 'dep' in input_file_format else None
    TEL = input_file_format['tel']-1 if 'tel' in input_file_format else None
    TITLE = input_file_format['TITLE']-1 if 'TITLE' in input_file_format else None                                                                                       
    EMAIL = input_file_format['email']-1 if 'email' in input_file_format else None
    ADDR = input_file_format['addr']-1 if 'addr' in input_file_format else None
    NOTE = input_file_format['note']-1 if 'note' in input_file_format else None
                                                                                    
    with open( input_file, 'r' ) as source_file:
        reader = csv.reader( source_file )                                
        i = 0         
        for row in reader:
                                       
            FN_VAL = row[FN] if FN is not None else ''                           
            NICKNAME_VAL = row[NICKNAME] if NICKNAME is not None else ''
            ORG_VAL = row[ORG] if ORG is not None else ''
            DEP_VAL = row[DEP] if DEP is not None else ''
            TEL_VAL = row[TEL] if TEL is not None else ''
            TITLE_VAL = row[TITLE] if TITLE is not None else ''
            EMAIL_VAL = row[EMAIL] if EMAIL is not None else ''   
            ADDR_VAL = row[ADDR] if ADDR is not None else ''
            NOTE_VAL = row[NOTE] if NOTE is not None else ''
                  
            # write each entry                                                                                                                                           
            #each_vcf = open('contacts/' + FN_VAL + '_' + TEL_VAL + ".vcf", 'w')
            each_vcf = open('contacts/' + NICKNAME_VAL.replace(" ","") + ".vcf", 'w')
            each_vcf.write( 'BEGIN:VCARD' + "\n")
            each_vcf.write( 'VERSION:4.0' + "\n")
            each_vcf.write( 'FN:' + FN_VAL + "\n")
            each_vcf.write( 'NICKNAME:' + NICKNAME_VAL + "\n")
            each_vcf.write( 'TEL;TYPE=work;VALUE=TEXT:' + TEL_VAL + "\n")
            each_vcf.write( 'EMAIL;PREF=1:' + EMAIL_VAL + "\n")
            each_vcf.write( 'ORG:' + ORG_VAL + ";" + DEP_VAL +"\n")
            each_vcf.write( 'TITLE:' + TITLE_VAL + "\n")
            each_vcf.write( 'ADR;TYPE=work:;;' + ADDR_VAL + ";;;;\n")
            each_vcf.write( 'NOTE:' + NOTE_VAL + "\n")
            each_vcf.write( 'END:VCARD' + "\n")
            each_vcf.write("\n")
            each_vcf.close()
  
            i += 1
  
        print(str(i) + " VCARDS written")
        print('----------------------')

這樣 csv 內的所有資料都換換成 vcf 目錄,每個檔案都是一個單一個人通訊錄,且是 VCF 4.0 format,這樣就完成通訊錄轉換。

vdirsync

Vdirsyncer 是一個命令列工具,用於在各種伺服器和本機檔案系統之間同步日曆和通訊錄。最受歡迎的用例是將伺服器與本機資料夾同步,並使用一組其他程式來更改本地事件和聯絡人。然後,Vdirsyncer 可以將這些變更同步回伺服器。

所以目標就是把剛剛轉檔好的 vcf 同步到 xandikos ,設定:

先建立目錄mkdir .vdirsyncer/

[general]
status_path = "~/.vdirsyncer/status/"

[pair narlabs_contacts]
a = "my_labs_local"
b = "my_labs_remote"
collections = ["from a", "from b"]

[storage my_labs_local]
type = "filesystem"
path = "~/.contacts/"
fileext = ".vcf"

[storage my_labs_remote]
type = "carddav"

# We can simplify this URL here as well. In theory it shouldn't matter.
url = "https://www.thomas.org/dav/user/"
username = "" 
password = "" 

執行 vdirsync sync 第一次跑可能會需要discover; 依照指令執行

vdirsyncer discover labs_contacts
vdirsyncer sync

Thunderbird

Thunder bird 安裝套件 tbsync 之後,開 tbsync, 新增帳號,選 carddav , 選 manual Configuration

依序輸入 name, server, user, pass

理論上就完成了!

之後我用 crontab 定期整理 lab 通訊錄,並且自動同步到主機,所有thunderbird 就可以快速更新通訊錄了!

Reference:

https://github.com/jelmer/xandikos
https://www.xandikos.org/
https://github.com/mridah/csv2vcf
https://vdirsyncer.pimutils.org/en/stable/index.html
https://vdirsyncer.pimutils.org/en/stable/tutorials/xandikos.html