2026年5月4日月曜日

kabuステーションAPIを使って株を売買するPythonスクリプト

kabuステーションAPIを使って株を売買するPythonスクリプト

概要

前回 kabuステーションAPIを使用して現在のポジションを取得しました
今回は株を売買する方法を紹介します

環境

  • M2 Pro Mac mini
  • macOS 26.4.1
  • UTM 4.7.5
    • Windows11 Home
  • kabuステーション 5.40.0.0
  • nginx 1.30.0
  • Python 3.12.13

株を購入する Python スクリプト

  • vim buy_stock.py
import time

import requests

# =========================
# 設定
# =========================
API_PASSWORD = "xxx"  # kabuステーションで設定したAPIパスワード
BASE_URL = "http://192.168.65.9:28081/kabusapi"  # 検証環境

SYMBOL = "1306"  # TOPIX ETF
EXCHANGE = 1  # 東証
QTY = 10  # 最小単位(検証なので10でOK)


# =========================
# トークン取得
# =========================
def get_token():
    url = f"{BASE_URL}/token"
    res = requests.post(url, json={"APIPassword": API_PASSWORD})

    if res.status_code != 200:
        print("トークン取得エラー:", res.text)
        return None

    return res.json()["Token"]


# =========================
# 共通ヘッダ
# =========================
def headers(token):
    return {"X-API-KEY": token}


# =========================
# 成行買い
# =========================
def buy_market(token):
    url = f"{BASE_URL}/sendorder"

    body = {
        "Symbol": SYMBOL,  # 銘柄コード
        "Exchange": EXCHANGE,  # 市場コード
        "SecurityType": 1,  # 商品種別 (1: 株式)
        "Side": 2,  # 売買区分 (2: 買)
        "CashMargin": 1,  # 信用区分 (1: 現物)
        "DelivType": 2,  # 受渡区分 (2: 預り金)
        "FundType": "02",  # 資産区分 (02: 保護)
        "AccountType": 4,  # 口座種別 (4: 特定)
        "Qty": QTY,  # 注文数量
        "FrontOrderType": 10,  # 執行条件 (10: 成行)
        "Price": 0,  # 注文価格
        "ExpireDay": 0,  # 注文有効期限 (0: 本日)
    }

    res = requests.post(url, json=body, headers=headers(token))

    if res.status_code != 200:
        print("注文エラー:", res.status_code, res.text)
        return False

    print("注文成功:", res.json())
    return True


# =========================
# ポジション取得
# =========================
def get_positions(token):
    url = f"{BASE_URL}/positions"
    res = requests.get(url, headers=headers(token))

    if res.status_code != 200:
        print("ポジション取得エラー:", res.text)
        return None

    return res.json()


# =========================
# メイン
# =========================
def main():
    token = get_token()
    if not token:
        return

    # ① 買う
    if not buy_market(token):
        return

    # 約定待ち(重要)
    print("約定待ち...")
    time.sleep(3)

    # ② ポジション取得
    positions = get_positions(token)

    print("=== 保有ポジション ===")
    if not positions:
        print("ポジションなし")
        return

    for p in positions:
        print(f"""
銘柄: {p.get('Symbol')}
数量: {p.get('LeavesQty')}
取得単価: {p.get('Price')}
現在値: {p.get('CurrentPrice')}
評価損益: {p.get('ProfitLoss')}
        """)


# =========================
if __name__ == "__main__":
    main()

売りの場合

上記のスクリプトで

  • Side -> 1
  • FundType -> " " (半角スペース2つ)

にすれば売りもできます

ログ格納場所

%appdata%\KabuS\Log

API リファレンス

https://kabucom.github.io/kabusapi/reference/index.html#operation/sendorderPost

body の各種パラメータはこちらを参照してください

あとは Github の Issue にいろいろ情報があります

https://github.com/kabucom/kabusapi/issues/

トラブルシューティング

注文エラー: 400 {"Code":1002,"Message":"数量は単位株数の倍数で入力してください"}

QTY = 10 以上にしましょう

注文エラー: 400 {"Code":4001005,"Message":"パラメータ変換エラー - 詳細はkabuSログファイルを確認してください"}

余計なパラメータがあったり不足しているパラメータがあるとこれが頻発します
自分は FundType が足りていませんでした

最後に

どうやら検証環境の場合は実際に購入することはできないようです
とりあえず以下のようになれば注文のリクエストは成功しています

注文成功: {'Result': 0, 'OrderId': None}

あとは各種パラーメタを調整すれば好きな銘柄の売買ができます

参考サイト

2026年5月3日日曜日

kabuステーションAPIを使って現在のポジション一覧を取得するPythonスクリプト

kabuステーションAPIを使って現在のポジション一覧を取得するPythonスクリプト

概要

前回 kabuステーションAPIを使用して認証トークンを取得しました
今回はポジションの一覧を取得します

環境

  • M2 Pro Mac mini
  • macOS 26.4.1
  • UTM 4.7.5
    • Windows11 Home
  • kabuステーション 5.40.0.0
  • nginx 1.30.0
  • Python 3.12.13

ポジションの一覧を取得する Python スクリプト

  • vim list_positions.py
import requests

# =========================
# 設定
# =========================
API_PASSWORD = "xxx"  # kabuステーションで設定したAPIパスワード
BASE_URL = "http://192.168.65.9:28081/kabusapi"  # 検証環境


# =========================
# トークン取得
# =========================
def get_token():
    url = f"{BASE_URL}/token"

    body = {"APIPassword": API_PASSWORD}

    res = requests.post(url, json=body)

    if res.status_code != 200:
        print("トークン取得エラー:", res.status_code, res.text)
        return None

    return res.json().get("Token")


# =========================
# ポジション取得
# =========================
def get_positions(token):
    url = f"{BASE_URL}/positions"

    headers = {"X-API-KEY": token}

    res = requests.get(url, headers=headers)

    # トークン期限切れ対応
    if res.status_code == 401:
        print("トークン期限切れ → 再取得")
        token = get_token()
        if not token:
            return None

        headers["X-API-KEY"] = token
        res = requests.get(url, headers=headers)

    if res.status_code != 200:
        print("ポジション取得エラー:", res.status_code, res.text)
        return None

    return res.json()


# =========================
# メイン処理
# =========================
def main():
    token = get_token()

    if not token:
        print("トークン取得失敗")
        return

    positions = get_positions(token)

    if not positions:
        print("ポジションなし or 取得失敗")
        return

    print("=== 保有ポジション ===")
    for p in positions:
        print(f"""
銘柄: {p.get('Symbol')}
数量: {p.get('LeavesQty')}
取得単価: {p.get('Price')}
現在値: {p.get('CurrentPrice')}
評価損益: {p.get('ProfitLoss')}
        """)


# =========================
if __name__ == "__main__":
    main()
  • uv run python list_positions.py

最後に

次回は実際に売買する方法を紹介します

2026年5月2日土曜日

kabuステーションAPIをコールして認証トークンを取得する

kabuステーションAPIをコールして認証トークンを取得する

概要

前回 kabuステーションをWindowsのVM上に構築しました
今回は実際に API をコールしトークンを取得します

環境

  • M2 Pro Mac mini
  • macOS 26.4.1
  • UTM 4.7.5
    • Windows11 Home
  • kabuステーション 5.40.0.0
  • nginx 1.30.0
  • Python 3.12.13

Windows 上に nginx を構築

Python スクリプトを Mac 上からコールするので kabuステーションが外部からアクセスできるように nginx を構築します

Windows 上で直接 Python スクリプトを実行してもいいですがいろいろとハマるので Mac 上から実行します

worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    map $http_upgrade $connection_upgrade { 
    default upgrade;
    ''      close;
    } 

    server {
        listen       28081;
        server_name  localhost;

        proxy_http_version 1.1;
        proxy_set_header Host localhost;
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection $connection_upgrade;

        location / {
            proxy_pass   http://127.0.0.1:18081/;
        }
    }
}

28081 ポートでアクセスできるようにします
本番の場合は 18080 に proxy します

とりあえずテストなのでバイナリを直接ダブルクリックで実行すれば OK です
起動できているかは localhost:28081/kabusapi にアクセスしてみましょう
停止する場合はタスクマネージャーを使って nginx.exe プロセスを停止します

トークン取得用スクリプト

Windows 上で直接作成しても OK ですし Mac 側で編集/作成して VM 側に共有しても OK です

requests が必要なのでインストールしておきましょう

  • vim fetch_token.py
import requests

# =========================
# 設定
# =========================
API_PASSWORD = "xxx"  # kabuステーションで設定したAPIパスワード
BASE_URL = "http://192.168.65.9:28081/kabusapi"  # 検証環境


# =========================
# トークン取得
# =========================
def get_token():
    url = f"{BASE_URL}/token"

    body = {"APIPassword": API_PASSWORD}

    res = requests.post(url, json=body)

    if res.status_code != 200:
        print("ERROR:", res.status_code, res.text)
        return None

    data = res.json()
    return data.get("Token")


if __name__ == "__main__":
    token = get_token()

    if token:
        print("取得成功:")
        print(token)
    else:
        print("トークン取得失敗")
  • uv run python fetch_token.py

これでトークンが表示されれば OK です
API_PASSWORD は前回 API を有効にした際に設定したパスワードを入力しましょう

最後に

kabu ステーション API を使って API をコールするための認証トークンを取得してみました
kabu ステーション API は Windows 上に構築しスクリプトは Mac や Linux 上からコールするようにしましょう
今回はとりあえずバイナリを直接実行していますが本格的に取引を行う場合は自動起動やプロセス監視/ログ監視が必要になると思います

次回は API を使ってポジションの一覧を取得する方法を紹介します

参考サイト

2026年4月30日木曜日

Mac に kabuステーションAPI環境を構築する

Mac に kabuステーションAPI環境を構築する

概要

まずは構築しAPIを有効にします

環境

  • M2 Pro Mac mini
  • macOS 26.4.1
  • UTM 4.7.5
    • Windows11 Home
  • kabuステーション 5.40.0.0

UTM/CrystalFetch インストール

公式サイトからダウンロードしインストールします

CrystalFetch で Windows11 の ISO イメージ取得

AppleSillicon 上で動作させるのでチェックをオンにします

UTM で Windows11 の VM 作成

  • ライセンスキーがあれば入れる
  • 各種 Windows のセットアップ
  • display output is not active に注意
    • インストール時は「virtio-ramfb-gl (GPU Supported)」 Retinaオン
    • Windowsインストール後は「virtio-grpu-gl-pci (GPU Supported)」Retinaオフ
  • 全画面時の解除はCtrl+Altでマウスのフォーカスを外す

kabuステーションのダウンロードとインストール

https://download.r30.kabu.co.jp/ap/kabustation/ から exe をダウンロードしインストールします

三菱UFJ eスマート証券のアカウントでログイン

口座番号とパスワードでログインします

kabuステーションの設定からAPIを有効化

  • 右上の「歯車」
  • 「システム設定」
  • API タブ
  • 「APIを利用する」にチェック
  • APIパスワードを両方入力
  • kabuステーションを再起動し右上のAPIマークが有効になっていることを確認

動作確認

ブラウザで http://localhost:18081/kabusapi にアクセスしリクエスト不正になることを確認します

最後に

次回は API をコールするためのトークンを取得します

2026年4月21日火曜日

pipenv から uv に移行する

pipenv から uv に移行する

概要

自分のプロジェクトを pipenv から uv に置き換えたので手順を紹介します

環境

  • macOS 26.3.1
  • Python 3.12.13
  • uv 0.11.7

流れ

  1. pyproject.toml の作成
  2. Dockerfile の修正

python 準備

  • cat .python-version
3.12.13

uv インストール

  • pip install uv

pyproject.toml の作成

[project]
name = "ai-gallery"
version = "0.1.0"
description = "AI Gallery application"
requires-python = ">=3.12"
dependencies = [
    "flask",
    "google-cloud-storage",
    "redis",
    "gunicorn",
    "google-genai",
    "pillow",
    "py-vapid",
    "pywebpush",
]

[dependency-groups]
dev = [
    "black",
    "isort",
    "pyright",
    "pytest",
    "bandit",
]

[tool.black]
line-length = 88
target-version = ["py312"]

[tool.isort]
profile = "black"
line_length = 88

[tool.pyright]
pythonVersion = "3.12"

uv sync

  • uv sync

これで uv.lock が作成されます
仮想環境のパスはプロジェクト直下の .venv に作成されます

Dockerfile 修正

pipenv -> uv に変更します

diff --git a/Dockerfile b/Dockerfile
index 499f280..4ec6df7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,16 @@
 FROM python:3.12.13-alpine3.23
 
-COPY . /home
 WORKDIR /home
 
-RUN pip install pipenv
-RUN pipenv install
+COPY pyproject.toml uv.lock ./
+
+RUN --mount=type=cache,target=/root/.cache/pip \
+    pip install uv && \
+    uv export --no-dev | pip install --no-cache-dir -r /dev/stdin
+
+COPY . .
 
 ENV WAIT=on
 ENV MODE=1
 
-CMD ["pipenv", "run", "python", "app.py"]
+CMD ["python", "app.py"]

今回は system 環境にインストールするようにしています

uv sync でも以下のような流れで動作するのでこの当たりは好きなように修正してください

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install uv && \
    uv sync --frozen --no-install-project --no-dev
CMD ["uv", "run", "--no-sync", "gunicorn", "-w", "1", "-b", "0.0.0.0:8080", "app:app"]

--no-sync オプションは実行時に uv sync を実行しないための設定です

一応 docker build -> run して動作確認しておくといいでしょう

README 修正

pipenv で実行している部分を uv に書き換えます
例えば以下のような部分を書き換えていきます

-- pipenv install -d
+- uv sync --all-groups
-- pipenv run isort . && pipenv run black . && pipenv run pyright . && npm run format
+- uv run isort . && uv run black . && uv run pyright . && npm run format
-- pipenv run pytest test/ && pipenv run bandit -r . && REDIS_PASSWORD="" pipenv run gunicorn -w 1 -b 0.0.0.0:8080 app:app
+- uv run pytest test/ && uv run bandit -r . && REDIS_PASSWORD="" uv run gunicorn -w 1 -b 0.0.0.0:8080 app:app
-- pipenv requirements > requirements.txt
+- uv export > requirements.txt

.dockerignore 修正

node_modules/
.venv/

compose.yaml 修正

pipenv -> uv のコマンド修正をします

command: pipenv run python check_new_message.py

command: uv run python check_new_message.py

各種シェルスクリプトの修正

あれば修正します
同様に pipenv -> uv で OK です

Github Actions 修正

各種 yaml ファイルを修正しましょう

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 50d8f31..48e8a98 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,11 +16,11 @@ jobs:
         uses: actions/setup-python@v5
       - name: Install dependencies
         run: |
-          python -m pip install --upgrade pipenv
-          pipenv install -d
+          python -m pip install --upgrade uv
+          uv sync --all-groups
       - name: Lint with isort
         run: |
-          pipenv run isort --check --diff .
+          uv run isort --check --diff .
       - name: Lint with black
         run: |
-          pipenv run black --check .
+          uv run black --check .

実際にアクションを実行して動作確認しましょう

vscode の設定の修正

  • .vscode/settings.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8a2fea7..1bd2c9b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,3 @@
 {
-  "python-envs.defaultEnvManager": "ms-python.python:pipenv",
   "python-envs.pythonProjects": []
 }

これでプロジェクト配下にある .venv を自動で読み込んでくれます

.env ファイルを使っている場合の注意事項

  • uv run --env-file .env

のように命じ的に指定しないと読み込んでくれないので注意しましょう

移行後

  • Pipfile と Pipfile.lock の削除
  • .isort.cfg の削除
  • pip uninstall pipenv
  • venv の削除
    • rm -rf ~/.local/share/virtualenvs/ai-gallery-nT0ArqSL
  • pipenv コマンドがプロジェクト内で使われていないか確認
    • git grep 'pipenv'
  • GAE などにもデプロイしている場合は動作確認
uv export > requirements.txt
gcloud app deploy -q

最後に

アプリ自体を修正するのは簡単ですが CI や各種スクリプトで使われているコマンドを書き換えるのが面倒かなという印象です

2026年4月6日月曜日

Dockerfile の RUN では --mount を使おう

Dockerfile の RUN では --mount を使おう

概要

ビルドを高速化しましょう

環境

  • macOS 26.3.1
  • docker 29.3.1

FROM python:3.12.11-alpine3.21

# 必要なパッケージをインストール
RUN apk add --no-cache nodejs npm

COPY . /home
WORKDIR /home

RUN pip install pipenv
RUN pipenv install

ENV REDIS_HOST=redis

# ミニファイ用ツールをインストール
RUN npm install -g clean-css-cli uglify-js

# CSS / JS をミニファイ
RUN uglifyjs static/js/fontawesome-all.min.js -o static/js/fontawesome-all.min.js
RUN cleancss -o static/css/footer.css static/css/footer.css

CMD ["pipenv", "run", "gunicorn", "-w", "1", "-b", "0.0.0.0:8080", "app:app"]

# syntax=docker/dockerfile:1.7

FROM python:3.12.11-alpine3.21

# 必要なパッケージ
RUN apk add --no-cache nodejs npm

WORKDIR /home

# -------------------------
# ① 依存関係だけ先にコピー(キャッシュ効かせる)
# -------------------------
COPY Pipfile Pipfile.lock ./

# pipenv + cache mount
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install pipenv && \
    pipenv install --deploy --system

# -------------------------
# ② npmツール(キャッシュ効かせる)
# -------------------------
RUN --mount=type=cache,target=/root/.npm \
    npm install -g clean-css-cli uglify-js

# -------------------------
# ③ アプリ本体(最後にコピー)
# -------------------------
COPY . .

ENV REDIS_HOST=redis

# -------------------------
# ④ minify(tmpfsで高速化&不要データ残さない)
# -------------------------
RUN --mount=type=tmpfs,target=/tmp \
    uglifyjs static/js/fontawesome-all.min.js -o static/js/fontawesome-all.min.js && \
    cleancss -o static/css/footer.css static/css/footer.css

CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:8080", "app:app"]

最後に

今回はライブラリのインストールなどに使いましたが一時的な認証情報を渡すときにも使えます

2026年3月29日日曜日

sshd でチャレンジレスポンス認証を試す

sshd でチャレンジレスポンス認証を試す

概要

前回 Ubuntu + pam の設定を行いました

チャレンジレスポンス認証は通常のパスワード認証と毎回違うパスワードで ssh ログインさせたりすることができます
OTP や外部の認証サーバと連携したりもできます

今回は簡単な問題をシェルスクリプトで作成しその問題が正解なら認証 OK とします

環境

  • macOS 26.3.1
  • docker 29.3.0
    • Ubuntu 24.04

pam.d/ssh

# --- Auth Phase ---
# 1. クイズを表示
auth       required     pam_echo.so info Challenge: 3 + 5 = ?

# 2. スクリプト判定(8 以外ならここで拒否される)
auth       required     pam_exec.so expose_authtok /usr/local/bin/math_challenge.sh

# 3. 資格情報 (setcred) を与えるためのモジュール
# 前のステップが required なので、ここには「8」と打った時しか到達しません。
# これを入れることで pam_setcred(): Permission denied を防ぎます。
auth       required     pam_permit.so


# --- Account & Session Phase ---
account    required     pam_permit.so

session    required     pam_unix.so
session    required     pam_env.so
# session  required     pam_loginuid.so  # Dockerでは引き続きコメントアウト

math_challenge.sh

#!/bin/bash
# 入力を読み取る
read -r USER_INPUT

# 判定(xargsで空白を除去)
ANSWER=$(echo "$USER_INPUT" | xargs)

if [ "$ANSWER" = "8" ]; then
    # 成功時は何も出力せず exit 0
    exit 0
else
    # 失敗時はデバッグ用に標準エラーへ(docker logsで見れる)
    echo "Auth Failed: Input was '$ANSWER'" >&2
    exit 1
fi

Dockerfile

FROM ubuntu:24.04

RUN apt-get update && \
    apt-get install -y openssh-server && \
    mkdir /var/run/sshd

# ユーザー作成
RUN useradd -m -s /bin/bash testuser && echo "testuser:testpass" | chpasswd

# 既存設定をリセットし、必要な設定だけを新規作成
RUN rm -rf /etc/ssh/sshd_config.d/* && \
    echo "Port 22" > /etc/ssh/sshd_config && \
    echo "ListenAddress 0.0.0.0" >> /etc/ssh/sshd_config && \
    echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \
    echo "KbdInteractiveAuthentication yes" >> /etc/ssh/sshd_config && \
    echo "PasswordAuthentication no" >> /etc/ssh/sshd_config && \
    echo "UsePAM yes" >> /etc/ssh/sshd_config

# 認証スクリプトをコピー
COPY math_challenge.sh /usr/local/bin/math_challenge.sh
RUN chmod +x /usr/local/bin/math_challenge.sh

# PAM設定を上書き
COPY pam.d/sshd /etc/pam.d/sshd

# (重要) CHALLENGE-RESPONSEを有効化
RUN sed -i 's/KbdInteractiveAuthentication no/KbdInteractiveAuthentication yes/' /etc/ssh/sshd_config

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D", "-e"]

compose.yaml

services:
  ssh:
    build: .
    container_name: ssh-challenge
    ports:
      - "2222:22"

動作確認

  • docker compose up -d --build
  • ssh -o PreferredAuthentications=keyboard-interactive testuser@localhost -p 2222

これで「8」と入力すればログインできるようになります
それ以外はログインエラーになります
さすがにチャレンジするパスワードが簡単すぎるので本当はもっと複雑な問題にします

最後に

Ubuntu24.04 でチャレンジレスポンス認証を試してみました
pam と連携することでより複雑なチャレンジレスポンス認証が実装できます

あとはこのスクリプトをより改良して外部の認証機構と連携したりすることができます

2026年3月28日土曜日

Ubuntu で pam 認証を有効にする

Ubuntu で pam 認証を有効にする

概要

PAM (Pluggable Authentication Modules) は通常の sshd 認証と違い外部の認証機構などを使って認証する仕組みです
sshd と組み合わせることで OTP などを実現できます
今回は Ubuntu 24.04 で pam を設定する方法を紹介します

環境

  • macOS 26.3.1
  • docker 29.3.0
    • Ubuntu 24.04

pam.d/sshd

# PAM構成の基本
auth       required     pam_unix.so nullok_secure
account    required     pam_unix.so
session    required     pam_unix.so
session    required     pam_loginuid.so

Dockerfile

FROM ubuntu:24.04

RUN apt-get update && \
    apt-get install -y openssh-server && \
    mkdir /var/run/sshd

# ユーザー作成
RUN useradd -m -s /bin/bash testuser && echo "testuser:testpass" | chpasswd

# 既存設定をリセットし、必要な設定だけを新規作成
RUN rm -rf /etc/ssh/sshd_config.d/* && \
    echo "Port 22" > /etc/ssh/sshd_config && \
    echo "ListenAddress 0.0.0.0" >> /etc/ssh/sshd_config && \
    echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \
    echo "KbdInteractiveAuthentication yes" >> /etc/ssh/sshd_config && \
    echo "PasswordAuthentication no" >> /etc/ssh/sshd_config && \
    echo "UsePAM yes" >> /etc/ssh/sshd_config

# PAM設定コピー
COPY pam.d/sshd /etc/pam.d/sshd

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]

compose.yaml

services:
  ssh:
    build: .
    container_name: ssh-challenge
    ports:
      - "2222:22"

動作確認

  • docker compose up -d --build
  • ssh -o PreferredAuthentications=keyboard-interactive testuser@localhost -p 2222

最後に

Ubuntu24.04 の sshd の場合設定が少し特殊なので注意しましょう
基本はデフォルトを削除し新規に作成するのが確実です

次回は pam を使ってチャレンジレスポンス認証を試します

2026年3月17日火曜日

AWS Certified Solutions Architect - Associate 03 合格メモ

AWS Certified Solutions Architect - Associate 03 合格メモ

概要

一応残しておきます
GCP の ACE に比べて難しい印象です

環境

  • AWS SAA (03) (2026/03/16時点)
  • テストセンターにて受験

料金

  • 150ドル (約20,000円)

対策

完全に過去問ゲーというわけにはいかない感じです
過去問からそのまま採用されるケースはほぼない印象です

教科書

kindle で 0 円だったのでこれにしましたが他のやつの方がいいかもです

模擬試験

全部で804問です

解き方のポイント

  • 時間がかなり無い印象
    • 自分は時間ギリギリまで使った
    • わからない問題は飛ばすか適当に回答もあり
  • 15問はダミー
    • これがよくわからない
    • 正解でも誤答でも無関係らしい
    • しかもどれかわからない
    • これがあるから先程の適当回答も時間がなければワンチャンありという感じ
  • 過去問がそのまま出るケースはほぼなかった
    • 模擬試験を暗記していれば回答先読みで即回答が使えるが SAA03 はほぼできなさそう
    • 模擬試験と全く同じ問題はなかったが同じようなやつで回答項目が違ったり問題を少しひねったやつは出てくる
    • 全体的に意地悪な問題が多かった印象
  • 問題の日本語がおかしい
    • 説明が少なすぎて頭で補完しなければならない
    • これを実現するためにこのコンポーネントが足りないがそれは使う前提になっていたりとか
    • このケースは実はコストも関係しているから暗黙的に安いものを選択するとか
  • 実務関係なく考えななければならない
    • これは GCP も同様だった
    • 資格モードにならないとダメ
  • 消去法
    • これは結構使えるかもしれない
    • 明らかに違うだろうというやつが何個かあったので2択まで絞れる問題が多かった印象

実際に出題された問題の記憶

  • NoETL
  • LakeFormation + 認証
  • NATゲートウェイの固定IP化
  • DR関連の問題 (これが結構多かった)
  • 最適な構成の問題 (これも結構多かった)
  • STS + DMS + コードの内のSQL変換方法
  • Global Accelerator
  • AI 関連 (Comprehend, Rekognition, SageMaker など)

全体的にいやらしい問題が多かった印象です
単純な EC2 や EBS の構成の問題は少なかったです

スコアレポートはでない

詳細なのは出ませんが点数などの結果は certmetrics で確認できます
ちなみに試験後画面ですぐに出たりすることはないので合否もスコアレポートも後で確認になります

暫定合格を確認する方法もない

ないです

合否通知はいつくるか

当日は20時50分くらいに certmetrics にログインしたら合格結果が出ていました
ちなみにバッジでおなじみの credly からのメールは 21時50分くらいに来ていました

最後に

GCP に比べると難しい印象でした
ただ GCP に比べて結果がその日に出るのは嬉しいです
GCP は結果が出るまでに数日かかるのでイライラします

模擬試験ゲーというわけにもいかないので実務 or 教科書による各種サービスの詳細な仕様までわかっていたほうがいいです
しかも基本的なサービスと言うよりかはわりとニッチなサービスも知っておく必要がありそうです

2026年2月26日木曜日

stable-diffusion で新しいモデルを追加し古いモデルを削除した場合はキャッシュも削除しなければいけないっぽい

stable-diffusion で新しいモデルを追加し古いモデルを削除した場合はキャッシュも削除しなければいけないっぽい

概要

そうしないと古いモデルを参照し続けてしまうっぽい

環境

  • macOS 26.3

現象

waiNSFWIllustrious_v140.safetensors を models/Stable-diffusion から削除したが API 経由でなぜか参照する事案が発生

ログ

Checkpoint waiNSFWIllustrious_v140.safetensors not found; loading fallback waiIllustriousSDXL_v160.safetensors [a5f58eb1c3]

対処方法

一旦 webui.sh を停止します
そして以下のキャッシュを削除します

  • rm cache/hashes/cache.db
  • rm cache/safetensors-metadata/cache.db

画像にもモデル情報が含まれているケースがあるので削除します

  • rm -rf outputs/txt2img-images/2025-09-30/*

これで再度 webui.sh を起動しましょう
ログが出ていなければ OK です

最後に

モデルを削除するだけではダメらしい

2026年2月22日日曜日

npm でパッケージをアップグレードする方法

npm でパッケージをアップグレードする方法

概要

いちいち package.json を編集するのは面倒です

環境

  • macOS 26.3
  • nodejs 22.21.0

方法

  • npx npm-check-updates -u
  • npm install

最後に

outdated とか使う方法があるらしいですがややこしいのでそういうのをまとめてやってくれる npm-check-updates を使うのが簡単です

参考サイト

2026年2月16日月曜日

Github actions で kitchen test をする場合は buildkit を無効にしなければ動かなくなった

Github actions で kitchen test をする場合は buildkit を無効にしなければ動かなくなった

概要

おそらく本当に最近仕様変更があった模様

環境

  • Github actions (2026/02/13時点)
  • Ruby 4.0.1
    • test-kitchen 3.9.1
    • kitchen-docker 3.0.0

Github actions の設定

name: Kitchen test

on:
  push:
    branches:
      - "master"

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/arm64

      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Install dependencies
        run: bundle install

      - name: Run Test Kitchen
        run: |
          bundle exec kitchen test
        env:
          CHEF_LICENSE: accept
          DOCKER_BUILDKIT: 0

デバッグログを表示する場合

name: Kitchen test

on:
  push:
    branches:
      - "master"

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/arm64

      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Install dependencies
        run: bundle install

      - name: Run Test Kitchen
        run: |
          bundle exec kitchen test --log-level debug || true
        env:
          CHEF_LICENSE: accept
          DOCKER_BUILDKIT: 0

      - name: Show Kitchen logs
        if: always()
        run: |
          echo "===== kitchen.log ====="
          cat .kitchen/logs/kitchen.log || true
          echo "===== instance log ====="
          cat .kitchen/logs/default-ubuntu-2204.log || true

最後に

内部的に buildkit を使うようになっており docker の出力形式が変更されたために起きていたエラーでした

2026年2月14日土曜日

スマホ版の Chrome ではちゃんと DOM を定義してから値を設定したりするほうがいい

スマホ版の Chrome ではちゃんと DOM を定義してから値を設定したりするほうがいい

概要

動的に appendChild とかするとリソースを消費しすぎと判断して描画を強制的に停止するようです

環境

  • iOS 26.2.1
  • Chrome 144.07559.95

ダメなコード

function addMessage(msg) {
  // avoid duplicates; update indicator if already present
  const existing = document.querySelector(`#log li[data-msg-id="${msg.id}"]`);
  if (existing) {
    const ind = existing.querySelector(".read-indicator");
    if (ind) {
      updateIndicator(ind, existing, msg.reads || []);
    }
    return;
  }
  // 新着メッセージ(相手からのもの)に対する通知・未読処理
  if (msg.message && msg.client_id !== CLIENT_ID) {
    // ブラウザ通知の呼び出し
    showBrowserNotification(msg);
    // タブが裏側にある場合、未読カウントを増やす
    if (document.visibilityState !== "visible") {
      unreadCount++;
      updateTabTitle();
    }
  }
  // new message
  const li = document.createElement("li");
  li.className = msg.client_id === CLIENT_ID ? "me" : "other";
  li.dataset.msgId = msg.id;

  const textSpan = document.createElement("span");
  textSpan.className = "msg-text";
  textSpan.textContent = msg.message;

  const ind = document.createElement("span");
  ind.className = "read-indicator";

  if (msg.client_id === CLIENT_ID) {
    // 自分のメッセージ: サーバーの reads 情報を使って表示
    updateIndicator(ind, li, msg.reads || []);
    li.appendChild(textSpan);
    li.appendChild(ind);
  } else {
    // 相手のメッセージ
    updateIndicator(ind, li, msg.reads || []);

    li.addEventListener("click", () => {
      if (isRead(msg.id)) return;
      setRead(msg.id);
      ind.classList.remove("unread");
      ind.classList.add("read");
      ind.title = "既読";
      // notify server
      markRead(msg.id);
    });

    li.appendChild(textSpan);
    li.appendChild(ind);
  }

  document.getElementById("log").appendChild(li);
}

良いコード

function addMessage(msg) {
  // 1. 基本チェック
  if (!msg || !msg.id) return;
  // read_event は applyReadEvent で処理されるため、ここではメッセージ本体がないものを除外
  if (msg.type === "read_event" || !msg.message) return;

  // 2. 重複チェック
  const log = document.getElementById("log");
  if (!log) return;
  const existing = document.querySelector(`#log li[data-msg-id="${msg.id}"]`);
  if (existing) return;

  // 3. 要素の作成
  const li = document.createElement("li");

  // 安全な ID チェック
  const currentId =
    typeof CLIENT_ID !== "undefined" ? CLIENT_ID : getClientId();

  li.className = msg.client_id === currentId ? "me" : "other";
  li.setAttribute("data-msg-id", msg.id); // dataset ではなく setAttribute を使用(古いブラウザ対策)

  const textSpan = document.createElement("span");
  textSpan.className = "msg-text";
  textSpan.textContent = String(msg.message); // 明示的に文字列変換

  const ind = document.createElement("span");
  ind.className = "read-indicator unread"; // デフォルトは unread

  // 4. 要素の組み立て(先に DOM に追加してから細部を調整するのがスマホでは安定します)
  li.appendChild(textSpan);
  li.appendChild(ind);
  log.appendChild(li);

  // 5. 既読インジケーターの更新(エラーが起きても表示自体は維持する)
  try {
    updateIndicator(ind, li, msg.reads || []);
  } catch (e) {
    console.warn("Indicator error ignored for safety", e);
  }

  // 6. イベントリスナー(相手のメッセージのみ)
  if (msg.client_id !== currentId) {
    li.addEventListener("click", function () {
      try {
        if (isRead(msg.id)) return;
        setRead(msg.id);
        ind.classList.remove("unread");
        ind.classList.add("read");
        markRead(msg.id);
      } catch (err) {
        console.error(err);
      }
    });

    // 通知処理(エラー回避のため try-catch)
    try {
      showBrowserNotification(msg);
    } catch (e) {}
  }

  // 7. スクロール
  log.scrollTop = log.scrollHeight;
}

比較

堅牢性とエラーハンドリング

「良いコード」は、外部要因でプログラムが止まらないよう工夫されています。

項目 ダメなコード 良いコード
ガード句 データの存在チェックが甘く、msg.idが欠落しているとエラーになる可能性がある。 冒頭で `!msg
例外処理 updateIndicator 等でエラーが起きると、後続の処理(DOM追加等)が止まる。 try-catch を活用し、一部の表示エラーが起きてもアプリ全体が止まらない。
変数の安全性 CLIENT_ID がグローバルにある前提。 typeof チェックやフォールバック(getClientId())を用意している。

責務の明確化と可読性

コードが整理されているため、後からの修正が容易です。

  • 単一責任の原則:
    • ダメ: addMessage 内で通知、未読カウント、DOM操作、重複チェックが混ざり、条件分岐(if (msg.client_id === CLIENT_ID) … else …)で同じような appendChild が重複している。
    • 良い: メッセージの型判定(read_event かどうか)を fetchMessages 側で仕分け、addMessage は「表示」に専念している。
  • DRY (Don’t Repeat Yourself):
    • ダメ: 自分のメッセージと相手のメッセージで、インジケーターの追加処理を2回書いている。
    • 良い: 共通の組み立て(li への追加)を先に行い、差分(イベントリスナー)だけを条件分岐で書いている。

ユーザー体験 (UX) と実用性

ブラウザやネットワークの特性を考慮した実装になっています。

  • スクロール処理: 「良いコード」では追加後に log.scrollTop = log.scrollHeight を行い、常に最新メッセージが見えるように配慮されています。
  • 通信の最適化: fetchMessages でキャッシュ回避のためのタイムスタンプを追加しており、古いデータを掴まされるリスクを減らしています。
  • 暗黙の型変換への対処: String(msg.message) とすることで、数値や null が送られてきても textContent で安全に表示できます。

改善のポイント(まとめ)

  • ❌ ダメな点
    • 重複コード: li.appendChild(textSpan) などが if/else 両方にあり、修正漏れの原因になる。
    • 不親切なエラー: ネットワークエラーやデータ不備で JS が停止し、画面が真っ白になるリスクがある。
    • 状態管理の欠如: スクロール位置の調整がないため、ユーザーが手動でスクロールする必要がある。
  • ✅ 良い点
    • 徹底したバリデーション: 「データはあるか?」「型は正しいか?」を常に疑っている。
    • 保守性の高い構造: コメントで工程(1〜7)が区切られており、どこで何をしているか一目でわかる。
    • API設計への配慮: lastId の更新により、重複してメッセージを取得しない仕組みが整っている。

最後に

スマホ版の Chrome も意識して JavaScript を書かないとダメなようです