動かざることバグの如し

近づきたいよ 君の理想に

codexでAPIキー設定方法が変わっていた件

環境

  • codex-cli 0.98.0

codexアップデートしたらAPIキー設定できなくなってた件

普段codexを使っているのだが、個人のAPIキーで使っている。以前はconfig.tomlでpreferred_auth_method = "apikey"と設定すれば簡単に切り替えられたのだが、codexを久々にアップデートしたらこの方法が使えなくなっていた。

~/.codex/config.tomlで

model = "gpt-5.2-codex"
model_reasoning_effort = "medium"
personality = "pragmatic"

preferred_auth_method = "apikey"

のところが

Additional properties are not allowed ('preferred_auth_method' was unexpected)

でエラーになってた。

対応

どうやらpreferred_auth_methodというプロパティがconfig.tomlから削除されたらしい。

まずはググって一番最初に出てきたやり方で試してみた。

❯ codex login --api-key sk-dummy
The --api-key flag is no longer supported. Pipe the key instead, e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`.

ダメじゃねーかw。エラーメッセージの通りパイプで渡す方式に変更されたようだ。--api-keyフラグが廃止されて--with-api-keyに置き換わった模様。

というわけでパイプで渡してみる。

printenv sk-dummy | codex login --with-api-key
Reading API key from stdin...
No API key provided via stdin.

これもダメ。どうやら環境変数として先に設定しておかないとprintenvで値が取得できないらしい。当然だが直接文字列をパイプしようとしても認識してくれないようだ。

正しい手順は以下の通り。まず環境変数APIキーを設定してから、それをパイプで渡す必要がある。

export OPENAI_API_KEY=sk-dummy

❯ printenv OPENAI_API_KEY | codex login --with-api-key

Reading API key from stdin...
Successfully logged in

これで無事ログインできた。config.tomlからpreferred_auth_methodの設定を削除して、codex login --with-api-key環境変数経由でAPIキーを渡す方式に移行する必要があるようだ。

セキュリティ的にはAPIキーを直接コマンド履歴に残さず、環境変数経由で渡す方式の方が確かに安全ではある。面倒になったのは仕方ない。

llama.cpp JSONスキーマでZodのdescribeが効かない問題

環境

  • llama.cpp b7971

困っていること

llama.cppもJSONスキーマに対応している。

github.com

しかし、Zodと組み合わせた場合、describeで書いた説明文がLLMに渡っていないようだ。

以下が検証コードで、Zodスキーマのdescribeに特定の文字列を指定し、OpenAI APIとllama.cppの両方で同じスキーマを使わせることで、describeの内容が反映されているかを確認している。

import { openai } from '@ai-sdk/openai';
import { createOpenAI } from '@ai-sdk/openai';
import { Output, generateText } from 'ai';
import { z } from 'zod';
const localOpenAI = createOpenAI({
  baseURL: 'http://192.168.16.21:8000/v1'
});

const run = async () => {
  const testSchema = z.object({
    secretCode: z.string().describe("必ず 'PINEAPPLE' という文字列を入れてください")
  });

  const { output } = await generateText({
    // model: localOpenAI.chat('main'),
    model: openai('gpt-4.1'),
    output: Output.object({
      schema: testSchema
    }),
    prompt: 'こんにちは'
  });

  console.log(output);
};

run();

OpenAIのAPI(openai('gpt-4.1'))ではレスポンスが「PINEAPPLE」となり、describeが正しく渡っていることが確認できた。 一方、llama.cpp(localOpenAI.chat('main'))を使うと「12345」など、describeの内容が反映されていない出力になった。

解決策

Zod v4で toJSONSchema が実装された。これを使うことで、describeの内容を含む正しいJSON Schemaを生成できる。

${JSON.stringify(z.toJSONSchema(testSchema))}

上記のように記述すると、以下のようなJSON Schemaが生成される。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "secretCode": {
      "type": "string",
      "description": "必ず 'PINEAPPLE' という文字列を入れてください"
    }
  },
  "required": [
    "secretCode"
  ],
  "additionalProperties": false
}

このJSON Schemaをプロンプトに含めることで、describeの内容がLLMに正しく伝わるようになる。

なんでllama.cppはdescribeが抜け落ちるの?

describeの内容が無視される理由は、llama.cppのがJSONスキーマを「文法制約(Grammar)」としてのみ使用し、「プロンプト(指示)」としてモデルに伝えていないためだ。

llama.cppは渡されたJSONスキーマをGBNF(Grammar-Based Normalization Form)という文法定義に変換する。しかし、describeやdescriptionに書かれた意味的な指示は文法定義には含まれない。

GitLabバックアップでアーカイブ済みリポジトリを除外する方法

環境

  • GitLab 18.8

やりたいこと

GitLabのバックアップでアーカイブ済みのリポジトリは正直消えてもいいのでバックアップ対象から除外したい。

調べてみた結果

GitLabのバックアップ機能を調べてみたが、結論から言うと「アーカイブ済みプロジェクトを自動的に除外する」ような機能は現時点では存在しない。

代わりに使えるのが SKIP_REPOSITORIES_PATHS 環境変数による特定リポジトリの手動指定だ。

GitLab 15.1から導入され、16.1で強化された SKIP_REPOSITORIES_PATHS を使うと、指定したプロジェクトやグループをバックアップ対象から除外できる。

sudo gitlab-backup create SKIP_REPOSITORIES_PATHS=group-a/archived-project

複数指定する場合はカンマ区切りだ。

sudo gitlab-backup create SKIP_REPOSITORIES_PATHS=group-a/old-project,group-b/legacy-repo

現実的な運用方法として、サブグループにまとめておけば、グループパスを1つ指定するだけで配下の全プロジェクトを除外できるので管理は楽になる。

sudo gitlab-backup create SKIP_REPOSITORIES_PATHS=group-a/archived

蛇足

GitLabのissue #18287で「アーカイブ済みプロジェクトをバックアップから除外したい」という要望は出ているが、現時点では未実装のようだ。公式ドキュメントにもそのようなオプションは記載されていない。

急いでいるなら SKIP_REPOSITORIES_PATHS で手動管理するしかなさそうだ。

参考リンク

電気圧力鍋でつくる適当カレーの作り方

分量は普通のカレールーの横長の箱の1/2(4皿分)とする。

用意するもの

  • カレールー 1/2箱
  • にんじん 1本
  • 玉ねぎ 1玉
  • じゃがいも 2個
    • 圧力鍋だとイモ消えるので消えて欲しくなければメークイン
  • 水 300ml
    • 今回は鍋で作る作り方が600mlだった場合 およそ半分か

カレーの作り方

  • 各野菜を切っていく
    • 圧力鍋だとイモ消えるので大きめ意識
    • にんじんはふんわりラップして電子レンジで600W 1〜2分
  • 切った野菜を電気圧力鍋の中に入れる
  • 水300mlを電気圧力鍋の中に入れる
  • 電気圧力鍋の電源を入れてダイヤルを回し、「カレー/シチュー」にセットする
  • 右下の「スタート」ボタンを押す
  • 以下の挙動をする つまり 30分後 にブザーが鳴る
    • 予熱: 表示部のランプが回転し、予熱が始まる
    • 圧力調理(10分) 圧力がかかるとカウントダウンが始まり、圧力(残)ランプが点灯する
    • 煮込み調理(味染み仕上げ・20分) 圧力調理が終了すると、自動的に煮込み調理(P2)が開始する
    • 完了・自動保温 ブザーが鳴り、自動的に保温に切り替わる
  • ブザー鳴ったらフタを開ける 保温にしたまま
  • ルーを割って入れ、やさしく混ぜながら溶かす
  • 適宜混ぜながら10〜20分保温で温める

picture 0

ZabbixログインパスワードをDBから初期化する

環境

  • Zabbix 7.0 LTS

やりたいこと

Zabbixにパスワード忘れてログインできなくなった。Adminなので他のユーザーで入って初期化、ということもできない。

こういうときはデータベースから直接パスワードリセットが一番早い。

コマンド

Zabbixのログインパスワードはデータベースのusers.passwdにハッシュで保存されている。Adminでログインできない状態なら、DBに直接入ってAdminのハッシュを差し替えるのが早い。

DBに接続

mysql -u root -p zabbix

対象ユーザーを確認(Zabbix 5.0以降)

SELECT username, passwd FROM users WHERE username = 'Admin';

ここでは「MyNewPass123」を作る例とする。

python3 -c "import bcrypt; print(bcrypt.hashpw(b'MyNewPass123', bcrypt.gensalt(rounds=10)).decode())"

生成したハッシュを貼り付けて更新

UPDATE users SET passwd = '$2a$10$...' WHERE username = 'Admin';

備考 なぜ毎回ハッシュ結果が変わるのか

bcryptは「同じパスワードでも毎回違うハッシュが出る」のが仕様だ。内部でランダムなソルトを生成して、それをハッシュ文字列に埋め込むからである($2a$10$...の中にコストやソルト情報が入っている)。

そのため、パスワードが同じでもハッシュが一致する必要はない。ログイン時は「入力したパスワード」と「DBに入っているハッシュ(ソルト込み)」から再計算して一致判定する。

動作確認用の簡単なスクリプトを用意した。

python3 - <<'PY'
import bcrypt
pw = b'hogehoge'
h1 = bcrypt.hashpw(pw, bcrypt.gensalt(rounds=10))
h2 = bcrypt.hashpw(pw, bcrypt.gensalt(rounds=10))
print(h1.decode())
print(h2.decode())
print("h1==h2:", h1 == h2)
print("check1:", bcrypt.checkpw(pw, h1))
print("check2:", bcrypt.checkpw(pw, h2))
PY

実行結果 当然両方ともtrueになる。

$2b$10$sbxFAbKtaqvhcIH3XR2HTuXRczfQ81AsBg/U.mfpH164EztW3O8Zm
$2b$10$jGo/TeMJp.mApELuN7jUC.01ATc1pYC67Ze32HI0mQyhCD/gpLOr.
h1==h2: False
check1: True
check2: True

参考リンク