[{"content":"本記事は Ubuntu 24.04 を前提としています。\nきっかけ git remote -v を眺めていたら、Personal Access Token (PAT) が remote URL に丸ごと埋め込まれている状態になっていました。\norigin https://ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxx@github.com/USER/REPO.git (fetch) origin https://ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxx@github.com/USER/REPO.git (push) .git/config はリポジトリに含まれないため「push しても外には出ない」ものの、ローカルで露出する経路は意外と多くあります。\nシェル履歴 (~/.bash_history ~/.zsh_history) エディタや CI のログ、画面共有・スクリーンショット うっかり配布した dotfiles リポジトリ マルウェアやリモートアクセスで .git/config が読まれる しかも classic PAT はスコープが粗く、repo 権限があれば private リポジトリ全体の閲覧・書き換え、強制 push による履歴破壊、GitHub Actions secrets の窃取まで可能になります。攻撃者は奪った瞬間には何もせず静かに watch することもできるため、「使われた形跡がない」では安心できません。\nこれを機に、PAT を URL に埋めず GitHub CLI (gh) の credential helper に任せる構成に切り替えました。\nゴール構成 remote URL は素の https://github.com/USER/REPO.git 認証は gh が credential helper として処理 (トークンは OS のセキュアストア管理) devcontainer を作り直しても再ログイン不要 手順 1. 露出したトークンを revoke (最優先) remote URL を書き換える前に、まず GitHub 側でトークンを無効化します。\nclassic PAT: https://github.com/settings/tokens fine-grained PAT: https://github.com/settings/personal-access-tokens 該当トークンを Delete した瞬間に無効になります。順序として これを先にやる ことが大切です。後の手順を待っている間に古いトークンが他経路で使われるリスクを断ちます。\n2. gh をインストール devcontainer 環境なら ghcr.io/devcontainers/features/github-cli feature で gh がすでに入っています。\nホスト (Ubuntu 24.04) に入れる場合は、以下の記事を参考にしてください。\nGitHub CLI (gh) のインストールと基本的な使い方 3. gh auth login で認証 gh auth login 対話で以下のように選びます。\n? Where do you use GitHub? GitHub.com ? What is your preferred protocol for Git operations? HTTPS ? Authenticate Git with your GitHub credentials? Yes ? How would you like to authenticate GitHub CLI? Login with a web browser Authenticate Git with your GitHub credentials? → Yes が肝で、これが git config --global credential.helper を gh 経由に書き換えてくれます。以後 git push のたびに gh が裏でトークンを取り出して認証してくれます。\ndevcontainer のようなヘッドレス環境ではブラウザは自動で開きません。表示される 8 桁のワンタイムコードをコピーし、ホスト側のブラウザで https://github.com/login/device に貼って承認します。\n✓ Authentication complete. ✓ Logged in as USER 4. remote URL からトークンを除去 git remote set-url origin https://github.com/USER/REPO.git git remote -v トークン部分が消えていれば OK です。git fetch origin がパスワードを聞かれずに通れば認証は機能しています。\n5. シェル履歴の残骸を確認 revoke 済みでも、ローカル履歴に残ったトークン文字列は気持ちが悪いものです。\ngrep -rn \u0026#39;ghp_\u0026#39; ~/.bash_history ~/.zsh_history 2\u0026gt;/dev/null ヒットしたら該当行を削除します (history -d \u0026lt;行番号\u0026gt; でメモリ上を消した後、ファイルも編集しておきます)。\ndevcontainer での認証共有 gh の認証情報は ~/.config/gh/hosts.yml (と OS のセキュアストア) に保存されます。devcontainer をリビルドすると ~/.config/gh が消えて再ログインが必要になるため、ホストの設定を bind mount で共有します。\n.devcontainer/devcontainer.json:\n\u0026#34;mounts\u0026#34;: [ \u0026#34;source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached\u0026#34;, \u0026#34;source=${localEnv:HOME}/.claude.json,target=/home/vscode/.claude.json,type=bind,consistency=cached\u0026#34;, \u0026#34;source=${localEnv:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind,consistency=cached\u0026#34; ] 前提として ホスト側に ~/.config/gh ディレクトリが存在している必要があります (なければ bind mount に失敗します)。ホストにも gh を入れて gh auth login 済みにしておくのが一番シンプルです。事情があってホスト側で gh を使わない場合でも、空ディレクトリだけ作っておけばマウント自体は通ります。\nmkdir -p ~/.config/gh # ホスト側で実行 設定変更後は VSCode の Dev Containers: Rebuild Container でリビルドすると反映されます。\n補足: SSH 鍵にしないのか SSH 鍵認証もアリですが、\n鍵ファイル自体の管理 (~/.ssh のパーミッション、複数マシン間の同期) が発生します GitHub Actions など他用途で gh をどのみち使うことが多いです そのため、個人開発の HTTPS なら gh auth login ひとつに寄せた方がメンテが楽だと感じています。複数アカウントの切替も gh auth switch で済むのが良いところです。\nまとめ PAT を URL に埋めるのは「公開リポジトリに含まれない」だけでローカル経路の露出リスクは大きいです。露出に気付いた時点で revoke が最優先となります。 gh auth login で credential helper を任せれば URL からトークンを排除でき、トークンは OS のセキュアストアに隔離されます。 devcontainer ではホストの ~/.config/gh を bind mount しておくと、リビルドのたびに再ログインしなくて済みます。 ","date":"2026-05-11T00:00:00+09:00","permalink":"/posts/2026/05/11/github-cli-auth/","title":"GitHub CLI (gh auth login) で Git の HTTPS 認証を安全にする"},{"content":"GitHub に gh があるように、GitLab には glab があります。ブラウザを開かずに Issue を確認したり、MR（マージリクエスト）を作成したり、CI/CD パイプラインの状況を監視したりできるため、エンジニアの生産性を大きく向上させてくれます。\n1. インストール方法 devcontainer を使っていない場合は、ホスト側に glab をインストールして使います。devcontainer を使う場合は、コンテナイメージに glab をインストールしておくか、devcontainer の Dockerfile / devcontainer.json で導入しておきます。\n一般的な手順としては、Dockerfile に curl で glab の公式パッケージを取得して apt install するか、apt リポジトリからインストールする形です。コンテナ内で glab --version を実行して、インストールが正しくできていることを確認してください。\nOS ごとの主要なインストールコマンドは以下の通りです。\nOS インストールコマンド macOS brew install glab Windows (winget) winget install glab Windows (scoop) scoop install glab Linux (Ubuntu/Debian) sudo apt install glab（※公式リポジトリ推奨） インストール後、glab --version で正常にインストールされたか確認しましょう。\n2. 初期設定（認証） インストールが完了したら、GitLab アカウントと連携させます。\nglab auth login 実行すると対話形式で以下の項目を聞かれます：\nGitLab インスタンスの種類: GitLab.com か 自前運用のサーバー（Self-Managed）かを選択。 認証方法: Web を選択するとブラウザが開いて簡単に認証できます。 プロトコル: SSH または HTTPS を選択。 設定が完了すると、~/.config/glab-cli/config.yml に設定が保存されます。\n3. devcontainer での認証情報共有 glab の認証情報は ~/.config/glab-cli/config.yml に保存されます。devcontainer をリビルドするとコンテナ内の ~/.config/glab-cli が消えて再ログインが必要になるため、ホスト側の設定を bind mount で共有しておくと便利です。\n一般的には、.devcontainer/devcontainer.json の mounts にホストの ~/.config/glab-cli を追加します。例:\n\u0026#34;mounts\u0026#34;: [ \u0026#34;source=${localEnv:HOME}/.config/glab-cli,target=/home/vscode/.config/glab-cli,type=bind,consistency=cached\u0026#34; ] もし gh など別の CLI も devcontainer 内で使うなら、同じように ~/.config/gh を共有することもできます。\nホスト側に ~/.config/glab-cli が存在していれば、ホストで一度 glab auth login しておけばコンテナ側でも同じ認証を使えます。ホストにディレクトリがない場合は、mkdir -p ~/.config/glab-cli で空ディレクトリを作成しておけばマウントが通ります。\nこのようにしておけば、glab の認証設定をホストと devcontainer で共通化し、コンテナ再構築後も再ログイン不要で作業を続けやすくなります。\n4. よく使う基本コマンド集 glab のコマンド体系は glab \u0026lt;リソース名\u0026gt; \u0026lt;アクション\u0026gt; という形式で、gh と非常に似ています。\n■ Merge Request (MR) を操作する 開発のメイン作業となる MR 操作です。\nMR の一覧表示: glab mr list MR の作成: glab mr create --fill（現在のブランチから自動入力で作成） MR の詳細表示: glab mr view \u0026lt;id\u0026gt; MR を承認: glab mr approve \u0026lt;id\u0026gt; MR をマージ: glab mr merge \u0026lt;id\u0026gt; ■ Issue (課題) を管理する Issue 一覧: glab issue list Issue 作成: glab issue create -t \u0026quot;タイトル\u0026quot; -description \u0026quot;内容\u0026quot; ■ CI/CD パイプラインを確認する glab 独自の強力な機能の一つが、CI パイプラインのリアルタイム監視です。\nパイプラインの状態表示: glab pipeline status 実行中のログをリアルタイム表示: glab pipeline ci view 失敗したジョブの再実行: glab ci retry 4. gh と glab の主な対応表 GitHub CLI に慣れている方向けのクイック比較です。\n機能 GitHub (gh) GitLab (glab) 認証 gh auth login glab auth login PR / MR 作成 gh pr create glab mr create PR / MR チェックアウト gh pr checkout \u0026lt;id\u0026gt; glab mr checkout \u0026lt;id\u0026gt; リポジトリ作成 gh repo create glab project create CI 監視 gh run watch glab pipeline ci view 5. 自社運用の GitLab (Self-Managed) で使うコツ glab は GitLab.com だけでなく、会社などで独自に立てている GitLab サーバーでも使えます。その場合、環境変数でホストを指定しておくと便利です。\nexport GITLAB_HOST=gitlab.example.com まとめ glab を導入することで、「コードを書く → プッシュする → ブラウザで MR を作る → CI の完了を待つ」という一連の流れをターミナルから一歩も出ずに行えるようになります。\nまずは glab mr list から始めて、快適な GitLab ライフを送りましょう！\n参考リンク:\nGitLab CLI 公式ドキュメント GitHub リポジトリ (gitlab-org/cli) ","date":"2026-05-10T00:00:00+09:00","permalink":"/posts/2026/05/10/glab/","title":"ターミナルから GitLab を操る：公式 CLI「glab」導入・活用ガイド"},{"content":"GitHub CLI (gh) は、GitHub が公式に提供しているコマンドラインツールです。\nブラウザを開かなくても、リポジトリの clone、Issue の確認、Pull Request の作成、GitHub Actions の実行状況確認などをターミナルから操作できます。Git と組み合わせて使うと、日常の GitHub 作業をかなり短い導線にできます。\nこの記事では、Windows と Ubuntu で gh をインストールし、最初に覚えておくと便利な基本コマンドをまとめます。\ngh でできること gh を使うと、たとえば以下のような作業をターミナルから実行できます。\nGitHub へのログイン リポジトリの clone / 作成 / 表示 Issue の一覧表示 / 作成 / 表示 Pull Request の一覧表示 / 作成 / checkout / merge GitHub Actions の workflow / run の確認 ブラウザで現在のリポジトリや PR を開く GitHub の画面を見に行く回数を減らせるので、コードを書いている流れを止めにくいのが大きなメリットです。\nインストール Windows にインストールする Windows では、公式手順として WinGet を使う方法が一番簡単です。\nPowerShell または Windows Terminal を開いて、以下を実行します。\nwinget install --id GitHub.cli インストール後、ターミナルを開き直して確認します。\ngh --version バージョン情報が表示されればインストール完了です。\nアップデートする場合は以下です。\nwinget upgrade --id GitHub.cli もし WinGet を使わない場合は、GitHub CLI の Releases ページから MSI インストーラーをダウンロードして導入することもできます。\nUbuntu にインストールする Ubuntu では、標準の apt リポジトリからそのままインストールできます。\nまずパッケージ情報を更新してから、gh をインストールします。\nsudo apt update sudo apt install gh 確認します。\ngh --version アップデートする場合は、通常の apt パッケージと同じです。\nsudo apt update sudo apt install gh もし apt で見つからない場合や、GitHub CLI の最新版を追いたい場合は、GitHub CLI 公式リポジトリを追加する方法もあります。\ntype -p wget \u0026gt;/dev/null || (sudo apt update \u0026amp;\u0026amp; sudo apt install wget -y) sudo mkdir -p -m 755 /etc/apt/keyrings wget -nv -O /tmp/githubcli-archive-keyring.gpg https://cli.github.com/packages/githubcli-archive-keyring.gpg sudo cp /tmp/githubcli-archive-keyring.gpg /etc/apt/keyrings/githubcli-archive-keyring.gpg sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg sudo mkdir -p -m 755 /etc/apt/sources.list.d echo \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\u0026#34; | sudo tee /etc/apt/sources.list.d/github-cli.list \u0026gt; /dev/null sudo apt update sudo apt install gh 通常利用なら、まずは sudo apt install gh で十分です。\n初期設定: GitHub にログインする インストールできたら、GitHub アカウントでログインします。\ngh auth login 対話形式でいくつか質問されます。個人利用で GitHub.com を使うなら、だいたい以下の選択で問題ありません。\n? Where do you use GitHub? GitHub.com ? What is your preferred protocol for Git operations? HTTPS ? Authenticate Git with your GitHub credentials? Yes ? How would you like to authenticate GitHub CLI? Login with a web browser ポイントは、Authenticate Git with your GitHub credentials? で Yes を選ぶことです。これにより、gh だけでなく通常の git push や git fetch の HTTPS 認証も gh に任せられます。\nブラウザが開いたら GitHub にログインして認証します。WSL やサーバー上などブラウザを開きにくい環境では、表示されたワンタイムコードを手元のブラウザで入力して認証します。\nログイン状態は以下で確認できます。\ngh auth status 普通の git コマンドでも gh の認証情報は使われる？ 使われます。ただし、gh auth login の途中で以下を選んでいることが前提です。\n? Authenticate Git with your GitHub credentials? Yes この設定を有効にすると、gh が Git の credential helper として設定されます。そのため、以下のような普通の Git コマンドでも、gh でログインした認証情報が利用されます。\ngit fetch origin git pull git push origin main 確認するには、Git の credential helper 設定を見ます。\ngit config --global --get credential.helper 環境によって表示は少し違いますが、gh auth git-credential を使う設定になっていれば OK です。\n注意点として、この仕組みが使われるのは基本的に HTTPS の remote URL です。\ngit remote -v remote URL が以下のようになっていれば、HTTPS 経由です。\norigin https://github.com/OWNER/REPO.git (fetch) origin https://github.com/OWNER/REPO.git (push) 一方で、git@github.com:OWNER/REPO.git のような SSH の remote URL を使っている場合は、Git の認証は SSH key 側で行われます。この場合でも gh コマンド自体の GitHub API 操作には、gh auth login の認証情報が使われます。\nよく使う基本コマンド ここからは、最初に覚えておくと便利なコマンドです。\nリポジトリ操作 リポジトリを clone します。\ngh repo clone OWNER/REPO たとえば GitHub CLI 本体のリポジトリなら以下です。\ngh repo clone cli/cli 現在のリポジトリをブラウザで開きます。\ngh repo view --web 新しいリポジトリを作成します。\ngh repo create 対話形式で公開範囲やローカルディレクトリの扱いを選べます。スクリプト化したい場合は、--public や --private などのオプションを付けて実行できます。\ngh repo create my-app --private --source=. --remote=origin Issue 操作 Issue の一覧を表示します。\ngh issue list Issue の詳細を表示します。\ngh issue view 123 Issue を作成します。\ngh issue create --title \u0026#34;ログイン画面の文言を修正する\u0026#34; --body \u0026#34;ボタンの表記を変更する\u0026#34; ブラウザで Issue を開きたい場合は --web を付けます。\ngh issue view 123 --web Pull Request 操作 Pull Request の一覧を表示します。\ngh pr list 現在のブランチから Pull Request を作成します。\ngh pr create コミットメッセージやブランチ情報からタイトル・本文を自動入力したい場合は --fill が便利です。\ngh pr create --fill Pull Request の詳細を表示します。\ngh pr view 123 Pull Request のブランチをローカルに checkout します。\ngh pr checkout 123 レビューや CI の状態を確認します。\ngh pr checks 123 マージします。\ngh pr merge 123 GitHub Actions 操作 workflow の一覧を表示します。\ngh workflow list 実行履歴を表示します。\ngh run list 最新の workflow run を確認します。\ngh run view 実行中の workflow を待ちます。\ngh run watch 失敗したジョブのログを確認する場合は、まず gh run list で run ID を確認してから以下を実行します。\ngh run view RUN_ID --log-failed 便利な使い方 現在のページをブラウザで開く 現在のリポジトリを GitHub 上で開きます。\ngh browse Issue や PR の URL を組み立てる必要がないので、地味に便利です。\n自分に関係する状況をまとめて見る 自分に関連する Issue、PR、レビュー依頼などを確認できます。\ngh status 朝の作業開始時に一度実行すると、見るべきものをざっと把握できます。\nエイリアスを作る よく使うコマンドは短い別名を付けられます。\ngh alias set prs \u0026#39;pr list --author @me\u0026#39; gh prs この例では、自分が作成した Pull Request 一覧を gh prs で表示できます。\nWindows と Ubuntu で共通して気をつけること ターミナルを開き直す Windows ではインストーラーが PATH を更新します。gh が見つからない場合は、Windows Terminal や PowerShell を開き直してください。\nUbuntu でも、インストール後にシェルがコマンドの場所をキャッシュしている場合があります。うまく認識されないときは、ターミナルを開き直すか hash -r を実行します。\nGit の認証方式をそろえる gh auth login で HTTPS を選び、Git の認証にも gh を使う設定にした場合、リモート URL も HTTPS にしておくと扱いやすいです。\ngit remote -v git remote set-url origin https://github.com/OWNER/REPO.git SSH を使いたい場合は、gh auth login の対話で SSH を選び、SSH key の登録も gh に任せることができます。HTTPS と SSH のどちらでも使えますが、チームや自分の環境で片方に寄せておくとトラブルシュートが楽です。\nGitHub Enterprise Server を使う場合 会社などで GitHub Enterprise Server を使っている場合は、ホスト名を指定してログインします。\ngh auth login --hostname github.example.com 通常の GitHub.com とは認証先が別になるため、gh auth status でどのホストにログインしているか確認しておくと安心です。\nまとめ gh を入れておくと、GitHub 上の操作をターミナルから自然に扱えるようになります。\nまずは以下の流れだけ覚えておけば十分です。\ngh auth login gh repo clone OWNER/REPO gh issue list gh pr list gh pr create --fill gh run list 慣れてきたら gh pr checkout、gh pr checks、gh run watch あたりを使うと、Pull Request と CI の確認がかなり楽になります。\n参考 URL GitHub CLI GitHub CLI Manual Installing gh on Windows Installing gh on Linux and BSD ","date":"2026-05-09T00:00:00+09:00","permalink":"/posts/2026/05/09/github-cli/","title":"GitHub CLI (gh) のインストールと基本的な使い方"},{"content":"いろいろテーマを探してみましたが、多機能かつドキュメントが充実していたのでこれにしてみました。\n以下の記事で作成した環境に対して適用します。\nhttps://kuttsun.blogspot.com/2024/09/jekyll-docker.html 尚、デモページ、およびドキュメントは以下です。\nhttps://mmistakes.github.io/minimal-mistakes/ https://github.com/mmistakes/minimal-mistakes テーマの適用 Gemfile に以下を追加します。\ngem \u0026#34;minimal-mistakes-jekyll\u0026#34; _cofig.yml でテーマを指定します。\ntheme: minimal-mistakes-jekyll 必要に応じて各記事のレイアウトを変更します。\nlayout: single 選択できるレイアウトについては以下を参照してください。\nhttps://mmistakes.github.io/minimal-mistakes/docs/layouts/ テーマを適用するだけならこれだけで OK でした。\nその他の設定 公式のドキュメントに詳しく書かれているので、ここでは個人的に必要なものだけ抜粋して記載します。\nスキンを変更する https://mmistakes.github.io/minimal-mistakes/docs/configuration/#skin _config.yml に以下のように記述します。\nminimal_mistakes_skin: \u0026#34;default\u0026#34; 記事内の目次を作成する https://mmistakes.github.io/minimal-mistakes/docs/layouts/#table-of-contents フロントマターで以下のように設定します。\n--- toc: true toc_label: \u0026#34;My Table of Contents\u0026#34; toc_icon: \u0026#34;cog\u0026#34; --- 記事を広げる https://mmistakes.github.io/minimal-mistakes/docs/layouts/#wide-page 記事を目次のスペースまで広げるには、フロントマターで以下を設定します。\nclasses: wide ページの作成 _pages というフォルダを作成し、そこに記事を追加します。\n_config.yml に以下を追加し、_pages フォルダのドキュメントを変換対象に指定します。\ncollections: pages: output: true カテゴリごとの記事一覧ページを作成 https://mmistakes.github.io/minimal-mistakes/docs/layouts/#archive-layout _pages に category-archive.md というファイルを作成し、以下のような内容を記述します。\n--- title: \u0026#34;Posts by Category\u0026#34; layout: categories permalink: /categories/ author_profile: true --- タグごとの記事一覧ページを作成 カテゴリとほぼ同じです。\nhttps://mmistakes.github.io/minimal-mistakes/docs/layouts/#archive-layout _pages に tag-archive.md というファイルを作成し、以下のような内容を記述します。\n--- title: \u0026#34;Posts by Tag\u0026#34; permalink: /tags/ layout: tags author_profile: true --- 年ごとの記事一覧ページを作成 カテゴリとほぼ同じです。\nhttps://mmistakes.github.io/minimal-mistakes/docs/layouts/#archive-layout _pages に year-achive.md というファイルを作成し、以下のような内容を記述します。\n--- title: \u0026#34;Posts by Year\u0026#34; permalink: /year-archive/ layout: posts author_profile: true --- サイドバーを作成する https://mmistakes.github.io/minimal-mistakes/docs/layouts/#custom-sidebar-navigation-menu https://mmistakes.github.io/minimal-mistakes/layout-sidebar-nav-list/ https://mmistakes.github.io/minimal-mistakes/layout-sidebar-custom/ _data/navigation.yml を作成し、以下のような内容を記述します。\ndocs: - title: Getting Started children: - title: \u0026#34;Quick-Start Guide\u0026#34; url: /docs/quick-start-guide/ - title: \u0026#34;Structure\u0026#34; url: /docs/structure/ - title: \u0026#34;Installation\u0026#34; url: /docs/installation/ - title: \u0026#34;Upgrading\u0026#34; url: /docs/upgrading/ - title: Customization children: - title: \u0026#34;Configuration\u0026#34; url: /docs/configuration/ - title: \u0026#34;Navigation\u0026#34; url: /docs/navigation/ - title: \u0026#34;UI Text\u0026#34; url: /docs/ui-text/ - title: \u0026#34;Authors\u0026#34; url: /docs/authors/ - title: \u0026#34;Layouts\u0026#34; url: /docs/layouts/ - title: Content children: - title: \u0026#34;Working with Posts\u0026#34; url: /docs/posts/ - title: \u0026#34;Working with Pages\u0026#34; url: /docs/pages/ - title: \u0026#34;Working with Collections\u0026#34; url: /docs/collections/ - title: \u0026#34;Helpers\u0026#34; url: /docs/helpers/ - title: \u0026#34;Utility Classes\u0026#34; url: /docs/utility-classes/ - title: Extras children: - title: \u0026#34;Stylesheets\u0026#34; url: /docs/stylesheets/ - title: \u0026#34;JavaScript\u0026#34; url: /docs/javascript/ サイドバーを表示させたい記事のフロントマターに以下を追加します。\nsidebar: nav: \u0026#34;docs\u0026#34; 全ての記事に表示したい場合は、_config.yml で以下のようにデフォルト値を設定します。\ndefaults: - scope: path: \u0026#34;\u0026#34; values: sidebar: nav: \u0026#34;docs\u0026#34; show_date: true 画面上部にナビゲーションを作成する https://mmistakes.github.io/minimal-mistakes/docs/navigation/ _data/navigation.yml を作成し、以下のような内容を記述します。\nmain: - title: \u0026#34;Quick-Start Guide\u0026#34; url: /docs/quick-start-guide/ - title: \u0026#34;Posts\u0026#34; url: /year-archive/ - title: \u0026#34;Categories\u0026#34; url: /categories/ - title: \u0026#34;Tags\u0026#34; url: /tags/ - title: \u0026#34;Pages\u0026#34; url: /page-archive/ - title: \u0026#34;Collections\u0026#34; url: /collection-archive/ - title: \u0026#34;External Link\u0026#34; url: https://google.com target: _blank 記事に投稿日を表示 https://mmistakes.github.io/minimal-mistakes/docs/configuration/#post-dates _config.yml に以下を設定します。\ndefaults: - scope: path: \u0026#34;\u0026#34; type: posts values: show_date: true 投稿日のフォーマットを指定 https://mmistakes.github.io/minimal-mistakes/docs/configuration/#post-dates _config.yml で以下のように設定します。\ndate_format: \u0026#34;%Y-%m-%d\u0026#34; コードブロックにコピーボタンを付ける https://mmistakes.github.io/minimal-mistakes/docs/configuration/#code-block-copy-button _config.yml に以下を設定します。\nenable_copy_code_button: true サイト内検索を有効にする https://mmistakes.github.io/minimal-mistakes/docs/configuration/#site-search _config.yml に以下を追加します。\nsearch: true 検索エンジンは変更できるようです（上記リンク参照）。\nパンくずリストの作成 https://mmistakes.github.io/minimal-mistakes/docs/configuration/#breadcrumb-navigation-beta https://mmistakes.github.io/minimal-mistakes/docs/configuration/#archive-settings 参考 URL https://gwenneg.com/2024/08/17/blogging-with-minimal-effort.html ","date":"2024-12-19T00:00:00+09:00","permalink":"/posts/2024/12/19/jekyll/","title":"Jekyll で Minimal Mistakes のテーマを適用する"},{"content":"Linux ではシステム起動時に /tmp ディレクトリの中身がクリアされます。\nしかし、docker コンテナの場合は、通常はコンテナを再起動しても /tmp ディレクトリの中身はクリアされません。\ndocker コンテナで起動時に /tmp ディレクトリの中身をクリアするには、以下のように tmpfs ボリュームを使用する方法があります。\n$ docker run --tmpfs /tmp my_image docker compose の場合は以下のように指定します。\nservices: my_service: image: my_image volumes: - type: tmpfs target: /tmp # その他の設定 尚、docker compose で手動でボリュームを再作成したい場合は、以下のように docker compose down -v コマンドを使用して既存のボリュームを削除し、その後 docker compose up -d コマンドで新しいボリュームを作成してコンテナを起動します。\n$ docker compose down -v $ docker compose up -d 参考 URL ","date":"2024-12-11T00:00:00+09:00","permalink":"/posts/2024/12/11/docker/","title":"docker コンテナ起動時に /tmp ディレクトリの中身を削除する"},{"content":"ローカル環境で画像生成AIを使いたかったので、docker を使って Stable Diffusion web UI を動かしてみました。\n環境 Ubuntu 20.04 docker 24.0.7 手順 Stable Diffusion web UI のリポジトリは以下になります。\nhttps://github.com/AUTOMATIC1111/stable-diffusion-webui Docker 版のリポジトリは以下です。\nhttps://github.com/AbdBarho/stable-diffusion-webui-docker まずはクローンしてきます。\n$ git clone https://github.com/AbdBarho/stable-diffusion-webui-docker.git あとは基本的に Setup に記載されている通りにすすめるだけです。\n$ cd stable-diffusion-webui-docker $ docker compose --profile download up --build $ docker compose --profile auto up --build しかし、私の場合、docker compose --profile auto up --build を実行したときにいくつかエラーが発生しました。\n最初に発生したエラーは以下です。\n@workspace Step 12/28 : RUN --mount=type=cache,target=/var/cache/apt apt-get update \u0026amp;\u0026amp; apt-get install -y fonts-dejavu-core rsync git jq moreutils aria2 ffmpeg libglfw3-dev libgles2-mesa-dev pkg-config libcairo2 libcairo2-dev build-essential the --mount option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled ERROR: Service \u0026#39;auto\u0026#39; failed to build : Build failed 生成AIに聞いてみた結果、どうやら buildx を手動でインストールする必要があるみたいでした。\nまずバイナリをダウンロードします:\ncurl -SL https://github.com/docker/buildx/releases/download/v0.10.4/buildx-v0.10.4.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx ダウンロードしたファイルに実行権限を付与します:\nchmod a+x ~/.docker/cli-plugins/docker-buildx Docker Buildx が正しくインストールされたことを確認します:\ndocker buildx version これで docker buildx コマンドが利用できるようになります。\n再度 docker-compose --profile auto up --build を実行したところ、今度は以下のエラーが発生しました。\nauto-cpu_1 | Installing extension dependencies (if any) auto-cpu_1 | Traceback (most recent call last): auto-cpu_1 | File \u0026#34;/stable-diffusion-webui/webui.py\u0026#34;, line 13, in \u0026lt;module\u0026gt; auto-cpu_1 | initialize.imports() auto-cpu_1 | File \u0026#34;/stable-diffusion-webui/modules/initialize.py\u0026#34;, line 23, in imports auto-cpu_1 | import gradio # noqa: F401 auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/gradio/__init__.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; auto-cpu_1 | import gradio.components as components auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/gradio/components/__init__.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; auto-cpu_1 | from gradio.components.bar_plot import BarPlot auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/gradio/components/bar_plot.py\u0026#34;, line 7, in \u0026lt;module\u0026gt; auto-cpu_1 | import altair as alt auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/__init__.py\u0026#34;, line 649, in \u0026lt;module\u0026gt; auto-cpu_1 | from altair.vegalite import * auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/vegalite/__init__.py\u0026#34;, line 2, in \u0026lt;module\u0026gt; auto-cpu_1 | from .v5 import * auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/vegalite/v5/__init__.py\u0026#34;, line 2, in \u0026lt;module\u0026gt; auto-cpu_1 | from altair.expr.core import datum auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/expr/__init__.py\u0026#34;, line 11, in \u0026lt;module\u0026gt; auto-cpu_1 | from altair.expr.core import ConstExpression, FunctionExpression auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/expr/core.py\u0026#34;, line 6, in \u0026lt;module\u0026gt; auto-cpu_1 | from altair.utils import SchemaBase auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/utils/__init__.py\u0026#34;, line 14, in \u0026lt;module\u0026gt; auto-cpu_1 | from .plugin_registry import PluginRegistry auto-cpu_1 | File \u0026#34;/opt/conda/lib/python3.10/site-packages/altair/utils/plugin_registry.py\u0026#34;, line 13, in \u0026lt;module\u0026gt; auto-cpu_1 | from typing_extensions import TypeIs auto-cpu_1 | ImportError: cannot import name \u0026#39;TypeIs\u0026#39; from \u0026#39;typing_extensions\u0026#39; (/opt/conda/lib/python3.10/site-packages/typing_extensions.py) これについては現時点でのバグっぽいですが、以下に回避策がありました。\nhttps://github.com/AbdBarho/stable-diffusion-webui-docker/issues/729 これで上手くいき、http://localhost:7860/ にアクセスすれば使用できました。\nSDXLモデルの設定 SDXL モデルの設定については以下が参考になりました。\nhttps://soroban.highreso.jp/article/article-042#651d4816a6d63105f25c3c7e-1aca58d91345337803792bb7 ただ、モデルと VAE の配置場所は現在のバージョンだと少し異なるようです。\nモデル: data/models/Stable-diffusion VAE: data/models/VAE 参考 URL ^ https://soroban.highreso.jp/article/article-042#651d4816a6d63105f25c3c7e-1aca58d91345337803792bb7\nhttps://highreso.jp/edgehub/stablediffusion/vae.html#index_id8 https://e-penguiner.com/stable-diffusion-webui-docker/ https://note.com/npaka/n/nc8b0e9a91d97 https://zenn.dev/karaage0703/articles/bf86fe4946417b https://pc.watch.impress.co.jp/docs/column/nishikawa/1485422.html ","date":"2024-12-10T00:00:00+09:00","permalink":"/posts/2024/12/10/stable-diffution/","title":"Stable Diffusion web UI を動かしてみた"},{"content":"asciidoctor-pdf で PDF を作成したときに、見出しレベル２の前に改ページが自動挿入されて無駄に余白があるページがあったので、これを無効化する方法です。\nPDF 作成時に指定するテーマファイルで設定します。\nhttps://docs.asciidoctor.org/pdf-converter/latest/theme/heading/#chapter これを踏まえ、heading-chapter で break-before を auto に指定すれば改ページが無効になりました。\nheading: chapter: break-before: auto ただ、auto なので、場合によっては改ページされる場合もあるかもしれません。\n尚、上記の設定は doctype が book の場合のみ有効なようです。\n参考 URL https://docs.asciidoctor.org/pdf-converter/latest/theme/heading/#chapter ","date":"2024-09-30T00:00:00+09:00","permalink":"/posts/2024/09/30/asciidoctor-pdf/","title":"asciidoctor-pdf で見出し前の改ページを無効にする"},{"content":"画像を読み込んでいるファイルを、別の階層にあるファイルから include ディレクティブで読み込むと、相対パスの位置が変わってしまいます。\n画像を読み込んでいるファイルを単体で表示したときにはそのファイルからの相対パスになりますが、include しているファイルを表示したときには、include しているファイルからの相対パスになってしまいます。\n同様のことで悩んでいる方はそれなりにいるようで、実際私もかなり困っています。\nhttps://qiita.com/hyt126/items/7a070bc2b1e185b9146f https://qiita.com/ihgs/items/3c7a4fa9a2a81b23da82 https://discuss.asciidoctor.org/Strange-issues-with-asciidoc-imagesdir-stops-working-td8194.html とりあえずの解決方法としては、私にとっては以下の方法が一番役に立ちました。\nhttps://stackoverflow.com/questions/75919047/images-in-various-locations-in-included-asciidoc-files ちょっと手間ですが、include 直前で imagedir を都度セットします。\n:imagesdir: hoge include::hoge/included_file.adoc ただし、多重で include している場合は上記では上手くいきません。\nこの場合は、試行錯誤の結果、以下のようにすることで上手くいきました。\nifdef::imagesdir[:imagesdir: {imagesdir}/hoge] ifndef::imagesdir[:imagesdir: hoge] include::hoge/included_file.adoc imagesdir が既に定義されている（＝別のファイルから include されている）場合は、現在の imagesdir に付加する形で imagesdir を再定義します。\nimagesdir が定義されていない（＝別のファイルから include されていない）場合は、前述と同じように imagesdir を定義します。\n参考 URL https://stackoverflow.com/questions/75919047/images-in-various-locations-in-included-asciidoc-files ","date":"2024-09-26T00:00:00+09:00","permalink":"/posts/2024/09/26/asciidoc/","title":"Asciidoc で include ディレクティブを使用している場合の画像へのパス"},{"content":"markdown だけでなく asciidoc も使えたら便利なので。\n以下の記事で作成した環境に対して asciidoc を使えるようにします。\nhttps://kuttsun.blogspot.com/2024/09/jekyll-docker.html 調べたところ以下のプラグインを使えば出来そうだったので、これを使ってみます。\nhttps://github.com/asciidoctor/jekyll-asciidoc Gemfile の変更 ドキュメントの通りに、Gemfile に以下を追加します。\ngroup :jekyll_plugins do gem \u0026#39;jekyll-asciidoc\u0026#39; end _config.yml に以下を追加します。\nplugins: - jekyll-asciidoc 尚、jekyll-asciidoc は他のプラグインより先に記述する必要があるそうです。\n詳細は以下を参照してください。\nhttps://github.com/asciidoctor/jekyll-asciidoc?tab=readme-ov-file#plugin-ordering これで、_post フォルダ内に asciidoc で書いたドキュメントを追加すれば OK です。\nファイル名は Jekyll の決まりとして、YYYY-MM-DD-title.adoc のように日付が入っている必要があります。\nhttps://jekyllrb-ja.github.io/docs/posts/ 以下、サンプル通りに記事を作成してみました。\n= Sample Page :page-layout: post :page-permalink: /sample/ :url-asciidoctor: https://asciidoctor.org This is a sample page composed in AsciiDoc. Jekyll converts it to HTML using {url-asciidoctor}[Asciidoctor]. [source,ruby] puts \u0026#34;Hello, World!\u0026#34; ただし、layuout は post に変更しています。\nレイアウトについては以下。\nhttps://qiita.com/a999/items/6c29ce072c19448ea579 これできちんと表示されることを確認しました。\ninclude ディレクティブを使用するときの相対パス asciidoc では include ディレクティブを使用して他の asciidoc ファイルを読み込むことができますが、このままやっても上手く描画されませんでした。\n同様の内容が issue に挙がっていました。\nhttps://github.com/asciidoctor/jekyll-asciidoc/issues/182 ベースディレクトリの設定がデフォルトではプロジェクトルートになっているので、ソースディレクトリとなるように設定すれば良さそうです。\nhttps://github.com/asciidoctor/jekyll-asciidoc?tab=readme-ov-file#specifying-the-base-directory _config.yml に以下を追記すれば上手くいきました。\nasciidoctor: base_dir: :docdir safe: unsafe 参考 URL https://github.com/asciidoctor/jekyll-asciidoc https://qiita.com/a999/items/6c29ce072c19448ea579 https://github.com/asciidoctor/jekyll-asciidoc/issues/182 ","date":"2024-09-12T00:00:00+09:00","permalink":"/posts/2024/09/12/jekyll-asciidoc/","title":"Jekyll で Asciidoc を使用する"},{"content":"一番基本的なところを試してみたかっただけなのですが、嵌ったのでメモしておきます。\n公式の Docker イメージは以下のようなのでこれを使いますが、長い間メンテナンスされていないようです。\nhttps://hub.docker.com/r/jekyll/jekyll/ 環境 Ubuntu 20.04 docker 24.0.7 新規作成 まずはドキュメントに従って、以下のコマンドでブログを新規作成してみます。\nexport site_name=\u0026#34;my-blog\u0026#34; docker run --rm \\ --volume=\u0026#34;$PWD:/srv/jekyll\u0026#34; \\ -it jekyll/jekyll \\ sh -c \u0026#34;chown -R jekyll /usr/gem/ \u0026amp;\u0026amp; jekyll new $site_name\u0026#34; \\ \u0026amp;\u0026amp; cd $site_name 以下のエラーが出て失敗します。\n/usr/local/lib/ruby/3.1.0/fileutils.rb:243:in `mkdir\u0026#39;: Permission denied @ dir_s_mkdir - /srv/jekyll/my-blog (Errno::EACCES) これでかなり嵌ったのですが、以下の記事を見つけました。\nhttps://stackoverflow.com/questions/57503011/unable-to-build-cloned-jekyll-site-jekyll-3-8-5-error-permission-denied-d どうやら環境変数を指定すればいいそうです。\nexport site_name=\u0026#34;my-blog\u0026#34; docker run --rm \\ -e JEKYLL_UID=1001 \\ -e JEKYLL_GID=1001 \\ --volume=\u0026#34;$PWD:/srv/jekyll\u0026#34; \\ -it jekyll/jekyll \\ sh -c \u0026#34;chown -R jekyll /usr/gem/ \u0026amp;\u0026amp; jekyll new $site_name\u0026#34; \\ \u0026amp;\u0026amp; cd $site_name これで、my-blog というフォルダが作成され、中に Jekyll のブログの雛形が作成されています。\nビルド 上記で作成した内容をビルドして静的サイトを生成します。\n引き続き、環境変数を指定したうえでドキュメント通りに以下を実行します。\nexport site_name=\u0026#34;my-blog\u0026#34; docker run --rm \\ -e JEKYLL_UID=1001 \\ -e JEKYLL_GID=1001 \\ --volume=\u0026#34;$PWD/$site_name:/srv/jekyll\u0026#34; \\ -it jekyll/jekyll \\ jekyll build my-blog の中に _site というフォルダが出力されました。\nサーバー起動 次はサーバーを起動してブラウザからアクセスしてみます。\nこちらも引き続き、環境変数を指定したうえでドキュメント通りに以下を実行します。\nexport site_name=\u0026#34;my-blog\u0026#34; docker run --rm \\ -e JEKYLL_UID=1001 \\ -e JEKYLL_GID=1001 \\ --volume=\u0026#34;$PWD/$site_name:/srv/jekyll\u0026#34; \\ -p 4000:4000 \\ -it jekyll/jekyll \\ jekyll serve --force_polling --livereload --host 0.0.0.0 以下のエラーが出ました。\n\u0026lt;internal:/usr/local/lib/ruby/site_ruby/3.1.0/rubygems/core_ext/kernel_require.rb\u0026gt;:85:in `require\u0026#39;: cannot load such file -- webrick (LoadError) webrick がないそうなので、my-blog 内の Gemfile に以下を追加します。\ngem \u0026#34;webrick\u0026#34; これで上記のコマンドを実行すれば無事サーバーが起動し、http://localhost:4000 にアクセスすることで閲覧できました。\n所感 公式の Docker イメージ使って基本的なことを確認したかっただけなのに全然動かねぇじゃんって思いました。\n参考 URL https://stackoverflow.com/questions/57503011/unable-to-build-cloned-jekyll-site-jekyll-3-8-5-error-permission-denied-d ","date":"2024-09-11T00:00:00+09:00","permalink":"/posts/2024/09/11/jekyll-docker/","title":"Jekyll + Docker で静的サイトを作成する"},{"content":"アイコン画像を別で用意して、それに差し替える方法です。\nドキュメント内で以下のように記述して、アイコンを画像ベースの設定に変更します。\n:icons: image そのうえで、PDF 作成用のテーマファイルで以下のように画像を指定します。\nadmonition: icon: note: image: \u0026#39;\u0026#39; # NOTEのアイコン画像のパスを指定 caution: image: \u0026#39;\u0026#39; # CAUTIONのアイコン画像のパスを指定 参考 URL https://docs.asciidoctor.org/asciidoc/latest/macros/icons-image/ https://docs.asciidoctor.org/pdf-converter/latest/theme/admonition/ ","date":"2024-09-09T00:00:00+09:00","permalink":"/posts/2024/09/09/asciidoctor-pdf/","title":"asciidoctor-pdf で admonition のアイコンを別の画像に差し替える"},{"content":"PySide6 で作成したアプリを Pyinstaller で exe 化して実行すると、オーバーフローのエラーが発生しました。\nエラーの内容は以下です。\nOverflowError: Python int too large to convert to C long 該当箇所のコードは、以下のように QLineChart を使ってグラフを作成している箇所でした。\nseries = QLineSeries() # 省略... qdt = QDateTime() qdt.setSecsSinceEpoch(int(dt.timestamp())) series.append(qdt.toMSecsSinceEpoch(), count) toMSecsSinceEpoch() はエポックタイムをミリ秒で返す関数です。\nこれを append 関数に渡すと、32bit の範囲を超えているとしてエラーが発生していました。\nビルド環境も実行環境も 64bit なのに、何故 32bit の long int として扱われているのか？\n調べてみたところ、以下の記事に辿り着きました。\nhttps://stackoverflow.com/questions/72568201/why-overflowerror-occured-only-on-pyside6 どうやら float でキャストすれば良さそうということで、以下のように修正したところ、正常に動作するようになりました。\nseries.append(float(qdt.toMSecsSinceEpoch()), count) 参考 URL https://stackoverflow.com/questions/72568201/why-overflowerror-occured-only-on-pyside6 ","date":"2024-08-30T00:00:00+09:00","permalink":"/posts/2024/08/30/pyside/","title":"[PySide] Windows で実行するとオーバーフローエラーが発生"},{"content":"pipenv でパッケージをインストールしたときに、全く関係ないパッケージが勝手にアップデートされて困ったことがありました。\n--keep-outdated を付与すればいいという情報もありますが、現在の pipenv ではこのオプションは削除されています。\nとりあえず以下のようにして回避しました。\n1. Pipfile 内の全てのパッケージのバージョンを指定\nPipfile をエディタで開き、でバージョンを指定していなかったパッケージに全てバージョンを記入しました。\nバージョンは、Pipfile.lock を参照して、現在インストールされているバージョンを記入しました。\n2. すべてのパッケージをアンインストール\n$ pipenv uninstall --all 3. Pipfile.lock を削除\n$ rm Pipfile.lock 4. 新しい依存関係をインストール\n$ pipenv install 参考 URL ","date":"2024-07-17T00:00:00+09:00","permalink":"/posts/2024/07/17/python/","title":"[python] pipenv で無関係のパッケージが勝手にアップデートされる"},{"content":"動画編集ソフトの Shotcut にはフレーズフレーム機能はありませんが、以下のようにすることで簡単にフリーズフレームを入れることができます。\n環境 Shotcut 24.06.26 図中の動画は以下のサイトから適当にダウンロードしてきました。\nhttps://pixabay.com/ja/ やり方 フリーズフレームを入れたい場所に再生ヘッドを移動し、「再生ヘッドで分割する」を選択します。\n「ファイル \u0026gt; 書き出し \u0026gt; フレーム」で、フレームを静止画としてエクスポートします。\nエクスポートした静止画を取り込み、それを分割した動画の間に挿入します。\nあとは、挿入した静止画を選択し、右クリックして「プロパティ」を開き、長さを変更します。\n","date":"2024-07-06T00:00:00+09:00","permalink":"/posts/2024/07/06/shotcut/","title":"Shotcut でフリーズフレームを入れる"},{"content":"redis の公式のイメージを docker compose で動かしていたのですが、command を指定しない場合のデフォルトの実行コマンドは redis-server で、この場合、コンテナを再起動してもデータが残っていました。\nしかし、コマンドを command: sh -c \u0026quot;redis-server\u0026quot; としたところ、コンテナ再起動でデータがクリアされたので、両者の違いについて ChatGPT に聞いてみました。\ncommand: \u0026quot;redis-server\u0026quot;: この設定では、単純に redis-server コマンドが実行されます。Docker はこのコマンドを直接実行し、Redis サーバーが起動します。この場合、Redis サーバーが直接起動されるため、Redis のデフォルトの動作が適用され、データが残ります。再起動してもデータがクリアされないのは、Redis サーバーが正しく起動し、データをインメモリに保持しているためです。\ncommand: sh -c \u0026quot;redis-server\u0026quot;: 一方、この設定では、sh -c コマンドを使用してシェルでコマンドを実行します。sh -c コマンドは、指定されたコマンドをシェル経由で実行します。この場合、redis-server コマンドがシェル経由で実行されるため、Docker はシェルプロセスを起動し、そのシェルプロセスが redis-server コマンドを実行します。この場合、シェルプロセスがメインプロセスとなり、redis-server がそのサブプロセスとして実行されます。そのため、正しくプロセスが管理されない可能性があり、コンテナの再起動時にデータがクリアされる可能性があります。\n","date":"2024-06-27T00:00:00+09:00","permalink":"/posts/2024/06/27/redis/","title":"redis コンテナでの redis-server の起動方法による違い"},{"content":"PySide6 で GUI アプリを作成している中で、I/O バウンドな処理の完了を待っていると GUI が固まりました。\n通常、バックグラウンドで何か処理を行う際に GUI の処理を止めないようにするために、QThread などを使って別スレッドで処理を行いつつ GUI スレッドを回したりします。\n今回もそうやって作っていたのですが、完了までに時間のかかる I/O バウンドな処理を行わせたい場合、上手くいかない場合がありました。 I/O バウンドな処理なので、ayncio を使って非同期処理にしてやればいいと思ったのですが、Qt のイベントループと asyncio を適切に統合する必要があるようで、どのように記述すればいいのかいまいち分かりませんでした。\nそんな中、qasync というライブラリを使えば、とても簡単に処理を組み込むことができました。\nhttps://github.com/CabbageDevelopment/qasync コードの記述も、QThread を使う場合に比べシンプルでスッキリします。\nちなみに、asyncqt というライブラリもありますが、こちらは既にメンテナンスされていないようです。 qasync は asyncqt のフォークで、現在でもメンテナンスされているようなので、使うなら qasync のほうが良いと思います。\n環境 python 3.11 pyside6 6.6.3.1 qasync 0.27.1 サンプルコード import sys import asyncio from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel from qasync import QEventLoop, asyncSlot class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(\u0026#34;qasync sample\u0026#34;) self.label = QLabel(\u0026#34;Press the button.\u0026#34;, self) self.button = QPushButton(\u0026#34;Start\u0026#34;, self) self.button.clicked.connect(self.on_button_clicked) layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.button) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) @asyncSlot() async def on_button_clicked(self): self.label.setText(\u0026#34;waiting...\u0026#34;) self.button.setEnabled(False) # I/O バウンドな処理を想定 await asyncio.sleep(5) self.label.setText(\u0026#34;Press the button.\u0026#34;) self.button.setEnabled(True) if __name__ == \u0026#34;__main__\u0026#34;: app = QApplication(sys.argv) loop = QEventLoop(app) asyncio.set_event_loop(loop) window = MainWindow() window.show() with loop: loop.run_forever() 参考 URL https://github.com/CabbageDevelopment/qasync ","date":"2024-04-22T00:00:00+09:00","permalink":"/posts/2024/04/22/pyside/","title":"[PySide] qasync を使って非同期処理をバックグランドで行う"},{"content":"Ubuntu 20.04 で確認しました。\n新しいユーザーを作成する ログインユーザー自身のユーザー名は変更できないので、一時的に管理者権限を持ったユーザーを作成します。\nUbuntu の場合は以下のように GUI で簡単に追加できます。\n設定 \u0026gt; ユーザー \u0026gt; ユーザーを追加 ユーザー名とグループ名の変更 作成した新しいユーザーでログインして行います。\nユーザー名の変更 以下のコマンドを実行します。\n$ sudo usermod -l new_username -d /home/new_username -m old_username 尚、usermod コマンドの -g オプションでグループ名も一緒に変更できるような記述も見つけましたが、私の場合、usermod: グループ 'new_username' は存在しません のエラーが表示されてできなかったため、ユーザー名とグループ名の変更を分けて行いました。\nグループ名の変更 以下のコマンドを実行します。\n$ sudo groupmod -n new_username old_username 以上で、変更後のユーザーでログインして問題なく使えることを確認しました。\nただ、唯一、ユーザーのアイコン画像だけがクリアされていました。\n参考 URL https://zenn.dev/creationup2u/articles/1d863f40fec16a https://minory.org/linux-usermod.html ","date":"2024-04-02T00:00:00+09:00","permalink":"/posts/2024/04/02/ubuntu/","title":"Ubuntu のユーザー名を変更する"},{"content":"ホストもコンテナも Windows になるので、Dockerfile の記述方法や、コンテナ起動時のオプションの指定方法なども Linux とは異なります。\nWindows 上で Docker を (Docker Desktop を使わずに) 動かす方法については以下を参照してください。\nhttps://kuttsun.blogspot.com/2023/11/docker-windows.html そのうえで、今回 Python の Windows コンテナを動かしてみました。\nDocker Hub にある Python の Windows コンテナとしては、windowsservercore-ltsc2022 と windowsservercore-1809 があるようです。\nhttps://hub.docker.com/_/python 今回は python:3.11-windowsservercore-1809 のイメージを使いました。\nDockerfile のサンプル FROM python:3.11-windowsservercore-1809 CMD [\u0026#34;cmd.exe\u0026#34;] Linux コンテナでは CMD [ \u0026quot;/bin/bash\u0026quot; ] のように記述していましたが、Windows コンテナでは CMD [\u0026quot;cmd.exe\u0026quot;] または CMD [\u0026quot;powershell.exe\u0026quot;] のように記述します。\nコンテナ起動例 \u0026gt; docker run -it --rm ^ --name コンテナ名 ^ -v %CD%\\workspace:C:\\workspace ^ --workdir=C:\\workspace ^ イメージ名 Linux ではカレントディレクトリを指定するために $(pwd) と記述したりしていましたが、Windows なので %CD% と記述します。\nパスの区切りは \\(バックスラッシュ) になります。\nコマンド途中での改行も、Linux ではバックスラッシュでしたが、Windows なので ^ になります。\nまた、Windows コンテナなので、マウント先のパスも C ドライブからのパスで記述します。\nとりあえずここまで。\n追々いろいろ追記するかもしれません。\n参考 URL https://qiita.com/ishibashi-futoshi/items/db807d64624f43be1be9 ","date":"2024-03-18T00:00:00+09:00","permalink":"/posts/2024/03/18/docker-python/","title":"[Docker] Python の Windows コンテナを動かしてみた"},{"content":"python で YOLOv8 を動かそうとしたら以下のエラーが出ました。\nfrom ultralytics import YOLO Traceback (most recent call last): File \u0026#34;/workspace/yolov8/test1.py\u0026#34;, line 1, in \u0026lt;module\u0026gt; from ultralytics import YOLO File \u0026#34;/usr/local/lib/python3.11/site-packages/ultralytics/__init__.py\u0026#34;, line 5, in \u0026lt;module\u0026gt; from ultralytics.data.explorer.explorer import Explorer File \u0026#34;/usr/local/lib/python3.11/site-packages/ultralytics/data/__init__.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; from .base import BaseDataset File \u0026#34;/usr/local/lib/python3.11/site-packages/ultralytics/data/base.py\u0026#34;, line 12, in \u0026lt;module\u0026gt; import cv2 File \u0026#34;/usr/local/lib/python3.11/site-packages/cv2/__init__.py\u0026#34;, line 181, in \u0026lt;module\u0026gt; bootstrap() File \u0026#34;/usr/local/lib/python3.11/site-packages/cv2/__init__.py\u0026#34;, line 153, in bootstrap native_module = importlib.import_module(\u0026#34;cv2\u0026#34;) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File \u0026#34;/usr/local/lib/python3.11/importlib/__init__.py\u0026#34;, line 126, in import_module return _bootstrap._gcd_import(name[level:], package, level) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ImportError: libGL.so.1: cannot open shared object file: No such file or directory このエラーは、OpenCV が GPU の OpenGL ライブラリを見つけられないことが原因で起きているエラーのようです。\nYOLOv8 (ultralytics) をインストールすると依存関係にある OpenCV もインストールされるようですが、以下のページによれば、検出結果の可視化などを行ったりする場合のみ OpenCV が必要で、他のコア機能は OpenCV に依存しないとあります。\nhttps://github.com/ultralytics/ultralytics/issues/2179 https://github.com/ultralytics/ultralytics/pull/3480 しかし、私の場合、検出結果の可視化とか何もしていない、というかそもそも最初の import 文のところでエラーになってしまっています。\n尚、opencv-python ではなく、opencv-python-headless をインストールすれば解決できる場合もあるそうですが、私の場合、それでも上記のエラーが発生しました。\n仕方がないので、OpenGL のライブラリをインストールして解決しました。\n$ sudo apt update $ sudo apt install -y libgl1-mesa-glx libglib2.0-0 参考 URL https://zenn.dev/techmadot/articles/opengl4-on-wsl https://github.com/ultralytics/ultralytics/tree/main https://docs.ultralytics.com/ja/quickstart/ ","date":"2024-02-28T00:00:00+09:00","permalink":"/posts/2024/02/28/yolov8/","title":"YOLOv8 を動かそうとしたら ImportError: libGL.so.1: cannot open shared object file: No such file or directory のエラーが出た"},{"content":"機械学習のサンプルデータとしてよく使われるものの一つに Iris（アヤメ）のデータセットがあります。その読み込み方法です。\nIris のデータセットを取得する方法自体はいくつかあるようです。\nhttps://py-memo.com/python/load-iris/ 今回は Iris のデータは機械学習用の定番のライブラリである scikit-learn を使いました。\nサンプルコード import pandas as pd from sklearn.datasets import load_iris iris = load_iris() df_iris = pd.DataFrame(iris.data, columns=iris.feature_names) # 目的変数である花の種類（target）のカラムを作成（花の種類を数値から文字列に変換して追加） df_iris[\u0026#39;target\u0026#39;] = iris.target_names[iris.target] print(df_iris) sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) target 0 5.1 3.5 1.4 0.2 setosa 1 4.9 3.0 1.4 0.2 setosa 2 4.7 3.2 1.3 0.2 setosa 3 4.6 3.1 1.5 0.2 setosa 4 5.0 3.6 1.4 0.2 setosa .. ... ... ... ... ... 145 6.7 3.0 5.2 2.3 virginica 146 6.3 2.5 5.0 1.9 virginica 147 6.5 3.0 5.2 2.0 virginica 148 6.2 3.4 5.4 2.3 virginica 149 5.9 3.0 5.1 1.8 virginica [150 rows x 5 columns] load_iris 関数で呼び出されたデータセットは numpy 形式です。\nこれを扱いやすいように Pandas でデータフレームに変換しています。\nまた、load_iris 関数では、説明変数は iris.data、目的変数は iris.target に実装されています。\nカラム名は iris.feature_names に入っています。\niris.target は花の種類を数値として保持しており、その数値に対応した花の名前は iris.target_names に定義されています。\n花の種類をカラムに追加する際には文字列に変換して追加しています。\n参考 URL https://py-memo.com/python/load-iris/ https://qiita.com/ao_log/items/fe9bd42fd249c2a7ee7a https://py-memo.com/python/load-iris/ https://aiacademy.jp/texts/show/?id=90 ","date":"2024-02-21T00:00:00+09:00","permalink":"/posts/2024/02/21/python/","title":"[Python] Iris データセットを Pandas で読み込む"},{"content":"WSL 上の Ubuntu で docker を使っていたのですが、ストレージの空き容量が少なくなってきたので docker イメージを削除しても空き容量が変化しませんでした。\n調べてみたところ、どうやら仮想ディスクの最適化まで行わないとディスクスペースは解放されないようです。\nhttps://01futabato10.hateblo.jp/entry/2022/12/02/095031 https://qiita.com/siruku6/items/c91a40d460095013540d ただ、私の PC はストレージ容量が少なく、頻繁に Docker イメージを消して容量を確保しているため、都度コマンドを打つのは面倒ということで、スクリプト化しました。\nコマンドとしては、diskpart を使う方法と、Optimize-VHD を使う方法があるみたいですが、Optimize-VHD は Hyper-V を有効化する必要があり、Windows Home では使えないということで、今回は diskpart を使うことにしました。\ndiskpart でスクリプトを実行する方法は以下に記載されています。\nhttps://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/diskpart-scripts-and-examples サンプル まずは diskpart で実行するスクリプトを作成します（ここでは sample.txt としました）。\nselect vdisk file=\u0026#34;%USERPROFILE%\\AppData\\Local\\Packages\\CanonicalGroupLimited.Ubuntu22.04LTS_79rhkp1fndgsc\\LocalState\\ext4.vhdx\u0026#34; attach vdisk readonly compact vdisk detach vdisk file のパスは環境によって異なると思うので、以下を参考に調べてください。\nhttps://01futabato10.hateblo.jp/entry/2022/12/02/095031 そのうえで、以下のバッチファイルを作成します。\nwsl --shutdown diskpart /s sample.txt あとはこのバッチファイルを実行するだけで OK です。\n参考 URL https://01futabato10.hateblo.jp/entry/2022/12/02/095031 https://qiita.com/siruku6/items/c91a40d460095013540d https://dev.classmethod.jp/articles/windows-virtual-disk-compression/ ","date":"2024-02-20T00:00:00+09:00","permalink":"/posts/2024/02/20/wsl/","title":"WSL で使用しているディスクスペースを解放する"},{"content":"ネットワークカメラの映像を取得するためにまず RTSP の接続文字列を取得する必要があったので、ONVIF の Profile S から取得してみました。\nONVIF Profile S の仕様は以下にあります。\nhttps://www.onvif.org/profiles/specifications/ 尚、単純に RTSP の接続文字列を取得したいだけなら ONVIF Device Manager というツールがあるので、それを使ったほうが楽です。\n環境 python 3.11 onvif-zeep 0.2.12 onvif2-zeep 0.3.4 ONVIF クライアントの python 実装である onvif-zeep onvif2-zeep を使ってみました。\nこの２つの違いですが、以下のページによれば、onvif2-zeep のほうは WSDL と H.265 に対応しているようです。\nhttps://pypi.org/project/onvif2-zeep/ 以下、onvif-zeep onvif2-zeep それぞれのサンプルコードです。\nonvif-zeep でのサンプル 準備 pip install onvif-zeep サンプルコード from onvif import ONVIFCamera def get_all_rtsp_urls(camera_ip, username, password): # ONVIFデバイスに接続 camera = ONVIFCamera(camera_ip, 80, username, password) # プロファイルを取得 media = camera.create_media_service() profiles = media.GetProfiles() #print(profiles) # すべてのプロファイルに対して RTSP URL を取得 for profile in profiles: name = profile[\u0026#39;Name\u0026#39;] try: token = profile[\u0026#39;token\u0026#39;] stream_uri = media.GetStreamUri({ \u0026#39;StreamSetup\u0026#39;: { \u0026#39;Stream\u0026#39;: \u0026#39;RTP-Unicast\u0026#39;, \u0026#39;Transport\u0026#39;: { \u0026#39;Protocol\u0026#39;: \u0026#39;RTSP\u0026#39; } }, \u0026#39;ProfileToken\u0026#39;: token }) uri = stream_uri.Uri print(f\u0026#39;{name}: {uri}\u0026#39;) except Exception as e: print(f\u0026#39;{name}: {e}\u0026#39;) if __name__ == \u0026#34;__main__\u0026#34;: camera_ip = \u0026#34;192.168.0.1\u0026#34; username = \u0026#34;username\u0026#34; password = \u0026#34;password\u0026#34; get_all_rtsp_urls(camera_ip, username, password) onvif2-zeep のサンプル 準備 pip install onvif2-zeep WSDL のダウンロード onvif2-zeep を使用する場合、WSDL のファイル一式が必要なようです。\n以下のリポジトリからダウンロードできます。\nhttps://github.com/onvif/specs サンプルコード 上記のコードから以下の２箇所を変更するだけです。\nfrom onvif2 import ONVIFCamera ... camera = ONVIFCamera(camera_ip, 80, username, password, \u0026#39;./wsdl\u0026#39;) ONVIFCamera の引数の最後で、WSDL ファイルのあるパスを指定します。\n実行結果 Panasonic のカメラで試した結果が以下となります。\nH264_1280x960: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile8 H264_800x600: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile7 H264_640x480: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile6 H264_320x240: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile5 JPEG_1280x960: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile4 JPEG_800x600: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile3 JPEG_640x480: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile2 JPEG_320x240: rtsp://192.168.0.1/ONVIF/MediaInput?profile=1_def_profile1 ただ、H.265 のストリームがあるカメラ場合、onvif-zeep onvif2-zeep のどちらで試しても以下のように Unknown error: Configuration not complete のエラーが出て取得することができませんでした。\nH26x_1: Unknown error: Configuration not complete H26x_2: Unknown error: Configuration not complete JPEG_1: rtsp://192.168.0.1/ONVIF/MediaInput?profile=def_profile5 JPEG_2: rtsp://192.168.0.1/ONVIF/MediaInput?profile=def_profile6 カメラ側のプロファイル設定の問題のような気がしますが、よくわかりませんでした。\nちなみに、他に TOA のカメラも試しましたが、こちらは H.265 の場合でも問題なく取得することができました。\n参考 URL https://www.onvif.org/profiles/specifications/ ","date":"2024-02-15T00:00:00+09:00","permalink":"/posts/2024/02/15/rtsp/","title":"[python] ONVIF 対応カメラの RTSP 接続文字列を取得する"},{"content":"強化学習について勉強しようと思い、まずはサンプルとしてよく取り上げられる OpenAI の gym の CartPole などを動かそうとしてみたところ、上手く動きませんでした。\n調べてみたところ、gym は現在ではもう更新されておらず、代わりに gym を fork した gymnasium というのがあるようなので、こちらを使って動かしてみました。\n実行環境は docker を使って構築します。\nとりあえず学習・推論はせず、単純に動かすだけです。\n環境 WSL2(Ubuntu 22.04) docker 24.0.7 python 3.11 swig 4.2.0 gymnasium 0.29.1 Dockerfile FROM python:3.11 # 先に swig をインストールしておかないと gymnasium のインストールに失敗しました RUN pip install swig RUN pip install gymnasium[box2d] CMD [ \u0026#34;/bin/bash\u0026#34; ] コンテナの起動 GUI を動かすので、コンテナは以下のように起動します。\ndocker run -it --rm \\ -v $(pwd)/workspace:/workspace \\ -v /etc/group:/etc/group:ro \\ -v /etc/passwd:/etc/passwd:ro \\ -u $(id -u $USER):$(id -g $USER) \\ -e DISPLAY \\ -v /tmp/.X11-unix:/tmp/.X11-unix:rw \\ --workdir /workspace \\ imagename これについては以下が参考になりました。\nhttps://qiita.com/Spritaro/items/f907a9b52cb78e4fbec0 CartPole のサンプル import gymnasium as gym # 環境を作成 # render_mode は描画モードの指定。human は人間が見てわかるように動画として表示するという意味 env = gym.make(\u0026#34;CartPole-v1\u0026#34;, render_mode=\u0026#34;human\u0026#34;) for episode in range(10): # 環境をリセット observation, info = env.reset() step = 0 while True: # 行動をランダムに選択 action = env.action_space.sample() # これがエージェントの行動になるので、本来はAIが行動を決定するべきところ # 選択した行動を実行 observation, reward, terminated, truncated, info = env.step(action) #print(env.render()) # 状態の可視化(human の場合は不要) # 終了した場合、次のエピソードへ if terminated or truncated: print(f\u0026#34;Episode{episode+1} finished after {step+1} timesteps\u0026#34;) break step += 1 env.close() LunarLander のサンプル 同様に以下のようにして LunarLander も動きました。\nimport gymnasium as gym # 環境を作成 # render_mode は描画モードの指定。human は人間が見てわかるように動画として表示するという意味 env = gym.make(\u0026#34;LunarLander-v2\u0026#34;, render_mode=\u0026#34;human\u0026#34;) for episode in range(10): # 環境をリセット observation, info = env.reset() step = 0 while True: # 行動をランダムに選択 action = env.action_space.sample() # これがエージェントの行動になるので、本来はAIが行動を決定するべきところ # 選択した行動を実行 observation, reward, terminated, truncated, info = env.step(action) #print(env.render()) # 状態の可視化(human の場合は不要) # 終了した場合、次のエピソードへ if terminated or truncated: print(f\u0026#34;Episode{episode+1} finished after {step+1} timesteps\u0026#34;) break step += 1 env.close() FrozenLake のサンプル 同様に以下のようにして FrozenLake も動きました。\nimport gymnasium as gym # 環境を作成 # render_mode は描画モードの指定。human は人間が見てわかるように動画として表示するという意味 env = gym.make(\u0026#34;FrozenLake-v1\u0026#34;, render_mode=\u0026#34;human\u0026#34;) for episode in range(10): # 環境をリセット、現在の観測値（observation）と情報（info）を取得 observation, info = env.reset() step = 0 while True: # 行動をランダムに選択 action = env.action_space.sample() # これがエージェントの行動になるので、本来はAIが行動を決定するべきところ # 選択した行動を実行 observation, reward, terminated, truncated, info = env.step(action) #print(env.render()) # 状態の可視化(human の場合は不要) # 終了した場合、次のエピソードへ if terminated or truncated: print(f\u0026#34;Episode{episode+1} finished after {step+1} timesteps\u0026#34;) break step += 1 env.close() 参考 URL https://github.com/openai/gym/issues/3240 https://note.com/kikaben/n/n57584c49d5c2 https://qiita.com/Spritaro/items/f907a9b52cb78e4fbec0 https://amateur-engineer-blog.com/reinforce-learning-getting-started/#toc5 ","date":"2024-02-12T00:00:00+09:00","permalink":"/posts/2024/02/12/gymnasium/","title":"Gymnasium の環境を動かしてみる"},{"content":"Barrier というソフトを使って、マウスとキーボードを共有します。\n環境 Windows 10 barrier 2.4.0 Ubuntu 20.04 barrier 2.3.2 Barrier を使う場合、どちらかをサーバーとして設定する必要がありますが、今回は Windows をサーバーとしました。\nWindows 側 インストール GitHub の Releases から最新の exe をダウンロードしてインストールします。\nBonjourというソフトをインストールするかどうか聞かれますが、インストールしませんでした。\n設定 「サーバー」にチェックを入れる 「サーバーの構成設定」を選択し、「モニタの結びつき」で任意のマスをクリックして、クライアント側のモニターを追加する モニター名を後述のクライアント側の名前に合わせて入力する メニューバーから、「Barrier \u0026gt; 設定の変更」を選択し、「SSL を使用」のチェックを外す。 開始を押す Ubuntu 側 インストール 以下のコマンドを実行します。\n$ sudo apt install barrier 設定 「クライアント」にチェックを入れる 「サーバーIP」にIPアドレスを入力する メニューバーから、「Barrier \u0026gt; Change Settings」を選択し、「Enable SSL」のチェックを外す。 開始を押す 追加の設定 ログイン画面（ロック画面）になるとマウスとキーボードが効かなくなるので、以下の設定も行いました。\nWindows (サーバー) 側 Barrier の設定で、権限昇格を「常に」に変更しました。\n恐らくセキュリティ的によろしくないので、やるなら自己責任でお願いします。\nUbuntu (クライアント) 側 Ubuntu 20.04 を使用している場合、GNOME デスクトップ環境がデフォルトで採用されているため、GDM (GNOME Display Manager) の設定を変更しました。\nまず、GDMの設定ファイルを開きます。\nsudo nano /etc/gdm3/custom.conf [daemon] セクションに以下を追加します。\n[daemon] Greeter-setup-script=/usr/bin/numlockx on 変更を有効にするためにGDMを再起動します。\nsudo service gdm restart ただし、セキュリティ的にはよろしくないので、やるなら自己責任でお願いします。\n参考 URL https://nanjib.com/archives/1587 https://ossyaritoori.hatenablog.com/entry/2019/09/26/Barrier%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6%E8%A4%87%E6%95%B0%E3%81%AEPC%E9%96%93%E3%81%A7%E3%83%9E%E3%82%A6%E3%82%B9%E3%83%BB%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E5%85%B1%E6%9C%89 ","date":"2024-01-26T00:00:00+09:00","permalink":"/posts/2024/01/26/barrier/","title":"Windows と Ubuntu でマウスとキーボードを共有する"},{"content":"GitLab Pages で公開しているサイトにアクセスしたときに、配下ディレクトリの一覧を表示させる方法です。\nGitLab 12.8 までは nginx の autoindex を有効にして index of で表示させることができたようですが、セキュリティの観点からその機能は削除されたそうです。\nなので、どうにかしてインデックスページを作成し、GitLab Pages に公開するしかありません。 例えば、Hugo や Jekyll で Sitemap やインデックスページの自動生成機能を使う方法があるようです。\nただ、今回私はシェルスクリプトファイルで作成することにしました。\n以下、サンプルです。\n#!/bin/bash # 公開ディレクトリ内のディレクトリ一覧を取得 dirs=(public/*/) # HTMLで一覧を表示するための変数 list=\u0026#34;\u0026lt;ul\u0026gt;\u0026#34; # ディレクトリのHTMLリストを生成 for d in \u0026#34;${dirs[@]}\u0026#34;; do # フォルダ一覧のみに制限するため、publicディレクトリ内のサブディレクトリのみを対象としています。 dir=$(echo $d | sed -e \u0026#39;s/public\\///\u0026#39; -e \u0026#39;s/\\///\u0026#39;) list=\u0026#34;$list\u0026lt;li\u0026gt;\u0026lt;a href=\u0026#39;$dir\u0026#39;\u0026gt;$dir\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt;\u0026#34; done list=\u0026#34;$list\u0026lt;/ul\u0026gt;\u0026#34; # index.htmlファイルを作成 title=\u0026#34;タイトル\u0026#34; cat \u0026gt; public/index.html \u0026lt;\u0026lt; EOF \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;$title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;$title\u0026lt;/h1\u0026gt; $list \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; EOF これを、.gitlab-ci.yml の script の最後に実行すれば OK です。\n上記はディレクトリに限定していますが、応用でファイル一覧を出力できると思います。\n","date":"2023-12-25T00:00:00+09:00","permalink":"/posts/2023/12/25/gitlab-pages/","title":"GitLab Pages でディレクトリ一覧を表示したい"},{"content":"mkdocs で作成したサイトを GitLab Pages で公開していたのですが、これをブランチ毎にディレクトリを分けて公開するようにしました。\n基本的には、キャッシュを有効にするだけで可能です。\n各ブランチでの成果物も一緒に Pages で公開したいため、キャッシュのキーは固定値にします。\n以下、 .gitlab-ci.yml の抜粋です。\npages: stage: build script: - mkdocs build - rm -rf public/${CI_COMMIT_REF_NAME} - mv site public/${CI_COMMIT_REF_NAME} artifacts: paths: - public cache: # ブランチ毎に別のキャッシュを利用したい場合はブランチ名をキーにするが、 # 今回は全ブランチのビルド結果も一緒に公開したいため、キーは全ブランチで共通の固定値にする key: \u0026#34;common-key\u0026#34; #key: \u0026#34;$CI_COMMIT_REF_NAME\u0026#34; paths: - public site ディレクトリが mkdocs の成果物が入っているディレクトリですが、これを public/ブランチ名 に配置しています。\n注意点 デフォルトでは、保護されたブランチと保護されていないブランチではキャッシュが共有されないため、この場合はプロジェクトの設定変更が必要です。\nこれを知らずに何度試してもキャッシュが共有されずはまっていました・・・。\nhttps://gitlab-docs.creationline.com/ee/ci/caching/#use-the-same-cache-for-all-branches 変更する設定は以下です。\nサイドメニューより、Settings \u0026gt; CI/CD を選択 General pipelines を開く Use separate caches for protected branches のチェックボックスをオフにする Save changes をクリックして保存 参考 URL https://dev.to/zenika/gitlab-pages-preview-the-no-compromise-hack-to-serve-per-branch-pages-5599 https://gist.github.com/donaldpipowitch/2590b20520b2cf6ae01aab4f7b55f8fa https://gitlab-docs.creationline.com/ee/ci/caching/#use-the-same-cache-for-all-branches ","date":"2023-12-20T00:00:00+09:00","permalink":"/posts/2023/12/20/gitlab-pages/","title":"GitLab Pages でブランチ毎にディレクトリを分けて公開したい"},{"content":"Python のアプリを Nuitka で実行ファイルにして動かすと、Websockets に関するエラーが出ました。\n尚、python で実行したときには正常に動作しています。\n環境 Python 3.11 Nuitka 1.9.2 Websockets 11.0.3 エラーの内容 具体的には以下のようなエラーが出ました。\nTraceback (most recent call last): File \u0026#34;/foo/main.py\u0026#34;, line 36, in subprocess File \u0026#34;/foo/asyncio/runners.py\u0026#34;, line 190, in run File \u0026#34;/foo/asyncio/runners.py\u0026#34;, line 118, in run File \u0026#34;/foo/asyncio/base_events.py\u0026#34;, line 653, in run_until_complete File \u0026#34;/foo/websocket_manager.py\u0026#34;, line 47, in run File \u0026#34;/foo/websocket_client.py\u0026#34;, line 38, in run File \u0026#34;/foo/websockets/imports.py\u0026#34;, line 77, in __getattr__ File \u0026#34;/foo/websockets/imports.py\u0026#34;, line 27, in import_name ModuleNotFoundError: No module named \u0026#39;websockets.legacy\u0026#39; とりあえずの対処法 以下に同じエラーの報告がありました。\nhttps://github.com/python-websockets/websockets/issues/974 上記を参考に以下のように変更すればとりあえず解決しました。\n変更前\nimport websockets async for websocket in websockets.connect(uri): 変更後\nimport websockets.client async for websocket in websockets.client.connect(uri): 本来の解決方法 上記のコードですが、どうやらこれは古い記述方法みたいです。\n最近の websockets (11.x 以降) では、接続の作成と処理は以下のように行うことを推奨しているようです。\nimport websockets while True: try: async with websockets.connect(uri) as websocket: # メッセージ処理 except Exception: # ここに再接続ロジックなど await asyncio.sleep(retry_delay) ただし、これで今回のエラーが解決するかどうかは試していません。\n参考 URL https://github.com/python-websockets/websockets/issues/974 ","date":"2023-12-01T00:00:00+09:00","permalink":"/posts/2023/12/01/python/","title":"[Python] ModuleNotFoundError: No module named 'websockets.legacy' のエラーが出た"},{"content":"Docker Desktop を使わずに、Windows 10 に Docker のバイナリを直接インストールして、Windows コンテナを動かすまでをやってみました。\nやり方は以下のドキュメントに記載されていました。\nhttps://docs.docker.com/engine/install/binaries/#install-server-and-client-binaries-on-windows 記載の通り、これで動かせるようになるのは Windows コンテナのみで、Linux コンテナは動かせません。\nWindows 上で Linux コンテナを動かしたい場合は、WSL を使う方法などがあります。\nhttps://kuttsun.blogspot.com/2022/01/wsl2-ubuntu-docker.html 環境 Windows 10 Pro Windows の機能の有効化 コントロールパネルの [Windows の機能の有効化または無効化] より、コンテナ にチェックを入れます。\nこれにチェックが入っていないと Docker コンテナが動きませんでした。\nまた、Hyper-V の有効化が必要という記事も見かけましたが、私の環境では Hyper-V を有効化しなくても動きました。\nHyper-V については以下を参照してください。\nhttps://learn.microsoft.com/ja-jp/virtualization/hyper-v-on-windows/reference/hyper-v-requirements インストール方法 バイナリのダウンロード 以下からインストールしたい Docker のバージョンのバイナリファイルをダウンロードします。\nhttps://download.docker.com/win/static/stable/x86_64/ 現時点で最新の 24.0.7 をダウンロードしました。\nProgramFiles への展開 ダウンロードした zip を ProgramFiles に展開するため、PowerShell を管理者で起動して以下を実行します。\n\u0026gt; Expand-Archive \u0026lt;ダウンロードした zip へのパス\u0026gt; -DestinationPath $Env:ProgramFiles $Env:ProgramFiles は環境変数 ProgramFiles を表示しており、通常は C:\\Program Files というパスになります。\n以降のコマンドは全て PowerShell で実行します。\nサービスへの登録 Docker をサービスへ登録します。\n\u0026gt; \u0026amp;$Env:ProgramFiles\\Docker\\dockerd --register-service \u0026amp; は、PowerShell においてコマンドやスクリプトを呼び出す演算子です。\n従って、環境変数展開後の C:\\ProgramFiles\\Docker\\dockerd という文字列をコマンドとして実行しています。\nサービスの開始 \u0026gt; Start-Service docker 動作確認 Hello World イメージを動かしてみます。\n\u0026gt; \u0026amp;$Env:ProgramFiles\\Docker\\docker run hello-world:nanoserver 基本的にはこれで上手くいくはずですが、プロキシ環境下の場合は、後述するプロキシ設定が必要になります。\n環境変数へパスを通す 毎回 \u0026amp;$Env:ProgramFiles\\Docker\\docker と打つのは面倒なため、$env:ProgramFiles\\docker\\ をPATHに追加します。\n\u0026gt; [Environment]::SetEnvironmentVariable(\u0026#34;Path\u0026#34;, $env:Path + \u0026#34;;$env:ProgramFiles\\docker\\\u0026#34;, [EnvironmentVariableTarget]::Machine) PowerShell を一度閉じて、再度開きます。\nこれで以下のように docker と打つだけで実行できるようになります。\n\u0026gt; docker run hello-world:nanoserver プロキシ設定 プロキシ環境下では、プロキシサーバーの設定を行っていないと Docker イメージを pull できません。\n設定方法については以下に記載がありました。\nhttps://learn.microsoft.com/ja-jp/virtualization/windowscontainers/manage-docker/configure-docker-daemon 環境変数 HTTP_PROXY HTTPS_PROXY を設定します。\n\u0026gt; [Environment]::SetEnvironmentVariable(\u0026#34;HTTP_PROXY\u0026#34;, \u0026#34;http://\u0026lt;IP address\u0026gt;:\u0026lt;port\u0026gt;\u0026#34;, [EnvironmentVariableTarget]::Machine) 設定後、Docker サービスを再起動します。\n\u0026gt; Restart-Service docker docker クライアントのプロキシ設定 Linux では ~/.docker/config.json に設定しますが、Windows では %USERPROFILE%\\.docker\\config.json に設定します。\n以下のように設定しておくことで、Dockerfile のビルドを行う際に --build-args などで都度プロキシの設定を行う必要がなくなります。\n{ \u0026#34;proxies\u0026#34;: { \u0026#34;default\u0026#34;: { \u0026#34;httpProxy\u0026#34;: \u0026#34;http://\u0026lt;IPアドレス\u0026gt;:\u0026lt;port\u0026gt;\u0026#34;, \u0026#34;httpsProxy\u0026#34;: \u0026#34;http://\u0026lt;IPアドレス\u0026gt;:\u0026lt;port\u0026gt;\u0026#34;, \u0026#34;noProxy\u0026#34;: \u0026#34;localhost,127.0.0.1,host.docker.internal\u0026#34; } } } もしかしたら、一度 Docker サービスを再起動する必要があるかもしれません。\nエラーが発生した場合 次のようなエラーが発生した場合、Docker サービスが起動していないか、プロキシにより通信が通っていないかのどちらか可能性が高いです。\nUnable to find image \u0026#39;hello-world:nanoserver\u0026#39; locally docker: Error response from daemon: Get \u0026#34;https://registry-1.docker.io/v2/\u0026#34;: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers). See \u0026#39;docker run --help\u0026#39; また、次のようなエラーが発生した場合、[Windows の機能の有効化または無効化] で コンテナ にチェックが入っていない可能性があります。\ndocker: Error response from daemon: hcs::CreateComputeSystem d5ccfe373c27d9053c87689f60075aed9acdca360d6cd42e1abdc5591bb60cbc: The request is not supported. アンインストール サービスから Docker を削除します。\n\u0026gt; \u0026amp;$Env:ProgramFiles\\Docker\\dockerd --unregister-service これだけではまだ完全にサービスは削除されていないので、一度 OS を再起動します。\nその後、docker のフォルダを削除します。\n\u0026gt; Remove-item -Recurse $Env:ProgramFiles\\Docker アップデート 現在のバージョンをアンインストールし、新しいバージョンをインストールします。\n余談：他の Windows コンテナを動かしてみた python3.11 の Windows コンテナを動かしてみました。\npython イメージのタグ一覧が以下です。\nhttps://hub.docker.com/_/python Windows コンテナをベースとしている python3.11 のイメージとして以下の２つがあります。\n3.11-windowsservercore-ltsc2022 3.11-windowsservercore-1809 私の PC では、3.11-windowsservercore-ltsc2022 は動かず、3.11-windowsservercore-1809 は動きました。\nベースとしている Windows コンテナの詳細は以下で確認できます。\nhttps://hub.docker.com/_/microsoft-windows-servercore 参考 URL https://www.ulvaniac.co.jp/2023/09/06/docker-desktop%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84windows%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AE%E5%8B%95%E4%BD%9C%E6%96%B9%E6%B3%95/ https://and-engineer.com/articles/YcGZxhYAACUAbPjg https://qiita.com/SGTY/items/1126e0b95c35843fd8c5 https://blog.shibata.tech/entry/2016/08/05/233212 https://learn.microsoft.com/ja-jp/virtualization/windowscontainers/manage-docker/configure-docker-daemon ","date":"2023-11-21T00:00:00+09:00","permalink":"/posts/2023/11/21/docker-windows/","title":"[Docker] Windows コンテナを動かす"},{"content":"pyinstaller, nuitka で作成した実行ファイルを別のマシンで実行するとエラーが出て動きませんでした。\n具体的には以下のような GLIBC に関するエラーが表示されました。\nPyinstaller の場合\n[10562] Error loading Python lib \u0026#39;/home/username/dist/main/libpython3.11.so.1.0\u0026#39;: dlopen: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.35\u0026#39; not found (required by /home/username/dist/main/libpython3.11.so.1.0) Nuitka の場合\n./sample.bin: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33\u0026#39; not found (required by ./sample.bin) ./sample.bin: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34\u0026#39; not found (required by ./sample.bin) ./sample.bin: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.35\u0026#39; not found (required by /home/username/main.dist/libpython3.11.so.1.0) ./sample.bin: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33\u0026#39; not found (required by /home/username/main.dist/libpython3.11.so.1.0) ./sample.bin: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32\u0026#39; not found (required by /home/username/main.dist/libpython3.11.so.1.0) ./sample.bin: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34\u0026#39; not found (required by /home/username/main.dist/libpython3.11.so.1.0) これの回避策は、サポートしたいシステムより古いシステムでビルドを行うことだそうです。\nhttps://dev.to/k4ml/python-compile-standalone-executable-with-nuitka-1ml1 https://github.com/Nuitka/Nuitka/issues/1610 私の場合、python:3.11 の Docker イメージを使ってビルドを行っていましたが、このイメージのディストリビューションは Debian 12 でした。 そこでビルドした成果物を Ubuntu 20.04 のマシンで動かすと上記のエラーが発生しました。\nなので、ビルドに使用する Docker イメージを python:3.11-buster に変更したところ、エラーなく動作するようになりました。\n尚、buster というのは Debian のコードネームのことで、Debian 10 のことだそうです。\n詳細は以下を参照してください。\nhttps://www.debian.org/releases/index.ja.html 参考 URL https://dev.to/k4ml/python-compile-standalone-executable-with-nuitka-1ml1 https://github.com/Nuitka/Nuitka/issues/1610 https://www.debian.org/releases/index.ja.html ","date":"2023-11-17T00:00:00+09:00","permalink":"/posts/2023/11/17/pyinstaller-nuitka/","title":"[Python] Pyinstaller, Nuitka で作成した実行ファイルで GLIBC のエラーが発生する"},{"content":"本来、スケールは大きな負荷を処理できるようにするために使用し、スケールしたコンテナ毎に異なる構成を使用すべきではないと思いますが、それでも、スケールしたコンテナ毎にぞれぞれ別の構成が適用できると便利な場合もあります。 その場合、複製したコンテナ内で、自身が何番目のコンテナなのかを把握できるのが最も都合が良いと考えます。\nしかし、調べてみてもやり方が分からず、同様の質問はいくつか見つかりますが、現状、docker compose にはそのような機能は提供されていないようです。 その代わりに、docker swarm でタスクスロットを使うことで、コンテナの番号をコンテナ内に設定することができました。\nhttps://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/commandline/service_create/ サンプル 以下のような docker-compose.yml を用意します。\nversion: \u0026#39;3\u0026#39; services: server: image: nginx deploy: replicas: 3 hostname: \u0026#34;server-{{.Task.Slot}}\u0026#34; 以下のコマンドでサービスを起動します。\n$ docker swarm init # Swarmを初期化する（すでに初期化済みであれば不要） $ docker stack deploy -c docker-compose.yml my_stack docker ps で確認すると、コンテナが3つ起動しているのが確認できます。\nここで、3つのうちの1つのコンテナに入ってみます。\n$ docker exec -it \u0026lt;コンテナ名\u0026gt; bash コンテナ内で hostname を確認すると、番号が振られていることが確認できます。\n# echo $(hostname) 尚、サービスを停止するには、以下のコマンドを使用します。\n$ docker stack rm my_stack 参考 URL https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/commandline/service_create/ https://forums.docker.com/t/how-to-identify-the-number-of-scaled-container/15041/11 https://github.com/docker/compose/issues/9153 ","date":"2023-11-14T00:00:00+09:00","permalink":"/posts/2023/11/14/docker/","title":"[Docker] docker-compose の --scale でコンテナを複製した際に、コンテナ側で何番目のコンテナなのかを判別したい"},{"content":"MongoDB 用の非同期ライブラリに motor を使用しており、pytest でテストコードを記述する際にモックをどうすればいいか悩みました。\n当初は pymongo を使って同期的に使用していたため、MongoDB のモックには mongomock を使用していましたが、motor を使用する場合には mongomock-motor が使うとテストできました。\nhttps://pypi.org/project/mongomock-motor/ インストール pip install mongomock-motor サンプルコード 上記のページにあるサンプルコードを pytest で実行する例です。\nimport pytest from mongomock_motor import AsyncMongoMockClient @pytest.mark.asyncio async def test_mock_client(): collection = AsyncMongoMockClient()[\u0026#39;tests\u0026#39;][\u0026#39;test-1\u0026#39;] assert await collection.find({}).to_list(None) == [] result = await collection.insert_one({\u0026#39;a\u0026#39;: 1}) assert result.inserted_id assert len(await collection.find({}).to_list(None)) == 1 参考 URL https://pypi.org/project/mongomock-motor/ ","date":"2023-11-13T00:00:00+09:00","permalink":"/posts/2023/11/13/python/","title":"[Python] pytest で motor のテストを行う"},{"content":"id -u コマンドを使用して実行ユーザーの UID（ユーザーID）を確認することでチェックできます。\n#!/bin/bash # スクリプトをsudoで実行しているか確認 if [ \u0026#34;$(id -u)\u0026#34; -ne 0 ]; then echo \u0026#34;このスクリプトはsudoで実行する必要があります。\u0026#34; exit 1 fi 通常、root ユーザーの UID は 0 となります。\n","date":"2023-11-12T00:00:00+09:00","permalink":"/posts/2023/11/12/linux/","title":"シェルスクリプトで sudo 権限で実行されているどうかをチェックする"},{"content":"Rocket.Chat を移行したのですが、アップロード済みファイルの URL と、サイト URL のリセット値が、移行前の古い URL のままになっていました。\n概要 Rocket.Chat を移行し、ドメイン (サイトURL) が変わった その際、Rocket.Chatを 3.10.14 から 6.2.12 にアップグレードしている MongoDB も 4.0 から 5.0 にアップグレード Rocket.Chat 起動時に指定する環境変数 ROOT_URL は移行後の URL を設定済み Rocket.Chat の管理者メニューで設定できる サイト URL も移行後の URL を設定済み この状況で、以下の２つの不具合を確認しました。\nアップロード済みのファイルの URL (ダウンロードリンク) が移行前の古い URL になっている Rocket.Chat の管理者メニューで設定できる サイト URL について、リセットボタンを押すと移行前の古い URL がセットされる 先に結論 1. については MongoDB のデータを直接修正すれば直りました。\n2. については未解決です。\nMongoDB のデータを確認することにしたきっかけ いろいろググっていく中で以下のページに辿り着きました。\nhttps://www.ryadel.com/en/rocket-chat-change-root_url-site-url-rocketchat/ こちらの方法を試しても今回発生している不具合は直りませんでしたが、これをきっかけに MongoDB の中身を調査していくことで修正できました。\nMongoDB の中身の確認方法については以下が参考になりました。\nhttps://www.mtioutput.com/entry/2019/02/21/180000 https://gihyo.jp/dev/serial/01/mongodb/0003 調査の過程と修正方法 Rocket.Chat と MongoDB は docker で動かしています。\nなので、まず MongoDB の docker コンテナに入ります。\n$ docker exec -it rocketchat-mongo bash コンテナに入ったら MongoDB シェルを開き、rocketchat のデータベースを選択します。\n# mongo \u0026gt; use rocketchat コレクションを表示してみます。\n\u0026gt; show collections たくさん表示されますが、ここから不具合内容に該当する箇所を推測して調査していきます。\n1. アップロードファイルのダウンロードリンクについて 前述の 1. についてですが、こちらはアップロードファイルに関することなので、rocketchat_uploads の中身を表示してみました。\n\u0026gt; db.rocketchat_uploads.find() 20件のみ表示されますが、適当なデータ一件について中身をみると以下のような感じになっていました。\n{ \u0026#34;_id\u0026#34;: \u0026#34;27JzjqpXHxAHBzaxp\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;hoge.jpg\u0026#34;, \u0026#34;size\u0026#34;: 51268, \u0026#34;type\u0026#34;: \u0026#34;image/jpeg\u0026#34;, \u0026#34;rid\u0026#34;: \u0026#34;MgKrXXN4XmQwtMMjgjXnWzpNvR6YrRpH4F\u0026#34;, \u0026#34;userId\u0026#34;: \u0026#34;jXnWzpNvR6YrRpH4F\u0026#34;, \u0026#34;store\u0026#34;: \u0026#34;GridFS:Uploads\u0026#34;, \u0026#34;_updatedAt\u0026#34;: ISODate(\u0026#34;2023-07-26T03:58:50.233Z\u0026#34;), \u0026#34;instanceId\u0026#34;: \u0026#34;R252ARSHBKR3RrXHY\u0026#34;, \u0026#34;identify\u0026#34;: { \u0026#34;format\u0026#34;: \u0026#34;jpeg\u0026#34;, \u0026#34;size\u0026#34;: { \u0026#34;width\u0026#34;: 495, \u0026#34;height\u0026#34;: 700 } }, \u0026#34;complete\u0026#34;: true, \u0026#34;etag\u0026#34;: \u0026#34;HoMjiYj2fbb799RFK\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/ufs/GridFS:Uploads/27JzjqpXHxAHBzaxp/%E5%90%8D%E7%A7%B0%E6%9C%AA%E8%A8%AD%E5%AE%9A%201.jpg\u0026#34;, \u0026#34;progress\u0026#34;: 1, \u0026#34;token\u0026#34;: \u0026#34;68c8A8CAb8\u0026#34;, \u0026#34;uploadedAt\u0026#34;: ISODate(\u0026#34;2023-07-26T03:58:50.852Z\u0026#34;), \u0026#34;uploading\u0026#34;: false, \u0026#34;url\u0026#34;: \u0026#34;http://old_domain:port/ufs/GridFS:Uploads/27JzjqpXHxAHBzaxp/%E5%90%8D%E7%A7%B0%E6%9C%AA%E8%A8%AD%E5%AE%9A%201.jpg\u0026#34;, \u0026#34;typeGroup\u0026#34;: \u0026#34;image\u0026#34; } 見てわかる通り、url のフィールドにファイルへの絶対パスが記録されているため、これを修正しないと直らないんじゃないかと思いました。\nというわけで、以下のコマンドでドメインの部分のみを置換します。\ndb.rocketchat_uploads.find().forEach(function(row) { row.url = row.url.replace(new RegExp(\u0026#34;old_domain\u0026#34;, \u0026#39;g\u0026#39;), \u0026#34;new_domain\u0026#34;); db.rocketchat_uploads.save(row); }); これで Rocket.Chat のほうを確認すると、URL が直っていました。\n2. サイト URL` のリセット値について 次に前述の 2. についてですが、設定に関することなので rocketchat_settings を確認してみました。 こちらはついては中身を全てを見たかったので、以下のようにして json ファイルに出力して確認しました。\n$ mongoexport -d rocketchat -c rocketchat_settings -o rocketchat_settings.json --type=json mongodb://localhost:27017 で、中身を確認してみましたが、それらしい設定は見当たりませんでした。\n他のコレクションもいろいろ確認してみましたが、それらしい設定は見つかっていません。\nというわけで未解決ですが、特に実害はないので放っておいてます。\n参考 URL https://www.ryadel.com/en/rocket-chat-change-root_url-site-url-rocketchat/ https://www.mtioutput.com/entry/2019/02/21/180000 https://gihyo.jp/dev/serial/01/mongodb/0003 ","date":"2023-10-19T00:00:00+09:00","permalink":"/posts/2023/10/19/rocketchat/","title":"Rocket.Chat 移行後にアップロードファイルの URL が古いままになっている"},{"content":"アップグレード手順については以下に記載されています。\nhttps://docs.rocket.chat/deploy/deploy-rocket.chat/updating-rocket.chat アップグレードのフローが以下になります。\nhttps://whimsical.com/upgrade-version-path-rocket-chat-51eoS7aUunTan5wLt2CBHU これに従ってアップデートしますが、いくつかはまったポイントがありました。\n環境 アップグレード前のバージョンは以下です。\nRocket.Chat Version: 3.10.4 NodeJS Version: 12.18.4 - x64 MongoDB Version: 4.0.28 MongoDB Engine: wiredTiger Platform: linux Rocket.Chat も MongoDB も docker で動かしています。\nRocket.Chat 3.18.7 へのアップグレード 現在のバージョンが 3.10.4 なので、3系の最新の 3.18.7 にアップグレードしました。\nすると、データベースバージョンのエラーが表示されたました。\n+----------------------------------------------------------------------+ | | | ERROR! SERVER STOPPED | | | | Your database migration failed: | | Start date cannot be later than expire date | | | | Please make sure you are running the latest version and try again. | | If the problem persists, please contact support. | | | | This Rocket.Chat version: 3.18.7 | | Database locked at version: 213 | | Database target version: 232 | | | | Commit: 660c9f5e896982932e1d02d35ddd6013c6b03e11 | | Date: Mon May 30 19:06:57 2022 -0300 | | Branch: HEAD | | Tag: 3.18.7 | | | +----------------------------------------------------------------------+ 対策は以下に書いてありました。\nhttps://martinschoeler.github.io/docs/administrator-guides/database-migration/ MongoDB のコンテナに入り、以下のコマンドを実行してバージョンを強制的に一つ上げます。\n# mongo \u0026gt; use rocketchat \u0026gt; db.migrations.update({_id: \u0026#39;control\u0026#39;},{$set:{locked:false,version:231}}) これで Rocket.Chat を再起動すれば OK でした。\nRocket.Chat 3.18.7 から 4.8.7 へのアップグレード 次に4系の最新の 4.8.7 にアップグレードしました。\nこれはすんなりいきました。\nMongoDB のアップグレード 上記のフローによれば、Rocket.Chat を5系にアップグレードする前に、MongoDB を 5.0 以上までアップグレードする必要があります。\n方法については以下に記載されています。\nhttps://docs.growi.org/ja/admin-guide/admin-cookbook/upgrade-mongodb.html MongoDB 4.0 から 4.2 へのアップグレード 現在のバージョンが 4.0 なので、まずは 4.2 にアップグレードしました。 これはすんなりいきました。\nMongoDB 4.2 から 4.4 へのアップグレード 次に 4.4 にアップグレードしたところ、以下のようなエラーとなりました。\n{\u0026#34;t\u0026#34;:{\u0026#34;$date\u0026#34;:\u0026#34;2023-10-13T00:38:13.842+00:00\u0026#34;},\u0026#34;s\u0026#34;:\u0026#34;W\u0026#34;, \u0026#34;c\u0026#34;:\u0026#34;STORAGE\u0026#34;, \u0026#34;id\u0026#34;:22347, \u0026#34;ctx\u0026#34;:\u0026#34;initandlisten\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Failed to start up WiredTiger under any compatibility version. This may be due to an unsupported upgrade or downgrade.\u0026#34;} {\u0026#34;t\u0026#34;:{\u0026#34;$date\u0026#34;:\u0026#34;2023-10-13T00:38:13.842+00:00\u0026#34;},\u0026#34;s\u0026#34;:\u0026#34;F\u0026#34;, \u0026#34;c\u0026#34;:\u0026#34;STORAGE\u0026#34;, \u0026#34;id\u0026#34;:28595, \u0026#34;ctx\u0026#34;:\u0026#34;initandlisten\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Terminating.\u0026#34;,\u0026#34;attr\u0026#34;:{\u0026#34;reason\u0026#34;:\u0026#34;95: Operation not supported\u0026#34;}} {\u0026#34;t\u0026#34;:{\u0026#34;$date\u0026#34;:\u0026#34;2023-10-13T00:38:13.842+00:00\u0026#34;},\u0026#34;s\u0026#34;:\u0026#34;F\u0026#34;, \u0026#34;c\u0026#34;:\u0026#34;-\u0026#34;, \u0026#34;id\u0026#34;:23091, \u0026#34;ctx\u0026#34;:\u0026#34;initandlisten\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Fatal assertion\u0026#34;,\u0026#34;attr\u0026#34;:{\u0026#34;msgid\u0026#34;:28595,\u0026#34;file\u0026#34;:\u0026#34;src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp\u0026#34;,\u0026#34;line\u0026#34;:958}} {\u0026#34;t\u0026#34;:{\u0026#34;$date\u0026#34;:\u0026#34;2023-10-13T00:38:13.843+00:00\u0026#34;},\u0026#34;s\u0026#34;:\u0026#34;F\u0026#34;, \u0026#34;c\u0026#34;:\u0026#34;-\u0026#34;, \u0026#34;id\u0026#34;:23092, \u0026#34;ctx\u0026#34;:\u0026#34;initandlisten\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;\\n\\n***aborting after fassert() failure\\n\\n\u0026#34;} MongoDB を 4.2 に戻してコンテナに入り、以下のコマンドを実行します。\n# mongo \u0026gt; db.adminCommand( { setFeatureCompatibilityVersion: \u0026#34;4.2\u0026#34; } ) これで Rocket.Chat を再起動すれば OK でした。\nMongoDB 4.4 から 5.0 へのアップグレード 次に 5.0 にアップグレードしたところ、同様のエラーとなりました。\nMongoDB を 4.4 に戻してコンテナに入り、以下のコマンドを実行します。\n# mongo \u0026gt; db.adminCommand( { setFeatureCompatibilityVersion: \u0026#34;4.4\u0026#34; } ) これで Rocket.Chat を再起動すれば OK でした。\nRocket.Chat 4.8.7 から 5.4.10 へのアップグレード MongoDB を 5.0 までアップグレードしたので、次は Rocket.Chat を5系の最新の 5.4.10 にアップグレードしました。\nすると以下のエラーが表示されました。\n/app/bundle/programs/server/node_modules/fibers/future.js:313 throw(ex); ^ MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017 at Timeout._onTimeout (/app/bundle/programs/server/npm/node_modules/meteor/npm-mongo/node_modules/mongodb/lib/sdam/topology.js:312:38) at listOnTimeout (internal/timers.js:557:17) at processTimers (internal/timers.js:500:7) { reason: TopologyDescription { type: \u0026#39;ReplicaSetNoPrimary\u0026#39;, servers: Map(1) { \u0026#39;localhost:27017\u0026#39; =\u0026gt; ServerDescription { _hostAddress: HostAddress { isIPv6: false, host: \u0026#39;localhost\u0026#39;, port: 27017 }, address: \u0026#39;localhost:27017\u0026#39;, type: \u0026#39;Unknown\u0026#39;, hosts: [], passives: [], arbiters: [], tags: {}, minWireVersion: 0, maxWireVersion: 0, roundTripTime: -1, lastUpdateTime: 17764280, lastWriteDate: 0, error: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017 at connectionFailureError (/app/bundle/programs/server/npm/node_modules/meteor/npm-mongo/node_modules/mongodb/lib/cmap/connect.js:381:20) at Socket.\u0026lt;anonymous\u0026gt; (/app/bundle/programs/server/npm/node_modules/meteor/npm-mongo/node_modules/mongodb/lib/cmap/connect.js:301:22) at Object.onceWrapper (events.js:520:26) at Socket.emit (events.js:400:28) at emitErrorNT (internal/streams/destroy.js:106:8) at emitErrorCloseNT (internal/streams/destroy.js:74:3) at processTicksAndRejections (internal/process/task_queues.js:82:21) } }, stale: false, compatible: true, heartbeatFrequencyMS: 10000, localThresholdMS: 15, setName: \u0026#39;rs0\u0026#39;, maxSetVersion: 2, maxElectionId: ObjectId { [Symbol(id)]: Buffer(12) [Uint8Array] [ 127, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 10 ] }, commonWireVersion: 13, logicalSessionTimeoutMinutes: undefined } } 解決策は以下にありました。\nhttps://github.com/RocketChat/Rocket.Chat/releases/tag/5.0.0 環境変数 MONGO_URL MONGO_OPLOG_URL に directConnection=true を追加します。\n例えば、MONGO_URL=mongodb://mongo/rocketchat?replicaSet=rs0\u0026amp;directConnection=true のような感じです。\nこれで Rocket.Chat を再起動すれば OK でした。\nRocket.Chat を最新にアップデート 最後に、Rocket.Chat を現在の最新バージョンである 6.3.9 にアップグレードしました。 以上で完了です。\n","date":"2023-10-11T00:00:00+09:00","permalink":"/posts/2023/10/11/rocketchat/","title":"Rocket.Chat のアップグレード手順"},{"content":"time.monotonic() を使えば OK です。\n元々、以下の様に time.time() を使って処理時間の計測を行っていたのですが、この場合、計測途中でシステムクロックが変更されてしまうと、計測時間も影響を受けてしまいます。\nimport time start = time.time() # この間にシステムクロックが変更されると計測時間にも影響を与える elapsed_time = time.time() - start # 過去に戻ると負の値に、未来に行くと大きな値になってしまう print(elapsed_time) time.monotonic() を使えばシステムクロックの影響を受けないので、純粋に処理にかかった実時間を計測できます。\n参考 URL https://docs.python.org/ja/3.10/library/time.html#time.monotonic ","date":"2023-09-08T00:00:00+09:00","permalink":"/posts/2023/09/08/python/","title":"[Python] OS の時刻（システムクロック）に影響を受けないように時間の計測を行う"},{"content":"github などで公開されている docker-compose.yml を見ていると、見慣れない表記を見かけることがあり、調べてみると YAML のアンカー/エイリアス機能であることがわかりました。\nこれと、docker compose の --profiles を組み合わせて、docker-compose.yml 一つで様々な環境で動かすことができるようにしているのを見るととても便利だなと思ったので、メモしておきます。\nyaml のアンカーとエイリアス yaml 自体の標準機能です。\nこれを使うことで、記述内容を共通化して DRY にできます。\nアンカー: \u0026amp; を付けることで、他の場所でも参照できるようにします。 エイリアス: * を付けて、アンカーで定義した内容を参照します。 サンプル1\naaa: \u0026amp;hoge bbb: 1111 ccc: 2222 ddd: *hoge 上記の場合、以下の様に展開されます。\naaa: bbb: 1111 ccc: 2222 ddd: bbb: 1111 ccc: 2222 サンプル2\nアンカー内にアンカーがあっても問題ありません。\naaa: \u0026amp;hoge bbb: 1111 ccc: \u0026amp;piyo 2222 ddd: *hoge eee: *piyo 上記の場合、以下の様に展開されます。\naaa: bbb: 1111 ccc: 2222 ddd: bbb: 1111 ccc: 2222 eee: 2222 マージ \u0026lt;\u0026lt;: と記述することで、アンカーで定義した内容をそのまま入れ込むことができます。\naaa: \u0026amp;hoge bbb: 1111 ccc: 2222 ddd: \u0026lt;\u0026lt;: *hoge eee: 3333 上記の場合、以下の様に展開されます。\naaa: bbb: 1111 ccc: 2222 ddd: bbb: 1111 ccc: 2222 eee: 3333 アンカーの上書き アンカー名と同じキーが後にあった場合、上書きされます。\naaa: \u0026amp;hoge bbb: 1111 ccc: 2222 ddd: 3333 eee: \u0026lt;\u0026lt;: *hoge bbb: 4444 fff: \u0026lt;\u0026lt;: *hoge ddd: 5555 上記は以下のように展開されます。\naaa: bbb: 1111 ccc: 2222 ddd: 3333 eee: bbb: 4444 ccc: 2222 ddd: 3333 fff: bbb: 1111 ccc: 2222 ddd: 5555 これを使って、内容の大部分を共通化しつつ一部分だけは変えたいということができます。\ndocker-compose.yml で使用する場合 docker-compose.yml 内で共通の設定を括りだそうと以下の様に書くとエラーになります。\ncommon: \u0026amp;common image: hoge service: app: \u0026lt;\u0026lt;: *common docker compose によって common 自体が解釈されるためです。\nこの場合、プレフィックスとして x- を付けると、docker compose からは無視されるようになります。\nx-common: \u0026amp;common image: hoge service: app: \u0026lt;\u0026lt;: *x-common docker compose の profile docker-compose.yml で定義されたサービスに profiles を定義することで、サービスのグループ分けやタグ付けのようなことができます。\n以下が公式ドキュメントによる説明です。\nhttps://docs.docker.jp/compose/profiles.html profiles で指定したサービスのみを起動させることができるようになります。\n上記を踏まえた実用的なサンプル 上記を踏まえると、yaml のアンカー/エイリアスにより記述内容を共通化しつつ、profile によって起動するコンテナを切り替えることができます。\n尚、今回の記事を書くことになった動機が、以下の Stable Diffusion 用の docker-compose.yml を見たときで、どういう意味なんだろうと思ったのがきっかけです。\nhttps://github.com/AbdBarho/stable-diffusion-webui-docker GPU を使う場合と、CPU を使う場合とで上手く共通化されており、また記述量も少なくサンプルとして丁度いいので、こちらをサンプルとして挙げさせていただきます。\ndocker-compose.yml の確認 ちなみに、docker compose によってどのように yaml ファイルが読み込まれるか確認するには、config オプションを使用します。\n$ docker compose config 参考 URL https://genzouw.com/entry/2021/06/19/082626/2661/# https://docs.docker.jp/compose/profiles.html https://techracho.bpsinc.jp/hachi8833/2020_02_07/87447 ","date":"2023-08-25T00:00:00+09:00","permalink":"/posts/2023/08/25/yaml/","title":"YAML のアンカー/エイリアスと docker compose の profiles を使って docker-compose.yml を作成する"},{"content":"Stable Diffusion を動かそうとして以下のエラーが出ました。\nImportError: cannot import name \u0026#39;_compare_version\u0026#39; from \u0026#39;torchmetrics.utilities.imports\u0026#39; (/usr/local/lib/python3.8/site-packages/torchmetrics/utilities/imports.py) 以下に対策が載っており、torchmetrics を 0.11.4 にダウングレードすれば解決しました。\nhttps://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/11648 $ pip install torchmetrics==0.11.4 参考 URL https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/11648 ","date":"2023-08-21T00:00:00+09:00","permalink":"/posts/2023/08/21/stable-diffusion/","title":"`ImportError: cannot import name '_compare_version' from 'torchmetrics.utilities.imports'` のエラーが出る"},{"content":"ずっとやり方が分からなかったのですが、英語版のマニュアルにやり方が書いてありました。\nサイドバーのTOCを生成するには、VSCode の Markdown Preview Enhanced の設定でスクリプトの実行を有効にする必要があります。\nhttps://shd101wyy.github.io/markdown-preview-enhanced/#/html?id=configuration 尚、上記の情報は現時点の日本語版のマニュアルには書いてありませんでした。\nhttps://shd101wyy.github.io/markdown-preview-enhanced/#/ja-jp/html?id=%e8%a8%ad%e5%ae%9a 環境 VSCode 1.80.1 Markdown Preview Enhanced v0.6.8 スクリプトの実行を有効にする 拡張機能の設定で enableScriptExecution で検索すると設定項目が表示されるのでチェックを入れます。\n尚、有効にするとセキュリティリスクがありますので、その点ご注意ください。\nhtml の出力 あとはマークダウンのプレビュー画面で右クリックし、HTML に出力するだけでサイドバーに目次が出力されます。\n尚、デフォルトではサイドバーは閉じた状態となっていました。\nマークダウン内に toc: true を記述すると、サイドバーが開いた状態となりました。\n以下サンプルです。\n--- html: toc: true --- # 見出し1 ## 見出し2 ### 見出し3 ### 見出し3 # 見出し1 ## 見出し2 参考 URL https://shd101wyy.github.io/markdown-preview-enhanced/#/html?id=configuration https://qiita.com/kumapo0313/items/a59df3d74a7eaaaf3137 ","date":"2023-07-20T00:00:00+09:00","permalink":"/posts/2023/07/20/markdown-preview-enhanced/","title":"[VSCode] Markdown Preview Enhanced で HTML 出力する際にサイドバーの目次を入れる"},{"content":"スタートメニューから Ubuntu 20.04 をアンインストールして、再度 MS ストアからインストールして起動しようとしたら以下のエラーが表示されました。\nError code: Wsl/Service/CreateInstance/MountVhd/ERROR_FILE_NOT_FOUND まず、インストールされているディストリビューションを確認します。\n\u0026gt; wsl -l Linux 用 Windows サブシステム ディストリビューション: Ubuntu-20.04 (既定) アンインストールしたはずのディストリビューションが残っていると思うので、以下のコマンドで登録を解除します。\n\u0026gt; wsl --unregister Ubuntu-20.04 これで、再度ディストリビューションを起動すれば OK です。\n参考 URL https://www.techgaku.com/fix-ubuntu-boot-error-on-wsl-after-reinstall/ ","date":"2023-07-14T00:00:00+09:00","permalink":"/posts/2023/07/14/wsl/","title":"WSL でディストリビューションを削除して再インストール後に起動エラーになる"},{"content":"WSL について、Windows のコンポーネント（組み込み）として提供されているものと、Microsoft Store で提供されているものがあるのを知りました。\nきっかけは、WSL2 上の Ubuntu で systemd を使おうとしたら使えなかったことで、調べていくと WSL 自体のバージョンが 0.67.6 以降だと systemd が使えることがわかりました。\nWSL 自体のバージョンは以下のコマンドで確認できるらしいのですが、\n\u0026gt; wsl --version 私の環境ではバージョンが表示されず、コマンドの説明が表示されてしまいました。\nこれは、どうやら WSL のバージョンが古いのが原因らしく、最新版は Microsoft Store からインストールできるらしいと。\nここで、2022年11月に正式版としてリリースされたストア版の WSL を知り、自分が使っていたのは「Windows の機能の有効化または無効化」からインストールしたコンポーネント版で、古いプレビュー版の WSL だということを知りました。\nhttps://qiita.com/omu_kato/items/f9a6b5a02e25f5f2a487 ストア版の WSL は普通に Microsoft Store で検索すれば見つかるので、そこでインストールするだけです。\n尚、既にコンポーネント版を使っていてもストア版をインストールして使えるようですが、私の環境では Ubuntu 22.04 を起動しようとすると Error code: Wsl/Service/CreateInstance/MountVhd/ERROR_FILE_NOT_FOUND のエラーが出てしまいました。\nこれについては、以下を参考に WSL から Ubuntu 22.04 の登録を解除すれば直りました。\nhttps://www.techgaku.com/fix-ubuntu-boot-error-on-wsl-after-reinstall/ 参考 URL https://qiita.com/omu_kato/items/f9a6b5a02e25f5f2a487 https://qiita.com/hiromasa-masuda/items/355fe0c882a1a0abe1e1 https://www.techgaku.com/fix-ubuntu-boot-error-on-wsl-after-reinstall/ ","date":"2023-07-06T00:00:00+09:00","permalink":"/posts/2023/07/06/wsl/","title":"Microsoft ストア版の WSL をインストールする"},{"content":"Windows で EasyTAG 2.4.3 の場合に発生します。\nいろいろ試しても上手くいかず悩んでいたのですが、以下に報告が上がっていました。\nhttps://gitlab.gnome.org/GNOME/easytag/-/issues/5 2.4.2 にダウングレードすると問題なく書き込めました。\n参考 URL https://gitlab.gnome.org/GNOME/easytag/-/issues/5 ","date":"2023-07-02T00:00:00+09:00","permalink":"/posts/2023/07/02/easytag/","title":"EasyTAG 2.4.3 で画像がある場合に mp3 ファイルのタグ書き込みに失敗する"},{"content":"環境変数を参照する Python アプリを systemd でサービス化したときにかなりはまってしまいました。\nsystemd はユーザーの環境変数を参照しないようなので、ユニット定義ファイルで環境変数を設定する必要があるそうで、Environment で記述したり、外部ファイルに定義して EnvironmentFile で読み込む方法があるようです。\nhttps://note.com/meiburg/n/n5cd098a8c744 https://qiita.com/yasushi-jp/items/97057509e96919c35f64 また、上記だと変数を展開することができないので、そのような場合には以下のように ExecStartPre で systemctl の set-environment を使う方法などがあるようです。\nhttps://qiita.com/kobanyan/items/f8e8a3bd5406e1d290fb ただ、今回の私の場合は、シェル変数 HOSTNAME を環境変数に export して参照したかったのですが、systemd にはホスト名を表す特殊な変数として %H があるようなので、これを使って以下のようにすれば解決できました。\n[Unit] Description=My Python App [Service] ExecStart=/path/to/your/python/app.py Environment=\u0026#34;HOSTNAME=%H\u0026#34; [Install] WantedBy=multi-user.target 参考 URL https://ja.stackoverflow.com/questions/62360/systemd-%E3%81%8B%E3%82%89%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E8%B5%B7%E5%8B%95%E6%99%82%E3%81%ABhome%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%9F%E3%81%84 https://qiita.com/kobanyan/items/f8e8a3bd5406e1d290fb https://stackoverflow.com/questions/37864999/referencing-other-environment-variables-in-systemd ","date":"2023-06-14T00:00:00+09:00","permalink":"/posts/2023/06/14/systemd/","title":"systemd で環境変数を展開する"},{"content":"Visual Studio Code のエディタで現在アクティブなタブの背景色が、デフォルトだと他のタブとあまり変わらなくて分かりづらいので、設定で変更しました。\nメニューより、ファイル \u0026gt; ユーザー設定 \u0026gt; 設定 を選択します。\n設定の検索より workbench.colorCustomizations と入力し、「settings.json で編集」をクリックします。\nそして、以下のように tab.activeBackground を追記します。\n\u0026#34;workbench.colorCustomizations\u0026#34;: { \u0026#34;tab.activeBackground\u0026#34;: \u0026#34;#1b77cf\u0026#34; } 参考 URL https://tekunabe.hatenablog.jp/entry/2021/05/23/134458 ","date":"2023-06-12T00:00:00+09:00","permalink":"/posts/2023/06/12/vscode/","title":"VSCode で現在アクティブなタブの背景色を変更する"},{"content":"Telegraf で GPU 情報を取得しよううとしたのですが、Jetson には nvidia-smi のコマンドがありません。\nどうしようかなと思っていたら、以下の記事に Jetson で GPU 情報を取得する方法が書いてありました。\nhttps://www.influxdata.com/blog/nvidia-jetson-series-part-1-jetson-stats/ これを参考に、InfluxDB にデータを保存し、Grafana で可視化まで行いました。\n尚、TIG Stack (Telegraf, InfluxDB, Grafana) については以下を参考にしてください。\nhttps://kuttsun.blogspot.com/2021/12/telegraf-influxdb-garafana.html 環境 Jetson Xavier NX Ubuntu 18.04 Telegraf 1.23.0 Telegraf, InfluxDB, Grafana は Docker で動作させています。\nJetson Stats のインストール まずはホストに Jetson Stats をインストールします。\n$ sudo apt-get update $ sudo apt-get install python3-pip $ sudo -H python3 -m pip install -U jetson-stats インストール後、一度ログアウト or 再起動します。\njtop を実行して動作確認します。\nTelegraf の設定 Jetson Stats を使って GPU の情報を取得する python スクリプトを作成し、それを Telegraf から定期的に実行するようにします。\nただし、今回 Telegraf は Docker コンテナで動かしているので、上記の記事そのままでは上手く行きません。\nPython スクリプトの作成 上記の記事にある以下のスクリプトを jetson_stats.py という名前で保存しました。\n# Import jtop python library. We will use this to access the Jetson_Stats service. from jtop import jtop import json, datetime if __name__ == \u0026#34;__main__\u0026#34;: with jtop() as jetson: # jetson.stats provides our system measurements as type dict. tmp = jetson.stats # time and uptime are proved as time objects. These needed to be converted before passing as a JSON string, tmp[\u0026#34;time\u0026#34;] = str(tmp[\u0026#34;time\u0026#34;].strftime(\u0026#39;%m/%d/%Y\u0026#39;)) tmp[\u0026#34;uptime\u0026#34;] = str(tmp[\u0026#34;uptime\u0026#34;]) # We then convert our dict -\u0026amp;gt; Json string influx_json= {\u0026#34;jetson\u0026#34;: tmp} print(json.dumps(influx_json)) このスクリプトを Telegraf から定期的に実行することになります。\nDockerfile の作成 今までは Telegraf の Docker イメージをそのまま使っていましたが、このコンテナには Python の実行環境が入っていないため、上記の Python スクリプトを動かすことができません。\nそこで、以下のような Dockerfile を作成しました。\nFROM telegraf:1.23.0 ARG DEBIAN_FRONTEND=noninteractive COPY jetson_stats.py /usr/local/bin/ RUN apt update \u0026amp;amp;\u0026amp;amp; \\ apt install -y --no-install-recommends \\ python3 \\ python3-pip \\ python3-setuptools \\ python3-wheel \u0026amp;amp;\u0026amp;amp; \\ apt autoremove \u0026amp;amp;\u0026amp;amp; apt clean \u0026amp;amp;\u0026amp;amp; rm -rf /var/lib/apt/lists/* RUN pip3 install \\ jetson-stats jetson_stats.py をイメージ内にコピーし、あとは Python の実行環境と Jetson Stats をインストールしています。\ndocker-compose.yml の変更 コンテナ内で Jetson Stats を使うためには、/run/jtop.sock をマウントする必要があるようです。\nただ、それだけではアクセス権限の関係で以下のようなエラーが表示されました。\njtop.core.exceptions.JtopException: I can\u0026#39;t access jetson_stats.service. Docker コンテナの実行ユーザーを /run/jtop.sock の所有グループに加えたら解決できたので、group_add のプロパティを追加します。\nhttps://github.com/rbonghi/jetson_stats 以上を踏まえ、docker-compose.yml を以下のように変更しました（今回に関連する部分のみ抜粋）。\nservices: telegraf: image: telegraf-python:latest container_name: telegraf hostname: ${HOSTNAME:?} environment: - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=all deploy: resources: reservations: devices: - capabilities: [gpu] restart: unless-stopped ports: - 6514:6514 volumes: - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro - /var/run/docker.sock:/var/run/docker.sock:ro - /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro # 以下は Jetson Xavier NX 用 - /run/jtop.sock:/run/jtop.sock:ro # https://github.com/rbonghi/jetson_stats user: ${TELEGRAF_UID_GID:?} group_add: - \u0026#34;${JTOP_GID:?}\u0026#34; 環境変数の追加 上記の docker-compose.yml 内にある JTOP_GID という環境変数を定義します。\n~/.bashrc に以下を追記します。\nexport JTOP_GID=$(stat -c \u0026#39;%g\u0026#39; /run/jtop.sock) 再読み込みします。\n~$ source .bashrc これで、/run/jtop.sock の所有グループを JTOP_GID で参照できます。\ntelegraf.conf の設定 telegraf.conf に以下を追記します。\n[[inputs.exec]] ## Commands array commands = [ \u0026#34;python3 /usr/local/bin/jetson_stats.py\u0026#34; ] ## Timeout for each command to complete. timeout = \u0026#34;5s\u0026#34; ## measurement name suffix (for separating different commands) name_suffix = \u0026#34;_jetson_stats\u0026#34; ## Data format to consume. ## Each data format has its own unique set of configuration options, read ## more about them here: ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md data_format = \u0026#34;json\u0026#34; ## Array of glob pattern strings or booleans keys that should be added as string fields. json_string_fields = [\u0026#34;jetson_uptime\u0026#34;, \u0026#34;jetson_nvp model\u0026#34;, \u0026#34;jetson_NVENC\u0026#34;, \u0026#34;jetson_NVDEC\u0026#34;, \u0026#34;jetson_NVJPG\u0026#34;] 内容は前述の参考記事の中身そのままです。\nテスト Docker コンテナに入って、以下のコマンドを実行します。\npython3 jetson_stats.py json が取得できていれば OK です。\nGrafana のダッシュボードを作成 Jetson Stats 用のテンプレートがないかなと探してみましたが、見つかりませんでした。\nどうやら自分で作成するしかなさそうです。\n参考として、jetson_stats.py を実行したら以下のような出力が得られます（わかりやすいように整形しています）。\n{ \u0026#34;jetson\u0026#34;: { \u0026#34;time\u0026#34;: \u0026#34;09/14/2022\u0026#34;, \u0026#34;uptime\u0026#34;: \u0026#34;0:40:39.970000\u0026#34;, \u0026#34;jetson_clocks\u0026#34;: \u0026#34;OFF\u0026#34;, \u0026#34;nvp model\u0026#34;: \u0026#34;MODE_10W_DESKTOP\u0026#34;, \u0026#34;CPU1\u0026#34;: 16, \u0026#34;CPU2\u0026#34;: 13, \u0026#34;CPU3\u0026#34;: 13, \u0026#34;CPU4\u0026#34;: 14, \u0026#34;CPU5\u0026#34;: \u0026#34;OFF\u0026#34;, \u0026#34;CPU6\u0026#34;: \u0026#34;OFF\u0026#34;, \u0026#34;GPU\u0026#34;: 0, \u0026#34;MTS FG\u0026#34;: 0, \u0026#34;MTS BG\u0026#34;: 0, \u0026#34;RAM\u0026#34;: 1413804, \u0026#34;EMC\u0026#34;: 1413804, \u0026#34;SWAP\u0026#34;: 0, \u0026#34;APE\u0026#34;: 150, \u0026#34;NVENC\u0026#34;: \u0026#34;OFF\u0026#34;, \u0026#34;NVDEC\u0026#34;: \u0026#34;OFF\u0026#34;, \u0026#34;NVJPG\u0026#34;: \u0026#34;OFF\u0026#34;, \u0026#34;fan\u0026#34;: 0.0, \u0026#34;Temp AO\u0026#34;: 42.0, \u0026#34;Temp AUX\u0026#34;: 42.0, \u0026#34;Temp CPU\u0026#34;: 42.5, \u0026#34;Temp GPU\u0026#34;: 41.5, \u0026#34;Temp thermal\u0026#34;: 42.15, \u0026#34;power cur\u0026#34;: 4166, \u0026#34;power avg\u0026#34;: 4166 } } これらの値が InfluxDB に格納されているはずなので、その値を使ってダッシュボードを作成していきます。\n尚、各プロパティの説明は以下にありました。\nhttps://rnext.it/jetson_stats/jtop.html#jtop.jtop.jtop.stats 単位くらい書いておいてほしいなぁ〜。\n参考 URL https://www.influxdata.com/blog/nvidia-jetson-series-part-1-jetson-stats/ https://github.com/rbonghi/jetson_stats https://github.com/rbonghi/jetson_stats/issues/63 https://qiita.com/manabuishiirb/items/a2620ec8020811540d81 https://grafana.com/grafana/dashboards/14493-nvidia-jetson/ ","date":"2023-05-27T00:00:00+09:00","permalink":"/posts/2023/05/27/telegraf/","title":"Telegraf で Jetson の GPU 情報を取得する"},{"content":"ボタン電池がなくなり、RTC が遥か未来になったときに MongoDB が動作しなくなりました。（正確には、Python アプリにおいて、MongoDB の初期化を行う際にエラーとなりました。）\nMongoDB のバージョンは 6.0.5 です。\n調べてみると、MongoDB の ObjectID は先頭の4バイトがタイムスタンプ(UNIX タイム)となっているそうです。\nMongoDB はこれを符号なし整数として扱っていて、その場合の上限は 4,294,967,295 であり、これは UTC で2106年2月7日6時28分16秒となります。\nというわけで、MongoDB はいわゆる 2106 年問題を孕んでいることになります。\nhttps://www.wdic.org/w/TECH/2106%E5%B9%B4%E5%95%8F%E9%A1%8C ちなみに、4バイトがタイムスタンプを符号付き整数として扱っている場合は 2038 年問題となります。\n参考 URL https://stackoverflow.com/questions/42097779/how-can-mongodb-handle-objectid-timestamp-beyond-tue-19-jan-2038 https://jira.mongodb.org/browse/GODRIVER-1092 https://www.wdic.org/w/TECH/2106%E5%B9%B4%E5%95%8F%E9%A1%8C https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb ","date":"2023-05-23T00:00:00+09:00","permalink":"/posts/2023/05/23/mongodb/","title":"MongoDB と 2106 年問題"},{"content":"Docker Compose の env_file ディレクティブを使用する場合、原則として env_file に指定されたファイル内でシェルのシンタックスを使用することはできません。\nしかし、env_file にはシェルスクリプトを指定することもできますので、以下のようにシェルスクリプトファイル（例えば、env.shとします）を作成することで対応できます。\n#!/bin/bash export MY_VARIABLE=my_value docker-compose.yml の env_file ディレクティブでシェルスクリプトファイルを指定します。\nversion: \u0026#39;3\u0026#39; services: myservice: env_file: - ./env.sh 注意点として、シェルスクリプトのパーミッションを正しく設定する必要があります。\nまた、シェルスクリプト内で環境変数を定義する場合には export する必要があります。\nちなみに、env_file で定義された環境変数は、コンテナ内では使えますが、docker-compose.yml 内では使えません。\n（これでちょっとはまりました。）\nhttps://zenn.dev/rhene/scraps/781fdbecd340d3 参考 URL https://maku77.github.io/p/8r3cmu5/ https://zenn.dev/rhene/scraps/781fdbecd340d3 ","date":"2023-05-12T00:00:00+09:00","permalink":"/posts/2023/05/12/docker/","title":"[Docker] docker-compose の env_file 内でシェルスクリプトを実行する"},{"content":"SyslogHandler を使ってログを記録していたのですが、Bad file descriptor のエラーが表示されてはまってしまいました。\n表示されたエラーは以下です。\nTraceback (most recent call last): File \u0026#34;/usr/lib/python3.8/logging/handlers.py\u0026#34;, line 940, in emit self.socket.sendto(msg, self.address) OSError: [Errno 9] Bad file descriptor アプリをマルチプロセスで動かしており、FastAPI ( +uvicorn) を使うとこのエラーが出るようになりました。\n環境 Python 3.8.10 対処方法 調べても原因がさっぱりわからなかったのですが、最終的に以下のページにたどり着きました。\nhttps://github.com/python/cpython/issues/87362 どうやら Python の logging モジュールの不具合のようで、Python のバージョンを 3.11 まで上げると直りました。\n参考 URL https://github.com/python/cpython/issues/87362 ","date":"2023-04-05T00:00:00+09:00","permalink":"/posts/2023/04/05/python/","title":"[Python] SyslogHandler でログを送信したときに `Bad file descriptor` のエラーになる"},{"content":"SyslogHandler で UDP でログを記録していたのですが、マルチプロセスでは Socket 通信のところでエラーが発生しました。\nマルチプロセスでのロギングでは、QueueHandler と QueueListner を使ってログを一箇所に集約して書き込むようにする必要があります。\n環境 Python 3.8.10 基本的な書き方 とりあえずマルチプロセスは置いておいて、QueueHandler と QueueListener の基本的な使い方です。\nimport os from multiprocessing import Queue from logging import StreamHandler, handlers, basicConfig, getLogger, Formatter, DEBUG # 標準出力の設定 stream_handler = StreamHandler() stream_handler.setFormatter(Formatter(\u0026#39;%(asctime)s [%(levelname)s] [%(name)s] %(message)s\u0026#39;)) # Syslog 出力の設定 syslog_handler = handlers.SysLogHandler(address=(\u0026#39;localhost\u0026#39;, 514)) syslog_handler.setFormatter(Formatter(f\u0026#39;{os.environ.get(\u0026#34;HOSTNAME\u0026#34;)} ris-main: %(message)s\u0026#39;)) # ファイル出力の設定 file_handler = handlers.TimedRotatingFileHandler(\u0026#39;log/sample.log\u0026#39;) file_handler.setFormatter(Formatter(fmt=\u0026#39;%(asctime)s.%(msecs)03d [%(levelname)s] [%(name)s] %(message)s\u0026#39;, datefmt=\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)) # キューハンドラの設定 que = Queue() queue_handler = handlers.QueueHandler(que) basicConfig(level=DEBUG, handlers=[queue_handler]) # リスナーの作成と開始 queue_listener = handlers.QueueListener(que, *[stream_handler, syslog_handler, file_handler]) queue_listener.start() logger = getLogger(__name__) logger.info(\u0026#39;this is info\u0026#39;) logger.debug(\u0026#39;this is debug\u0026#39;) logger.critical(\u0026#39;this is critical\u0026#39;) queue_listener.stop() logger を使って書いたログは QueueHandler によりキューに入れられます。\nQueueListner はキューに溜まったログを取り出して、StreamHandler SyslogHandler TimedRotatingFileHandler で記録されます。\n尚、上記は basicConfig を使って設定していますので、ライブラリなどのロギングにも影響します（対処方法は後述）。\nマルチプロセスで試してみた 上記を踏まえ、プロセスを３つ立ち上げ、各プロセス100回ログを記録させてみたサンプルが以下です。\nimport os from multiprocessing import Process, Queue from logging import StreamHandler, handlers, basicConfig, getLogger, Formatter, DEBUG def hoge(index): logger = getLogger(__name__) for count in range(100): logger.info(f\u0026#39;({index}) this is info\u0026#39;) logger.debug(f\u0026#39;({index}) this is debug\u0026#39;) logger.critical(f\u0026#39;({index}) this is critical\u0026#39;) # 標準出力の設定 stream_handler = StreamHandler() stream_handler.setFormatter(Formatter(\u0026#39;%(asctime)s [%(levelname)s] [%(name)s] %(message)s\u0026#39;)) # Syslog 出力の設定 syslog_handler = handlers.SysLogHandler(address=(\u0026#39;localhost\u0026#39;, 514)) syslog_handler.setFormatter(Formatter(f\u0026#39;{os.environ.get(\u0026#34;HOSTNAME\u0026#34;)} ris-main: %(message)s\u0026#39;)) # ファイル出力の設定 file_handler = handlers.TimedRotatingFileHandler(\u0026#39;log/sample.log\u0026#39;) file_handler.setFormatter(Formatter(fmt=\u0026#39;%(asctime)s.%(msecs)03d [%(levelname)s] [%(name)s] %(message)s\u0026#39;, datefmt=\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)) # キューハンドラの設定 que = Queue() queue_handler = handlers.QueueHandler(que) basicConfig(level=DEBUG, handlers=[queue_handler]) # リスナーの作成と開始 queue_listener = handlers.QueueListener(que, *[stream_handler, syslog_handler, file_handler]) queue_listener.start() # プロセスの立ち上げ processes = [] for i in range(3): p = Process(target=hoge, args=(i,)) p.start() processes.append(p) # プロセスの終了を待つ for process in processes: try: process.join() except KeyboardInterrupt: process.join(1) queue_listener.stop() 問題なくログが記録できていることが確認できました。\nbasicConfig を使わずに設定する 上述したように、basicConfig を使うとライブラリなどの全てのモジュールに影響するので、logger に対して設定を行うようにします。\nただ、そうなると都度設定を行う必要が出てくるので、以下のページを参考に共通の関数を作成しました。\nhttp://joemphilips.com/post/python_logging/ ただし、QueueHandler を使う場合、キューを渡してやる必要があるので、それはグローバル変数として用意することにしました。\nここらへん、もっと良い実装方法はないかなぁとは思いますが・・・。\n以下、サンプルです。\n# mylogger.py import multiprocessing import logging import logging.handlers log_queue = multiprocessing.Queue() def getLogger(modname) -\u0026gt; logging.Logger: \u0026#34;\u0026#34;\u0026#34; 共通のロガーを作成するための関数 logging.getLogger と簡単に差し替えられるように同じ関数名としておく \u0026#34;\u0026#34;\u0026#34; # キューハンドラの設定 queue_handler = logging.handlers.QueueHandler(log_queue) logger = logging.getLogger(modname) logger.addHandler(queue_handler) logger.setLevel(logging.DEBUG) return logger # main.py import os from logging import handlers, StreamHandler, Formatter, DEBUG, INFO from mylogger import getLogger, log_queue # 標準出力の設定 stream_handler = StreamHandler() stream_handler.setFormatter(Formatter(\u0026#39;%(asctime)s [%(levelname)s] [%(name)s] %(message)s\u0026#39;)) stream_handler.setLevel(DEBUG) # Syslog 出力の設定 syslog_handler = handlers.SysLogHandler(address=(\u0026#39;localhost\u0026#39;, 514)) syslog_handler.setFormatter(Formatter(f\u0026#39;{os.environ.get(\u0026#34;HOSTNAME\u0026#34;)} ris-main: %(message)s\u0026#39;)) syslog_handler.setLevel(INFO) # ファイル出力の設定 file_handler = handlers.TimedRotatingFileHandler(\u0026#39;log/sample.log\u0026#39;) file_handler.setFormatter(Formatter(fmt=\u0026#39;%(asctime)s.%(msecs)03d [%(levelname)s] [%(name)s] %(message)s\u0026#39;, datefmt=\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)) file_handler.setLevel(INFO) # リスナーの作成と開始 queue_listener = handlers.QueueListener( log_queue, *[stream_handler, syslog_handler, file_handler], respect_handler_level=True) queue_listener.start() logger = getLogger(__name__) logger.info(\u0026#39;this is info\u0026#39;) logger.debug(\u0026#39;this is debug\u0026#39;) logger.critical(\u0026#39;this is critical\u0026#39;) queue_listener.stop() また、ここでは標準出力とそれ以外で、出力するログレベルを変えています。\nこの場合、QueueHandler の respect_handler_level を True にして、各ハンドラー側のレベルを優先するように設定してやる必要があります。\n参考 URL https://docs.python.org/ja/3.8/library/logging.handlers.html https://ikatakos.com/pot/programming/python/packages/multiprocessing/queuehandler https://rob-blackbourn.medium.com/how-to-use-python-logging-queuehandler-with-dictconfig-1e8b1284e27a http://joemphilips.com/post/python_logging/ ","date":"2023-04-04T00:00:00+09:00","permalink":"/posts/2023/04/04/python/","title":"[Python] マルチプロセスにおけるロギング (QueueHandler, QueueListener)"},{"content":"スレッドを使わずに定期的に GUI を更新するために、QTimer の使い方と挙動について簡単に調べてみました。\n環境 Python 3.8.10 PySide 6.2.2.1 サンプルコード import sys import time from datetime import datetime from PySide6.QtCore import (QTimer, Signal) from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel) class MainWindow(QWidget): \u0026#34;\u0026#34;\u0026#34;メインウィンドウ \u0026#34;\u0026#34;\u0026#34; countup = Signal(int) def __init__(self, parent=None): super().__init__(parent) self.__count = 0 layout = QVBoxLayout() self.__label = QLabel(self.__current_count()) layout.addWidget(self.__label) self.setLayout(layout) self.__timer = QTimer(self) self.__timer.timeout.connect(self.__timeout) self.__timer.start(1000) self.__timer.singleShot(10000, self.__single_shot1) self.__timer.singleShot(15000, self.__single_shot2) def __timeout(self): self.__count += 1 print(f\u0026#39;{datetime.now()}, {self.__count}\u0026#39;) self.__label.setText(self.__current_count()) #time.sleep(3) def __current_count(self) -\u0026gt; str: return f\u0026#39;現在のカウント: {self.__count}\u0026#39; def __single_shot1(self): print(f\u0026#39;{datetime.now()}, 10秒経過\u0026#39;) def __single_shot2(self): print(f\u0026#39;{datetime.now()}, 15秒経過\u0026#39;) if __name__ == \u0026#34;__main__\u0026#34;: app = QApplication(sys.argv) main_window = MainWindow() main_window.show() ret = app.exec() sys.exit(ret) 2023-03-27 14:04:40.166652, 1 2023-03-27 14:04:41.115886, 2 2023-03-27 14:04:42.066937, 3 2023-03-27 14:04:43.026873, 4 2023-03-27 14:04:44.026648, 5 2023-03-27 14:04:45.026877, 6 2023-03-27 14:04:46.026574, 7 2023-03-27 14:04:47.026898, 8 2023-03-27 14:04:48.026477, 9 2023-03-27 14:04:49.027008, 10秒経過 2023-03-27 14:04:49.027169, 10 2023-03-27 14:04:50.027045, 11 2023-03-27 14:04:51.026868, 12 2023-03-27 14:04:52.027034, 13 2023-03-27 14:04:53.026916, 14 2023-03-27 14:04:54.026824, 15秒経過 2023-03-27 14:04:54.027010, 15 処理に時間がかかった場合はどうなる？ 上記のコードでは、1秒ごとに __timeout 関数が実行されますが、もしその中で処理に時間がかかったとしたらどうなるか？\n試しに3秒のスリープを入れてみました（上記のコメントアウトを解除）。\n2023-03-27 14:07:10.458991, 1 2023-03-27 14:07:13.462465, 2 2023-03-27 14:07:16.464238, 3 2023-03-27 14:07:19.467192, 4 2023-03-27 14:07:22.470589, 10秒経過 2023-03-27 14:07:22.471410, 5 2023-03-27 14:07:25.475184, 6 2023-03-27 14:07:28.478740, 15秒経過 2023-03-27 14:07:28.480526, 7 2023-03-27 14:07:31.484492, 8 3秒経過後、すぐに __timeout がコールされています。\n前の処理が完了しないと次の処理は行われないようです。\n参考 URL https://fereria.github.io/reincarnation_tech/11_PySide/01_PySide_Basic/00_Tutorial/13_timer/ https://fereria.github.io/reincarnation_tech/11_PySide/00_Tutorial/13_timer/ ","date":"2023-03-28T00:00:00+09:00","permalink":"/posts/2023/03/28/qtimer/","title":"[PySide] QTimer の使い方"},{"content":"nginx を Docker コンテナとして動かしており、ホスト側へリバースプロキシを行おうとした際にはまってしまいました。\n環境 docker 20.10.21 nginx 1.23.3 問題と対策 Docker コンテナ内からホストにアクセスする際には host.docker.internal を指定します。\nこれについては以下の記事を参照してください。\nhttps://kuttsun.blogspot.com/2022/06/docker.html その上で、nginx の設定で以下のようにリバースプロキシの設定を行いました。\nserver { listen 80 default_server; server_name _; location /hoge { resolver 127.0.0.11 valid=30s; set $dummy_var http://host.docker.internal:5000; rewrite /hoge/(.*)$ /$1 break; proxy_pass $dummy_var; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } すると、以下のように名前解決できないというエラーが表示されました。\n尚、コンテナ内に入って curl などを使ってテストすると、きちんと host.docker.internal の名前解決がされていました。\nいろいろ調べてみると、どうやら proxy_pass では /etc/hosts が参照されないそうです。\nどのように対応しようか悩んでいたところ、upstream の設定ではきちんと /etc/hosts が参照されるようでしたので、以下のように設定して回避することにしました。\nupstream backends { server host.docker.internal:5000; } server { listen 80 default_server; server_name _; location /hoge { resolver 127.0.0.11 valid=30s; set $dummy_var http://backends; rewrite /hoge/(.*)$ /$1 break; proxy_pass $dummy_var; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } 参考 URL https://qiita.com/yKanazawa/items/2136c4880e8d48370981 https://rougeref.hatenablog.com/entry/2022/06/13/185501 https://qiita.com/tksugimoto/items/804e0051bf1b1ddab168 ","date":"2023-03-14T00:00:00+09:00","permalink":"/posts/2023/03/14/nginx/","title":"Nginx の proxy_pass で名前解決されない"},{"content":"Docker で動かしている Nginx でリバースプロキシの設定を行い、別の Docker コンテナに転送するように設定したときにちょっとはまったのでメモです。\nコンテナ同士はコンテナ名を使ってアクセスできるので、Nginx の設定では以下のようにリバースプロキシの設定を行っています。\nlocation /some_path { proxy_pass http://container-name.docker-network:port; } このとき、Nginx は 起動時に upstream の名前解決を行っているので、上記の URL の名前解決ができない場合（つまり転送先のコンテナが起動していない）と host not found in upstream というエラーが表示されて起動に失敗します。\n私の場合、仕様によって起動するコンテナの数が異なったり、開発中は一部のコンテナだけ起動して確認したりするので、その都度 Nginx の設定を変えるのは面倒です。\nなので、名前解決できなくても Nginx が起動できるように回避策がないかと調べた所、以下の記事に書いてありました。\nhttps://serverfault.com/questions/700894/make-nginx-ignore-site-config-when-its-upstream-cannot-be-reached resolver のディレクティブを使って名前解決してやることで回避できるそうです。\nlocation /some_path { resolver 127.0.0.1 valid=30s; # resolver 8.8.8.8 valid=30s; # or some other DNS # resolver 127.0.0.11 valid=30s; # or Docker\u0026#39;s DNS server set $dummy_var http://container-name.docker-network:port; proxy_pass $dummy_var; } 私の場合、Nginx を Docker で動かしているので、127.0.0.11 をリゾルバに指定することで、転送先のコンテナが起動していなくても Nginx が起動できるようになりました。\n参考 URL https://architecting.hateblo.jp/entry/2021/01/12/124135 https://serverfault.com/questions/700894/make-nginx-ignore-site-config-when-its-upstream-cannot-be-reached ","date":"2023-02-16T00:00:00+09:00","permalink":"/posts/2023/02/16/nginx/","title":"Nginx で upstream の 名前解決を無効化（回避）する方法"},{"content":"watch コマンドと date コマンドを組み合わせると簡単にできます。\n例えば以下のような感じです。\n$ watch -t -n 0.01 date +%T.%N watch は引数で指定したコマンドを一定間隔ごとに繰り返し実行するコマンドです。\n-t はヘッダを非表示にします。\n-n はコマンドを実行する間隔（秒）です。\nここでは date コマンドを 0.01 秒ごとに実行しています。\n+%T.%N は日時の書式です。\n例えば、以下のようにすれば日付も表示されます。\n$ watch -t -n 0.01 \u0026#34;date \u0026#39;+%Y/%m/%d %T.%N\u0026#39;\u0026#34; 書式にスペースを含む場合、シングルコーテーションやダブルコーテーションで括る必要があります。\n参考 URL https://qiita.com/ikesato/items/4e1d2ff5251f6805b2a0 https://hydrocul.github.io/wiki/commands/date.html ","date":"2023-02-02T00:00:00+09:00","permalink":"/posts/2023/02/02/linux/","title":"Linux で端末にリアルタイムに日時を表示する"},{"content":"put や get がコルーチンになっていたので、どういう動きになるのか確認してみました。\n環境 Python 3.8.1 サンプルコード import asyncio async def put(q:asyncio.Queue): count = 0 while count \u0026lt;= 100: print(f\u0026#39;({count}) before put\u0026#39;) await q.put(count) print(f\u0026#39;({count}) after put\u0026#39;) count += 1 if count % 5 == 0: # 5回送ったらスリープ await asyncio.sleep(0.01) async def get(q:asyncio.Queue): count = 0 while count \u0026lt; 100: count = await q.get() print(f\u0026#39;({count}) get\u0026#39;) async def main(): q = asyncio.Queue() await asyncio.gather(*[ put(q), get(q) ]) asyncio.run(main()) 出力は以下のようになりました。\n(0) before put (0) after put (1) before put (1) after put (2) before put (2) after put (3) before put (3) after put (4) before put (4) after put (0) get (1) get (2) get (3) get (4) get (5) before put (5) after put (6) before put (6) after put (7) before put (7) after put (8) before put (8) after put (9) before put (9) after put (5) get (6) get (7) get (8) get (9) get put したあと、sleep したところで制御が他に移っています。\nget のほうは、キューの中身を全部取り終わって次のデータ待ちになったところで制御が他に移っています。\n","date":"2023-02-01T00:00:00+09:00","permalink":"/posts/2023/02/01/python-asyncio/","title":"[Python] asyncio.Queue の動きを確認してみた"},{"content":"async のついていない関数を非同期化して、async のついた非同期関数と同じように扱いたいというときの方法です。\n環境 python 3.8.1 概要 同期関数を非同期化するには run_in_executor を使用します。\nただし、CPU バウンドな処理を非同期化しても並列では実行されないので、マルチプロセス化などを行う必要があります。 これについては以下の記事が参考になりました。\nhttps://pod.hatenablog.com/entry/2019/03/21/162511 また、run_in_executor で実行する関数に名前付き引数を渡す場合、functools.partial を使う必要があります。\n引数で渡せる値は、Python の仕様上 Picklable な値のみだそうです。\nラムダ式やモジュールトップレベル以外で定義された関数やクラスは渡せません\nhttps://docs.python.org/ja/3/library/pickle.html#what-can-be-pickled-and-unpickled サンプルコード 上記を踏まえたサンプルコードです。\n同期処理として CPU バウンドを想定した処理を２つ、非同期処理として I/O バウンドを想定した処理を２つ、計４つの処理を並列で動かしています。\nimport asyncio import time from functools import partial from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor def cpu_bound(index): # CPU バウンドな処理を想定 count = 0 while count \u0026lt; 1000: print(f\u0026#39;cpu_bound[{index}] count={count}\u0026#39;) count += 1 time.sleep(0.01) async def io_bound(index): # I/O バウンドな処理を想定 count = 0 while count \u0026lt; 1000: print(f\u0026#39;io_bound[{index}] count={count}\u0026#39;) count += 1 await asyncio.sleep(0.01) async def main(): loop = asyncio.get_running_loop() executor = ProcessPoolExecutor(max_workers=2) tasks = [ loop.run_in_executor(executor, partial(cpu_bound, 0)), loop.run_in_executor(executor, partial(cpu_bound, 1)), io_bound(0), io_bound(1), ] await asyncio.gather(*tasks) asyncio.run(main(), debug=True) ProcessPoolExecutor により、CPU バウンドな処理はそれぞれ個別のプロセスとして動作させています。\nI/O バウンドな処理はどちらもメインプロセスで動作します。\n従って、I/O バウンドな処理のほうが少し終わるのが遅くなります。\n参考 URL https://cpoint-lab.co.jp/article/202208/23186/ https://pod.hatenablog.com/entry/2019/03/21/162511 https://hachibeedi.github.io/entry/unify-sync-and-async-function-in-python/ https://qiita.com/smatsumt/items/d8f290e40077a14210f2 https://docs.python.org/ja/3/library/pickle.html#what-can-be-pickled-and-unpickled ","date":"2023-01-26T00:00:00+09:00","permalink":"/posts/2023/01/26/python/","title":"[Python] 同期処理を非同期化して扱う"},{"content":"InfluxDB v2 ではそれ自体でダッシュボードの作成もできますが、まだまだ使いにくかったり細かい設定ができなかったりしたので、使い慣れている Grafana を使うことにしました。\nまた、この機会に Grafana もオンプレミス版からクラウドサービスの Grafana Cloud に変更しました。\nGrafana Cloud へのサインアップ方法などは割愛します。\nデータソースの設定 サイドメニューから Data Sources を選択します。 Add data source をクリックし、InfluxDB をクリックします。 InfluxDB の情報を入力します。 InfluxDB Cloud の場合、Query Language は Flux を選択します。\nまた、URL, トークン、バケット名が必要になるので、以下を参考に取得しておきます。\nhttps://kuttsun.blogspot.com/2022/12/influxdb-cloud.html ダッシュボード（パネル）の作成 InfluxDB v1 では GUI でポチポチと設定していくだけでグラフが表示できましたが、InfluxDB v2 では Flux で処理を書いていく必要があります。\n最初は難しかったですが、慣れればこちらのほうが簡単に思えてきました。\nいくつか例を挙げます。\n基本 以下にサンプルがあります。\nhttps://grafana.com/docs/grafana/latest/datasources/influxdb/query-editor/#use-macros from(bucket:\u0026#34;バケット名\u0026#34;) |\u0026gt; range(start: v.timeRangeStart, stop: v.timeRangeStop) |\u0026gt; filter(fn:(r) =\u0026gt; r._measurement == \u0026#34;メジャーメント名\u0026#34;) |\u0026gt; filter(fn:(r) =\u0026gt; r[\u0026#34;_field\u0026#34;] == \u0026#34;表示したいフィールドの名前\u0026#34;) |\u0026gt; aggregateWindow(every: v.windowPeriod, fn: mean) 基本的にはこれでグラフが作成できます。\n一日ごとのデータを表示 例えば株価の終値を表示したい場合などは以下のようにすることでできました。\nimport \u0026#34;timezone\u0026#34; option location = timezone.location(name: \u0026#34;Asia/Tokyo\u0026#34;) from(bucket:\u0026#34;バケット名\u0026#34;) |\u0026gt; range(start: -30d, stop: now()) |\u0026gt; filter(fn:(r) =\u0026gt; r._measurement == \u0026#34;メジャーメント名\u0026#34;) |\u0026gt; filter(fn:(r) =\u0026gt; r[\u0026#34;_field\u0026#34;] == \u0026#34;表示したいフィールドの名前\u0026#34;) |\u0026gt; aggregateWindow(every: 1d, fn: last) ポイントは aggregateWindow(every: 1d, fn: last) で、一日ごとの最後の値を取得しています。\nまた、タイムゾーンを指定しないと UTC での終値（つまり、日本時間の9時の時点の値）が取得されてしまったので、JST を指定しました。\nhttps://www.influxdata.com/blog/time-zones-in-flux/ 尚、私の場合はグラフではなくテーブル表示にしたかったため、グラフの表示期間に関わらず range(start: -30d, stop: now()) で直近30日固定表示にしています。\n累積グラフの作成 cumulativeSum を使います。\nhttps://docs.influxdata.com/influxdb/cloud/query-data/flux/cumulativesum/ 例えば、日次損益の集計を行う場合などは以下のようにするとできました。\nimport \u0026#34;timezone\u0026#34; option location = timezone.location(name: \u0026#34;Asia/Tokyo\u0026#34;) from(bucket:\u0026#34;バケット名\u0026#34;) |\u0026gt; range(start: -30d, stop: now()) |\u0026gt; filter(fn:(r) =\u0026gt; r._measurement == \u0026#34;メジャーメント名\u0026#34;) |\u0026gt; filter(fn:(r) =\u0026gt; r[\u0026#34;_field\u0026#34;] == \u0026#34;表示したいフィールドの名前\u0026#34;) |\u0026gt; aggregateWindow(every: 1d, fn: last) |\u0026gt; cumulativeSum() 一日ごとの値は取り出す方法は前述の例と同じです。\nあとはそれを cumulativeSum() で加算していくだけです。\nこれにより、一日ごとの値を加算してグラフに表示できます。\n参考 URL https://qiita.com/aikige/items/22dc9ebc64e32a5bdfae https://qiita.com/ekzemplaro/items/f462dfc0d81329b39c09 https://rabbit-note.com/2022/09/12/influxdb-2-flux/ ","date":"2023-01-06T00:00:00+09:00","permalink":"/posts/2023/01/06/influxdb-cloud/","title":"Grafana Cloud で InfluxDB v2 のデータを可視化する"},{"content":"今までは AWS や GCP 上に InfluxDB の環境を用意して使っていたのですが、InfluxDB Cloud というクラウドサービスがあることを知ったので、使ってみました。\nバージョンも今までは v1 を使っていましたが、クラウドなので v2 に変わります。\n準備 バケットの作成 InfluxDB v1 系でいう、データベースとリテンションポリシーを合わせたようなものかなと思います。\nサイドメニューより Load Data \u0026gt; Buckets を選択します。 画面右側の GENERATE BUCKET \u0026gt; クリックします。 バケット名を入力し、必要に応じてデータの保存期間を指定します（デフォルトでは 30 日経ったら古いデータが削除されます）。 トークンの取得 サイドメニューより Load Data \u0026gt; API Tokens を選択します。 画面右側の GENERATE API TOKEN \u0026gt; All Access API Token を選択します。 トークンが表示されるのでコピーします。 コピーしたトークンは環境変数などに設定して使います。\n$ export INFLUX_TOKEN=コピーしたトークン URL の取得 API エンドポイントとなる URL は以下で取得できます。\n画面上部のメールアドレスを選択 \u0026gt; Settings をクリック Python からデータを書き込む 以下の記事を参考にしてください。\nhttps://kuttsun.blogspot.com/2022/12/python-influxdb-18-influxdbclient.html 必要な情報はバケット名、トークン、URL です。\nダッシュボードの作成 別途記事にします。\n参考 URL https://dev.classmethod.jp/articles/send-and-visualize-data-with-influxdb-cloud/ ","date":"2022-12-26T00:00:00+09:00","permalink":"/posts/2022/12/26/influxdb-cloud/","title":"Influxdb Cloud を使ってみた"},{"content":"python で InfluxDB v1 に対してデータの読み書きを行う際に、ayncio を使って非同期化したいなと思ったのですが、今使っているライブラリの InfluxDBClient は asyncio に対応していないようでした。\n代わりに influxdata 社の influx-client を使うと asyncio を使えそうだったのですが、こちらは基本的に InfluxDB v2 以降にのみ対応しているようで、Flux という言語にしか対応していません。\nInfluxDB v1 以前は InfluxQL という SQL に近い言語が使われていました。\nこちら、以下のページが参考になりました。\nhttps://rabbit-note.com/2022/09/12/influxdb-2-flux/ ただ、InfluxDB 1.7以降でも設定を変更することで Flux が使えるようになるそうです。\nhttps://github.com/influxdata/influxdb-client-python#influxdb-1-8-api-compatibility 従って、InfluxDB v1 でも Flux を有効にしたうえで、Flux でクエリを記述してデータを取得するようにすれば、asyncio を使って非同期化することができそうです。\nちなみに、データの書き込みに関しては Flux は関係ありませんでした。\n以下、InfluxDB v1 の前提で書いていますが、v2 でも基本的には同じです。\nbucket token の指定方法が異なるだけです。\n環境 InfluxDB 1.8.10 Flux 言語を有効化する InfluxDB を Docker で動かしていたのですが、環境変数に以下を追加するだけで OK でした。\nINFLUXDB_HTTP_FLUX_ENABLED=true サンプルコード データの書き込み (write_api) import asyncio from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync from influxdb_client.client.write.point import Point from influxdb_client.domain.write_precision import WritePrecision if __name__ == \u0026#39;__main__\u0026#39;: async def main(): url = \u0026#39;http://\u0026lt;url\u0026gt;:8086\u0026#39; username = \u0026#39;username\u0026#39; password = \u0026#39;password\u0026#39; dbname = \u0026#39;dbname\u0026#39; rpname = \u0026#39;rpname\u0026#39; # https://zeppelin.apache.org/docs/0.10.1/interpreter/influxdb.html bucket = f\u0026#39;{dbname}/{rpname}\u0026#39; # InfluxDB 1.8 では dbname/rpname がバケット名として使われる token = f\u0026#39;{username}:{password}\u0026#39; # InfluxDB 1.8 では username:password がトークンとして使われる org = \u0026#34;-\u0026#34; # InfluxDB 1.8 ではよくわからん data = [] data.append({ \u0026#39;fields\u0026#39;: { \u0026#39;hoge\u0026#39;: 1 }, \u0026#39;measurement\u0026#39;: \u0026#39;measurement\u0026#39;, \u0026#39;tags\u0026#39;: { \u0026#39;hostname\u0026#39;: \u0026#39;hostname\u0026#39;, } }) points = [ Point.from_dict(i, WritePrecision.MS) for i in data ] async with InfluxDBClientAsync(url=url, token=token, org=org) as client: write_api = client.write_api() successfully = await write_api.write(bucket=bucket, record=points) print(successfully) データの読み込み (query_api) import asyncio from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync if __name__ == \u0026#39;__main__\u0026#39;: async def main(): url = \u0026#39;http://\u0026lt;url\u0026gt;:8086\u0026#39; username = \u0026#39;username\u0026#39; password = \u0026#39;password\u0026#39; dbname = \u0026#39;dbname\u0026#39; rpname = \u0026#39;rpname\u0026#39; # https://zeppelin.apache.org/docs/0.10.1/interpreter/influxdb.html bucket = f\u0026#39;{dbname}/{rpname}\u0026#39; # InfluxDB 1.8 では dbname/rpname がバケット名として使われる token = f\u0026#39;{username}:{password}\u0026#39; # InfluxDB 1.8 では username:password がトークンとして使われる org = \u0026#34;-\u0026#34; # InfluxDB 1.8 ではよくわからん query = f\u0026#34;\u0026#34;\u0026#34; from(bucket: \u0026#34;{bucket}\u0026#34;) |\u0026gt; range(start: -10m) |\u0026gt; filter(fn:(r) =\u0026gt; r._measurement == \u0026#34;measurement\u0026#34;) |\u0026gt; filter(fn: (r) =\u0026gt; r[\u0026#34;_field\u0026#34;] == \u0026#34;field\u0026#34;) |\u0026gt; aggregateWindow(every: 1m, fn: mean, createEmpty: false) \u0026#34;\u0026#34;\u0026#34; async with InfluxDBClientAsync(url=url, token=token, org=org) as client: tables = await client.query_api().query(query=query) for table in tables: print(table) for record in table.records: print (record.values) asyncio.run(main()) Flux については以下のページが参考になりました。\nhttps://qiita.com/riverplus/items/20456e4188a4c0f5f62d 参考 URL https://powersj.io/posts/influxdb-docker-dev/ https://github.com/influxdata/influxdb-client-python#async-write-api https://influxdb-client.readthedocs.io/en/stable/api.html#queryapi ","date":"2022-12-21T00:00:00+09:00","permalink":"/posts/2022/12/21/influxdb-client/","title":"[Python] InfluxDB 1.8 で influxdb_client を使う"},{"content":"Grafana 自体は Rocket.Chat に対応していませんが、Webhook を使うことで Rocket.Chat に通知できます。\nアラートの基本的な設定方法については以下を参照してください。\nGrafana でアラートを設定する ここでは Rocket.Chat への通知の設定のみ記載します。\nRocket.Chat 側の設定 管理画面のメニューから サービス連携 を選択し、Incoming で新規作成を行います。\nスクリプトを有効にし、以下のスクリプトを記入します。\nclass Script { process_incoming_request({ request }) { let color = \u0026#34;#00FF00\u0026#34;; // green switch(request.content.state) { case \u0026#39;ok\u0026#39;: color = \u0026#39;#00FF00\u0026#39;; break; case \u0026#39;paused\u0026#39;: color = \u0026#39;#666666\u0026#39;; break; case \u0026#39;alerting\u0026#39;: color = \u0026#39;#FF0000\u0026#39;; break; case \u0026#39;pending\u0026#39;: color = \u0026#39;#CCCCCC\u0026#39;; break; case \u0026#39;no_data\u0026#39;: color = \u0026#39;#333333\u0026#39;; break; default: color = \u0026#39;#666666\u0026#39;; } return { content:{ text: request.content.title, \u0026#34;attachments\u0026#34;: [{ \u0026#34;color\u0026#34;: color, \u0026#34;title\u0026#34;: request.content.title, \u0026#34;title_link\u0026#34;: request.content.ruleUrl, \u0026#34;text\u0026#34;: request.content.message, \u0026#34;image_url\u0026#34;: request.content.imageUrl, }] } }; } } その他、投稿先チャンネルや投稿ユーザーなどの基本的な設定を行います。\nGrafana の設定で Webhook URL が必要になるのでコピーしておきます。\nGrafana 側の設定 Grafana のサイドメニューから Alert rules を選択し、Contact Points で以下の設定を行います。\nNew contact point をクリック Name にはわかりやすい名前を適当に入力する Contact point type で Webhook を選択する Webhook URL に上記で取得した URL を入力する Rocket.Chat に関する設定は以上で完了です。\n参考 URL https://gist.github.com/ATofighi/79895715ae0a8f5bdeff058a64012275 ","date":"2022-12-13T00:00:00+09:00","permalink":"/posts/2022/12/13/grafana-rocketchat/","title":"Grafana から Rocket.Chat へ通知を行う"},{"content":"Grafana のアラートで Slack のチャンネルへ通知を行う方法です。\nアラートの基本的な設定方法については以下を参照してください。\nGrafana でアラートを設定する ここでは Slack への通知の設定のみ記載します。\nWebhook URL の取得 まず、通知したい Slack のチャンネルの Webhook URL を取得します。 Webhook URL は、以下からワークスペースとチャンネルを指定して取得できます。\nhttps://slack.com/services/new/incoming-webhook Contact Point の設定 Grafana のサイドメニューから Alert rules を選択し、Contact Points で以下の設定を行います。\nNew contact point をクリック Name にはわかりやすい名前を適当に入力する Contact point type で Slack を選択する Webhook URL に上記で取得した URL を入力する Slack に関する設定は以上です。\n参考 URL https://fclout.hateblo.jp/entry/2020/06/20/Grafana%E3%81%8B%E3%82%89Slack%E3%81%B8%E9%80%9A%E7%9F%A5%E3%81%99%E3%82%8B ","date":"2022-12-07T00:00:00+09:00","permalink":"/posts/2022/12/07/grafana-slack/","title":"Grafana から Slack へ通知を行う"},{"content":"Ubuntu 18.04 が emergency mode で起動した時に、キーボードの配列が日本語配列になっていなくて困ったのでメモです。\n以下を実行すれば日本語配列のキーボードが使えるようになりました。\nloadkeys jp 尚、上記のコマンドを入力するために、私の環境では以下のようにキーを押す必要がありました。\n入力したい文字 実際に押したキー l k o o a 左下の ctrl d s k j e e y y s a スペース . j h p p これが何の配列になっていたのかはわかりませんでした。\n参考 URL https://kledgeb.blogspot.com/2016/05/ubuntu-1604-60.html ","date":"2022-12-02T00:00:00+09:00","permalink":"/posts/2022/12/02/ubuntu/","title":"Ubuntu の Emergency Mode で日本語配列のキーボードを使う"}]