お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

Ubuntu22.04 LTSのターミナルのレンダリングが異常に重いのでバージョンを落として運用するためのメモ
Node.jsの開発環境だけ入れる

環境

環境 Version
Windows 11 Pro Build 22621.3155
Ubuntu 20.04.6 LTS
zsh 5.8 (x86_64-ubuntu-linux-gnu)
MariaDB Ver 15.1 Distrib 10.3.38-MariaDB
Docker 20.10.17, build 100c701
Git 2.42.0

WSL2の構成

  1. 管理者権限のPowerShellで以下を流す
    dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
    dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
    
  2. OS再起動
  3. Linuxカーネル更新プログラム パッケージをインストールする
  4. Ubuntuを入れる

Ubuntu側のセットアップ

適当な開発環境をサクッと

# systemdの有効化
cat <<EOF | sudo tee /etc/wsl.conf
[boot]
command=service docker start; service nginx start; service mysql start;

[interop]
appendWindowsPath = false

[user]
default=ここにユーザー名
EOF

sudo apt update -y
sudo apt upgrade -y
sudo apt install -y \
    zsh \
    ssh \
    net-tools \
    traceroute \
    unzip \
    mariadb-server \
    nginx \
    git \
    keychain

# chsh
sudo apt -y install zsh unzip traceroute
chsh -s $(which zsh)

# Docker
sudo apt remove docker docker-engine docker.io containerd runc
sudo apt update
# 依存関係のインストール
sudo apt install -y \
   apt-transport-https \
   ca-certificates \
   curl \
   gnupg \
   lsb-release
# Docker公式のGPG鍵を追加
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# リポジトリをパッケージマネージャーに登録
echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
# docker本体のインストール
sudo apt install -y dockerdocker-ce docker-ce-cli containerd.io docker-compose-plugin
# sudo緩和
sudo usermod -aG docker $USER

# WSLをリブート

# dot file設定
git clone https://github.com/Lycolia/my-dotfiles.git
rm -Rf my-dotfiles/.git/
mv my-dotfiles/.* .
rm -Rf my-dotfiles/

# Node.js @ nvm
export NVM_DIR="$HOME/.nvm" && (
  git clone https://github.com/nvm-sh/nvm.git "$NVM_DIR"
  cd "$NVM_DIR"
  git checkout `git describe --abbrev=0 --tags --match "v[0-9]*" $(git rev-list --tags --max-count=1)`
) && \. "$NVM_DIR/nvm.sh"
nvm install --lts

# Keychain設定
cat <<'EOF' | tee -a ~/.zshrc
Keychain
keychain -q --nogui $HOME/.ssh/id_ed25519
source $HOME/.keychain/hostname-sh
EOF

トラブルシューティング

上記手順に書いてないことをすると起きるが、一応付記しておく

<3>WSL (639) ERROR: CreateProcessEntryCommon:345: getpwnam(USER_NAME) failed 0というエラーが出る

wsl.conf[user]セクションでdefault=USER_NAMEが存在しないユーザーを指定していると出る

Dockerやnginxのサービスが起動しない

systemdが有効になっていると起動しないので/etc/wsl.confからsystemd=trueを消す

投稿日:
OS::Linux::コマンド

ifの書き方メモ。中身はtestコマンドなので[[]]の中はtestと同じ書き方が可能。コマンドなので演算子の間とかはスペースが無いとうまく動かない。余談だがzshでwhichすると組み込みコマンドであることが確認できる

which [[
zsh: bad pattern: [[
which [
[: shell built-in command
which test
test: shell built-in command

基本構文

あくまでコマンドなので演算子の間には半角スペースが必要。[より[[の方が高機能らしい

https://fumiyas.github.io/2013/12/15/test.sh-advent-calendar.html
https://yukidarake.hateblo.jp/entry/2015/12/14/205610

if-elif-else

if [[ $var = "条件" ]]; then
    # 処理
elif [[ $var -eq 0 ]]; then
    # 処理
else
    # 処理
fi

and / or

[[]]&&||で区切れば成立する。これらは演算をしているわけではなく[[の終了コードを見て次のコマンドを実行するかどうかを判断しているだけ

if [[ $var1 = "条件1" ]] && [[ $var2 = "条件2" ]]; then
    # 処理
fi

if [[ $var1 = "条件1" ]] || [[ $var2 = "条件2" ]];
    # 処理
fi
動作機序

testコマンドは条件を満たせば0、満たさなければ1を終了コードに設定するため、単純にこれを利用している。[[で代替可能なため基本的にtestは使わない方がいい

# 条件1の$?が0なら条件2が走る
[[ $var1 = "条件1" ]] && [[ $var2 = "条件2" ]]
# 条件1の$?を無視して条件2が走る
[[ $var1 = "条件1" ]] || [[ test $var2 = "条件2" ]]

オプション

文字列用

オプション 意味 補足
string = string 等しい -
string != string 等しくない -
string =~ string 正規表現一致 -
-n string 空文字でない non-zero
-z string 空文字 zero
-d path ディレクトリ directory
-s path ファイルサイズが 0 を超える size

数値用

オプション 意味 補足
0 -eq 0 等しい equals
0 -ne 0 等しくない not equals
0 -lt 0 未満 less than
0 -le 0 以下 less than or equal to
0 -gt 0 超える greater than
0 -ge 0 以上 greater than or equal to

ファイル用

オプション 意味 補足
-d path ディレクトリ directory
-e path 存在するパス exists
-f path ファイル file

参考

フルスクラッチで組むやつ

確認環境

Env Ver
Ubuntu 20.04.4 LTS
nginx 1.18.0 (Ubuntu)
MariaDB 15.1 Distrib 10.3.34-MariaDB
grafana-server Version 9.2.5 (commit: 042e4d216b, branch: HEAD)

前提

  • Windows側からhttp://grafana.test/としてアクセスする
  • DBにはMariaDBを使用

hostsの編集

Windows側のhostsに以下を追記

127.0.0.1 grafana.test

各種環境のインストール

sudo apt update
sudo apt install -y nginx mariadb-server

sudo apt-get install -y apt-transport-https software-properties-common wget
sudo wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install grafana

nginxの設定

cat <<'EOF' | sudo tee /etc/nginx/conf.d/granafa.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

upstream grafana {
  server localhost:4000;
}

server {
  listen       80;
  server_name  grafana.test;
  access_log   /var/log/nginx/grafana.access.log;
  error_log    /var/log/nginx/grafana.error.log;


  location / {
    proxy_set_header Host $http_host;
    proxy_pass  http://grafana;
  }

  location /api/live/ {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $http_host;
    proxy_pass http://grafana;
  }
}
EOF

sudo service nginx start

MariaDBの設定

ユーザー作成

idpwの部分は適当に変える

sudo service mysql start
sudo mysql
CREATE USER 'id'@'%' IDENTIFIED BY 'pw';
GRANT ALL PRIVILEGES ON *.* TO 'id'@'%' WITH GRANT OPTION;
quit

外部接続テスト

適当なRDBクライアントから繋げればOK

grafanaの設定

  1. sudo nano /etc/grafana/grafana.iniで適当にいじる
# The http port  to use
http_port = 4000

# The public facing domain name used to access grafana from a browser
domain = grafana.test
  1. sudo service grafana-server start
  2. http://grafana.test/ へアクセス
    1. IDPW共にadminが初期

DBの読み込み

  1. MariaDBに適当なDBとテーブルを作る
  2. Grafanaにログインする
  3. サイドバーからConfiguration -> Data sources
  4. DBの情報を入れて接続
  5. 後はよしなにやる

割とハマってだるいので今回はサンプル程度にPRの一覧を取得して個別にSlackに投げるものを作ってみます

PRが2個ある場合、出力イメージはこんな感じ。2投稿に分けて投稿します

GitHub ActionからのSlackへの投稿イメージ

API トークンの入手

まずはSlack APIを叩くためのトークンをゲットします

  1. Create an appからアプリを作成
  2. 左のメニューからFeatures -> OAuth & Permissions
  3. Scopesを設定
    1. 今回はBot Token Scopesをchat:writeとします
  4. 左のメニューからSettings -> Install App to Your Teamでアプリをインストール
  5. トークンが吐き出されるのでメモする

GitHub Actions Workflowsの作成

SECRETの設定

  1. Slack APIトークンをリポジトリのSecretsに突っ込んでおきます
  2. 名前は一旦SLACK_TOKENとします

Workflowsの作成

前提
  • PR一覧の取得には actions/github-script を利用します
    • GitHub内部の情報を抜いたり、JSで処理を組みたいときに重宝します
    • APIリファレンスが読みやすいので、使うのにはあんま苦労しないと思います
  • Slack APIを叩くのにはcurlを利用します
    • actions/github-scriptから叩くのは多分難しいです
ベースの作成

これに肉付けをしていきます

name: Post to slack example
on:
  workflow_dispatch:
jobs:
  post-slack:
    runs-on: ubuntu-latest
    steps:
PR一覧の取得

List pull requestsにある通りに進めていきます

- uses: actions/github-script@v6
  id: set-result
  with:
    result-encoding: string
    script: |
      const { data: respPulls } = await github.rest.pulls.list({
        owner: context.repo.owner,
        repo: context.repo.repo,
      });

      console.log(respPulls);
PR一覧の加工

こんなデータを取る感じで組んでいきます
API仕様は List pull requests を参照

type PullRequest = {
  id: number;
  reviewers: string[];
};

先ほど取得したrespPullsを上記の型付けになるように加工します

const getReviewersName = (requested_reviewers) => {
  return requested_reviewers.map((reviewers) => {
    return reviewers.login;
  });
};

const getPullRequests = (pulls) => {
  return pulls.map((pull) => {
    return {
      id: pull.number,
      reviewers: getReviewersName(pull.requested_reviewers),
    };
  });
};

const pulls = getPullRequests(respPulls);
Slackに投げるメッセージの作成

こんなメッセージをPRの数分組んでいきます

なお実際にSlackでメンションを作る場合はGitHubのスクリーン名とSlackのユーザー IDの突き合わせ処理が別途必要です。やり方は別途後述します

@foo @bar
https://example.com/pulls/1

やっていること

  • 上記のフォーマットでメッセージを作成
  • シェルスクリプトで配列として扱うためにBase64にエンコード
    • 改行コードが混ざっていると扱いづらいので
  • エンコードした文字列をスペース区切り文字列として連結
    • AAA BBB CCC ...みたいな
  • 最後にWorkflowsの戻り値として設定しています
const encodedMessages = pulls.reduce((messages, pull) => {
  const reviewersBuff = pull.reviewers
    .reduce((acc, cur) => {
      return `${acc}${cur} `;
    }, '')
    .replace(/ $/, '');

  const reviewers = reviewersBuff === '' ? 'レビュアー未設定' : reviewersBuff;

  const message = `${reviewers}\\nhttps://example.com/pulls/${pull.id}`;
  const encodedMessage = Buffer.from(message).toString('Base64');

  return `${messages}${encodedMessage} `;
}, '');

return encodedMessages;
curlを利用してSlack APIを叩く

やっていること

  • encodedMessages=(${{steps.set-result.outputs.result}})
    • 前項で作った文字列を配列として取得しています
  • for message in ${encodedMessages[@]}
    • foreach的なやつです
    • 改行コードがこの時点で存在すると上手くいきません
  • decoded_mes=$(echo ${message} | base64 -di)
    • ここでBase64エンコードをデコードします
  • postSlack "$decoded_mes"
    • 別引数にならないように""で固めます
  • curl叩いてるところ
    • -dの中をヒアドキュメントで展開するのが味噌です
    • 単純に文字列として扱うと変数展開が起きてJSONが壊れます
      - run: |
        postSlack() {
            local mes=$1

            curl -sS https://slack.com/api/chat.postMessage \
                -H 'Authorization: Bearer ${{ secrets.SLACK_TOKEN }}' \
                -H 'Content-Type: application/json; charset=UTF-8' \
                -d @- <<EOF
                {
                    token: "${{ secrets.SLACK_TOKEN }}",
                    channel: "#api-test",
                    text: "$mes"
                }
        EOF
        }

        encodedMessages=(${{steps.set-result.outputs.result}})

        for message in ${encodedMessages[@]}
        do
            decoded_mes=$(echo ${message} | base64 -di)
            postSlack "$decoded_mes"
        done
コード全体
name: Post to slack example
on:
  workflow_dispatch:
jobs:
  post-slack:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v6
        id: set-result
        with:
          result-encoding: string
          script: |
            const getReviewersName = (requested_reviewers) => {
              return requested_reviewers.map((reviewers) => {
                return reviewers.login;
              });
            };

            const getPullRequests = (pulls) => {
              return pulls.map((pull) => {
                return {
                  id: pull.number,
                  reviewers: getReviewersName(pull.requested_reviewers),
                }
              });
            }

            const { data: respPulls } = await github.rest.pulls.list({
              owner: context.repo.owner,
              repo: context.repo.repo,
            });

            const pulls = getPullRequests(respPulls);

            const encodedMessages = pulls.reduce((messages, pull) => {
              const reviewersBuff = pull.reviewers.reduce((acc, cur) => {
                return `${acc}${cur} `
              }, '').replace(/ $/, '');

              const reviewers = reviewersBuff === '' ? 'レビュアー未設定' : reviewersBuff;

              const message = `${reviewers}\\nhttps://example.com/pulls/${pull.id}`;
              const encodedMessage = Buffer.from(message).toString('Base64');

              return `${messages}${encodedMessage} `
            }, '');


            return encodedMessages;
      - run: |
          postSlack() {
            local mes=$1

            curl -sS https://slack.com/api/chat.postMessage \
                -H 'Authorization: Bearer ${{ secrets.SLACK_TOKEN }}' \
                -H 'Content-Type: application/json; charset=UTF-8' \
                -d @- <<EOF
                {
                    token: "${{ secrets.SLACK_TOKEN }}",
                    channel: "#api-test",
                    text: "$mes"
                }
          EOF
          }

          encodedMessages=(${{steps.set-result.outputs.result}})

          for message in ${encodedMessages[@]}
          do
              decoded_mes=$(echo ${message} | base64 -di)
              postSlack "$decoded_mes"
          done

Appendix:Slackにメンションを投げる方法

Slackへ実際にメンションを投げるのはユーザーIDを指定する必要があります
参考:Formatting text for app surfaces

"text": "<@U024BE7LH> Hello"のようにすることでメンションを投げられます

ユーザーIDはSlackアプリから相手のプロフィールを開き、そこにあるハンバーガーメニューみたいなやつから取れます。一応取得用のAPIもあります

ユーザーIDをSlackアプリ画面から取得する方法

投稿日:
OS::Linux::コマンドソフトウェア::Bash

配列をループして処理したい時に使える技。複数行ある場合も想定

一行文字列の配列

スペースで区切った文字列を()で括っておくとfor inとして使える

取り出した要素は${foo}で拾えます。配列全体は${bar[@]}で取れる。以下のようにダブルクォートで囲っていると機能しなくなるので注意
("AAA BBB CCC")

#!/bin/bash

textList=(YWFhYWEK LWFhYQogICAtIOOBguOBguOBguOBgg== LWFhYQogICAtIOOBguOBguOBguOBgg==)

for text in "${textList[@]}"
do
  echo ${text}
done

出力結果

YWFhYWEK
LWFhYQogICAtIOOBguOBguOBguOBgg==
LWFhYQogICAtIOOBguOBguOBguOBgg==

複数行文字列の配列

Base64辺りでエンコードしておくと扱いが楽

#!/bin/bash

textList=(YWFhYWEK LWFhYQogICAtIOOBguOBguOBguOBgg== LWFhYQogICAtIOOBguOBguOBguOBgg==)

for text in "${textList[@]}"
do
  pure=$(echo ${text} | base64 -d)
  echo "$pure"
done

出力結果

aaaaa
-aaa
   - ああああ
-aaa
   - ああああ

lsの結果を配列にする

#!/bin/bash

# .と..は除外
# 最後にxargsを入れておくと改行が飛ぶ
results=($(ls -1a | grep -vP "^\.+$" | xargs))
for item in "${results[@]}"
do
    echo "$item";
done

スペース区切りの文字を配列にする

IFS変数に区切り文字を設定することで実現する。IFSとはInternal Field Separatorのこと

#!/bin/bash
IFS=$'\n'

text=$(cat <<'EOF'
aa bb
a b c
EOF
)
textList=($(echo "$text"))

for text in "${textList[@]}"
do
  echo "${text}"
done

出力結果

aa bb
a b c