OmniAICreator/Qiita-1.07M huggingface dataset:
DiscordのWebhookを使ってNode.jsから画像をPOSTする


以外とまとまって無かったのでメモ。

DiscordのAPIを使うとクライアントキーとか発生して面倒なのでWebhookの仕組みだけ使って画像を送ってみます。Gyazoとかに上げつつURLをシェアするパターンもありそうですが、今回は直アップロードとなります。

## テキストを送る場合

Fetchでテキストを送るときはこんな感じです。

https://qiita.com/n0bisuke/items/d6970e180355ce0e2fdc

## Fetchのmultipart/form-dataで画像をポストする

https://github.com/otoneko1102/discord-webhook-sender/blob/main/script.js

こちらのサンプルを見るとmultipart/form-dataを使ってポストしてました。

BloB指定になる模様なのでNode.jsからやる場合はfsモジュールで読み込みます。

`screenshot.png`という名前のファイルが同じ場所にあるイメージです。

```ts
'use strict'

import fs from 'fs';

const URL = `WebhookURL`;

const main = async () => {
    try {
        const filePath = `screenshot.png`;
        const file = fs.readFileSync(filePath)
        const formData = new FormData()
        formData.append('file', new Blob([file], { type: 'image/png' }), 'file.png')

        const response = await fetch(URL, {
            method: 'POST',
            body: formData
        });
    
        const responseData = await response.json();
        console.log(response.status);
        console.log(responseData);
    } catch (error) {
        console.log(error);
    }

}

main();

```

実行するとこんな感じ。サーバー不要でありがたいですね。

> ![スクリーンショット 2024-01-05 19.34.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/4726eea2-f1bd-d396-371f-092dbd8985d4.png "スクリーンショット 2024-01-05 19.34.50.png")

## おまけ テキストも同時に送る

"payload_json"を指定してあげればテキストも一緒に送信できます。

```js
'use strict'

import fs from 'fs';

const URL = `WebhookURL`;

//テキストも送信
const payload = {
    username: 'n0bisuke BOT',
    content: 'Node.js Fetch APIからポスト'
};

const main = async () => {
    try {
        const filePath = `screenshot.png`;
        const file = fs.readFileSync(filePath)
        const formData = new FormData()
        formData.append('file', new Blob([file], { type: 'image/png' }), 'file.png')
        formData.append("payload_json", JSON.stringify(payload)); //テキストも送信

        const response = await fetch(URL, {
            method: 'POST',
            body: formData
        });
    
        const responseData = await response.json();
        console.log(response.status);
        console.log(responseData);
    } catch (error) {
        console.log(error);
    }

}

main();

```

> ![スクリーンショット 2024-01-05 19.37.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/1dd0296e-d1b4-f68d-64ab-48d8ebac36e5.png "スクリーンショット 2024-01-05 19.37.46.png")

## 参考

https://zenn.dev/suzuesa/scraps/4b34463048688d
有効グラフの隣接行列の覚え方

毎回忘れてしまうのでメモ

## 前提
隣接行列は節点空間から節点空間への線形写像で，元信号を1-hop先へ写す．

<figure>
    <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642842/5278c583-fac6-bb10-93cf-f420c02fa608.png">
</figure>

<figure>
    <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642842/a426e4ff-4129-2936-8b14-4e24af1f794f.png">
</figure>

左図の元信号を隣接行列で写すと，右図のようになる．
逆に，そのような行列$\boldsymbol{A}$ が有向グラフの隣接行列である．
これを式で表すと，
```math
\boldsymbol{A}(f_1, f_2, f_3, f_4)^\top = (f_3, f_1, f_2, f_2)^\top
```
となる．
## 有向グラフの隣接行列
以上の議論から，今回の例における隣接行列は以下のようになる．
```math
\boldsymbol{A} = 
\begin{pmatrix}
 0 & 0 & 1 & 0 \\
 1 & 0 & 0 & 0 \\
 0 & 1 & 0 & 0 \\
 0 & 1 & 0 & 0 
\end{pmatrix}
```
列ごとに見ると解釈しやすく，各列は元信号がどこに写るかを表している．
例えば，1列目では(2,1)成分のみ1であり，元信号の$f_1$は節点2に写ることを表している．

実際，信号$\boldsymbol{f}$に作用させると，

```math
\boldsymbol{A}\boldsymbol{f} = 
\begin{pmatrix}
 0 & 0 & 1 & 0 \\
 1 & 0 & 0 & 0 \\
 0 & 1 & 0 & 0 \\
 0 & 1 & 0 & 0 
\end{pmatrix}
\begin{pmatrix}
f_1 \\ f_2 \\ f_3 \\ f_4
\end{pmatrix}
=
\begin{pmatrix}
f_3 \\ f_1 \\ f_2 \\ f_2
\end{pmatrix}
```
となり，たしかに一致する．
信じてたのに裏切られた気分（JSのconstは変更可能←）

# はじめに

独学趣味プログラマです。

独学で趣味だと、自分なりの流儀がいろいろとできてきますよね。
命名規則とか、functionを全てアロー関数で書くとか、逆にアロー関数憎し！！とか。
そんな「自分ルール」の一つが、
#### varは絶対に使わない。
#### letも極力使わず、constで書く。

でした。深い理由はないです。

`var`に関してはなんか問題あるって聞いたから。

`let`ではなく`const`である理由は、
イミュータブルという概念（というかHaskell）に何となく憧れがあるから。
# 問題
さて、問題が起こったのは偶然でした。
```js
const todayAt18 = new Date();
todayAt18.setHours(18, 0, 0, 0); // 時間を18時、分、秒、ミリ秒を0に設定
```
とある文脈で、ChatGPTが提示してきたコードです。

#### あれ？constなのに変更されてる？

ChatGPTに聞いてみると……

> JavaScriptにおいて、`const` キーワードで宣言された変数は再代入ができませんが、オブジェクトや配列の内容は変更することが可能です。`const` は変数が指すメモリアドレスを固定するもので、そのメモリアドレスに格納されているデータの変更を防ぐものではありません。

> 例えば、`const` で宣言された `Date` オブジェクトは、そのプロパティ（例えば日付や時間）を変更することができます。以下のコードでは、`todayAt18` は一度 `const` で宣言されていますが、その後に `setHours` メソッドを使って時間を変更しています。

```javascript
const todayAt18 = new Date();
todayAt18.setHours(18, 0, 0, 0); // 時間を18時に設定
```

> このコードでは `todayAt18` 変数自体に新しいオブジェクトを再代入することはできませんが、`todayAt18` が指す `Date` オブジェクトの中身（この場合は時間）を変更することは可能です。

#### え、あ、ほんとに？

# 解決方法
調べてみたところ、ほんとにそのようです。
つまり、
### 配列やオブジェクトなら中身をいくらでも変更できる。

まじか……俺信じてたのに……

じゃあ`const` (定数の意) なんて名前にすんなよ！！全然定数じゃねえよ！！

### じゃあconstって何？
簡単に聞いた話ですが、
#### メモリの先頭を保持してるイメージ
だそうです。なるほど。
だから、配列がいくら変わろうが、`length`が長くなろうが、一向にかまわんというわけですね。

ゆるゆるじゃん……

ただ、配列 in 配列 はできないらしい。
# 終わりに

#### 定数だけでコードを書いてる俺
に酔ってたのに……
ただのめんどくさいバカじゃん……

と、少し悲しくなりつつ。

とはいえ、`const`を極力使う意識（イミュータブル）は
いい感じに脳の刺激になってると感じるので
これからも続けていこうと思います。
NetworkX入門 (1)

# インストール
## Anaconda
自分はAnacondaを使っているので、次のようにインストールした：

`conda install networkx`

NetworkX自体に画像書き出し機能がないので、matplotlibもインストールしておく。

`conda install matplotlib`

## pip
pipを使用している人は、次の通り：

`pip install networkx`

`pip install matplotlib`

# 小さいグラフを作る
```:python
import networkx as nx
import matplotlib.pyplot as plt

G = nx.Graph()
G.add_node(1)
G.add_edge(1, 2)
```

# 画像を生成して確認してみる
```:python
nx.draw_networkx(G)
plt.savefig('file01.png')
```

こうすると、file01.pngが生成される。

![file01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78475/35efd6a5-59f2-c5bf-ec81-9f314ae7c06d.png)

ノードが２つと、エッジが１つのグラフができている。
簡単に説明すると、`G.add_node(1)`で①のノードを追加し、`G.add_edge(1, 2)`で①から②へのエッジを追加している。②のノードは明示的に追加していないが、エッジ追加により追加される。
ノードより、エッジ操作の方が優先される印象。

# 画像以外の確認方法
```:python
print(G.nodes)
print(G.edges)
```

こうすると、次のように出力される。

```
[1, 2]
[(1, 2)]
```

画像がないとわかりにくく感じるだろうが、慣れてくると、これだけでも十分にわかってしまう。
mindの研究 その64

# 概要
mindが、謎なので調べてみた。
ハッカソンなので、開発言語比較やってみた。

# 開発言語比較

|言語	|mind	|c#	|javascript	|elixir	|vbs
|:-|:-|:-|:-|:-|:-|
|日本語で書ける。	|〇	|×	|×	|×	|×
|exeが作れる。	    |〇	|〇	|×	|×	|×
|win32apiが簡単に呼べる。	|〇	|〇	|×	|×	|×
|jsonパーサ	        |×	|〇	|〇	|〇	|×
|正規表現エンジン	|×	|〇	|〇	|〇	|〇
|ソケット	        |弱い	|〇	|〇	|〇	|×
|gui	            |×	|〇	|〇	|×	|×
|ActiveXが簡単に呼べる。	|×	|×	|×	|×	|〇
|データーベース	|×	|〇	|〇	|〇	|〇
|乱数	|×	|〇	|〇	|〇	|〇
|使い所	|rpa	|exe	|web	|embedded	|rpa


以上。
【Laravel10】変数展開の際に気をつけること（エラー対処：syntax error, unexpected token）

Laravelのビューファイル（拡張子「.blade.php」）にif文を記述したところ、構文エラーが発生するなどの問題が発生してしまいました。
初歩的な内容ではありますが、対処方法の備忘録として作成した記事です。
# 問題
* 「syntax error, unexpected token "<"」というエラーが発生。
* 記述した関数がそのままブラウザに表示される。
# エラー発生時のコード
※$items：itemsテーブルから取得したデータ。

```php:resources\views\item\example.blade.php
                        {{-- 省略 --}}
                        <tbody>
                            @foreach ($items as $item)
                                <tr>
                                    <td>{{ $item->id }}</td>
                                    <td><a href="items/detail/{{ $item->id }}">
                                        @if(mb_strlen({{ $item->name }})  > 10 )
                                            mb_substr({{ $item->name }}, 0, 10).'……' 
                                        @else
                                            {{$item->name}} 
                                        @endif
                                    </a></td>
                                    {{-- 省略 --}}
                                </tr>
                            @endforeach
                        </tbody>
                        {{-- 省略 --}}
```
# 原因
* 二重中括弧（二重波括弧）を余分に記述していた。
* 変数展開のために二重中括弧で囲んだ箇所があったが、囲む位置を間違えていた。
# 修正後のコード
* if文の条件部分から二重中括弧を削除
　　　⇒エラーが発生しなくなりました。
* 二重中括弧で囲む箇所を修正
　　修正前：【mb_substr({{ $item->name }}, 0, 10).'……'】
　　修正後：【{{mb_substr( $item->name, 0, 10).'……' }}】
　　　⇒期待通りに表示されるようになりました。
```php:resources\views\item\example.blade.php
                        {{-- 省略 --}}
                        <tbody>
                            @foreach ($items as $item)
                                <tr>
                                    <td>{{ $item->id }}</td>
                                    <td><a href="items/detail/{{ $item->id }}">
                                        @if(mb_strlen( $item->name )  > 10 )
                                        {{mb_substr( $item->name, 0, 10).'……' }}
                                        @else
                                            {{$item->name}} 
                                        @endif
                                    </a></td>
                                    {{-- 省略 --}}
                                </tr>
                            @endforeach
                        </tbody>
                        {{-- 省略 --}}
```
# 参考
[readouble Laravel 10.x Bladeテンプレート](https://readouble.com/laravel/10.x/ja/blade.html)
テキストエディタでコマンドを作って貼るのをやめる。

## はじめに

複数のIPアドレスに対して同じようなコマンドを大量に実行する場合に、テキストエディタで編集して貼り付けることで対応出来ますが、ループで実行したほうが簡単なのでその方法を記載しておきます。

## 説明

この例では複数のIPアドレスに対して再起動コマンドを実行する例を記載します。

以下のコマンドを実行すると入力待ちになります。

```
[root@ol72 ~]# while read ip
> do
> ssh root@${ip} 'shutdown -r now'
> sleep 1
> done
```

IPアドレスのリストを貼り付けます。

```
192.168.100.101
192.168.100.102
192.168.100.103
```

1行読み込む（貼り付ける）ごとにコマンドが実行されます。
この例では実際には以下のコマンドが実行されます。

```
ssh root@192.168.100.101 'shutdown -r now'
ssh root@192.168.100.102 'shutdown -r now'
ssh root@192.168.100.103 'shutdown -r now'
```

SSH接続をしているので公開鍵認証の設定をしておくと良いかと思います。

最終行まで読み込むと再度入力待ちとなりますので、Ctrl-Dを入力するとプロンプトに戻ります。

```
Ctrl-D
[root@ol72 ~]#
```

テキストエディタでコマンドを作成していると編集する行数も多く誤りが発生する事があるので上記の方法のほうが良いかと思います。
ovfファイルの作成方法

### 目的
システム障害では、障害対策として
・サーバのバックアップやスナップショットを取得する
・データベースをバックアップし、NASやS3などの外部のストレージにて保管する
・バックアップ運用がない場合、高可用性構成（High Availability）構成とし、ミッションクリティカルでもサービスを継続する
といった運用が必要となります。

一方、WindowsPCで1 台の仮想マシンを実行するユーティリティを利用しており、
自作の仮想マシンで何らかの不具合が発生した場合、
基本的には何も実装していないため、バックアップ運用を行う仕組みを構築する必要があります。

仕組みを検討した結果、VMWare Workstation Playerインストール時にovftoolを装備しているため、
ovftoolを用いてサーバテンプレートとしてエクスポートすることを目的としています。

---

### 前提
自作サーバの要件としては下記になります。
* ホストOS：Windows10 CPU、メモリ：8GB
* VMWare Workstation Player 17がインストール済
* <サーバ名>.vmxファイルがあり、vmdkという拡張子の他、サーバを稼働するために必要なファイルがある。
* 仮想サーバのファイルサイズは数10GB

---

### 実施内容

OVFファイルをエクスポートするコマンドが用意されています。
コマンドプロンプトを用いて、ovftool.exeを使用します。
ovftool.exe --noImageFiles [vmxファイルフルパス] [ovfファイルフルパス]
* イメージでマウントされているisoファイルを入れたくない場合、noImageFilesオプションを指定。
* vmxファイルフルパス：ovfファイルを取得したい仮想マシンのvmxファイルのフルパスを指定
* ovfファイルフルパス：作成したovfファイルのフルパスを指定します。事前にフォルダを作成する必要はありません。

---
* ※サーバ名や、パスに関しては、秘匿としたいので、仮名にしています。

```
C:\Users\USER>"C:\Program Files (x86)\VMware\VMware Player\OVFTool\ovftool.exe" --noImageFiles "F:\フニャフニャ～\homeserver_test\homeserver_test.vmx" "F:\フニャフニャ～\homeserver_test_ovf\homeserver_test.ovf"
Opening VMX source: F:\フニャフニャ～\homeserver_test\homeserver_test.vmx
Opening OVF target: F:\フニャフニャ～\homeserver_test_ovf\homeserver_test.ovf
Writing OVF package: F:\フニャフニャ～\homeserver_test_ovf\homeserver_test.ovf
Disk progress: 1%

```

---


上記実行完了するとDisk progressが100%となりますので、
該当のファイルがあるか確認してみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551020/64b41337-29fe-f4ea-3aa0-6766ceffa49d.png)

---

### ovfファイルからサーバを起動する
ovfファイルを使用して仮想サーバを作成することが可能です。
注意点としては、コピー前の設定と変わらないため、
IPアドレスが固定になっているかつ、新しいサーバとして作成する場合、
変更が必要となります。

---

### あとがき
ovfファイル自体は容易に作成ができますが、
Windowsバッチファイルとして、仮想マシンが停止していたらovftoolを自動的に実行する仕組みがあればうれしいですね…
（停止するときのトリガーを検知する仕組みが難しいですがもう少し調べてみようと思います★）
```
@echo off
set VMX_FILE=%1
set OVF_OUTPUT_PATH=%2
set OVF_TOOL_PATH="C:\Program Files (x86)\VMware\VMware Player\OVFTool\ovftool.exe"

REM Check if ovftool is installed
if not exist %OVF_TOOL_PATH% (
    echo Error: VMware OVF Tool is not installed.
    exit /b 1
)

REM Check if the virtual machine is powered off
%OVF_TOOL_PATH% "vi://%VMX_FILE%" > nul 2>&1
if %errorlevel% equ 0 (
    echo Virtual machine is powered off. Creating OVF file...
    %OVF_TOOL_PATH% --noImageFiles %VMX_FILE% %OVF_OUTPUT_PATH%
) else (
    echo The virtual machine is running. ほげほげお!
    exit /b 1
)

```
```
create_ovf.bat vmx ovf
```
そのエラーはいつからどのくらい出ているのか。

## はじめに

障害対応等でエラーがいつからどのくらい出ているのかわかると原因特定や状況説明に役立ちます。

## 説明

以下は16時台のメールログに特定のキーワードがいつからどのくらい出ているのかを1分単位で集計したものです。

```
[root@ol72 ~]# grep 'Jan  5 16:' /var/log/maillog | grep -i 'reject' | cut -d : -f 1-2 | sort | uniq -c
      3 Jan  5 16:00
      0 Jan  5 16:01
      2 Jan  5 16:02
    110 Jan  5 16:03
    278 Jan  5 16:04
    511 Jan  5 16:05
   1001 Jan  5 16:06
   1981 Jan  5 16:07
   2000 Jan  5 16:08
   3084 Jan  5 16:08
   5987 Jan  5 16:10
   4532 Jan  5 16:11
   6271 Jan  5 16:12
   2354 Jan  5 16:13
[root@ol72 ~]#
```

コマンドの指定は以下の通りです。

|コマンド|コマンド説明|
|:----|:----|
|'Jan  5 16:'|ログの検索対象とする時間帯|
|/var/log/maillog|検索対象のログファイル|
|'reject'|ログから検索するキーワード|
|cut -d : -f 1-2|ログから集計する時間の粒度（例では:区切りの1,2個目を指定することで月日時分までを指定している。cut -d : -f 1とすることで1時間単位にすることも可能）|
文字を入力したはずなのに…　アセンブラの謎

## アセンブラで扱う文字列

アセンブラで文字列を扱うとき、定数に書き込まないで、strcpy()すると、
こんな感じに、数字が出力されるんですよね。

1819043144

```c:
#include <stdio.h>
#inlcude <stdlib.h>
#include <string.h>

int main(void)
{
    char *p = (char*)malloc(16);
    strcpy(p, "Hell");
    printf("%s\n", p);
    
    return 0;
}
```
上記のコードを、

```bash:
gcc -masm=intel -S test.c
```

すると、下記のようなアセンブリが出力されます。

この数字の意味が知りたくて調査してました。

ポインタを使うと、ラベルがついてそのラベルにアクセスるみたいな感じになると思いますが、
文字列リテラル 'Hell' を

strcpy(destination, "Hell");

すると、謎の数字に変換されちゃうんです。

mov DWORD PTR [rax], 1819043144

と出力され、Hellと書いたはずの文字列が数字に変換されているようです。

かなり苦戦しましたが、決着しました。

```bash:
mov DWORD PTR 8[rax], 1819043144

mov edi, 16
call malloc@PLT
mov QWORD PTR -8[rbp], rax

mov rax, QWORD PTR -8[rbp]
mov DWORD PTR [rax], 1819043144
mov BYTE PTR 4[rax], 0

mov rax, QWORD PTR -8[rbp]
mov rdi, rax
call puts@PLT

mov eax, 0

leave

ret

```
定数の1819043144は、16進数の'6c6c6548'を32ビットの整数に収まるように十進数に変換したもので、Cのコードからのアセンブリコードへの変換の過程を以下に示します。

1...
'Hell' -----> 16進数で'48656c6c'

2...
リトルエンディアンにバイトオーダーを変換すると

'48656c6c' -----> '6c6c6548'

3...
'Hell' はアスキーで４文字です。

私のマシンの処理系で、int は ４バイトなので、'Hell'までは int に収まります。

なので、'6c6c6548' は、
~~QWORDに収まるということですね。~~

ここ間違いなんですが、
DWORDです。４バイトなのでDWORDです。

DWORDに収まるということですね。に訂正いたします。

4...
計算していきます

6 * 16^7 = 1,610,612,736
c * 16^6 =   201,326,592
6 * 16^5 =     6,291,456
c * 16^4 =       786,432
6 * 16^3 =        84,576
5 * 16^2 =         1,280
4 * 16^1 =            64
8        =             8

           1,819,043,144

になるということになります。

確認が取れてないんですが、

```bash:
mov BYTE PTR 4[rax], 0
```
は、Null文字じゃないかと思っています。

でも、ちょっと変なんですが、

最初、'Hello'で調査してたんですが、５文字あるので int に収まらないかなと思って、'Hell'(地獄)に変えてアセンブリ変換しては動かすという調査をしていました。

でも、'Hello'で出力すると、

```bash:
mov BYTE PTR 4[rax], 111
//間違いです。
mov WORD PTR 4[rax], 111
//に訂正いたします
//'o'を一文字付加するだけなので、BYTE でも行けるんじゃないかな？と思ってやった跡が残ってしまっていたようです。
```

が付きます。
Null文字はそのあとに付加されないんですよね。
二文字、たとえば"oo"を付けて、Null文字らしきものが再度ついていれば、断定できるかもしれません。

111 = 0x6f

0x6f = 'o' 

確かに、'Hello'となりますが、なんですかね？

アセンブラ、ムズイです。
Ghost-CMSインストール時のエラー[2024]

Ghost（CMS）のインストール「`ghost install`」で「Systemdを使わない」を選択すると、謎の権限エラーになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/990851/580a9159-d7d5-442c-ae3c-cd18c845d3d4.png)

```Error:Error
Message: The content folder is not owned by the current user.
Ensure the content folder has correct permissions and try again
```


調べてみると、Ghost CMSをインストールしたディレクトリ内で、`content`ディレクトリ配下のすべてが作成コマンドを打ったユーザーではなく`ghost`ユーザーで作成されていた。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/990851/b1b71556-ddc0-c7be-c1a5-55f3f51404c4.png)



ちょっと危険な気もするが、`content`含め、その配下をすべて権限変更したところ何とかなった。

```
sudo chown my-user:my-user -R content
```

これも含めて、Ghost-cliを使ったGhost-CMSのインストールでは権限がらみの問題がやけに多いみたい。
あとやたら負荷がやばい。EC2のt2.microでやろうとしたら落ちた。

作ってもらってる分際で文句は言えないけども...。


https://github.com/TryGhost/Ghost-CLI/issues/711

https://manshurtigh.se/fix-permission-and-ownership-issues-when-updating-ghost/

ちなみに冒頭、「Systemdを使う」を選択しても謎のエラーに悩まされる模様。

```Error:Error
Could not communicate with Ghost
```

Node系ののBlogCMSの道行きはまだ暗そうですね...。

探している途中で遭遇した記事

https://forum.ghost.org/t/cant-start-or-update-ghost-ghost-undefined/26384

https://forum.ghost.org/t/ghost-start-results-in-could-not-communicate-with-ghost-error/10180
EC2インスタンスのEBSのボリュームが枯渇したときの対処方法

## 概要
自社のメンバーからWebServerに反映できないと連絡を受け対応した。
ベストプラクティスでは無いと思うのであくまでご参考程度に読んでいただけると幸いです。

備忘録として記事に残しておく。

## 現状を知る
メンバーからディスク容量が一杯というエラーが出ますと言われていたのでとりあえずEC2上のディスク容量を確認してみる。
```terminal
[サーバー上のアプリディレクトリ内]$ df -h
```

案の定ディスク容量が枯渇していた。
```terminal
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/xvda1       493G  0G  493G   100% /
devtmpfs         490M   56K  490M    1% /dev
tmpfs            498M     0  498M    0% /dev/shm
```

### どのファイルが容量をたくさん食っているかを調べる

とりあえずホームディレクトリは問題なさそう。
```terminal
[サーバー上のアプリディレクトリ内]$ sudo du -h --max-depth=1
744M    ./backup
8.0K    ./shell
24K     ./.ssh
8.0K    ./.pki
8.0K    ./.vim
744M    .
```

ルートディレクトリを見てみる。
`/var`の使用率が凄い。
```terminal
[サーバー上のアプリディレクトリ内]$ sudo du -h --max-depth=1 /
56K     /dev
45M     /lib
476G    /var
942M    /usr
56M     /root
7.4M    /bin
24K     /tmp
0       /sys
~~ 省略 ~~
492G    /
```

更に深ぼっていく。
apacheのlogファイルが凄いことになっている。
```terminal
[サーバー上のアプリディレクトリ内]$ sudo du -h --max-depth=1 /var/log
1.8M    /var/log/letsencrypt
466GG    /var/log/httpd
4.0K    /var/log/ntpstats
28M     /var/log/audit
8.0K    /var/log/mail
176G    /var/log
```

更に更に調べていく。
どうやらapacheのアクセスログがストレージをほぼほぼ独占している状態みたい。
```terminal
[サーバー上のアプリディレクトリ内]$ sudo ls -lh /var/log/httpd/xxx.com/
合計 466G
-rw-r--r-- 1 root root 405G  1月  5 09:19 access_log
-rw-r--r-- 1 root root  61G  1月  5 09:19 error_log
```
## 対処方法
使用していないログファイル&割りと緊急事態だったのでとりあえずログファイルの容量を削りました。
ログファイルのサイズがあまりにも大きいため一度で削除するのは危険と判断し、少しずつ消していくことに。

:::note warn
＊稼働しているWebServerを止めてからApacheのログを削除した方が良いです。
:::

とりあえず2GBだけ削減。
```terminal
sudo truncate -s 403G /var/log/httpd/xxx.com/access_log
```
この時にはWebServerへの反映は行えるようになっていました。
特にapacheの再起動等は行わなくても大丈夫でした。

問題ないと判断し、100GB分のログは残るようにして、50GBずつ削除していきました。
```terminal
sudo truncate -s 350G /var/log/httpd/xxx.com/access_log
```

## 今後、枯渇しないようにする
apacheのaccess_logは今後も必要ないと判断し、cronで定期的に100GB分のログだけ残して(これでもかなり多いが)削除していくようにしました。
```terminal
0 0 * * * /usr/bin/sudo truncate -s 100G /var/log/httpd/xxx.com/access_log
```
このサーバーの時間設定がUTCの為、日本時間の朝9時にこのコマンドが走る。

本来ならばapacheの`logrotate`を使用して保存から30日経過したログファイルは削除みたいな感じでやった方が良さそうですが、、。


## 最後に
今回は緊急事態でしたが焦らずに対応できてよかったです。

ではまたっ！！
突然 git のリモートリポジトリにアクセスできなくなった

## 年明け早々gitにトラブル発生

年明け早々、クリアな頭でプルリクに対するレビュー対応して、再度PUSHしようとすると以下のようなメッセージが・・・
（環境はWSL2のUbuntu）
```
ssh: Could not resolve hostname github.com: Temporary failure in name resolution
fatal: Could not read from remote repository.

Please make sure you have correct access rights and the repository exists.
```

勘弁してくれ。

## やったこと
[1. SSHキーが登録されているか確認](#1-sshキーが登録されているか確認)

[2. WSL、PCの再起動](#2-wsl、pcの再起動)

[3. wi-fiの再起動](#3-wi-fiの再起動)

[4. pingが通るか確認](#4-pingが通るか確認)

[5. Windowsのファイアウォールにwsl2からの通信を許可するルールを追加する](#5-windowsのファイアウォールにwsl2からの通信を許可するルールを追加する)

[6. /etc/resolv.conf にnameserverを記述](#6-/etc/resolvconf-にnameserverを記述)

※今回の事象、いくつかの問題に切り分けられそうなので以下にリストアップしてみました
SSHキーの問題：１
WSLやWindowsシステムの問題：２
インターネット接続の問題：３，４
DNSの問題：５，６

### １．SSHキーが登録されているか確認
まずはエラーメッセージにあった
```
Please make sure you have correct access rights and the repository exists.
```
この部分を解決する。
といっても、Githubのマイページから、settings > SSH and GPG keys
に飛んで、keyが登録されているかを確認するだけ。
今回はちゃんと登録されていたのでOK。

### 2. WSL、PCの再起動

これは皆さんおなじみの解決方法。
とはいえ、エラーが起きてぐるぐるしてる頭では意外と忘れがちなもの。
割と何も考えずに再起動してみると上手くいくことが多い（特にExcel）。
これで上手くいけば、たぶんWSLやWindowsに何かしらの不具合が生じていた可能性が高そう。

### 3. wi-fiの再起動

インターネット接続に関する問題かどうかを切り分けたいですよね。
これに関してはStackOverFlowで言及がありました。
https://stackoverflow.com/questions/20370294/git-could-not-resolve-host-github-com-error-while-cloning-remote-repository-in

> Spent a couple hours trying to fix this. 
Re-connecting my wifi did the trick.

wifiを接続し直したら直ることもあるみたいですね。
今回は治らなかったので次に進みます。。。

### 4. pingが通るか確認

次は、本当にインターネットに接続されているか？を確認する。
まずはWSL上でpingを打ってみる。

```
$ ping github.com
ping: github.com: Temporary failure in name resolution
```

ダメっぽい。。。

今度はコマンドプロンプトで通るかやってみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513151/52b52fb3-7d14-0735-da42-3deb195ba953.png)
お、通るやんけ

今度はgithubのネームサーバのIPアドレス20.27.177.113にWSLからpingを打ってみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513151/fbc91a60-bd58-c14d-73d3-be226e0e998e.png)
どうやら通るみたいだ。
なので、インターネットはつながっているが、名前解決だけできてなさそうということがわかる。

### 5. WindowsのファイアウォールにWSL2からの通信を許可するルールを追加する

この方法はChatGPTとおしゃべりしてる時に教えてもらった方法。
「firewall確認してみたら？」と言ってくれた。
調べたらドストライクなことを書いてくださっている記事を見つけたので、実行。
[WSL2でDNSの名前解決ができなくなったときの対処法(Firewallにルールを追加)](https://qiita.com/sunasaji/items/55d6d74041d1d4e9bce2)

上記実行した結果、pingが通りました。。。
pingが通ったので、git pushも問題なく実行できました。
こちらの記事に命を救われました。ありがとうございました。

### 6. /etc/resolv.conf にnameserverを記述

これは5でも解決しなかった場合の最終手段。
設定ファイルに接続したいDNSサーバーのIPアドレスを記入するだけ。
今回はgithubのIPアドレスを入力。
※sudoしているのはread only で開かないようにするため。他の方法あれば教えてください

```
$ sudo vi /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 20.27.177.113 

```
`:wq`で上書き保存を忘れずに。
さすがにここまですれば通るはず。

## おわりに

今回はgit push しようとして失敗していたが、
リモートリポジトリにアクセスできていないので、git cloneしようとしている人にも刺さる内容かなと思う。
今回のエラーはDNSの問題であることが分かったが、
結局なぜ突然名前解決できなくなってしまったかは不明なままだった。
CloudFormationで作成したVPCエンドポイントにAWS CLIにてタグを作成しよう

# はじめに
CloudFormationで作成したVPCエンドポイントにAWS CLIにてタグを作成する。

CloudFormationにて事前にVPCエンドポイントを作成する必要があり、その際にOutputsにて、VPCエンドポイントIDを出力する必要がある。

# スクリプト

```sh:create_vpc_endpoint_tags.sh
#!/bin/sh

cd `dirname $0`

STACK_NAME=${CloudFormationのスタック名}

# CloudFormationのスタックの出力からエンドポイントのIDを取得する
VPC_ENDPOINT_IDS=($(
    aws cloudformation describe-stacks \
        --stack-name ${STACK_NAME} \
        --query "Stacks[*].Outputs[*].OutputValue[]" \
        --output text
    ))
    
# CloudFormationのスタックの出力名を取得する
EXPORT_NAMES=（$(
    aws cloudformation describe-stacks \
        --stack-name ${STACK_NAME} \
        --query "Stacks[*].Outputs[*].ExportName[]" \
        --output text
    )）

# VPCエンドポイントにタグを作成する
for i in ${!VPC_ENDPOINT_IDS[@]}
do
    aws ec2 delete-tags \
        --resources ${VPC_ENDPOINT_IDS[$i]}

    aws ec2 create-tags \
        --resources ${VPC_ENDPOINT_IDS[$i]} \
        --tags "Key=Name,Value=${EXPORT_NAMES[$i]}"
done

exit 0
```
2023年の振り返りと2024年の意気込み

# 2023年の振り返り
## 本業
　私は，IT企業でエンジニアをやっている１年目の社会人です．第一志望の企業に内定を頂き希望を胸に入社したものの，配属先は全然希望通りじゃない．理想とのギャップに苦悩を抱えていましたが人間住めば都というもので働いてみると意外と自由で働きやすい．好きに開発がし放題．自分で案件を取ってきて，それを形にするために動き続ける．誰かに仕事を与えられるのではなく，自分の足で掴みに行って成果を上げる．そんな部署でした（部署でこんな働き方をしているのは自分だけですが，，，）．おそらく，希望通りの部署であれば目の前の仕事に追われていただけかも～～
 
ということで，１年目の成果をここに記録しておきます．
1. 資格数：６個
1. 案件数：２個
1. 売上：数百万（しょぼいので正確な数字は載せません）
1. 開発したアプリの実証実験＆2024年に大学と連携で実用開始

結構頑張ったのでは！？？
でも，まだ甘えていたくはないので頑張ります～～～

はい，次今年の目標

# 2024年目標
1. 資格取得：２か月に１個の目安
1. 副業で１円でもいいから稼ぐ！
1. 関わっているプロジェクトの成功！！
1. 毎月２冊の読書（小説から専門書まで幅広く）
1. TOEIC 600点を取る！（まずはスモールステップで！）
1. Qiitaの記事を毎月一本は上げる，，，


という感じですかね～
頑張れるのか俺！といった感じですが男ならやるしかないということで今年も頑張ります～～～
機械学習の成功への鍵：正規化と標準化の理解

## はじめに
データサイエンスと機械学習において、データの前処理は非常に重要なステップです。特に、データのスケーリング（正規化と標準化）は、アルゴリズムのパフォーマンスに大きな影響を与えることがあります。ここでは、例としてtransactions.csv というデータセットを使用して、正規化と標準化の違いを具体的に説明します。

### データセット
transactions.csv は、以下のような列を持つトランザクションデータです。

TransactionID: トランザクションID
TransactionDate: トランザクションの日付
CustomerName: 顧客の名前
ProductName: 商品名
Quantity: 数量
UnitPrice: 単価
TotalPrice: 合計価格
PaymentMethod: 支払い方法

![スクリーンショット 2024-01-05 17.47.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3364428/e515cb9b-bf1c-a3f3-9303-3872e3adb3e3.png)

## 正規化と標準化
### 正規化 (Normalization)
正規化は、データを0と1の間にスケーリングするプロセスです。これにより、異なるスケールを持つ変数間の比較を容易にします。例えば、0が最小値、1が最大値となるようにデータを変換します。一般的な正規化の式は次の通りです：

![スクリーンショット 2024-01-05 17.34.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3364428/4c544acb-8531-5bb8-b305-be14204fe6ec.png)

## 標準化 (Standardization)
標準化は、データの平均を0、標準偏差を1に変換するプロセスです。これにより、データは正規分布に近い形になります。標準化の一般的な式は以下です：

![スクリーンショット 2024-01-05 17.34.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3364428/726415f1-df90-a51d-e17f-4f1aa934c4db.png)

ここで μ は平均、σ は標準偏差です。
## Pythonでの実装
以下のPythonコードは、Quantity と UnitPrice 列に対して正規化と標準化を実行します。
```
import pandas as pd
from sklearn.preprocessing import MinMaxScaler, StandardScaler

transactions_data = pd.read_csv('transactions.csv')

# 正規化と標準化のためのスケーラーの初期化
min_max_scaler = MinMaxScaler()
standard_scaler = StandardScaler()

# 正規化
transactions_data['Normalized_Quantity'] = min_max_scaler.fit_transform(transactions_data[['Quantity']])
transactions_data['Normalized_UnitPrice'] = min_max_scaler.fit_transform(transactions_data[['UnitPrice']])

# 標準化
transactions_data['Standardized_Quantity'] = standard_scaler.fit_transform(transactions_data[['Quantity']])
transactions_data['Standardized_UnitPrice'] = standard_scaler.fit_transform(transactions_data[['UnitPrice']])
```
このコードでは、データセット内の Quantity と UnitPrice 列をそれぞれ正規化および標準化します。MinMaxScaler と StandardScaler は、それぞれ正規化と標準化を行うためのscikit-learnライブラリのクラスです。
### 出力例
![スクリーンショット 2024-01-05 18.08.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3364428/31806f9f-543b-0d28-8111-4049114371ef.png)
Normalized_Quantity と Normalized_UnitPrice 列は、それぞれ正規化された Quantity と UnitPrice の値を示しています。このデータでは Quantity 列のすべての値が同じため、正規化後の値もすべて0になっています。
またStandardized_Quantity とStandardized_UnitPrice 列は、それぞれ標準化された Quantity と UnitPrice の値を示しています。
標準化では、データの平均が0、標準偏差が1になるように変換されます。
このデータでは Quantity 列の平均が1に近く、標準偏差が小さいため、標準化後の値はほぼ -0.5 になっています。

## まとめ
正規化と標準化は、データを異なる尺度に変換することで、機械学習アルゴリズムのパフォーマンスを向上させることができます。どちらの手法を使用するかは、データの特性と使用するアルゴリズムによって異なります。この記事が、これらの重要な前処理ステップの違いを理解するのに役立つことを願っています。
【Behavior Designer】ツリー内でノードの再評価が実行されない更新（Updateのみの処理）を構築する

# この記事について
表題のとおりです。
UnityでBehavior Treeを扱うことができるアセット「[Behavior Designer](https://assetstore.unity.com/packages/tools/visual-scripting/behavior-designer-behavior-trees-for-everyone-15277?locale=ja-JP)」ですが、「Updateのみ」を行うことに非常に苦戦しました。

- Unity Asset Store
 [Behavior Designer - Behavior Trees for Everyone](https://assetstore.unity.com/packages/tools/visual-scripting/behavior-designer-behavior-trees-for-everyone-15277?locale=ja-JP)

Compositeノードの「Abort Type」を設定したりいろいろ試しましたが、Behavior Tree自体が「状態が変わったら再評価を行う」ための仕組みですので、「Selector」ノードや「Sequence」ノードを設定するとただUpdateを行いたいだけなのに再評価が走ってしまいます。
それでもなんとかツリー内で処理を完結させたい。そんな場合の対処法備忘録です。

# 本題
私がやりたかったのは以下のケースです。

- 「Behavior Designer」別売りの「[Movement Pack](https://assetstore.unity.com/packages/tools/behavior-ai/behavior-designer-movement-pack-16853)」にある「Can See Object」が便利なので毎フレーム更新して対象の取得を行いたい。
- でも、別にツリー内の判定に使いたいわけではない。
- ツリーに配置すると「見えている(Success)」「見えていない(Failure)」の状態が変わるたびに他のノードにも再評価の被害が及ぶため、それを阻止したい。裏でUpdateが回っているような感じにしたい。

ということになります。

# 解決方法
以下の例のように構築します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272076/5dbb661c-db44-43ee-578b-e63dd81a0d48.png)

# 解説
Entry直下の「Parallel」ノードは「並列実行」とよく解説されています。
確かにその通りなのですが、並列実行できるということは「複数の状態を持てる」「ある状態を持っていても別の更新を行うことができる」ということになります。
これを根っこに設定すると、ざっくりと同時更新を行うブロックを複数設けることができます。
実行後の挙動を順を追ってみていくと以下のとおりです。

Treeの開始と同時にまず左側のブロックが評価されます。Repeaterを「Repeat Forever」に設定しているので、これ以降ずっとRepeaterは「Running」を返します。1フレームに無限ループしてしまわないよう、「Instant」のチェックを外しておきます。
そして、Repeaterが稼働し、「Can See Object」や「Can Hear Object」のUpdateが走ります。

根っこがParallelなので、左のブロックのRepeaterがRunningになったら、そのまま右のブロックの評価に移ります。右のブロックは普通のツリーと同じように振る舞います。

そして、例えば「Idle」のRunningに結果が決まったら次のフレームに移ります。

次のフレームでは、また左側のブロックのRepeaterが稼働し、「Can See Object」や「Can Hear Object」のUpdateが走ります。
通常、ツリーでは左側のノードの結果が変わった場合右側のノードの再評価が走りますが、Parallelなので左ブロックの結果は右ブロックに影響しません。
そしてそのまま右側のツリーは前フレームの続きから稼働を継続する、という挙動になり、要件が満たせました。

# おわりに
改めてになりますがこの設計が有用になるケースは
・別スクリプトで左側ブロック相当の処理を行ってもいいが、ツリーで内でUpdateすることで解決したい
という場合に限ります。

他に賢い方法があるかもしれませんが、以上が私なりの解決策になります。

お読みいただきありがとうございました。
Raspberry Pi 4Bと時雨堂のmomo

# 1. はじめに
　WebRTCを色々調査しなくてはならなくなったので、以前みたことあるサイトのライブラリを使って実験を開始することにした。
 * 使い慣れたRaspberry Pi 4Bを使う
 * OSはRaspberry Pi OSのBullseyeベースとBookwormベースを試す
     * 2023-05-03-raspios-bullseye-arm64-lite.img.xz
     * 2023-12-11-raspios-bookworm-arm64-lite.img.xz
 * カメラはUSBカメラ。Raspberry Piカメラは使わない。
 * 時雨堂のmomo（かなり前から知り合い内で話題になっている）
https://github.com/shiguredo/momo
 
# 2. 機材
* Raspberry Pi 4B
* UVCカメラ: Buffalo BSW500M

# 3. Bullseyeの場合
OSのインストールはRaspberry Pi Imagerを使う。古いOSは公式からダウンロードしておくといい。自分で変更したOSやローカルに保存したOSは、Use customを選択してインストールできる。
絶族は単純で、UVCカメラが繋がっているだけ。
Power -- Raspberry Pi 4B -- USB Cam

## 3.1. ライブラリの更新と追加
まずは定番
```
sudo apt update
sudo apt upgrade
```

## 3.2. momoの入手と展開
64bit OSなのでarmv8を使う。
```
wget https://github.com/shiguredo/momo/releases/download/2023.1.0/momo-2023.1.0_raspberry-pi-os_armv8.tar.gz
tar zxvf momo-2023.1.0_raspberry-pi-os_armv8.tar.gz
```

## 3.2. momoが必要とするライブラリの追加
もっと多いかとおもったが・・・
```
sudo apt install -y libsdl2-dev
sudo apt install -y libxtst6
```

## 3.3 パッケージインストール前に出たエラー
のちの検証用として記載しておく
```
./momo: error while loading shared libraries: libSDL2-2.0.so.0: cannot open shared object file: No such file or directory
./momo: error while loading shared libraries: libXtst.so.6: cannot open shared object file: No such file or directory
```

## 3.4. 失敗したコマンド
のちの検証用として記載しておく
SDLをインストールしようとして失敗した。apt upgradeの手前で試したのが要因か？
```
sudo apt install -y libSDL2-2.0
```

## 3.5 テストコマンド
--log-level 1 はデバッグログを出力する。
--no-audio-device　はマイクがないのでその対策
test　はテスト(開発)用にHTTPサーバー立ててくれる
```
./momo --log-level 1 --no-audio-device test
```

## 3.6 受信テスト
Raspberry PiのIPアドレスを使って、以下のようなURLにアクセスをすると、再生用(？)画面が出てくる。
```
http://＜IPアドレス＞:8080/html/test.html
```

Connectすると映像が出て、Disconnectすると映像が消える。
ちなみにもう一度、Connectすると、たまに失敗して映像がでない。さらにmomoのログが大量に出ていることがある。詳細な調査が必要。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865486/2114c9f6-300b-23f0-41e1-d4e41a9c8f44.png)

とりあえず映像が見えてので、この段階ではOKとする。

## 3.7 その他
解像度にFHDを指定した状態で、MJPEGのハードウェアエンコードも指定すると再生映像に黄色い帯が出てしまう。指定なしやHDの指定では出ない。
```
./momo --log-level 4 --no-audio-device --resolution FHD --hw-mjpeg-decoder 1 test
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865486/a03d07af-3d48-c19d-2b9a-8beeb2b51c76.png)



# 4. Bookwormの場合
未

# 5. 今後
まずはBookworm
その次は、もうちょっと細かいところ
その1 Nerves化構想を思い立つ〜Arch Linuxを元にT2チップを備えない古いIntel MacをNerves化しようとする日々

「Arch LinuxをT2チップを備えない古いIntel Macにインストールしようとする日々」シリーズに取り組んでいて，「Arch Linuxを元にT2チップを備えない古いIntel MacをNerves化しよう」という構想が沸々と湧いてまいりました．引き続き，駄文を連ねていこうと思います．

## 背景

2023年末， @pojiro に依頼して，Zybo Z7-10へのNerves移植を進めてきました．@pojiro が[Nerves 大好きな私が2023年にやったこと](https://qiita.com/pojiro/items/e66c1082462b955e83d9#移植)にまとめてくれています．

https://qiita.com/pojiro/items/e66c1082462b955e83d9#移植

気づいた人もいるかと思うのですが，これは，[Nerves，月へ行く](https://qiita.com/zacky1972/items/b9bde6aa6724960340ab)で構想を発表した，月面探査車YAOKI向けのKintex UltraScaleのFPGAボードによるNervesシステムの予行演習に位置付けているものです．

https://qiita.com/zacky1972/items/b9bde6aa6724960340ab

そういうわけで，私の中で，Nerves移植熱が急上昇しております．そして，LinuxとElixir, Nervesの関係性を深く理解したいという意欲に目覚めております．

## Arch Linuxに触れて湧いた，ある意欲

2023年末から，@mnishiguchi の勧めにしたがって，Arch Linuxと戯れてきました．その体験を通して得た経験知を，「Arch LinuxをT2チップを備えない古いIntel Macにインストールしようとする日々」シリーズと銘打ち，下記のようにしたためてきました．

1. [Arch LinuxをブートできるUSBメモリを用意する](https://qiita.com/zacky1972/items/9f447f9a11f91e90f6e8)
2. [デュアル・ブート環境にするためにパーティションを区切る](https://qiita.com/zacky1972/items/4b3d8240ff1f4a599908)
3. [いよいよArch Linuxをインストールする](https://qiita.com/zacky1972/items/da1db6795b84151186ab)
4. [ネットワークの設定を見る](https://qiita.com/zacky1972/items/fcce6bdeaf2b87697e3f)
5. [Mac Pro (Mid 2010)にArch Linuxをインストールする](https://qiita.com/zacky1972/items/2904a0a07f9335fdb2de)
6. [ネットワークが繋がらない最小構成のままElixirをインストールして実行してみる](https://qiita.com/zacky1972/items/9a145632c6c12c650bed)
7. [ネットワークが繋がらない最小構成のままElixirをasdfではなくソースコードビルドしてインストールする](https://qiita.com/zacky1972/items/ab537e53fd30ac0d15a6)

このまま，T2チップを備えない古いIntel MacをArch Linux化していっても良いのですが，6,7でElixirを入れられるということがわかってしまったので，「Nerves化したい！」という意欲が沸々と湧いてまいりました．

## 「Arch Linuxを元にT2チップを備えない古いIntel MacをNerves化しよう」という構想

Arch Linuxを元にすれば，T2チップを備えない古いIntel MacのNerves化は，なんとなく出来そうな予感がします！

つづく

https://qiita.com/zacky1972/items/4ce0032514978a7d2f1f
WorkSpaces作成時のエラー対処法

# 発生した事象
WorkSpaces作成時、WorkSpaces用のENIが作成されては消えてを繰り返し、最終的にWorkSpacesのステータスがエラーとなった。エラーメッセージには「There was an issue joining the WorkSpace to your domain. Verify that your service account is allowed to complete domain join operations. If you continue to see an issue, contact AWS Support.」と表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/11d0a986-8e85-2409-3d9c-46a7d762fa1a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/d865272b-3e38-24ce-fbd6-e4fc73b2ca34.png)

# 前提
認証にはSimple ADを使用。

# エラー発生原因と解決策
WorkSpacesにディレクトリを登録する際、下記画面のようにサブネットを指示するフェーズがある。ここで、誤ってSimple ADのドメインコントローラーが存在するサブネットを指定しなかった。そのためドメイン参加操作を完了することが出来ずにエラーが発生していた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/8da40c93-73cf-3c3a-bf0a-a1a735a7ce06.png)

上画像のようにディレクトリ登録時に正しいサブネットを指定したうえで、再度WorkSpaces作成時、無事作成できた。

# 参考URL

https://repost.aws/ja/knowledge-center/workspaces-domain-join-error
【Swift】Library,Framework, Packageの違いを理解する

## 実際のところ
便利な機能をインポートする目的や、処理の共通化の目的などでLibraryやFrameworkを使用したりするが、その違いを説明するのは難しい。
今回調べたので、記事にする。

## 違いを明確にしていく対象

- Library
- Framework
- Package

## その前に 〜StaticとDynamicの違い〜
それぞれを比較する前に、
リンクの方式としてStatic Link（静的リンク）とDynamic Link（動的リンク）がある。

リンクとは？
> プロジェクトのソースコードをコンパイルして生成されたオブジェクトファイルや外部ライブラリなどを全て連結(リンク)して実行ファイルまたは、ライブラリを生成する機能で、リンカ が行います。

https://yamatooo.blog/entry/2020/10/05/083000

ざっくり、アプリのような最終成果物を作るために必要な工程というイメージ。

StaticとDynamicの違い
- リンクされるタイミングの違い
    - Static：ビルド時にリンクされる
    - Dynamic：アプリ起動時にリンクされる
- アプリ内に含まれるかどうか
    - Static：含まれる（バイナリコードがアプリ内に保持される）
    - Dynamic：含まれない（バイナリコードへの参照がアプリ内に保持される）

App本体とWidgetなどのApp Extensionの両方などから参照される機能などは、Dynamic Link方式を採用することで、各アプリのファイルサイズを削減することができる。

詳しくは、[iOSDC2019 ライブラリのインポートとリンクの仕組み… / Kishikawa Katsumi](https://www.youtube.com/watch?v=FZoYyAEPJ8w&t=1070s) 参照

## それぞれの違いについて
### Library vs Framework
Bundleを持つか、持たないかの違い。

#### Bundleとは
> Appleはバンドルを使って、アプリ、フレームワーク、プラグイン、その他多くの特定のタイプのコンテンツを表現します。
> バンドルは含まれるリソースを明確に定義されたサブディレクトリに整理し、バンドル構造はプラットフォームやバンドルの種類によって異なります。
> バンドルオブジェクトを使うことで、バンドルの構造を知らなくてもバンドルのリソースにアクセスすることができます。

https://developer.apple.com/documentation/foundation/bundle

ディレクトリを一つのファイルのように見せるもの。
決まった構成で作られるので、ヘッダーファイルや画像ファイルなどを含めることができる。

- Frameworkを使うメリット
    - Bundleを持つことができるので、UIで使用する画像ファイルなども保持することができる

### Package vs Others
Packageをここで並べるのは適切でないのかもしれないが、理解が混同していたため、ここに記載する。

- Packageのメリット
    - 使用している依存関係を管理することができる

Package内から複数のLibraryやFrameworkなどを用いる場合に、それらのバージョンなどもSwiftファイルで簡単に管理できる。
一方、LibraryやFrameworkなどでそれらを実現したい場合は、独自にCocoaPodsなどを使用して、専用の依存関係を解決するファイルで管理する必要がある。

[library(name:type:targets:)](https://developer.apple.com/documentation/packagedescription/product/library(name:type:targets:))を使用して、リンク方式を指定することができる。

## 最後に
依存関係を整理するためにPackageを使用するのが、良さそうだが、
開発現場でSwift Package Managerを使用していなかったり、本当に少ない機能のみのコードを共通化したいとかであれば、FrameworkやLibraryの使用も検討してみても良さそうだ。

調べている途中に、WWDC2023の[Meet mergeable libraries
](https://developer.apple.com/videos/play/wwdc2023/10268/)というのに出会ったが、今回は力尽きたので、またの機会に。
軽く調べたところ、StaticとDynamicの良いところどりができるらしい。（ビルド時間とアプリの起動時間を共に短くすることができる）
めっちゃええやん。

## 参考
https://yamatooo.blog/entry/2020/10/03/083000
https://qiita.com/hironytic/items/3fcd825cc1ef135f5b0f
https://www.youtube.com/watch?time_continue=1070&v=FZoYyAEPJ8w&embeds_referring_euri=https%3A%2F%2Fzenn.dev%2F&feature=emb_title
https://medium.com/@zippicoder/libraries-frameworks-swift-packages-whats-the-difference-764f371444cd
Amazon Athenaを使ってJsonファイルを読み込もうとして数時間ハマった件について

# はじめに
Amazon Cognitoのユーザープールから任意の条件のユーザー情報を取得する必要があったので、Athenaを使うことにしました。
Athenaを初めて使うこともあり、セットアップにかなり時間がかかってしまいました。
今回はその際に躓いたところを紹介していきます。

# AthenaでJSONを読み込む
## 今回使用したJSONのデータ構造
Cognitoのユーザープールからデータ取得する際にはCLIを使用しました。
参考：[CognitoユーザープールからAWS CLIでユーザーを検索してみる](https://dev.classmethod.jp/articles/search-for-user-with-aws-cli-from-cognito-user-pool/)
```json:example.json
{
  "Users": [
      {
          "Username": "22704aa3-fc10-479a-97eb-2af5806bd327",
          "Enabled": true,
          "UserStatus": "FORCE_CHANGE_PASSWORD",
          "UserCreateDate": 1548089817.683,
          "UserLastModifiedDate": 1548089817.683,
          "Attributes": [
              {
                  "Name": "sub",
                  "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
              },
              {
                  "Name": "email_verified",
                  "Value": "true"
              },
              {
                  "Name": "email",
                  "Value": "mary@example.com"
              }
          ]
      }
  ]
}
```
上記のJSONデータをよく見るとキーは`Users`しかなく、配列になっています。
サンプルとして1ユーザー情報しか入っていないが、実際は`Users`に複数のユーザー情報が入っています。

## テーブル作成
Athenaでネストや配列を含むデータを読み込みためには以下のように記載することができる。
```sql: CREATE TABLE
CREATE EXTERNAL TABLE IF NOT EXISTS mydatabase.mytable(
`Users` array<struct<`UserName`: string,
`UserCreateDate`: string,
`UserLastModifiedDate`: string,
`Enabled`: boolean,
`UserStatus`: string,
`Attributes`: array<struct<`Name`:string, `Value`: string>>
>>
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 'JSONファイルが入ったS3バケットURI';
```
`array<>`でJSONの配列を表現する。
`struct<>`でネストされたJSONの値を表現する。今回は`Users`配下や`Attributes`配下がネストされた値となる。
上記のSQL文でテーブルは作成することができました。
*ここでも`struct`の書き方や型の定義ミスでかなり時間がかかりました。
ネストしていると凡ミスも多いなと。。

これであとはクエリを実行するだけ。

## クエリ実行
以下のクエリを実行したところ、、、
```sql: query
select user
from mytable
cross join unnest(users) as t(user)

/*`unnest`は配列の1要素を1行にする関数です。
これがないと配列のデータを取り出すことができません。*/
```
エラー！
```text: error
HIVE_CURSOR_ERROR: Row is not a valid JSON Object - JSONException: A JSONObject text must end with '}' at 2 [character 3 line 1]
```

## 原因
**Athenaで使用するJSONは1レコード1行でないといけないようです。**
参考：[Amazon Athena で JSON データの読み取りを試みるとエラーが発生するのはなぜですか?](https://repost.aws/ja/knowledge-center/error-json-athena)

それでは、修正して実行してみよう！
まずは、JSONの修正から
```json: example(修正後)
{"Users":[{"Username":"22704aa3-fc10-479a-97eb-2af5806bd327","Enabled":true,"UserStatus":"FORCE_CHANGE_PASSWORD","UserCreateDate":1548089817.683,"UserLastModifiedDate":1548089817.683,"Attributes":[{"Name":"sub","Value":"22704aa3-fc10-479a-97eb-2af5806bd327"},{"Name":"email_verified","Value":"true"},{"Name":"email","Value":"mary@example.com"}]}]}
```
凄い横長になりましたね。。
*実際は9万行近くあったので、見れたものじゃなかったです。

そして、再度クエリを実行したところ
```text: result
22704aa3-fc10-479a-97eb-2af5806bd327
```
無事データの取得ができました！！

# おわりに
AthenaはS3のログを解析するのによく使われるサービスと机上で勉強した知識だけだったので、
実際に触ってみてかなり勉強になりました。

今回はAthenaを使ってみて躓いた点をブログとしてまとめてみました。
これからAthenaを使う人の参考になれば幸いです。

PS.これが初のブログ執筆です。これからも知見共有的なブログを書いていこうと思います。
Goで構造体へのポインタをデリファレンスした変数の扱いに関して

## 概要
Go言語において、構造体へのポインタをデリファレンスして得られた値を新しい変数に格納すると、その変数は元の構造体のデータのコピーになるということを知ったので備忘録のため投稿します

## 実例
下記を見ると、構造体へのポインタをデリファレンスした`copiedPerson`を変更しても`originalPerson`の値が変わらないことが分かります。
```go
package main

import "fmt"

type Person struct {
	firstName string
	lastName  string
}

func main() {
	originalPerson := &Person{"Taro", "Yamada"}
	fmt.Println(originalPerson) // &{Taro Yamada}

	// 構造体のフィールドを変更します。
	originalPerson.firstName = "太郎"
	originalPerson.lastName = "山田"
	fmt.Println(originalPerson) // &{太郎 山田}

	// copiedPerson は originalPerson のコピーです。
	copiedPerson := *originalPerson
	// copiedPerson のフィールドを変更しても、originalPerson には影響しません。
	copiedPerson.firstName = "次郎"
	fmt.Println(originalPerson) // &{太郎 山田}
	fmt.Println(copiedPerson)   // {次郎 山田}
}
```
PostgreSQL11環境でyumがエラーになった。

## はじめに

PostgreSQL11をインストールした環境でyumがエラーになったのでやったことを残しておきます。

## 環境
OracleLinux7.9
PostgreSQL11.x

PostgreSQL11.xは以下の手順でインストール済みです。
現在はバージョン11がサポート終了のため選択出来ません。
https://www.postgresql.org/download/linux/redhat/

## やったこと

yum updateを実行するとエラーになるのでリポジトリのRPMを更新したらupdateが通るようになった。

```
[root@ol72 ~]# yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
...snip...
[root@ol72 ~]#
```

pgdg-redhat-all.repoに
baseurl=https://download.postgresql.org/pub/repos/yum/11/
からはじまる記載がなくなった。

```
[root@ol72 ~]# cd /etc/yum.repos.d/
[root@ol72 yum.repos.d]# cat pgdg-redhat-all.repo
...snip...
[pgdg15]
name=PostgreSQL 15 for RHEL / CentOS $releasever - $basearch
baseurl=https://download.postgresql.org/pub/repos/yum/15/redhat/rhel-$releasever-$basearch
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/PGDG-RPM-GPG-KEY-RHEL7
repo_gpgcheck = 1
...snip...
[root@ol72 yum.repos.d]#
```

リポジトリサーバ（download.postgresql.org）でもバージョン11のディレクトリ（/pub/repos/yum/11/）が削除されていた。

バージョン11はサポート終了なのでアップデートはしないが、再インストール用に適当なミラーサイトからRPMを保存しておく。

```
[root@ol72 ~]# rpm -qa | grep postgres | xargs -I {} wget https://mirror.pnl.gov/postgresql/pgrpm-11/redhat/rhel-7.9-x86_64/{}.rpm
...snip...
[root@ol72 ~]# sha256sum *rpm
754ae560a05db6c379674d0fc07eb8250d7c5998154122660a02fd380e918bf8  postgresql11-11.22-1PGDG.rhel7.x86_64.rpm
950f93b3cc95f1b032a647e1bdefe25090ea67ba04b21f958ff69dc6c068cb73  postgresql11-libs-11.22-1PGDG.rhel7.x86_64.rpm
d70b32f39af85ed2e422657b24c3e9341c9c449a2b77eb1aab1b4b6e27234e83  postgresql11-server-11.22-1PGDG.rhel7.x86_64.rpm
[root@ol72 ~]#
```

## おわりに

古いバージョン使ってないでとっととバージョンアップしましょうということなんでしょうけど、バージョンアップできないシステムのテスト用の環境も必要なので余計な手間をかけずに過去のファイルにアクセスできるとうれしい。
Chrome Ver.120.0.6099.199で利用可能なChromeDriverをM1 Macにインストール

# はじめに
Chrome Ver.115以降のバージョンでは、ダウンロードサイトが異なります。

Chrome Ver.114までは[WebDriver for Chrome - Downloads](https://chromedriver.chromium.org/downloads)ですが、Chrome Ver.115以降は
[Chrome for Testing availability](https://googlechromelabs.github.io/chrome-for-testing/)になります。

# ダウンロード
この記事を書いている2024年1月5日現在、私のChromeは最新バージョンの`120.0.6099.199`ですので、[Chrome for Testing availability](https://googlechromelabs.github.io/chrome-for-testing/)にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3195108/c2a1d459-18e1-e93e-8446-16fda0382416.png)

`120.0.6099.199`はStable(upcoming)ですので、`120.0.6099.109`を利用します。

[Stableのリンク](https://googlechromelabs.github.io/chrome-for-testing/#stable)をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3195108/01c5bd1f-902d-aa40-75d7-61e1a7e65f0d.png)

M1 Macですので、`mac-arm64`を[ダウンロード](https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/mac-arm64/chromedriver-mac-arm64.zip)します。

```bash
-rw-r--r--@  1 username  staff     8295917  1  5 16:52 chromedriver-mac-arm64.zip
```
# インストール
`chromedriver-mac-arm64.zip`を解凍して、`/usr/local/bin`にコピーします。

```bash
-zsh ~/Downloads % unzip chromedriver-mac-arm64.zip
Archive:  chromedriver-mac-arm64.zip
  inflating: chromedriver-mac-arm64/LICENSE.chromedriver
  inflating: chromedriver-mac-arm64/chromedriver
```

```bash
-zsh ~/Downloads % sudo cp chromedriver-mac-arm64/* /usr/local/bin
Password:hogehoge
```

初回起動時は[『"chromedriver"が悪質なソフトウェアかどうかをAppleでは確認できない』への対応](https://qiita.com/kurokoya/items/ac9951da61fa4fff3dde)をする必要がありますが、chromedriverが実行できるようになります。

```bash
-zsh ~/Downloads % chromedriver --version
ChromeDriver 120.0.6099.109 (3419140ab665596f21b385ce136419fde0924272-refs/branch-heads/6099@{#1483})
```

以上です。
『"chromedriver"が悪質なソフトウェアかどうかをAppleでは確認できない』への対応

# はじめに
M1 MacにChromeDriverをインストールして実行した時に、
#### “chromedriver”が悪質なソフトウェアかどうかをAppleでは確認できないため、このソフトウェアは開けません。

というポップアップが表示されました。

`OK`をクリックすると、ターミナルは以下の表示になります。
```bash
-zsh ~/Downloads % chromedriver --version
zsh: killed     chromedriver --version
```

# 対応方法

初回の実行時は、chromedriverを許可する必要があります。

`システム環境設定`→`セキュリティとプライバシー`を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3195108/541be7ff-e1f3-1e76-53ab-b3593583dac2.png)

`このまま許可`をクリックします。

#### “chromedriver”が悪質なソフトウェアかどうかをAppleでは確認できないため、このソフトウェアは開けません。

というポップアップが表示されますが、`OK`をクリックします。

chromedriverの実行ができるようになり、ターミナルは以下の表示になります。

```bash
-zsh ~/Downloads % chromedriver --version
ChromeDriver 120.0.6099.109 (3419140ab665596f21b385ce136419fde0924272-refs/branch-heads/6099@{#1483})
```

以降のchromedriverは問題なく実行できます。
「知識ゼロから学ぶ ソフトウェアテスト」を読んでみて思ったこと

# 「知識ゼロから学ぶ ソフトウェアテスト」を読んでみて思ったこと

[前回の記事](https://qiita.com/teppei12345/items/cbd1de72e1fc9f430cda)にて、今期の目標についてお話ししました。今期の目標のひとつに「ソフトウェアテストを極める」という目標があるのですが、今回は[ソフトウェアテスト テスターちゃん](https://www.amazon.co.jp/%E3%83%9E%E3%83%B3%E3%82%AC%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%86%E3%82%B9%E3%83%88%E5%85%A5%E9%96%80-%E3%83%86%E3%82%B9%E3%82%BF%E3%83%BC%E3%81%A1%E3%82%83%E3%82%93-Vol-1-%E6%9D%BE%E8%B0%B7-%E5%B3%B0%E7%94%9F/dp/4863543026)の次に通読した[知識ゼロから学ぶ ソフトウェアテスト](https://www.amazon.co.jp/%E7%9F%A5%E8%AD%98%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%86%E3%82%B9%E3%83%88-%E3%80%90%E6%94%B9%E8%A8%82%E7%89%88%E3%80%91-%E9%AB%98%E6%A9%8B-%E5%AF%BF%E4%B8%80/dp/4798130605)についてまとめていこうと思っています。  

## プログラムの振る舞いをテストする

テスト手法の基本的かつ必須の手法である「制御パステスト」はカバレッジ率を取得するために利用されます。  

### ステートメントカバレッジ

コード内の命令文(ステートメント)を少なくとも1回は実行します。そのため、分岐処理において true & false のテストがまんべんなくできません。そのため、簡単ではありますが、非常に弱いテスト手法です。  

### ブランチカバレッジ

ステートメントカバレッジでは、分岐処理のすべてはカバーできません。そこで、「ブランチカバレッジ」を利用します。こちらは、分岐処理に対してそれぞれ true & false のテストがまんべんなく実行できます。しかし、テストケースの数がかなり増えてしまいます。  

### カバレッジ基準

カバレッジを取得する際、どの程度カバレッジが通っていれば良いとするのかは判断に迷ってしまいます。著書では、「一般商用ソフトウェアであれば60 ~ 90%程度で十分だ」と主張されています。  

## ブラックボックステスト

プログラマやシステムエンジニアのような職業の方であれば「ブラックボックステスト」という用語は耳にしたことがあるでしょう(私は基本情報技術者試験でも習いました！)。このテストには「同値分割法」「境界値分析法」の2つがあります。  

### 同値分割法

入力領域を「同値クラス」という部分集合に分割し、その部分集合に入る入力値を等価とみなす作業です。たとえば下記のプログラムを考えてみましょう。  

ex) 入力Aと入力Bを掛け算した結果を出力する(入力条件は下記を参照)。  
**入力条件**  
入力A：1 から 999まで入力可能  
入力B：1 から 999まで入力可能  

まずは入力値を2つの部分集合に分ける必要があります。1つは有効同値、1つは無効同値です。有効同値とはプログラムが期待する入力値であるのに反して、無効同値とはプログラムが期待しない入力値です。  
つまり、下記のような場合分けを考えます。  

~ 0：無効同値  
1 ~ 999：有効同値  
1000 ~：無効同値  

上記集合のうち、一般的には中央値付近のもっともユーザが使いそうなデータを入力する場合が多いです。  
しかし、無効同値のテストをすべて実施すると非常に時間がかかってしまいます。そのため、ある程度無効同値のテストを省略する必要があります。にもかかわらず、省略によってバグを検出できなかったという事態は避けなくてはいけません。  

### 境界値分析法

同値分割法と一緒に利用し、無効同値と有効同値の間に着目したテストです。プログラム上では、この「無効同値と有効同値の間」に必ず条件分が必要になり、その分が正しく書けていないことがあり得ます。そのため、境界値分析法でテストします。  

上記の例をもとに考えてみると、境界値分析法で必要なテストケースとしては、「0」「1」「999」「1000」です。  

### ディシジョンテーブル

すべての入力の組み合わせを表にし、その入力に対する動作もしくは出力を明記します。  

上記の例をもとに考えると、入力の組み合わせとして下記があげられます。  
* A, B共に入力が正しい場合(ルール1)  
* Aの入力が正しくて、Bの入力が正しくない場合(ルール2)  
* Aの入力が正しくなくて、Bの入力が正しい場合(ルール3)  
* A, B共に入力が正しくない場合(ルール4)  

上記の組み合わせを表にすると下記のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659914/de70171a-6e4c-d375-0599-091436449f5c.png)

しかし、この方法には問題があり、表を使ったテストは非常に小さいソフトウェアか、大きなソフトウェアの一部分をテストするときにしか使うことができません。反して、項目が少なく複雑な動きをするソフトウェアには非常に有効です。  

### モンキーテスト(ランダムテスト)

何も考えず、行き当たりばったりに入力や操作を行う手法のことです。あらかじめ同値分割や境界値分析などを考える必要がないため、一見効率的であるようにも思えますが、あまり役に立ちません。  

### まとめ

テストを実施する手順としては、下記を参照してください。  

* まずは境界値テストを実施  
* 入力エリアが2つ以上ある場合はディシジョンテーブルテストを実施
Pythonで「辞書の長さ(要素数)を取得する」の動作を確認してみた

# 概要

Pythonで「辞書の長さ(要素数)を取得する」の動作を確認してみました。
以下のページを参考にしました。

https://www.javadrive.jp/python/dictionary/index4.html

# 実装

以下のファイルを作成しました。
```sample.py
colorlist = ["Blue", "Red", "Green", "White", "Black"]
print("要素数は " + str(len(colorlist)) + " です。")
```

以下のコマンドを実行しました。
```
$ python3 sample.py 
要素数は 5 です。
```

# まとめ

何かの役に立てばと。
nodenvでNode.jsをバージョン管理する

# はじめに
フロントエンド開発の機会が増えてきたので、知識整理も含めて簡単にまとめてみます。

nodenvを使用すると、どこでどのバージョンを使用するといったバージョン管理が出来るようになります。

# nodenvをインストール
nodeenvは[Homebrew](https://brew.sh/)を使ってインストール出来ます。

```shell
$ brew install nodenv
```

インストールが完了したら、nodenv自体とnode.jsのバージョンを確認してみます。

nodenv自体のバージョン

```shell
$ nodenv -v
nodenv 1.4.1
```

node.jsのバージョン
```shell
$ node -v
v14.16.1
```

## インストール済のNode.jsを確認する
versionsサブコマンドでインストール済のNode.jsを確認出来ます。

```shell
$ nodenv versions       
* system => v14.16.1 (set by /Users/xxx/.nodenv/version)
```

systemと言う名前で「v14.16.1」がインストールされています。
出力結果より、
ユーザーディレクトリ配下に「.nodenv/version」と言うディレクトリ階層が作られ、そこに実態が格納されています。

# 新しいバージョンをインストールする
バージョン管理なので、さっそく別バージョンに切り替えていきましょう。

## インストールできるバージョンを確認
まず、インストールできるバージョンを確認します。
installサブコマンドに「-l」オプションを付けて実行します。

```shell
$ nodenv install -l
0.1.14
0.1.15
0.1.16
・・・
```

バージョンのラベルが大量に出てくるので、利用したいバージョンがあるか確認します。

## 指定したバージョンをインストール
バージョンの一覧から使用するバージョンを見つけたら、installサブコマンドにバージョンを付けてインストールします。
```shell
$ nodenv install 16.13.2
```

インストール出来たか確認してみます。
```shell
$ nodenv versions       
* system => v14.16.1 (set by /Users/xxx/.nodenv/version)
  16.13.2
```

インストール済の一覧に指定したバージョンが表示されるようになりました。
ただし、*マークを見るとv14.16.1がアクティブになっているようです。

# 使用するNode.jsのバージョンを指定する
バージョンを指定する方法はローカル設定とグローバル設定の2つがあります。

### ローカル設定
ローカル設定でバージョンを指定した場合、指定したディレクトリ内で有効なバージョンを指定出来ます。

特定のディレクトリ内でバージョンを指定します。
```shell
$ nodenv local 16.13.2
```

ローカル設定すると、.node-versionと言うファイルが作成されます。
```shell
$ ls -a
.               .node-version 
```

ファイルの中身をみると指定したバージョンが記載されています。
```.node-version
16.13.2
```

コマンド上で確認したい場合は、バージョンを指定せずに「nodenv local」、または「nodenv versions」で確認出来ます。
```shell
$ nodenv local
16.13.2

nodenv versions       
  system
* 16.13.2 (set by /Users/xxx/workspace/yarn-webpack-sample/.node-version)
```

ちなみに1階層上のディレクトリに移動し、バージョンを再確認してみます。
```shell
$ cd ..
$ nodenv versions
* system => v14.16.1 (set by /Users/xxx/.nodenv/version)
  16.13.2
```

階層が変わったので、アクティブなバージョンがsystemに変わってますね。

### グローバル設定
グローバル設定でバージョンを指定すると、全シェルで使用されるようになります。

```shell
$ nodenv global 16.13.2
```

```shell
$ nodenv versions      
  system
* 16.13.2 (set by /Users/xxx/.nodenv/version)
```

## 環境変数
その他、[環境変数](https://github.com/nodenv/nodenv#user-content-environment-variables)を使っても各種設定が可能です。

# 参考文献
https://github.com/nodenv/nodenv

https://brew.sh/