にあえん

主にバックエンド・インフラ周りをやってます。 あそびばはこちら -> ナナオのあそびば

ヒグチアイのライブに行った

こんにちは、ナナオです。 ライブに行ってきました。 ヒグチアイというアーティストです。 圧巻でした。 ソロで活動しているアーティストなのですが、今回のライブではバンドで演奏していました。 ちなみに私が好きな曲はこの備忘録という曲と走馬灯という曲です。 有名な曲は進撃の巨人のOPに採用された悪魔の子ですね。 いい曲ばかりなので、ぜひ聞いてみてね。

2026年2月14日 · にあえん

Goのセットアップとチュートリアル【2】

こんにちは、ナナオです。 前回の続きでチュートリアルをやっていこうと思います。 map package main import "fmt" func main() { // このmapはnilのため、make関数で初期化する必要がある var m map[string]int // make関数でmapを作成する m = make(map[string]int) // このマップは初期化済み m = map[string]int{ "A": 1, "B": 2, } // キーの追加 m["Answer"] = 42 fmt.Println("The value:", m["Answer"]) // キーの変更 m["Answer"] = 48 fmt.Println("The value:", m["Answer"]) // キーの削除 delete(m, "Answer") fmt.Println("The value:", m["Answer"]) // キーの取得と存在確認 v, ok := m["Answer"] fmt.Println("The value:", v, "Present?", ok) } 関数 関数も変数として定義することができますし、引数にもできます。 package main import ( "fmt" "math" ) func compute(fn func(float64, float64) float64) float64 { return fn(3, 4) } func main() { hypot := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(5, 12)) fmt.Println(compute(hypot)) fmt.Println(compute(math.Pow)) } メソッド メソッドはレシーバ引数を実装することで可能です。 ...

2026年2月13日 · にあえん

Goのセットアップとチュートリアル【1】

こんにちは、ナナオです。 Goを触り始めるにあたり、どこで勉強するかなどをまとめます。 インストール インストールはmiseでやりました。 Home | mise-en-place mise use -g go どこで勉強したか(するか) GoにはA tour of Goという公式のチュートリアルが用意されているので、そこでやりました。 A Tour of Go ローカルからこのチュートリアルを実行したい場合は以下のコマンドを実行します。 go install golang.org/x/website/tour@latest $GOROOT/bin/tour 印象に残ったところ naked return 関数内の変数をそのまま返却する実装方法 ほかの言語にはない特徴的な記法ですね(実務で使うかどうかは置いといて) package main import "fmt" func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func main() { fmt.Println(split(17)) } whileはない goではfor文がwhileを兼任しています。 package main import ( "fmt" ) func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) } 更にrustでいうloop文もforで表現可能です。 ...

2026年2月12日 · にあえん

hugoの予約投稿をtauriで作る2

こんにちは、ナナオです。 前回の続きです。 hugoを予約投稿するための超ミニマムなCMSを作った hugoの予約投稿アプリをtauriで作る windows側で起動できるようにする 前回は画面表示はできたけど表示がおかしい感じになっていました。 おそらくWSLで起動したことが原因なので、修正します。 windows側のディレクトリに移動して、powershellで再度tauri-cliをインストールします。 cargo install tauri-cli 再度起動してみます。 cargo tauri dev 今度はちゃんと表示されました。 hugoディレクトリの監視とデプロイを行うようにする 今の状態だと予約投稿する記事の管理はGUIでできるようになりましたが、予約投稿自体ができていません。 ということで少し実装を修正します。 lib.rsを以下のように修正します。 use chrono::{Local, NaiveDateTime}; use regex::Regex; use serde::{Deserialize, Serialize}; 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, Serialize, Deserialize, Clone)] struct ScheduledPost { date: String, file: String, } #[derive(Debug, Serialize, Deserialize, Clone)] struct Config { workdir: String, scheduled: Vec<ScheduledPost>, } const CONFIG_PATH: &str = "../config.yaml"; // パスは適宜調整 #[tauri::command] fn get_config() -> Result<Config, String> { let content = fs::read_to_string(CONFIG_PATH).map_err(|e| e.to_string())?; let config: Config = serde_yaml::from_str(&content).map_err(|e| e.to_string())?; Ok(config) } #[tauri::command] fn save_config(config: Config) -> Result<(), String> { let yaml = serde_yaml::to_string(&config).map_err(|e| e.to_string())?; fs::write(CONFIG_PATH, yaml).map_err(|e| e.to_string())?; Ok(()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .setup(|app| { if cfg!(debug_assertions) { app.handle().plugin( tauri_plugin_log::Builder::default() .level(log::LevelFilter::Info) .build(), )?; } // スレッドを立てて監視を開始 thread::spawn(|| { start_monitoring(); }); Ok(()) }) .invoke_handler(tauri::generate_handler![get_config, save_config]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } fn start_monitoring() { println!("Hugo Local CMS: 予約投稿監視を開始しました..."); // 処理済みファイルを記録するSet (日付+ファイル名 をキーにする) let mut processed: HashSet<String> = HashSet::new(); loop { println!("{} ファイルチェック開始", Local::now().to_rfc3339()); // 設定ファイルの読み込み let config = match load_config(CONFIG_PATH) { 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, &post.date); // 処理済みリストに追加 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, target_date_str: &str) { let full_path = Path::new(work_dir).join(relative_path); // 1. ファイルのドラフト解除 if let Err(e) = modify_draft_status(&full_path, target_date_str) { eprintln!("❌ ファイル修正失敗 [{:?}]: {}", full_path, e); return; } // 2. Hugoコマンド実行 execute_hugo(work_dir); } fn modify_draft_status(path: &PathBuf, target_date_str: &str) -> std::io::Result<()> { // ファイル読み込み let content = fs::read_to_string(path)?; // 書き換えるコンテンツ let mut new_content = content; // 正規表現: 行頭の "draft = true" (スペース許容) を検索 // (?m) はマルチラインモード有効化 let re = Regex::new(r"(?m)^(draft\s*:\s*)true").unwrap(); if re.is_match(&new_content) { // 置換実行 ( ${1} は "draft = " の部分 ) new_content = re.replace(&new_content, "${1}false").to_string(); println!( "📝 Draftフラグを解除しました: {:?}", path.file_name().unwrap() ); } // dateの書き換え // --- 日付のフォーマット変換 --- // config.yaml の "2026/01/20 18:45:00" -> "2026-01-20T18:45:00+09:00" let dt = NaiveDateTime::parse_from_str(target_date_str, "%Y/%m/%d %H:%M:%S").unwrap(); let formatted_date = format!("{}T{}+09:00", dt.date(), dt.time()); let re = Regex::new(r"(?m)^(\s*date\s*:\s*).*").unwrap(); if re.is_match(&new_content) { let replacement = format!("${{1}}{}", formatted_date); new_content = re.replace(&new_content, replacement.as_str()).to_string(); println!("📅 日付を更新しました: {}", formatted_date); } // 書き込み fs::write(path, new_content.as_bytes())?; println!("📝 Frontmatterの更新完了: {:?}", 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(()) } } } これで既存の機能を移し替えることができました。 ...

2026年2月11日 · にあえん

tigの使い方

こんにちは、ナナオです。 今回は私が愛用しているtigの使い方を説明していこうと思います。 tigとは tigはターミナルなどで動作する、gitのTUIツールです。 コミットやステージング、スタッシュなどの操作をTUIで実行できるのが特徴です。 同じようなTUIツールだと、最近はlazygitなどが有名ですね。 インストール インストールはbrewで行いました。 brew install tig あとは任意のgitリポジトリのディレクトリでコマンドを実行します。 tig 以下のような画面になります。 この状態でsキーを押すと、ステージングファイルなどを見ることができます。 qキーを押すことで前の画面に戻れます。 カーソルの移動はvimっぽくjkキーで行えます。 また、.tigrcというファイルをホームディレクトリに配置することで、独自のキーカスタマイズも可能です。 私はこれを利用して、リポジトリへのプッシュやgit flowを行えるようにしています。 git-flowを行えるようにする.tigrc↓ dotfiles/tigrc.git-flow at master · darcyparker/dotfiles · GitHub fetch / pull / pushを行う.tigrc↓ Tig で Git を自由自在に操作するための .tigrc 設定例 #hub - Qiita # F で fetch (default: :toggle file-name / :toggle commit-title-refs) bind generic F ?git fetch %(remote) bind main F ?git fetch %(remote) # Alt-f で :toggle file-name / :toggle commit-title-refs bind generic <Esc>f :toggle file-name bind main <Esc>f :toggle commit-title-refs # U で pull bind generic U ?git pull %(remote) # Alt-u で該当ブランチを更新 bind main <Esc>u ?sh -c "git checkout %(branch) && git pull %(remote) --ff-only && git checkout -" bind diff <Esc>u ?sh -c "git checkout %(branch) && git pull %(remote) --ff-only && git checkout -" bind refs <Esc>u ?sh -c "git checkout %(branch) && git pull %(remote) --ff-only && git checkout -" # P で remote への push bind generic P ?git push -u %(remote) %(repo:head) # S で stash save bind generic S ?git stash save "%(prompt Enter stash comment: )" # Bindings for git-flow. # # Flow bindings start with the capital F and then follow the first character of # each operation. If executed from the refs view, the operations (that make # sense to) work on the selected branch. Otherwise, they work on the currently # checked out branch. # # Commands that finish a flow require confirmation to run. Commands that create # a new flow prompt for user input and run when that input is accepted with no # confirmation prompt. # # Note: Bindings assume the standard git-flow paths of feature, release, hotfix # and support. # # To use these keybindings copy the file to your HOME directory and include it # from your ~/.tigrc file: # # $ cp contrib/git-flow.tigrc ~/.tigrc.git-flow # $ echo "source ~/.tigrc.git-flow" >> ~/.tigrc # Get rid of default bindings for F, as that will be the entry point for all # git-flow related commands with this binding. bind main F none bind generic F none # General bind generic Fi ?git flow init # Feature bind generic Ffl !git flow feature bind generic Ffs !git flow feature start "%(prompt New feature name: )" bind generic Fff ?sh -c "git flow feature finish `echo %(repo:head) | sed -e s/feature.//`" bind refs Fff ?sh -c "git flow feature finish `echo %(branch) | sed -e s/feature.//`" # Release bind generic Frl !git flow release bind generic Frs !git flow release start "%(prompt New release name: )" bind generic Frf ?sh -c "git flow release finish `echo %(repo:head) | sed -e s/release.//`" bind refs Frf ?sh -c "git flow release finish `echo %(branch) | sed -e s/release.//`" # Hot Fix bind generic Fhl !git flow hotfix bind generic Fhs !git flow hotfix start "%(prompt New hotfix name: )" bind generic Fhf ?sh -c "git flow hotfix finish `echo %(repo:head) | sed -e s/hotfix.//`" bind refs Fhf ?sh -c "git flow hotfix finish `echo %(branch) | sed -e s/hotfix.//`" # Support bind generic Fsl !git flow support bind refs Fss !git flow support start "%(prompt New support name: )" %(branch)% まとめ カスタマイズもしやすいtig、おすすめです。 ...

2026年2月10日 · にあえん

家で豚骨ラーメンを作った

こんにちは、ナナオです。 ラーメンを作りました。 家ラーメンは何回か作ったことがありますが、今日は割とうまくできたほうかな、と思います。 (厨房は毎度のごとくギットギトになる…) 麺は肉のハナマサで買った超極太麺を使って、スープは豚骨をベースにモミジ、豚肩肉、豚バラをにんにくショウガ長ネギと一緒に煮込んだ感じです。 味は結構雑味が多いですが、優しめのスープになっています。 雑味が多いあたり、丁寧さを省いた性格がそのまま出た気がします。

2026年2月9日 · にあえん

zsh syntax highlighting: unhandled ZLE widget 'menu search'の直し方

こんにちは、ナナオです。 以前antidote + starshipで環境を整えたのですが、その後zshを使っていたら突然以下のようなエラー(?)になりました。 zsh-syntax-highlighting: unhandled ZLE widget 'menu-search' zsh-syntax-highlighting: (This is sometimes caused by doing `bindkey <keys> menu-search` without creating the 'menu-search' widget with `zle -N` or `zle -C`.) zsh-syntax-highlighting: unhandled ZLE widget 'recent-paths' zsh-syntax-highlighting: (This is sometimes caused by doing `bindkey <keys> recent-paths` without creating the 'recent-paths' widget with `zle -N` or `zle -C`.) なんだこれー?と思って調べてみたら、どうやら読み込み順が悪かった模様。。 unhandled ZLE widget · Issue #951 · zsh-users/zsh-syntax-highlighting · GitHub 私の.zsh_plugins.txtは以下のように定義していました。 ohmyzsh/ohmyzsh path:plugins/magic-enter ohmyzsh/ohmyzsh path:plugins/kubectl ohmyzsh/ohmyzsh path:plugins/terraform hlissner/zsh-autopair marlonrichert/zsh-edit zsh-users/zsh-completions marlonrichert/zsh-autocomplete zsh-users/zsh-autosuggestions zsh-users/zsh-history-substring-search zsh-users/zsh-syntax-highlighting どうやらzsh-syntax-highlightingはzsh-autocompleteよりも前に定義していなければいけない模様。 ...

2026年2月8日 · にあえん

pythonは鮮度が命なんだよォ!

こんにちは、ナナオです。 ここ5年ほどpythonを触ってきて、Webバックエンドを中心に、良いところも悪いところも一通り踏んできました。 そんな中、新規プロジェクトでpythonを導入する一番のメリットが何かと言われたら何でしょうか? チームメンバーがpythonを使っているから?社内の既存プロジェクトがpythonばかりだから? たしかにそれも理由の一つにはなるでしょう。ですが、プロジェクトのことを考えたときにメンバーが使っているからというのがメリットになるでしょうか? 新しい言語のラーニングコストと、プロジェクトそのもののコスト(運用、パフォーマンスなど)を天秤にかければ、ラーニングコストはそこまで大きなコストではないです。 pythonを採用する最も大きなメリット、それは実装速度です。 とにかくすぐ実装できる コードの制約があまりなく、すぐ実行可能なのはpythonです。これはスクリプト言語の強みでもあります。 バックエンドは以下のように数行で実装できます。 FastAPI from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} pythonになじみのない方向けに説明すると、@はデコレーターといって、簡単に言えばラッパー関数を端的に表現したものになります。 https://zenn.dev/ryo_kawamata/articles/learn_decorator_in_python ただ、Rustでも数行で書くことはできます。 GitHub - actix/actix-web: Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. use actix_web::{get, web, App, HttpServer, Responder}; #[get("/hello/{name}")] async fn greet(name: web::Path<String>) -> impl Responder { format!("Hello {name}!") } #[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(greet) }) .bind(("127.0.0.1", 8080))? .run() .await } 型情報があるというのはいいことです。スレッドセーフなのも最高。Rustは最高の言語のひとつだと思います。 ...

2026年2月7日 · にあえん

hiberfil.sysという謎のファイルが圧迫していたので削除した

どうも、ナナオです。 実は今私が使っているノートPC、容量が128GBしかないんです。 それなのにDockerとかk8sとか入れているもんだから、とにかく容量が不足してしょうがない。。 流石にそろそろ増設しようとは思っていますが、それまでの間容量を減らすためにWizTreeというアプリで何が容量を食っているのか観察したところ、hiberfil.sysというファイルで10GB近く容量を食っていることに気づきました。 今回はこのファイルの正体と、削除する方法を備忘録として書いておきます。 hiberfil.sysの正体 結論から言えば、コンピュータを休止状態にするために必要なファイルです。 メモリの内容を休止状態後も保持するためにあるみたいですね。 hiberfil.sysを削除する このファイル、単に削除してもすぐ復活してしまうので、休止状態を一度オフにしてあげる必要があります。 管理者権限でコマンドプロンプトを立ち上げ、以下のコマンドを実行します。 powercfg.exe /hibernate off 再度有効にする場合はonで実行すればいいだけです。 powercfg.exe /hibernate on 参考 休止状態を無効にして再び有効にする方法 - Windows Client | Microsoft Learn

2026年2月6日 · にあえん

hugoの予約投稿アプリをtauriで作る

こんにちは、ナナオです。 今回は、前回作ったhugoの予約投稿ツールをtauriでUI作って見やすくしようよ、という記事になります。 Githubはこちら↓ GitHub - satodaiki/hugo-local-cms: hugoの予約投稿管理 tauriのインストールと起動まで まずはプロジェクトにtauriを入れます。 やり方はこちらを参考にします。 プロジェクトの作成 | Tauri フロントエンドのビルドにはviteを利用します。 ❯ npm create vite@latest . > npx > "create-vite" . │ ◇ Current directory is not empty. Please choose how to proceed: │ Ignore files and continue │ ◇ Select a framework: │ React │ ◇ Select a variant: │ TypeScript │ ◇ Use rolldown-vite (Experimental)?: │ No │ ◇ Install with npm and start now? │ Yes │ ◇ Scaffolding project in /home/nanao/Project/personal/hugo-local-cms... │ ◇ Installing dependencies with npm... added 175 packages, and audited 176 packages in 14s 45 packages are looking for funding run `npm fund` for details found 0 vulnerabilities │ ◇ Starting dev server... React + TypeScriptで実装することにしました。 ...

2026年2月5日 · にあえん