M5Stamp C3U Mateが認識しないとき

こんにちは、ナナオです。 普段はあまり弄らないのですが、IoTデバイスを実装しようと思います。 以下の書籍を参考に、家でIoTボタンを実装しようとしています。 https://techbookfest.org/product/rwkYYevZRmhdfFGBksTeMk?productVariantID=ebjVWFS2sp9GKNFZCK3xwH ですが、なぜかうまくいかない。。というときに参考にしてください。 何がいかんかったか 書籍の手順でセットアップしたはずなのに、なぜかうまくいかない。。と思っていたら、正しい手順は以下にありました。 m5-docs つまり、買うデバイスを間違えていたのが原因でした。 誤: M5Stamp C3U Mate 正: M5Stamp C3 Mate いやー、、、一文字違いで完全にうっかりミスしていました。 USBドライブいろいろ入れてみたんですが、反応しなくて当然ですね。 感想 教訓: デバイス名はちゃんと確認しよう!!!!

2026年1月23日 · にあえん

hugoを予約投稿するための超ミニマムなCMSを作った

こんにちは、ナナオです。 私のブログはhugoというツールで投稿しているのですが、静的ビルドを行ってデプロイするため、予約投稿ができません。 よくある方法として、Github Actionsを使った方法がありますが、今回はRustを使った超ミニマムなCMSを作って予約投稿をしていきたいと思います。 仕様 CMSとは言っているものの、ミニマム実装の段階ではHugoのディレクトリ監視ツールのようなものです。 以下のような設定のconfig.yamlファイルを読み込み、dateの時間になったらhugoのビルドとデプロイを行います。 # 監視対象のhugoプロジェクトディレクトリパスを指定 workdir: path/to/hugo_project # スケジュールされた記事の情報 scheduled: - date: 2026/1/20 00:00:00 # 予約投稿する日時 file: posts/2026/01/test.md # 予約投稿する記事のディレクトリ(workdirからの相対パス) 実装 早速実装していきます。 実装は特にこだわりはないですが、やはり最高のプログラミング言語Rustで行います。テンション上がるしね。 use chrono::{Local, NaiveDateTime}; use regex::Regex; use serde::Deserialize; use std::collections::HashSet; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use std::thread; use std::time::Duration; // 設定ファイルの構造体定義 #[derive(Debug, Deserialize)] struct Config { workdir: String, scheduled: Vec<ScheduledPost>, } #[derive(Debug, Deserialize)] struct ScheduledPost { date: String, file: String, } fn main() { println!("Hugo Local CMS: 予約投稿監視を開始しました..."); // 処理済みファイルを記録するSet (日付+ファイル名 をキーにする) let mut processed: HashSet<String> = HashSet::new(); loop { println!("{} ファイルチェック開始", Local::now().to_rfc3339()); // 設定ファイルの読み込み let config = match load_config("config.yaml") { Ok(cfg) => cfg, Err(e) => { eprintln!("設定読み込みエラー: {}", e); thread::sleep(Duration::from_secs(10)); continue; } }; let now = chrono::Local::now().naive_local(); for post in config.scheduled { let key = format!("{}{}", post.date, post.file); // 既に処理済みならスキップ if processed.contains(&key) { continue; } // 日付パース (フォーマット: 2026/01/20 18:45:00) let pub_date = match NaiveDateTime::parse_from_str(&post.date, "%Y/%m/%d %H:%M:%S") { Ok(dt) => dt, Err(e) => { eprintln!("日付パースエラー ({}): {}", post.file, e); continue; } }; // 時間チェック if now > pub_date { println!("⏰ 予約時刻になりました: {}", post.file); // publish処理を実行 publish(&config.workdir, &post.file); // 処理済みリストに追加 processed.insert(key); } } println!("{} ファイルチェック終了", Local::now().to_rfc3339()); // 30秒待機 thread::sleep(Duration::from_secs(30)); } } fn load_config(path: &str) -> Result<Config, Box<dyn std::error::Error>> { let content = fs::read_to_string(path)?; let config: Config = serde_yaml::from_str(&content)?; Ok(config) } fn publish(work_dir: &str, relative_path: &str) { let full_path = Path::new(work_dir).join(relative_path); // 1. ファイルのドラフト解除 if let Err(e) = modify_draft_status(&full_path) { eprintln!("❌ ファイル修正失敗 [{:?}]: {}", full_path, e); return; } // 2. Hugoコマンド実行 execute_hugo(work_dir); } fn modify_draft_status(path: &PathBuf) -> std::io::Result<()> { // ファイル読み込み let content = fs::read_to_string(path)?; // 正規表現: 行頭の "draft = true" (スペース許容) を検索 // (?m) はマルチラインモード有効化 let re = Regex::new(r"(?m)^(draft\s*=\s*)true").unwrap(); if re.is_match(&content) { // 置換実行 ( ${1} は "draft = " の部分 ) let new_content = re.replace(&content, "${1}false"); // 書き込み fs::write(path, new_content.as_bytes())?; println!( "📝 Draftフラグを解除しました: {:?}", path.file_name().unwrap() ); } else { println!("ℹ️ Draftフラグの変更不要: {:?}", path.file_name().unwrap()); } Ok(()) } fn execute_hugo(work_dir: &str) { println!("🏗️ Hugoビルドを開始します..."); // 1. hugo if run_command(work_dir, "hugo", &[]).is_err() { return; } // 2. hugo deploy if run_command(work_dir, "hugo", &["deploy"]).is_err() { return; } println!("✅ Hugoのビルドとデプロイが完了しました。"); } fn run_command(dir: &str, command: &str, args: &[&str]) -> Result<(), ()> { println!("🚀 実行中: {} {:?} (in {})", command, args, dir); let status = Command::new(command) .args(args) .current_dir(dir) // 作業ディレクトリ指定 .status(); // 実行して終了ステータスを待つ match status { Ok(s) if s.success() => Ok(()), Ok(s) => { eprintln!("エラー: コマンドが失敗しました (Exit Code: {:?})", s.code()); Err(()) } Err(e) => { eprintln!("エラー: コマンドを実行できませんでした: {}", e); Err(()) } } } 実装内では30秒ごとにconfig.yamlを走査します。 ...

2026年1月22日 · にあえん

ollama + gemma3 + opencodeで最高のコーディング環境を手に入れる

前回の記事からの続きになります。 あれからGLM-4.7やMiniMax M2.1といった無料のモデルを使ってみたのですが、いまいちしっくりくる実装が出力されないことがよくありました。 ただ、Gemini 3 Flashといったモデルはすぐにトークンを使い切ってしまうため、びくびくしながらプロンプトを打っています。 そんなのは嫌だ!!!! ということで今回はollamaを使って今度こそ最高のコーディング環境を作りたいと思います。 ollamaのインストール ollamaのインストールからやっていきます。 WSLの環境に入れるので、以下のlinuxへのインストールを試します。 Linux - Ollama 以下のインストールスクリプトを実行します。 curl -fsSL https://ollama.com/install.sh | sh ちなみに最初miseからインストールを試みたのですが、こちらは失敗しました。 ❯ mise use -g ollama mise ollama@0.14.2 ⠁ 0smise ERROR Failed to install aqua:ollama/ollama@latest: no asset found: ollama-linux-amd64.tgz Available assets: ollama-darwin.tgz Ollama-darwin.zip ollama-linux-amd64-rocm.tar.zst ollama-linux-amd64.tar.zst ollama-linux-arm64-jetpack5.tar.zst ollama-linux-arm64-jetpack6.tar.zst ollama-linux-arm64.tar.zst ollama-windows-amd64-rocm.zip ollama-windows-amd64.zip ollama-windows-arm64.zip Ollama.dmg OllamaSetup.exe sha256sum.txt mise ERROR Run with --verbose or MISE_VERBOSE=1 for more information ということでmiseの公式リポジトリに質問として投げています。 Failed to install ollama · jdx/mise · Discussion #7758 · GitHub ...

2026年1月21日 · にあえん

OpenCode + Gemini(とGLM-4.7)を使って無料で最強のコーディングエージェントを手に入れる

こんにちは、ナナオです。 前回Gemini CLIをセットアップしましたが、今回はOpenCodeとGeminiを組み合わせて最強のコーディング環境を手に入れようと思います。 初期設定 まずはOpenCodeをインストールします。 miseでインストール可能です。 mise use -g opencode 使用するプロバイダを選択します。 Geminiを使いたいので、Googleを選択します。 ❯ opencode auth login ┌ Add credential │ ◆ Select provider │ Search: │ ○ OpenCode Zen │ ○ Anthropic │ ○ GitHub Copilot │ ○ OpenAI │ ● Google │ ○ OpenRouter │ ○ Vercel AI Gateway │ ... │ ↑/↓ to select • Enter: confirm • Type: to search └ APIキーを入力して完了です。 ❯ opencode auth login ┌ Add credential │ ◇ Select provider │ Google │ ◇ Enter your API key │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ │ └ Done opencode auth listで追加したプロバイダにGoogleがいることを確認します。 ...

2026年1月20日 · にあえん

miseを使ってみる(そしてanyenvからの脱却)

こんにちは、ナナオです。 今回は巷で話題のmiseを使っていこうと思います。 Home | mise-en-place GitHub - jdx/mise: dev tools, env vars, task runner miseとは miseはThe front-end to your dev envという公式の説明がありますが、要は開発環境のための主要ツールになることを目指して作られたツールです。 できることは主に以下の三つです。 開発ツールの管理 環境変数の管理 タスクの管理 開発ツールの管理とは、node.js、Python、Terraformなどのバージョン管理を行えるという意味です。 環境変数の管理はそのままの意味で、環境変数の管理を行えます。 タスクの管理とは、例えばビルドやテストといった作業の管理を行えます。 miseのインストール brewが入っていれば以下のコマンドで入ります。 brew install mise miseでインストールした開発ツールを使用できるようにアクティベートしておきましょう。 .zshrcに以下のコマンドを追加します。 # miseのアクティベート eval "$(mise activate zsh)" これで準備が整いました。 タスクを定義する 普段よく使っているブログの公開コマンドを定義します。 [tasks.blog-deploy] description = 'ブログのビルドおよび公開' run = [ 'hugo', 'hugo deploy', ] dir = "/mnt/c/Users/Nanao/Project/tools/hugo-blog" 実行するとブログの公開ができました。楽ちん~ anyenvから移行する ということで本題です。anyenvは遅いし移行しようと思います。 長い間!!!!!!!クソお世話になりました!!!!!(ドン) anyenvのアンインストール まずanyenvでインストールしている~~envを削除します。 anyenv versionsでインストールされている環境を確認します。 ❯ anyenv versions goenv: * 1.20.6 (set by /home/nanao/.anyenv/envs/goenv/version) 1.22.9 1.23.3 nodenv: system * 22.7.0 (set by /home/nanao/.anyenv/envs/nodenv/version) pyenv: system 3.12.0 * 3.12.1 (set by /home/nanao/.anyenv/envs/pyenv/version) 3.12.2 3.12.3 3.12.4 3.12.5 3.13.2 rbenv: * 3.2.2 (set by /home/nanao/.anyenv/envs/rbenv/version) 結構いろいろインストールしていました。 ...

2026年1月19日 · にあえん

Koyebで一人で複数アカウントを持ったら叱られた

こんにちは、ナナオです。 先日遊びで作ったkoyebのアカウントとは別にkoyebのアカウントを作ってデプロイしたら、この画面になりました。 We cannot offer you access to Koyeb Your account matches patterns often associated with violations of our Terms of Service While we cannot provide details about the reason(s) triggering this behavior, we have determined that providing access to Koyeb is not possible If you believe this is a bug, you can verify your identity 訳文は以下の通り。 Koyebへのアクセスを提供できません あなたのアカウントは、当社の利用規約に違反することが多いパターンと一致しています。 この動作を引き起こした理由の詳細についてはお答えできませんが、Koyebへのアクセスを提供することは不可能であると判断いたしました。 これがバグ(誤判定)だと思われる場合は、本人確認を行うことができます。 私の場合、一人で複数のアカウントを作って、無料プランで複数デプロイしようとしたこが原因ではないか、という結論に落ち着きました。 koyebの利用規約は以下の通りです。 Terms of Service | Koyeb 直接「同一人物が複数アカウントを使って複数のサービスをデプロイすることを禁じる」といった旨は書かれてはいませんが、一般常識としてそんなことするなよ、ということでしょうか。。 ...

2026年1月18日 · にあえん

Koyebで無料のDiscord Botを構築してみた

こんにちは、ナナオです。 サーバー維持費、気になりますよね。 今回は無料で使えるサービスでDiscordのBotを作っていきたいと思います。 初期設定 まずは土台作りとして、Discordとボットのコードを実装していきます。 Discord側の初期設定 以下のURLからデベロッパーコンソールにアクセスします。 Discord Developer Portal ログインすると以下の画面になります。 「New Application」をクリックして新しくアプリケーションを作成します。 作成したアプリケーションのBotタブに行き、Reset Tokenでトークンを発行し、コピーしておきます。 Message Content Intentもオンにしておきます。 これをしないとBotがメッセージを読むことができないです。 ボットをサーバーに招待します。 InstallationタブからGuild InstallのScopesにbotを追加し、権限を設定します。(ここ重要) 今回は管理者権限にしました。 Install Linkをコピーし、ブラウザに貼り付けます。 インストールに成功すると、以下のようなウィンドウが表示されます。 サーバー側にも通知が飛びます。 Pythonの実装 コードを書いていきましょう。 パッケージをUVで作成します。 uv init --package discord-bot-playground giboでignorefileに追記します。 gibo dump Python >> .gitignore .envファイルを作成し、先ほど作成したDiscord botのトークンを貼り付けておきます。 DISCORD_BOT_TOKEN="xxx..." 必要なライブラリを追加します。 uv add discord.py dotenv コードを書きます。 ここでは簡単なコマンド実行をしていきます。 デコレーターでめちゃくちゃ簡単に実装できます。 import os from dotenv import load_dotenv import discord load_dotenv() DISCORD_BOT_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") DISCORD_GUILD_ID = int(os.environ.get("DISCORD_GUILD_ID")) guild_id = discord.Object(id=DISCORD_GUILD_ID) intents = discord.Intents.default() client = discord.Client(intents=intents) tree = discord.app_commands.CommandTree(client) @client.event async def on_ready(): await tree.sync(guild=guild_id) @tree.command() async def hello(interaction: discord.Interaction): """Says hello!""" await interaction.response.send_message(f"Hi, {interaction.user.mention}") client.run(DISCORD_BOT_TOKEN) ポイントとしてはon_readyでsyncする際にギルドIDをしているところです。 ...

2026年1月17日 · にあえん

Gemini CLIを使ってみる

こんにちは、ナナオです。 巷ではClaude Codeを使ったコーディングが流行っていますが、やはり気になるのはコスト。 ということで、今回は無料で使えるGeminiを使って快適なAIコーディング環境を構築していきたいと思います。 セットアップ まずはGemini CLIをインストールしていきます。 インストール方法は以下のリポジトリを参考にします。 GitHub - google-gemini/gemini-cli: An open-source AI agent that brings the power of Gemini directly into your terminal. brew install gemini-cli 早速任意のプロジェクトで以下のコマンドを実行しました。 ❯ gemini init Please set an Auth method in your /home/banan/.gemini/settings.json or specify one of the following environment variables before running: GEMINI_API_KEY, GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_GENAI_USE_GCA おっと、GEMINI_API_KEYが設定されていないのでエラーになりました。 miseを使っているので、以下のように環境変数を設定します。 APIキーは以下から取得します。 Sign in - Google Accounts mise set -g GEMINI_API_KEY="xxx..." 再度実行します。 ❯ gemini init Error when talking to Gemini API Full report available at: /tmp/gemini-client-error-Turn.run-sendMessageStream-2026-01-13T09-51-13-224Z.json [API Error: You have exhausted your daily quota on this model.] An unexpected critical error occurred:[object Object] 一日に使用する上限に達していました。 ...

2026年1月16日 · にあえん

k8sでNFSを使ったストレージの動的プロビジョニングを実施する

こんにちは、ナナオです。 k8sを運用していて、永続ストレージが欲しくなるケース、ありますよね。 今回はそんな時に役立つ動的プロビジョニングを実施してみたいと思います。 動的プロビジョニングとは k8sで永続ストレージを使用するには、PVとPVCを作成する必要があります。 PVはストレージの接続方法やディレクトリパスを指定するリソースで、PVCはストレージとデプロイメントやポッドを繋ぐためのインターフェースのような役割を果たすリソースです。 ただ、ストレージが必要になるたびに毎回PVを作ってPVCを作って…とするのは面倒です。 その手間を減らしてくれるのが動的プロビジョニングになります。 動的プロビジョニングでは、一度ストレージの接続方法とディレクトリパスを指定してあげれば、あとは勝手にそのディレクトリ内でPVを作ってPVCと紐づけてくれます。 実践 早速実践してみましょう。 動的プロビジョニングを行うために以下のツールをhelmを使用してインストールします。 GitHub - kubernetes-sigs/nfs-subdir-external-provisioner: Dynamic sub-dir volume provisioner on a remote NFS server. helmは以下のコマンドでダウンロード可能です。 brew install helm また、helmによるインストールの管理にはhelmfileというツールを使用します。 GitHub - helmfile/helmfile: Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases. Generate all-in-one manifests for use with ArgoCD. こちらもダウンロードはbrewコマンドで一発で行えます。 brew install helmfile 準備が整いました。 以下のようにプロビジョナーを設定します。 nfs: server: 192.168.0.94 path: /k8s-data storageClass: name: nfs-client defaultClass: true # デフォルトのストレージクラスにする reclaimPolicy: Retain # PVC削除時にNASのデータを残すか(Retainなら残る) 先ほど実装したプロビジョナーの設定を参照するhelmfileを実装します。 repositories: - name: nfs-subdir-external-provisioner url: https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ releases: - name: nfs-provisioner namespace: kube-system chart: nfs-subdir-external-provisioner/nfs-subdir-external-provisioner version: 4.0.18 values: - ./values/nfs-provisioner.yaml 実装したhelmfileを適用します。 ...

2026年1月15日 · にあえん

k3sでワーカー(エージェント)ノードを追加する

こんにちは、ナナオです。 前回の記事でk3sで複数のコントロールプレーンの構築ができました。 今回はまた2台ほど新たにPCをセッティングしたので、ワーカーを追加していきたいと思います。 なお、OSはすべてUbuntu Serverを使用しています。 ワーカーの追加 各ノードにアクセスし、以下のコマンドを実行します。 curl -sfL https://get.k3s.io | K3S_URL=https://<サーバーのIP>:6443 K3S_TOKEN=<トークン> sh - トークンはコントロールプレーンノードの/var/lib/rancher/k3s/server/node-tokenに格納されているものを使用します。 コマンドの実行に成功すると、kubectl get nodeの結果に追加したワーカーが追加されているはずです。 ❯ kubectl get node NAME STATUS ROLES AGE VERSION mouse1 Ready control-plane,etcd 43h v1.34.3+k3s1 nanaonuc6caysserver Ready control-plane,etcd 43h v1.34.3+k3s1 nuc2 Ready <none> 62s v1.34.3+k3s1 <- 追加されている!! thinkcentre1 Ready control-plane,etcd 45h v1.34.3+k3s1 ということで、もう一台にも同じコマンドを実行してあげます。 ❯ kubectl get node NAME STATUS ROLES AGE VERSION mouse1 Ready control-plane,etcd 43h v1.34.3+k3s1 nanaonuc6caysserver Ready control-plane,etcd 43h v1.34.3+k3s1 nuc2 Ready <none> 3m43s v1.34.3+k3s1 thinkcentle2 Ready <none> 41s v1.34.3+k3s1 thinkcentre1 Ready control-plane,etcd 45h v1.34.3+k3s1 …はい、コントロールプレーンの追加に比べたら驚くほど簡単にセットアップできました。 ...

2026年1月14日 · にあえん