GtiHub Actionsでfork元の更新を定期的にfetchする

forkしてちょっとだけ変更を加えてそのまま放置…
その間にも、fork元はコミットが進んでいるかも。
fork元の先進的な変更点は取り入れたいけれど、自分が変更したところは上書きされたくない!

そんなときはGitHub Actionsを使ってみようという話。

この記事は、SLP KBIT AdventCalendar2022 5日目の記事です。

adventar.org

おおまかな手順

  1. clone
  2. git remote add upstream https://github.com/upstream/repository.git
  3. git fetch upstream main
  4. git rebase -X ours upstream/main main
  5. git push -f origin main

とすると、fork元の変更を取り入れた後に、自分の変更が加えられる。

これを毎日実行すると、fork元で更新があれば取り入れ、自分が変更したコミットはその上に積まれる。

注意点:

  • 自分で変更を加えたファイルは、fork元で更新があってもそれを検知できない(変更差分の判定から除外するため)
    • 変更したファイルが多いと不適
  • actions/checkout の with: fetch-depth: 0 を設定しておかなければrebaseに失敗する(デフォルトではコミット履歴は1つしか取得しないため)
  • workflowファイルが更新された場合、tokenが secrets.GITHUB_TOKEN だと失敗する(権限がないため)

更新の有無を確認する

自分が変更を加えたファイルを除き、変更があるか否かを確認する。

自分が変更したファイルはここにパスを追記して更新の確認から除外しておかなければならない。

git diff main upstream/main --name-only  -- ':!own/modified/file'

force pushする

rebase したことにより、コミット履歴が書き換わっている。
よってpush時はforceオプションを使用する。

--force-with-leaseを使ってもいいが、fetchしているため使用するメリットはない。

更新がなかった場合のearly exit

更新がなかった場合はさっさと処理を終了したい。
しかしexit 0としても次のstepは実行されてしまう。
かといってexit 1などと異常終了するとactionが失敗したとみなされ、失敗メールがうっとおしい。

そのため今回は変数に更新成功か失敗かを記し、以降のstepではその変数をifで確認して実行させるように対処している。

Example

README.md に変更を加えて、他のファイルはfork元の変更を取り入れる場合。
毎日03:12にチェックを行う。

update用のactionを .github/workflows/update.yaml に作成:

on:
  schedule:
    - cron: '12 3 * * *'

env:
  UPSTREAM_URL: "https://github.com/upstream/repository.git"

jobs:
  update:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
          token: ${{ secrets.PAT }}

      - name: update
        id: update
        run: |
          git config --local user.name "${GITHUB_ACTOR}"
          # メールアドレスをこれにするとbotとしてコミットされる
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git remote add upstream "${{ env.UPSTREAM_URL }}"
          git fetch --quiet upstream main
          # 自分が変更したファイルは更新の確認から除外する
          if [ -n "$(git diff main upstream/main --name-only -- ':!README.md' ':!.github/workflows/update.yaml')" ]; then
            echo "Update to follow the upstream repository."
            git rebase -X ours upstream/main main
            git remote set-url origin "https://${{ secrets.PAT }}@github.com/${GITHUB_REPOSITORY}.git"
            git push --force-with-lease origin ${GITHUB_REF#refs/heads/}
            echo "::set-output name=UPDATE_STAT::ok"
          else
            echo "No update found."
            echo "::set-output name=UPDATE_STAT::fail"
            exit 0
          fi

      - name: update success report
        if: ( steps.update.outputs.UPDATE_STAT == 'ok' )
        run: echo successflly updated!

      - name: no update report
        if: ( steps.update.outputs.UPDATE_STAT == 'fail' )
        run: echo not updated.

ansibleでmysqlにinsertしたらData too long for columnだけど手動だと成功する

結論: 環境変数 LANG (LC_ALL) を UTF-8 対応のにする


やりたかったこと: mysql -uuser -ppassword < insert_data.sql のansible化

ansibleではこのようなタスクを定義:

- name: insert data
  shell: mysql -uuser -ppassword < insert_data.sql

実行するとエラー

mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1406 (22001) at line 6: Data too long for column 'hoge' at row 1
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1452 (23000) at line 3: Cannot add or update a child row: a foreign key constraint fails (`huga`.`piyo`, CONSTRAINT `piyo_1` FOREIGN KEY (`puyo`) REFERENCES `puyo` (`poyo`))

(パスワード直書きはこの際置いておく)

ググると、 Data too long エラーの解決策は、sql_mode を strict じゃないのにするといいらしい。
しかし、同じデータを使っている他のサーバの sql_mode は全く同じ内容で、しかも strict も存在する。

謎すぎるためとりあえずシェルに入って手動で実行してみる。

$ mysql -uuser -ppassword < insert_data.sql

…できた。??

手動ではできたということは、コマンドの実行環境が違う疑いが持てる。

# ログインシェルのbashで試行
- name: shell with login shell
  shell: bash -lc "mysql -uuser -ppassword < insert_data.sql"

# become (sudo) なしで試行
- name: shell without become
  shell: mysql -uuser -ppassword < insert_data.sql
  become: no

どちらもだめ。

仕方ないので現在の変数定義をみてみる。

- name: show login shell environments
  shell: bash -lc declare
  become: no

手動でシェルに入って見てみた分と比較。(どちらも一部抜粋)

--- 手動でシェルに入った分
+++ ansibleでの分

-BASH=/bin/bash
-BASHOPTS=checkwinsize:cmdhist:complete_fullquote:expand_aliases:extquote:force_fignore:globasciiranges:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath
+BASH=/usr/bin/bash
+BASHOPTS=checkwinsize:cmdhist:complete_fullquote:extquote:force_fignore:globasciiranges:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath
 IFS=$' \t\n'
-LANG=C.UTF-8
+LANG=C
+LC_ALL=C
+LC_CTYPE=C.UTF-8
+LC_MESSAGES=C

BASHOPTSが微妙に違うな〜などと見ていると、あれ、LANG系が全然違うぞと。
そういえばinsertするデータは日本語が入ってたなと。

試しにLANGをCにして実行してみる

LANG=C mysql -uuser -ppassword < insert_data.sql

無事に失敗!

ということで犯人はansibleが勝手に環境変数を変えた結果のLANGでした。

なのでansibleではこのようなタスクを定義して対処:

- name: insert data
  shell: LC_ALL=C.UTF-8 mysql -uuser -ppassword < insert_data.sql

うー、それにしてもなんで変数が変わってるのか…。

こんな記事

redj.hatenablog.com

もあることからansible側を掘らないとわからないかも。

とりあえず対処できたのでヨシ!

tour of go で学んだ Error() の定義と呼び方

問題 https://go-tour-jp.appspot.com/methods/20

検証コード Go Playground - The Go Programming Language

Error() の実行

Error() を実行させるためには、 error インタフェースを満たす値を fmt の print系の関数に渡してやる。
print系関数の中身では、渡された引数の error をチェックして not nil なら Error() を実行するような動きになるため。

// func ReturnErrFn() error {}
fmt.Println(ReturnErrFn())

「error インタフェースを満たす値」とは、Error() メソッドを呼び出せる型のこと。

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("Err: ErrNegativeSqrt: %v", float64(e))
}

つまり、ある変数がこのメソッドを呼び出せるなら、その変数は error インタフェースを満たしている。(error インタフェースは Error() メソッドのみを実装しているため)
よってその変数を fmt の print系に渡せば、通常のprintではなく Error() が実行される。

Error() メソッドのレシーバは変数かポインタか?

ポインタレシーバにすると、呼び出し元の変数はポインタでなければならない。

func (e *ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("Err: ErrNegativeSqrt: %v", float64(e))
}

// ok
fmt.Println(RetErr(&errNegativeSqrt))

// fail
fmt.Println(RetErr(errNegativeSqrt))

対して変数レシーバの場合、呼び出し元は変数でもポインタでもどちらでもよい。

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("Err: ErrNegativeSqrt: %v", float64(e))
}

// ok
fmt.Println(RetErr(&errNegativeSqrt))

// ok
fmt.Println(RetErr(errNegativeSqrt))

有効な error インタフェース値の返却方法?

なんからの関数で error を返却する場合、エラーとなる書き方がある。

type ErrNegativeSqrt float64
func RetErr(x float64) error {
    // fail
    return &ErrNegativeSqrt(x)

    // fail
    var errNegativeSqrt *ErrNegativeSqrt = &(ErrNegativeSqrt(x))
    return errNegativeSqrt

    // ok
    var errNegativeSqrt ErrNegativeSqrt = ErrNegativeSqrt(x)
    return &errNegativeSqrt

    // ok (error == nil なのでこれを print しても Error() は実行されない)
    var errNegativeSqrt *ErrNegativeSqrt
    return errNegativeSqrt
}

一旦現在のスコープの変数にコピーしてからそのアドレスを返却する必要がありそう。
実行結果(右辺)のアドレスはその行が終了した時点でdropされて dangling pointer のようになっているのだろうか。。

chrome os flex 導入メモ

chrome os flex (早期アクセス版) を導入したときのメモ

機器: DELL Inspilon 5505 (AMD Ryzen 4500U) サポート対象外機器

Wireguard VPN を使う

インストールしなくても組み込みで使える。

設定 > ネットワーク > 接続を追加 > 組み込みのVPNを追加

ネームサーバーの指定はカンマ区切りで4つ。

VSCode を使う

設定 > デベロッパー > Linux開発環境

オンにしておく。

次に、vscode公式から .deb ファイルをダウンロード

chrome osLinux開発環境は、debian bullseyeなので、debファイルからアプリケーションをインストールできる。

ファイル から、ダウンロードしてきた .deb ファイルをダブルクリックしてインストール。

Setting syncをするためには、terminal から gnome-keyring をインストールしておく。決めたパスワードは毎回聞かれるので覚えておく。

このままでは日本語入力ができないため、fcitx5-mozcを入れる。

インストールするのは fcitx5, fcitx5-mozc

入れただけでは起動しないので、systemd unit ファイルを /usr/local/lib/systemd/system/fcitx5.service として作る。

[Unit]
Description=fcitx5
After=fcitx5.service

[Service]
Type=simple
ExecStart=/usr/bin/fcitx5
Restart=always
User=<ユーザー名>
Group=<ユーザー名のプライマリグループ>
Environment='XDG_RUNTIME_DIR=/run/user/<UserのUID>'

[Install]
WantedBy=multi-user.target

/etc/environment環境変数を設定しておく。

GTK_IM_MODULE=fcitx
QT_IM_MODULE=fcitx
XMODIFIERS=@im=fcitx

有効化しておくと日本語入力が可能になる。

firefoxを入れる

chrome os 側でtarから利用はできないため、Linuxアプリとして入れなければならない。

debパッケージをubuntuリポジトリからDLしてきてダブルクリック。

使用感

良い:

  • UIがきれい
  • debパッケージをインストールできる
  • wireguardが使える
  • Linux(manjaro)で頻繁発生していたフリーズ現象は確認していない
  • Linux(ubuntu)で発生していた文鎮化現象は確認していない

悪い:

  • setkeycodes のような key remapができない
  • ショートカットキーがUS配列
  • wireguardを使うとブラウジングが遅い、apt update先に接続待ちが発生する
  • hdmiマルチディスプレイがたまに反応しない
  • Lidを閉じたときにたまに画面がブラックアウトする
  • firefoxの全画面表示ができない(F11では可能)
  • (firefox)たまにフォントのドットが荒くなる。
  • (firefox)動画を視聴しているとたまにテアリングが発生する
  • 頻繁に動画がピンク色になる

早期アクセス版かつサポート対象外機器なので動作が不安定なのは仕方ない

GitHub Actionsでフォーマッタかけてpushするjob

フォーマッタかけるの,人手だと忘れる.

- run: |
    cargo fmt
    if [ -n "$(git diff)" ]; then
      git config --local user.name "${GITHUB_ACTOR}"
      git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com"
      git remote set-url origin "https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git"
      git add .
      git commit -m "fmt: ${GITHUB_SHA::7}"
      git push origin ${GITHUB_REF#refs/heads/}
    fi

commitするユーザーをgithub-actions botにしたくば,user.emailgithub-actions[bot]@users.noreply.github.comに変える.
そしたら画像のようにoctocatのアイコンが現れる.

f:id:yassi-htn:20220305225644p:plain
commit by github-actions[bot]

OpenWrtをスイッチ化してWireGuardを入れた

この記事は、SLP KBIT AdventCalendar2021 24日目の記事です。

多くのメンバーが面白い記事を書いてくれています。ぜひどうぞ。

adventar.org

さて、今年のアドカレでは、最近勉強し始めたRustでシェルでも作ってみようかな、と考えていました。
しかしながら、師走というだけあり、色々と忙しくなってしまい、、できませんでした!(また今度そのうちやります)

ということで、過去に下書きとして残していた記事(というよりメモ)を整えて、今年の僕のアドカレ担当分とします。

作業用BGMに毎年恒例の薪燃え見てます。ずっと見てられる。

live.nicovideo.jp


この記事は、「自宅のOpenWrtルーターをWireGuard用のサーバーとして使っていたが、2重ルーターになってたのとメインのルーターの物理ポートが足りなくなったのでVPNサーバ兼スイッチングハブにした」という内容です。

こんな感じになる:

f:id:yassi-htn:20211225010321p:plain
構成図

使用するルーター: Buffalo WZR-HP-AG300H
OpenWrtのバージョン: OpenWrt 21.02.1

コマンドラインではなく、LuCI(Webインタフェース)を使って操作していきます。

IPv6は使用していません。

switch化

WANポートのインタフェースを消すので、OpenWrtのLANポートと操作用PCをLANケーブルで繋いで作業を行います。

事前準備として、メインルーターとOpenWrtはこの時点では繋がないでおきます。
また、パスワードの設定は済ませておきます。(設定していないと、IPアドレスが変わったときに認証されないようです)

作業場所: Network -> Interfaces

  1. WAN, WAN6を消す
  2. LANをEdit
    • General Settingsタブで
    • Advanced Settingsタブで
      • Use default gatewayにチェックが付いていることを確認する
      • DNS serverを適当に決めたり決めなかったり
    • FW無効化
    • DHCP ServerタブのGeneral SetupタブでIgnore interfaceにチェックを付ける
  3. Networks -> Devicesタブのbr-lanの設定でEnable IPv6のチェックを外してIPv6無効化
    • br-lanのBridge portsにはeth0.1とeth1を指定
  4. Save & Apply
  5. カウントダウンが開始する。残り70sぐらいになったら設定が適用されていると思われるので、以下の操作をカウントダウン中に行う(0になれば失敗します。もう一度チャレンジしましょう)
    1. PCからLANケーブルを抜く
    2. メインルーターとopenWRTのLANポートをLANケーブルでつなぐ
    3. 1.で抜いたLANケーブルをPCに挿し直す
    4. メモしておいたIPアドレスにアクセスする

設定が反映されインターネットに接続できていることを確認できればOK

WireGuardを入れる

WireGuardとは、シンプルでセキュアなVPNです。

  • UDPを利用し、L3トンネルを実現します。
  • 公開鍵を使って認証したPeer同士で通信が可能になります。

作業場所: System -> Software

  1. Update lists... を押してパッケージリストをアップデート
  2. 次のパッケージをインストールします: luci-app-wireguard, luci-proto-wireguard
  3. luci-app-wireguard を入れたら luci-proto-wireguard も一緒に入るかも。
  4. System -> Reboot から 再起動
  5. Networks -> Interfaces -> Add new interface でwireguard用のインタフェースを作成する
    • インタフェース名: wg0
    • protocol: WireGuardVPN
    • port: 51820 (なんでもいい)
    • FW: lanにしてたがなんでもいいと思う
    • Peers: 以下の事項に注意しながら適当に埋める。
      • Allowed_IPsにはVPN接続時に振られるIPアドレスを書く。
      • Route Allowed IPsはチェックを入れるのでよい。
      • Endpoint Hostは、後述するWireGuard用に作成するインタフェースのIPアドレスを指定。使うネットワークのアドレスなので、ここで決めてしまう。
      • Endpoint Portは51820。
      • Persistent Keep Aliveは25にした。

こんな感じになる:

f:id:yassi-htn:20211224225517p:plain
wg0 peer example settings

作成後に設定を変えるときは、Editで編集してSave&ApplyしてインタフェースのRestartをする。

メインルータの設定

ポート変換などの機能で OpenWRTのLANインタフェースのIPUDPポート51820に インターネットから来るUDP 51820 のトラフィックを投げるように設定する。

Client Peerの設定

クライアントにあたるPeerの設定を書く。
Windowsなら、クライアントソフトのAdd Tunel -> Add empty tunnelで作成し、Editで書いていく。

[Interface]
PrivateKey = <PrivateKey>
ListenPort = 51820
Address = wg0のAllowedIPs/32
DNS = <DNS>

[Peer]
PublicKey = <PubKey>
PresharedKey = <PSK>
AllowedIPs = 0.0.0.0/0
Endpoint = <dnshostname>:51820

client peerのpubkeyを先ほどのwg0インタフェースのpeerに登録する。

wgwan用トラフィックルールの作成

WGはどうもNATでないと動作しない(WANに来たトラフィックを見ている?)みたい?なので、wg0がリッスンするNATデバイスをつくってやることにする。

作業場所: Network -> Firewall

Add からゾーン(ここでは wgwan とする)を作成する。
これらのforwardingsなどの設定は、既存のwanゾーンに合わせている。
ただし、wgwanのForwardはAcceptにしている。(もしかしたらrejectでいいかもしれない)

作成したら、Traffic Rules タブに移動する。

Addからルールを作成する。

Incoming IPv4, Protocol UDP, From wgwan To this device, port 51820, Accept input

メモによると、このままだとルータに有線接続した際にipでluciにアクセスできなかったとのことで allow WAN -> device:80,443 とするルールを追加していました。

wg0用NATインタフェースの作成

作業場所: Network -> Interfaces

  • Add new interface でインタフェースを作成
    • Protocol: Static address
    • Device: br-lan
    • IPv4 address: wg0で作成したPeerのEndpoint Hostで決めたIPアドレス
    • netmask, gateway, bloadcast: メインルーターになるように。冒頭で設定したLANインタフェースと同じ。
    • DHCP Serverタブで、DHCPサーバーを使用する。設定はデフォルトでおk。
    • Firewallで、先ほど作成した wgwan を指定する。

Phew!
以上です。

当時の記憶があまりないので色々検証できてないですが、現状動いているので多分これで動きます。
メモをみるとSwitchデバイス(eth0.1あたり)やルーティングテーブルいじったり、迷走してましたね。。。

ちなみに、固定IPはお金がかかるので、DDNS(NoIP)を登録して使っています。

(あ、日付変わってた…メリークリスマス!🎅)

ansible_user, remote_user などの違いと使い分け [変数 != キーワード]

Ansibleの実行ユーザーとそのパスワードに使われる変数の使い分け

同じような変数(?)が複数あり、どれを使えばいいのか迷ったため。
調べていくと、変数とキーワードの違いが分かっていなかったため少し混乱した。

remote_user

リモートにsshでログインするユーザー名。キーワード。

playbookのキーワードとは、変数ではなく、設定値である。
変数は、あくまで変更を容易くする"データ"であり、playbookの実行やansibleによる状態の記述を補助するものとして扱う。

つまり、キーワードとは、お馴染みのtaskswhenなどと同じ分類のものである。…と言われるとわかりやすいと思う。
playbookのと書いているのは、他にもrole, block, taskのキーワードがあるためである。これらの種類によって、各場所(必要なインデントの量)が変わる。

キーワードは変数ではないため、gather_facts: noと変数を設定しても意味がない。

- hosts: test_server
  # これは(playbookの)キーワード。効果を発揮する。
  gather_facts: no
  vars:
    # これは変数。特に意味がない。
    gather_facts: no
  roles:
    - role: test

キーワードで設定されたものは、対応する変数によって上書きされ得る。
例えば、become: yesと、キーワードを設定していても、変数の定義でansible_become: noとすれば、権限昇格されない。

- hosts: test_server
  # これはキーワード
  become: yes
  vars:
    # この変数でbecomeの可否が上書きされる
    ansible_become: no
  roles:
    - role: test

ansible_user

同じくsshでログインするユーザー名。これは変数。

特殊な変数 Special Variables — Ansible Documentation

ansible_ssh_user

同上(古いので非推奨1)

ansible_password

sshのpassword。変数。
さらに、becomeのpasswordとしても使える。そのため、これを設定しておけばansible_become_passwordは書かなくてもよい。
2021.07.17 修正
SSHを公開鍵認証で接続できているなら、これは書かなくてもよい。ただし、ansible_become_passwordは必須。

ansible_ssh_password

同上(古いので非推奨2)

ansible_become_password

becomeのpassword。変数。
これをsshのpasswordとして使うことはできない。

ansible_become_pass

同上(古いので非推奨3)

(余談)ansible.cfgについて

become_userremote_userなどのキーだけでなく、ANSIBLE_REMOTE_USERのような環境変数としての記述もできる。

キーと環境変数一覧 Ansible Configuration Settings — Ansible Documentation

しかし、ここにはpasswordについてのキー、環境変数は定義されていない。


...結局
ユーザー名とパスワードのセットで書いておきたいし、group_varsにansible_useransible_passwordを書いておくのでFAにしようと思う。

Ref

Controlling how Ansible behaves: precedence rules — Ansible Documentation


  1. 詳細は↓

  2. こちらへ↓

  3. どうぞ!tekunabe.hatenablog.jp