管理ツールによる一括セットアップ
管理ツールを使って組織内の開発者端末に一括でセットアップするために利用できる、配信用スクリプトを提供しています。ご利用の管理ツールに応じて、カスタマイズしてご利用ください。
この機能を利用するには、Guard を有効化した基本サブスクリプションが必要です。詳しくは料金と請求を参照してください。
概要
一括セットアップは、管理者が管理ツール(Jamf、Intune、Ansible など)を使って、組織内の開発者端末に Takumi Guard のレジストリプロキシ設定を一括で配信する仕組みです。開発者自身がコマンドを実行したり、設定ファイルを編集する必要はありません。
以下の図は、一括セットアップの全体構成を示しています。
管理者がコンソールで Bot と管理用 API キーを作成し、セットアップスクリプトとともに管理ツール経由で各端末に配信します。各端末ではスクリプトが自動的に Guard API にトークン発行をリクエストし、npm・pip・uv・Poetry・Bundler・Go modules の設定ファイルを更新します。以降、各端末からのパッケージインストールは Guard レジストリプロキシを経由するようになります。
セットアップスクリプトによって発行されたトークンは、コンソールの Guard > トークン で一覧・管理できます。

セットアップは大きく 2 つのフェーズに分かれます。
- 準備:Takumi / Shisho Cloud コンソールで Bot を作成し、セットアップスクリプトをダウンロードします
- 配信:管理ツール用のラッパースクリプトを作成し、セットアップスクリプトと合わせて対象端末に配信します
前提条件
配信の前に、Takumi / Shisho Cloud コンソールで以下の設定を完了してください。
-
Bot を作成する:設定 > Bot ページの「ボットの追加」ボタンから作成してください

-
ロールを付与する:Bot に「Takumi Guard トークン発行者」ロールを付与してください

-
API キーを取得する:作成した Bot の詳細ページで「API キーの作成」ボタンから静的 API キーを作成し、安全に保管してください

Bot の作成と API キーの発行手順の詳細はこちらも参照してください。
セットアップスクリプト
トークンの発行とパッケージマネージャーの設定を行うセットアップスクリプトを提供しています。お使いのプラットフォームに合わせてダウンロードしてください。
- macOS / Linux: https://shisho.dev/releases/takumi-guard-setup-0.10.3.sh
- Windows: https://shisho.dev/releases/takumi-guard-setup-0.10.3.ps1
対象端末には curl がインストールされている必要があります(macOS および Linux の場合)。
使い方
セットアップスクリプトは、実行したユーザーの設定ファイルのみを変更します。組織内の全ユーザーに一括展開する場合は、後述の配信例のラッパースクリプトを使用してください。
セットアップスクリプトを以下のように実行してください。API キーは環境変数 TG_BOT_API_KEY で渡してください。
TG_BOT_API_KEY="shisho_apikey_..." ./setup.sh <BOT_ID> <USER_IDENTIFIER>
各パラメータの意味は以下の通りです。
| パラメータ | 説明 |
|---|---|
TG_BOT_API_KEY | (環境変数)Bot の API キー |
BOT_ID | Takumi / Shisho Cloud コンソールの Bot ID |
USER_IDENTIFIER | デバイスやユーザーを識別するユニークな値 |
TG_DIRECT_WRITE | (任意の環境変数)パッケージマネージャーの事前検証をせず、設定ファイルへ直接書き込みます。詳しくはこちらを参照してください |
対象を限定したい場合は、第 3 引数でスコープを指定してください。
TG_BOT_API_KEY="..." ./setup.sh BOT_ID USER_IDENTIFIER npm,rubygems
| スコープ | 設定されるパッケージマネージャー |
|---|---|
npm | npm, pnpm, yarn(v2+), bun |
pypi | pip, uv, poetry |
rubygems | Bundler |
golang | Go modules |
packagist | Composer |
Poetry は、スクリプトによるユーザー単位の設定だけでは保護が有効になりません。別途、リポジトリごとの設定が必要です。詳しくは、Python パッケージマネージャー Poetry を利用する場合を参照してください。
参考:USER_IDENTIFIER の決め方
USER_IDENTIFIER は、デバイスやユーザーを識別する文字列です。組織内で一貫した命名規則を決めておくことを推奨します。
文字種の制約は以下 の通りです。
- 使用可能な文字:
a-z,A-Z,0-9,-,_,.,@,+ - 文字数: 4〜255 文字
以下の例を参考に、組織に合った識別子を選んでください。
| 例 | 値 | 説明 |
|---|---|---|
| デバイスのシリアルナンバー + OS ユーザー名 | C02X1234_jdoe | デバイス BIOS のハードウェアシリアルナンバーを使用 |
| 資産管理 ID + 社員 ID | ASSET0042_EMP12345 | 組織で管理している ID を使用 |
| MDM のデバイス ID + OS ユーザー名 | a401c7d0_jdoe | MDM ツールが割り当てるデバイス ID を使用 |
選択した識別子が組織内でユニークであることを確認してください。一部の識別子(シリアルナンバーなど)は、自作 PC や仮想マシンなどの環境では空になったり、デバイス間でユニークでない場合があります。各デバイスとユーザーを確実に区別できる値を使用してください。
参考:詳細な挙動
初回実行時
セットアップスクリプトがトークンを発行し、既存の設定ファイルのタイムスタンプ付きバックアップ(例:~/.npmrc-backup-20260408-162351)を作成してから、Guard の設定を追記します。既存の Guard 以外の設定は変更されません。
トークンが上書きされるケース
セットアップスクリプトは、Guard が管理対象とするすべての設定ファイルを確認し、既存のトークンを検出した場合はそのトークンを検証します。また、設定ファイルごとに異なる複数のトークンが存在する場合は、それらすべてのトークンを検証します。
検出されたすべてのトークンが、以下のいずれかの条件に該当する場合、新しい組織ユーザートークンを発行し、既存のトークンをその新しいトークンで上書きします。一方で、有効かつ指定された組織に紐づく組織ユーザートークンが 1 つでも存在する場合は、そのトークンを再利用します。
- メール認証トークンである場合
- 存在しないトークンである場合
- 無効化(失効)されたトークンである場合
- 別の組織で発行されたトークンである場合
上書き前には、各設定ファイルのタイムスタンプ付きバックアップが作成されます。
再実行時
有効な組織ユーザートークンを検出して再利用するため、上記のケースを除き、新しいトークンは発行されません。また、既に Guard が設定済みのツールはスキップされます(変更もバックアップも行われません)。スコープの増分追加にも対応しています。たとえば、先に npm スコープで実行し、後から pypi スコープを追加できます。
パッケージマネージャーの事前検証
セットアップスクリプトは、Guard の設定を行う前に、対象となるすべてのパッケージマネージャーを一つずつ確認します。以下のいずれかに該当する場合、そのパッケージマネージャーに対して Guard の設定を行います。
- パッケージマネージャーの設定ファイルが存在する
- パッケージマネージャーの CLI が利用可能
また、設定対象となるパッケージマネージャーが一つでも見つかった場合にのみ、必要に応じて組織ユーザートークンを発行します。一方で、設定対象が一つも見つからない場合は、トークンの発行や設定の変更を行わずにセットアップを終了します。
これにより、Guard の保護対象となるパッケージマネージャーを利用していない端末に対するトークン発行を防ぎ、不要な課金の発生を抑制します。
事前検証を行わずに Guard を設定する方法も用意しています。詳しくは「事前検証をせずに設定する」を参照してください。
手動ロールバック
セットアップスクリプトは、設定ファイルを変更する前にタイムスタンプ付きの永続バックアップを作成します(例:~/.npmrc-backup-20260408-162351)。Guard の設定を元に戻したい場合は、このバックアップファイルを元のファイル名にコピーしてください(例:cp ~/.npmrc-backup-20260408-162351 ~/.npmrc)。
端末の入れ替えや従業員の退職などで不要になったトークンは、Takumi / Shisho Cloud コンソー ルから失効させてください。詳しくは組織ユーザートークンの失効を参照してください。
パッケージマネージャーに別のレジストリ(プライベート npm レジストリなど)が既に設定されている場合、セットアップスクリプトは既存のレジストリ設定を Guard のレジストリで上書きします。上書き前にタイムスタンプ付きのバックアップファイルが作成されますが、念のため、配信前に対象端末で使用中のレジストリ設定を確認してください。
配信例
セットアップスクリプトの配信方法は、利用する管理ツールや OS によって異なります。ここでは、代表的な OS やトークン発行方針に応じた配信例を紹介します。なお、以下のラッパースクリプトはあくまでサンプルです。利用する環境や運用に合わせて適宜修正してください。
- 端末あたり1トークン発行: 端末ごとにトークンを発行します。端末上のいずれかのアカウントに有効な トークンが存在しないかを確認したうえで、存在する場合は再利用し、存在しない場合だけ新しくトークンを 1 つ発行したうえで、端末上の各開発者アカウントに対して同じトークンで設定します。
- 端末のユーザー毎に個別トークン発行: (端末, ユーザー)の組ごとにトークンを発行します。1 台の端末に複数の開発者アカウントが存在する環境(共用ワークステーション、MDM が作成する管理者アカウントと利用者アカウントが併存する端末、IdP が発行するアカウントとビルトインローカルアカウントが併存する端末など)では、同じ端末でアカウント数分のライセンスを消費することになります。
いずれのラッパースクリプトも、有効なトークンを検出した場合は再利用するため、定期実行はどちらのモデルでも安全です。
実際の環境では、管理用のサービスアカウントや、Takumi Guard の設定対象としたくないユーザーを除外したい場合があります。そのような場合は、ラッパースクリプト側で対象ユーザーを絞り込むことで対応できます。
macOS / Linux の例:
# 特定のユーザー名をスキップ
case "$USER_NAME" in
corp-admin|service-account|Guest) continue ;;
esac
Windows の例:
# 特定のユーザー名をスキップ
$Skip = @('corp-admin', 'Guest', 'defaultuser0')
if ($Skip -contains $UserName) { continue }
同様の方法で、対象ディレクトリや所属グループによる絞り込みも実装できます。たとえば、macOS / Linux では /Users/dev-* や /home/dev-* 配下のユーザーのみを対象にしたり、id -Gn "$USER_NAME" | grep -q developers を利用して特定のグループに所属するユーザーのみを対象にしたりできます。Windows でも、Get-CimInstance Win32_GroupUser などを利用して同様の制御が可能です。
macOS
管理ツールによるスクリプト実行は通常 root 権限で行われるため、以下のラッパースクリプトがユー ザーごとに切り替えながら、各開発者の環境へセットアップを行います。配信方法については、Jamf Pro のスクリプト実行機能など、利用する管理ツールの手順に従ってください。
- 端末あたり1トークン発行
- 端末のユーザー毎に個別トークン発行
#!/bin/bash
# Admin deployment wrapper (one token per device)
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
BOT_ID="BTXXXXXXXXXXXXXXXXXXXXXXXXXX"
export TG_BOT_API_KEY="shisho_apikey_XXXXX"
# TODO: Update VERSION when a newer setup script becomes available
VERSION="0.10.3"
# Set to true to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check).
TG_DIRECT_WRITE=false
# Download the setup script
SCRIPT_PATH=$(mktemp /tmp/takumi-guard-setup.XXXXXX)
curl -sL -o "$SCRIPT_PATH" "https://shisho.dev/releases/takumi-guard-setup-${VERSION}.sh"
chmod 755 "$SCRIPT_PATH"
trap 'rm -f "$SCRIPT_PATH"' EXIT
# Device-level USER_IDENTIFIER: serial number only -- the token is shared
# across every user on this device, so the identifier does not embed a user.
# Fall back to the hostname if empty or a placeholder, and strip disallowed characters.
SERIAL=$(ioreg -l | grep IOPlatformSerialNumber | awk -F'"' '{print $4}')
SERIAL=$(printf '%s' "$SERIAL" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
case "$SERIAL" in ''|None|0|'System Serial Number'|'To Be Filled By O.E.M.'|'Default string'|'Default System Serial Number'|'Not Specified'|'Not Applicable'|'Not Available') SERIAL=$(hostname) ;; esac
SERIAL=$(printf '%s' "$SERIAL" | LC_ALL=C tr -cs '0-9A-Za-z._@+-' '-' | sed 's/^-*//; s/-*$//')
[ "${#SERIAL}" -ge 4 ] || SERIAL="unknown-device"
USER_IDENTIFIER="$SERIAL"
# Phase 1 -- discover an existing active token on this device.
DEVICE_TOKEN=""
for USER_HOME in /Users/*; do
USER_NAME=$(basename "$USER_HOME")
[ "$USER_NAME" = "Shared" ] && continue
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
id "$USER_NAME" >/dev/null 2>&1 || continue
CANDIDATES=$(sudo -u "$USER_NAME" -H "$SCRIPT_PATH" discover 2>/dev/null) || continue
while IFS= read -r CAND; do
[ -n "$CAND" ] || continue
STATUS=$(HOME="$USER_HOME" USER_HOME="$USER_HOME" TG_BOT_ID="$BOT_ID" "$SCRIPT_PATH" verify "$CAND" 2>/dev/null)
case "$STATUS" in
active)
DEVICE_TOKEN="$CAND"
break 2
;;
unknown)
# Network down / API unreachable: abort instead of burning a
# fresh license under uncertainty.
echo "[Error] Could not verify token status against the Shisho Cloud API. Re-run after the API is reachable." >&2
exit 1
;;
esac
done <<EOF
$CANDIDATES
EOF
done
# Phase 2 -- if no active token exists yet, mint exactly one for the device.
if [ -z "$DEVICE_TOKEN" ]; then
# If no profile on this device has anything to configure, skip the
# mint -- otherwise every no-op deploy would still consume a license.
# precheck exit codes: 0 = configurable and writable (mint-able); 1 = nothing
# configurable here (benign); 3 = configurable but the config file is NOT
# writable. Mint ONLY when a profile returns 0. If no profile is mint-able but
# some returned 3, report which and exit non-zero so the config permissions
# are fixed and the run repeated (rather than a silent skip).
HAS_ANY_TARGET=false
UNWRITABLE_REPORT=""
for USER_HOME in /Users/*; do
USER_NAME=$(basename "$USER_HOME")
[ "$USER_NAME" = "Shared" ] && continue
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
id "$USER_NAME" >/dev/null 2>&1 || continue
PC_OUT=$(sudo -u "$USER_NAME" -H TG_DIRECT_WRITE="${TG_DIRECT_WRITE:-}" "$SCRIPT_PATH" precheck 2>&1) && PC_RC=0 || PC_RC=$?
if [ "$PC_RC" -eq 0 ]; then
HAS_ANY_TARGET=true
break
elif [ "$PC_RC" -eq 3 ]; then
UNWRITABLE_REPORT="${UNWRITABLE_REPORT} ${USER_NAME}: ${PC_OUT}
"
fi
done
if [ "$HAS_ANY_TARGET" = false ]; then
if [ -n "$UNWRITABLE_REPORT" ]; then
echo "[Failed] Config files are not writable; fix permissions and re-run:" >&2
printf '%s' "$UNWRITABLE_REPORT" >&2
exit 1
fi
echo "[Done] No configurable tools found on this device. Skipping token mint."
exit 0
fi
DEVICE_TOKEN=$(HOME=/var/root USER_HOME=/var/root TG_BOT_ID="$BOT_ID" "$SCRIPT_PATH" issue "$USER_IDENTIFIER")
if [ -z "$DEVICE_TOKEN" ]; then
echo "[Error] Failed to obtain device token" >&2
exit 1
fi
fi
# Phase 3 -- install the same token for every developer account. Each install
# is its own process; check its exit code, report a per-account verdict, and
# exit non-zero at the end if any account failed (e.g. config not writable).
ANY_FAILED=false
for USER_HOME in /Users/*; do
USER_NAME=$(basename "$USER_HOME")
[ "$USER_NAME" = "Shared" ] && continue
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
id "$USER_NAME" >/dev/null 2>&1 || continue
if sudo -u "$USER_NAME" -H zsh -lic "
TG_DIRECT_WRITE='${TG_DIRECT_WRITE:-}' $SCRIPT_PATH install '$DEVICE_TOKEN'
"; then
echo "[Done] $USER_NAME configured"
else
echo "[Failed] $USER_NAME was not configured -- fix the reported permissions and re-run" >&2
ANY_FAILED=true
fi
done
# Clean up credentials
unset TG_BOT_API_KEY
[ "$ANY_FAILED" = true ] && exit 1
exit 0
#!/bin/bash
# Admin deployment wrapper
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
BOT_ID="BTXXXXXXXXXXXXXXXXXXXXXXXXXX"
export TG_BOT_API_KEY="shisho_apikey_XXXXX"
# TODO: Update VERSION when a newer setup script becomes available
VERSION="0.10.3"
# Set to true to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check).
TG_DIRECT_WRITE=false
# Download the setup script
SCRIPT_PATH=$(mktemp /tmp/takumi-guard-setup.XXXXXX)
curl -sL -o "$SCRIPT_PATH" https://shisho.dev/releases/takumi-guard-setup-${VERSION}.sh
chmod 755 "$SCRIPT_PATH"
trap 'rm -f "$SCRIPT_PATH"' EXIT
# Get the device serial number for USER_IDENTIFIER generation, falling back to
# the hostname and stripping characters the identifier does not allow.
SERIAL=$(ioreg -l | grep IOPlatformSerialNumber | awk -F'"' '{print $4}')
SERIAL=$(printf '%s' "$SERIAL" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
case "$SERIAL" in ''|None|0|'System Serial Number'|'To Be Filled By O.E.M.'|'Default string'|'Default System Serial Number'|'Not Specified'|'Not Applicable'|'Not Available') SERIAL=$(hostname) ;; esac
SERIAL=$(printf '%s' "$SERIAL" | LC_ALL=C tr -cs '0-9A-Za-z._@+-' '-' | sed 's/^-*//; s/-*$//')
[ "${#SERIAL}" -ge 4 ] || SERIAL="unknown-device"
# Run the setup script for each user on this machine
for USER_HOME in /Users/*; do
USER_NAME=$(basename "$USER_HOME")
# Skip system directories and non-existent paths
[ "$USER_NAME" = "Shared" ] && continue
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
# Skip if the user account does not exist
id "$USER_NAME" >/dev/null 2>&1 || continue
# Build a unique identifier for this device + user combination
SAFE_USER_NAME=$(printf '%s' "$USER_NAME" | LC_ALL=C tr -cs '0-9A-Za-z._@+-' '-' | sed 's/^-*//; s/-*$//')
[ -n "$SAFE_USER_NAME" ] || SAFE_USER_NAME="uid-$(id -u "$USER_NAME" 2>/dev/null)"
USER_IDENTIFIER="${SERIAL}_${SAFE_USER_NAME}"
# Run the setup script as this user.
# Use zsh -lic (login + interactive) so that ~/.zshrc is loaded and
# package managers installed outside the default PATH are found.
# setup.sh exits non-zero -- and never mints -- when a config file is not
# writable. Check the exit code, report a per-user verdict, and exit non-zero
# at the end if any user failed so the permissions are fixed and re-run.
if sudo -u "$USER_NAME" -H zsh -lic '
TG_BOT_API_KEY="'"$TG_BOT_API_KEY"'" TG_DIRECT_WRITE="'"${TG_DIRECT_WRITE:-}"'" \
"'"$SCRIPT_PATH"'" "'"$BOT_ID"'" "'"$USER_IDENTIFIER"'"
'; then
echo "[Done] $USER_NAME (USER_IDENTIFIER: $USER_IDENTIFIER)"
else
echo "[Failed] $USER_NAME was not configured -- fix the reported permissions and re-run" >&2
ANY_FAILED=true
fi
done
# Clean up credentials
unset TG_BOT_API_KEY
[ "${ANY_FAILED:-false}" = true ] && exit 1
exit 0
Linux
管理ツールによるスクリプト実行は通常 root 権限で行われるため、以下のラッパースクリプトがユーザーごとに切り替えながら、各開発者の環境へセットアップを行います。
- 端末あたり1トークン発行
- 端末のユーザー毎に個別トークン発行
#!/bin/bash
# Admin deployment wrapper (one token per device)
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
BOT_ID="BTXXXXXXXXXXXXXXXXXXXXXXXXXX"
export TG_BOT_API_KEY="shisho_apikey_XXXXX"
# TODO: Update VERSION when a newer setup script becomes available
VERSION="0.10.3"
# Set to true to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check).
TG_DIRECT_WRITE=false
# Download the setup script
SCRIPT_PATH=$(mktemp /tmp/takumi-guard-setup.XXXXXX)
curl -sL -o "$SCRIPT_PATH" "https://shisho.dev/releases/takumi-guard-setup-${VERSION}.sh"
chmod 755 "$SCRIPT_PATH"
trap 'rm -f "$SCRIPT_PATH"' EXIT
# Device-level USER_IDENTIFIER: serial number only, falling back to the hostname
# and stripping characters the identifier does not allow.
SERIAL=$(dmidecode -s system-serial-number 2>/dev/null)
SERIAL=$(printf '%s' "$SERIAL" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
case "$SERIAL" in ''|None|0|'System Serial Number'|'To Be Filled By O.E.M.'|'Default string'|'Default System Serial Number'|'Not Specified'|'Not Applicable'|'Not Available') SERIAL=$(hostname) ;; esac
SERIAL=$(printf '%s' "$SERIAL" | LC_ALL=C tr -cs '0-9A-Za-z._@+-' '-' | sed 's/^-*//; s/-*$//')
[ "${#SERIAL}" -ge 4 ] || SERIAL="unknown-device"
USER_IDENTIFIER="$SERIAL"
# Phase 1 -- discover an existing active token on this device.
DEVICE_TOKEN=""
for USER_HOME in /home/*; do
USER_NAME=$(basename "$USER_HOME")
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
id "$USER_NAME" >/dev/null 2>&1 || continue
CANDIDATES=$(sudo -u "$USER_NAME" -H "$SCRIPT_PATH" discover 2>/dev/null) || continue
while IFS= read -r CAND; do
[ -n "$CAND" ] || continue
STATUS=$(HOME="$USER_HOME" USER_HOME="$USER_HOME" TG_BOT_ID="$BOT_ID" "$SCRIPT_PATH" verify "$CAND" 2>/dev/null)
case "$STATUS" in
active)
DEVICE_TOKEN="$CAND"
break 2
;;
unknown)
echo "[Error] Could not verify token status against the Shisho Cloud API. Re-run after the API is reachable." >&2
exit 1
;;
esac
done <<EOF
$CANDIDATES
EOF
done
# Phase 2 -- if no active token exists yet, mint exactly one for the device.
if [ -z "$DEVICE_TOKEN" ]; then
# If no profile on this device has anything to configure, skip the
# mint -- otherwise every no-op deploy would still consume a license.
# precheck exit codes: 0 = configurable and writable (mint-able); 1 = nothing
# configurable here (benign); 3 = configurable but the config file is NOT
# writable. Mint ONLY when a profile returns 0. If no profile is mint-able but
# some returned 3, report which and exit non-zero so the config permissions
# are fixed and the run repeated (rather than a silent skip).
HAS_ANY_TARGET=false
UNWRITABLE_REPORT=""
for USER_HOME in /home/*; do
USER_NAME=$(basename "$USER_HOME")
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
id "$USER_NAME" >/dev/null 2>&1 || continue
PC_OUT=$(sudo -u "$USER_NAME" -H TG_DIRECT_WRITE="${TG_DIRECT_WRITE:-}" "$SCRIPT_PATH" precheck 2>&1) && PC_RC=0 || PC_RC=$?
if [ "$PC_RC" -eq 0 ]; then
HAS_ANY_TARGET=true
break
elif [ "$PC_RC" -eq 3 ]; then
UNWRITABLE_REPORT="${UNWRITABLE_REPORT} ${USER_NAME}: ${PC_OUT}
"
fi
done
if [ "$HAS_ANY_TARGET" = false ]; then
if [ -n "$UNWRITABLE_REPORT" ]; then
echo "[Failed] Config files are not writable; fix permissions and re-run:" >&2
printf '%s' "$UNWRITABLE_REPORT" >&2
exit 1
fi
echo "[Done] No configurable tools found on this device. Skipping token mint."
exit 0
fi
DEVICE_TOKEN=$(HOME=/root USER_HOME=/root TG_BOT_ID="$BOT_ID" "$SCRIPT_PATH" issue "$USER_IDENTIFIER")
if [ -z "$DEVICE_TOKEN" ]; then
echo "[Error] Failed to obtain device token" >&2
exit 1
fi
fi
# Phase 3 -- install the same token for every developer account. Each install
# is its own process; check its exit code, report a per-account verdict, and
# exit non-zero at the end if any account failed (e.g. config not writable).
ANY_FAILED=false
for USER_HOME in /home/*; do
USER_NAME=$(basename "$USER_HOME")
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
id "$USER_NAME" >/dev/null 2>&1 || continue
USER_SHELL=$(getent passwd "$USER_NAME" | cut -d: -f7)
if sudo -u "$USER_NAME" -H "$USER_SHELL" -lic "
[ -f \"\$HOME/.bashrc\" ] && . \"\$HOME/.bashrc\" 2>/dev/null
TG_DIRECT_WRITE='${TG_DIRECT_WRITE:-}' $SCRIPT_PATH install '$DEVICE_TOKEN'
"; then
echo "[Done] $USER_NAME configured"
else
echo "[Failed] $USER_NAME was not configured -- fix the reported permissions and re-run" >&2
ANY_FAILED=true
fi
done
# Clean up credentials
unset TG_BOT_API_KEY
[ "$ANY_FAILED" = true ] && exit 1
exit 0
#!/bin/bash
# Admin deployment wrapper
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
BOT_ID="BTXXXXXXXXXXXXXXXXXXXXXXXXXX"
export TG_BOT_API_KEY="shisho_apikey_XXXXX"
# TODO: Update VERSION when a newer setup script becomes available
VERSION="0.10.3"
# Set to true to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check).
TG_DIRECT_WRITE=false
# Download the setup script
SCRIPT_PATH=$(mktemp /tmp/takumi-guard-setup.XXXXXX)
curl -sL -o "$SCRIPT_PATH" https://shisho.dev/releases/takumi-guard-setup-${VERSION}.sh
chmod 755 "$SCRIPT_PATH"
trap 'rm -f "$SCRIPT_PATH"' EXIT
# Get the device serial number for USER_IDENTIFIER generation, falling back to
# the hostname and stripping characters the identifier does not allow.
SERIAL=$(dmidecode -s system-serial-number 2>/dev/null)
SERIAL=$(printf '%s' "$SERIAL" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
case "$SERIAL" in ''|None|0|'System Serial Number'|'To Be Filled By O.E.M.'|'Default string'|'Default System Serial Number'|'Not Specified'|'Not Applicable'|'Not Available') SERIAL=$(hostname) ;; esac
SERIAL=$(printf '%s' "$SERIAL" | LC_ALL=C tr -cs '0-9A-Za-z._@+-' '-' | sed 's/^-*//; s/-*$//')
[ "${#SERIAL}" -ge 4 ] || SERIAL="unknown-device"
# Run the setup script for each user on this machine
for USER_HOME in /home/*; do
USER_NAME=$(basename "$USER_HOME")
# Skip the root account and non-existent paths
[ "$USER_NAME" = "root" ] && continue
[ ! -d "$USER_HOME" ] && continue
# Skip if the user account does not exist
id "$USER_NAME" >/dev/null 2>&1 || continue
# Build a unique identifier for this device + user combination
SAFE_USER_NAME=$(printf '%s' "$USER_NAME" | LC_ALL=C tr -cs '0-9A-Za-z._@+-' '-' | sed 's/^-*//; s/-*$//')
[ -n "$SAFE_USER_NAME" ] || SAFE_USER_NAME="uid-$(id -u "$USER_NAME" 2>/dev/null)"
USER_IDENTIFIER="${SERIAL}_${SAFE_USER_NAME}"
# Run the setup script as this user.
# Detect the user's login shell and run it in login + interactive mode
# so that ~/.bashrc (or ~/.zshrc) is loaded and package managers
# installed outside the default PATH are found.
# Explicitly source .bashrc as a fallback for environments where
# .bash_profile does not source it (e.g. CentOS).
USER_SHELL=$(getent passwd "$USER_NAME" | cut -d: -f7)
# setup.sh exits non-zero -- and never mints -- when a config file is not
# writable. Check the exit code, report a per-user verdict, and exit non-zero
# at the end if any user failed so the permissions are fixed and re-run.
if sudo -u "$USER_NAME" -H "$USER_SHELL" -lic '
[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc" 2>/dev/null
TG_BOT_API_KEY="'"$TG_BOT_API_KEY"'" TG_DIRECT_WRITE="'"${TG_DIRECT_WRITE:-}"'" \
"'"$SCRIPT_PATH"'" "'"$BOT_ID"'" "'"$USER_IDENTIFIER"'"
'; then
echo "[Done] $USER_NAME (USER_IDENTIFIER: $USER_IDENTIFIER)"
else
echo "[Failed] $USER_NAME was not configured -- fix the reported permissions and re-run" >&2
ANY_FAILED=true
fi
done
# Clean up credentials
unset TG_BOT_API_KEY
[ "${ANY_FAILED:-false}" = true ] && exit 1
exit 0
macOS / Linux で root ユーザーにも Guard を設定したい場合は、ラッパースクリプト内でユーザー切り替えをせずにセットアップスクリプトを一度実行してください。
Windows
Windows では、管理ツールがスクリプトを実行する方式に応じて、複数の配信パターンがあります。ここでは、ログオン中のユーザーのみに Guard を設定する方法と、端末上のすべてのユーザーに一括で設定する方法を紹介します。
- ログオン中のユーザーのみに設定する: 管理ツールがそのユーザーの資格情報でスクリプトを実行する形となります。Microsoft Intune の「ログオンした資格情報を使用してこのスクリプトを実行する」オプションや、グループポリシーのログオンスクリプトがこの形にあたります。そのユーザーの WSL(Windows Subsystem for Linux) ディストリビューションへの設定も可能です。
- 端末上のすべてのユーザーに一括で設定する: 管理ツールがスクリプトを
SYSTEM権限で実行する形となります。Microsoft Configuration Manager(旧 SCCM)、グループポリシーのスタートアップスクリ プト、PDQ Deploy など、多くの MDM ツールが既定でこの動作です。SYSTEM権限で動かす必要があるため、配信経路は基本的に管理ツール経由となります。
Windows の既定の実行ポリシーでは、署名されていない PowerShell スクリプト(.ps1)の実行が制限されています。そのため、本節で紹介しているラッパースクリプトは、ExecutionPolicy Bypass を指定して実行する必要があります。
Microsoft Intune、Microsoft Configuration Manager、グループポリシーのスタートアップスクリプトなどの主要な管理ツールでは、PowerShell スクリプトを自動的にこの設定で実行するため、追加の設定は不要です。
一方、PsExec などを利用して手動で実行する場合は、次のように起動してください。
powershell.exe -ExecutionPolicy Bypass -File <ラッパースクリプト>.ps1
ログオン中のユーザーのみに設定する
このラッパースクリプトはログオン中のユーザー自身のセッションで動作し、ユーザーごとの個別トークンを発行したうえで設定します。以下のスクリプトを保存し、管理ツールから各端末へ配信するか、共有ストレージなど別経路で配布のうえ、各端末での実行を依頼してください。
また、Windows ホストに加えて、そのユーザーに登録され ている WSL ディストリビューションにも同じトークンで Guard を設定します。ラッパースクリプトは開発用途の WSL を検出し、Docker Desktop などの内部用途のディストリビューションを除いて設定します。
なお、WSL への Guard 設定は、スクリプト冒頭の $ConfigureWsl で有効・無効を切り替えられます。既定値は $true で、$false に設定した場合は Windows ホストにのみ Guard を設定します。WSL への設定の詳細については、こちらも参照してください。
このラッパースクリプトは、Windows ホストおよび各 WSL 環境に対して、それぞれ独立して Guard を設定します。いずれかで設定に失敗しても処理は停止せず、残りの環境への設定を継続します。失敗した環境で行われた変更は自動的にロールバックされ、実行前の状態に戻ります。また、設定済みの他の環境には影響しません。
実行結果は環境ごとにログへ出力されます。正常に設定された環境は [Done] <env> configured、設定に失敗した環境は [Failed] <env> was not configured で始まる行で確認できます。
いずれかの環境で設定に失敗した場合、終了コードは非 0 となるため、MDM ツールなどから失敗を検知できま す。失敗した環境については、原因を解消したうえで再実行してください。
ラッパースクリプトの既定では、次のものを設定対象から除外しています。利用環境によっては調整してください。
- Docker Desktop、Rancher Desktop、Podman が内部で利用するディストリビューション
- 各ディストリビューション内の開発者ユーザー以外のアカウント(UID 1000〜59999 の範囲外にある root ユーザーやシステムアカウントなど)
# Admin deployment wrapper (logged-in user mode, with WSL coverage)
# Run as the logged-in user (user context). Requires PowerShell 5.1 or later.
# Relaunch in 64-bit PowerShell when started from a 32-bit host (e.g. Intune's
# default script host), where wsl.exe is not visible.
if (-not [Environment]::Is64BitProcess -and [Environment]::Is64BitOperatingSystem) {
& "$env:windir\Sysnative\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File $MyInvocation.MyCommand.Path
exit $LASTEXITCODE
}
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
$BotId = 'BTXXXXXXXXXXXXXXXXXXXXXXXXXX'
$env:TG_BOT_API_KEY = 'shisho_apikey_XXXXX'
# Set to 'true' to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check). The child powershell.exe
# inherits this value.
$env:TG_DIRECT_WRITE = 'false'
# Whether to also configure the user's WSL distributions ($false to skip WSL).
$ConfigureWsl = $true
# TODO: Update VERSION when a newer setup script becomes available
$Version = '0.10.3'
# Download both setup scripts: setup.ps1 for the Windows host, setup.sh to run
# inside each WSL distribution.
$ScriptPath = Join-Path ([System.IO.Path]::GetTempPath()) ("takumi-guard-setup-{0}.ps1" -f [guid]::NewGuid())
$ShPath = Join-Path ([System.IO.Path]::GetTempPath()) ("takumi-guard-setup-{0}.sh" -f [guid]::NewGuid())
$ListUsers = Join-Path ([System.IO.Path]::GetTempPath()) ("guard-listusers-{0}.sh" -f [guid]::NewGuid())
try {
Invoke-WebRequest -Uri "https://shisho.dev/releases/takumi-guard-setup-$Version.ps1" -OutFile $ScriptPath -UseBasicParsing
Invoke-WebRequest -Uri "https://shisho.dev/releases/takumi-guard-setup-$Version.sh" -OutFile $ShPath -UseBasicParsing
# Build a unique identifier for this device + user combination. Fall
# back to COMPUTERNAME when the BIOS serial is empty or a placeholder
# value (common on VMs).
$Serial = (Get-CimInstance -ClassName Win32_BIOS).SerialNumber
if (-not $Serial -or ($Serial.Trim() -in @('', 'None', '0', 'System Serial Number', 'To Be Filled By O.E.M.', 'Default string', 'Default System Serial Number', 'Not Specified', 'Not Applicable', 'Not Available'))) {
$Serial = $env:COMPUTERNAME
}
# Strip characters the identifier does not allow (spaces, etc.).
$Serial = ($Serial -replace '[^0-9A-Za-z._@+\-]+', '-').Trim('-')
if ($Serial.Length -lt 4) { $Serial = 'unknown-device' }
$SafeUserName = ($env:USERNAME -replace '[^0-9A-Za-z._@+\-]+', '-').Trim('-')
if (-not $SafeUserName) { $SafeUserName = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value }
$UserIdentifier = "${Serial}_${SafeUserName}"
# Forward TG_DIRECT_WRITE into WSL (wsl.exe does not pass Windows
# environment variables through without WSLENV).
if ($env:WSLENV) { $env:WSLENV = "${env:WSLENV}:TG_DIRECT_WRITE/u" } else { $env:WSLENV = 'TG_DIRECT_WRITE/u' }
# --- WSL helpers -------------------------------------------------------
# wsl.exe's own stdout is UTF-16LE, so strip null bytes before splitting.
# Distributions used internally by Docker Desktop, Rancher Desktop, and
# Podman are not development environments, so they are excluded.
function Get-WslDistros {
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) { return @() }
wsl.exe --status *> $null
if ($LASTEXITCODE -ne 0) { return @() } # e.g. SYSTEM context: WSL not visible
$raw = (wsl.exe -l -q | Out-String) -replace "`0", ""
@($raw -split "`r?`n" | ForEach-Object { $_.Trim() } |
Where-Object { $_ -and $_ -notmatch '^(docker-desktop|rancher-desktop|podman-)' })
}
# The user-list script is written to a file and invoked via its
# /mnt/<drive>/... path to avoid shell-quoting issues.
function ConvertTo-WslPath([string]$WinPath) {
'/mnt/' + $WinPath.Substring(0, 1).ToLower() + ($WinPath.Substring(2) -replace '\\', '/')
}
[IO.File]::WriteAllText($ListUsers, "awk -F: '`$3>=1000 && `$3<60000 {print `$1}' /etc/passwd`n", [Text.UTF8Encoding]::new($false))
$ShWsl = ConvertTo-WslPath $ShPath
$ListUsersWsl = ConvertTo-WslPath $ListUsers
# Real Linux users (UID 1000..59999) in a distro. A non-zero exit means
# the enumeration itself failed: warn and skip the distribution.
function Get-WslUsers([string]$Distro) {
$raw = wsl.exe -d $Distro -u root --cd / -- bash $ListUsersWsl 2>$null
if ($LASTEXITCODE -ne 0) {
# Write-Host keeps the warning out of the function's return value.
Write-Host "[Warn] WSL ${Distro}: could not enumerate Linux users (exit $LASTEXITCODE); skipping this distribution."
return $null
}
@($raw -split "`r?`n" | ForEach-Object { ($_ -replace "`0", "").Trim() } | Where-Object { $_ })
}
# Run a setup.sh subcommand inside a WSL (distro, user). `--cd "~"` keeps
# the Linux home as CWD so npm does not pick up Windows-side config.
function Invoke-WslSh([string]$Distro, [string]$User, [string[]]$ShArgs) {
wsl.exe -d $Distro -u $User --cd "~" -- bash $ShWsl @ShArgs
}
# Collect the WSL (distro, user) targets once. With $ConfigureWsl = $false
# the list stays empty and only the Windows host is configured.
$WslTargets = @()
if ($ConfigureWsl) {
foreach ($d in Get-WslDistros) {
$users = Get-WslUsers $d
if ($null -eq $users) { continue } # enumeration failed; already warned
foreach ($u in $users) {
$WslTargets += [pscustomobject]@{ Distro = $d; User = $u }
}
}
}
# --- Phase 1: discover an existing active token (Windows + WSL) ---------
$Token = $null
$candidates = @()
$candidates += @(& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath discover 2>$null)
if ($LASTEXITCODE -ne 0) {
Write-Output "[Warn] Windows host: discover failed (exit $LASTEXITCODE); an existing token here may be missed."
}
foreach ($t in $WslTargets) {
$candidates += @(Invoke-WslSh $t.Distro $t.User @('discover'))
if ($LASTEXITCODE -ne 0) {
Write-Output "[Warn] WSL $($t.Distro):$($t.User): discover failed (exit $LASTEXITCODE); an existing token here may be missed."
}
}
foreach ($c in ($candidates | Where-Object { $_ })) {
$env:TG_BOT_ID = $BotId
$status = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath verify $c 2>$null
Remove-Item Env:TG_BOT_ID -ErrorAction SilentlyContinue
if ($status -eq 'active') { $Token = $c; break }
if ($status -eq 'unknown') {
Write-Output "[Error] Could not verify token status against the Shisho Cloud API. Re-run after the API is reachable."
exit 1
}
}
# --- Phase 2: mint exactly one token if none is active (Windows + WSL) --
if (-not $Token) {
# precheck exit: 0 = mint-able; 1 = nothing configurable (benign);
# 3 = configurable but not writable. Mint only on 0.
# If nothing is mint-able but some env returned 3, report + exit non-zero.
$HasTarget = $false
$unwritable = @()
$pcOut = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath precheck 2>&1
$rc = $LASTEXITCODE
if ($rc -eq 0) { $HasTarget = $true }
elseif ($rc -eq 3) { $unwritable += "Windows host: $(($pcOut | Out-String).Trim())" }
if (-not $HasTarget) {
foreach ($t in $WslTargets) {
$wpcOut = Invoke-WslSh $t.Distro $t.User @('precheck') 2>&1
if ($LASTEXITCODE -eq 0) { $HasTarget = $true; break }
elseif ($LASTEXITCODE -eq 3) { $unwritable += "WSL $($t.Distro):$($t.User): $(($wpcOut | Out-String).Trim())" }
}
}
if (-not $HasTarget) {
if ($unwritable.Count -gt 0) {
Write-Output "[Failed] Config files are not writable; fix permissions and re-run:"
$unwritable | ForEach-Object { Write-Output " $_" }
exit 1
}
Write-Output "[Done] No configurable tools found on this device. Skipping token mint."
exit 0
}
$env:TG_BOT_ID = $BotId
$Token = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath issue $UserIdentifier
Remove-Item Env:TG_BOT_ID -ErrorAction SilentlyContinue
if (-not $Token) { throw "Failed to obtain device token" }
}
# --- Phase 3: install the same token (Windows + WSL) -------------------
# Each environment is configured by its own process; a failure rolls back
# only that environment. Keep configuring the rest, print a per-environment
# verdict, and exit non-zero at the end if anything failed.
$AnyFailed = $false
Write-Output "----- Windows host -----"
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath install $Token 2>&1 | ForEach-Object { "$_" }
if ($LASTEXITCODE -eq 0) { Write-Output "[Done] Windows host configured" }
else { Write-Output "[Failed] Windows host was not configured (setup exited $LASTEXITCODE)"; $AnyFailed = $true }
foreach ($t in $WslTargets) {
$label = "WSL $($t.Distro):$($t.User)"
Write-Output "----- $label -----"
Invoke-WslSh $t.Distro $t.User @('install', $Token) 2>&1 | ForEach-Object { "$_" }
if ($LASTEXITCODE -eq 0) { Write-Output "[Done] $label configured" }
else { Write-Output "[Failed] $label was not configured (setup exited $LASTEXITCODE)"; $AnyFailed = $true }
}
if ($AnyFailed) { exit 1 }
} finally {
Remove-Item Env:TG_BOT_API_KEY -ErrorAction SilentlyContinue
Remove-Item Env:TG_BOT_ID -ErrorAction SilentlyContinue
Remove-Item $ScriptPath, $ShPath, $ListUsers -Force -ErrorAction SilentlyContinue
}
端末上のすべてのユーザーに一括で設定する
このラッパースクリプトは SYSTEM 権限で実行されます。セットアップスクリプトをダウンロードしたうえで、Win32_UserProfile を利用して端末上の各ユーザープロファイルを順に巡回しながらセットアップを行います。
なお、WSL ディストリビューションは Windows の仕様上、SYSTEM 権限のプロセスから参照できないため、このラッパースクリプトでは WSL に Guard を設定できません。詳しくは、Windows Subsystem for Linux(WSL)への設定 を参照してください。
- 端末あたり1トークン発行
- 端末のユーザー毎に個別トークン発行
# Admin deployment wrapper (all-users, one token per device)
# Run as SYSTEM (or any administrator). Requires PowerShell 5.1 or later.
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
$BotId = 'BTXXXXXXXXXXXXXXXXXXXXXXXXXX'
$env:TG_BOT_API_KEY = 'shisho_apikey_XXXXX'
# Set to 'true' to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check). The child powershell.exe
# inherits this value.
$env:TG_DIRECT_WRITE = 'false'
# TODO: Update VERSION when a newer setup script becomes available
$Version = '0.10.3'
# Download the setup script
$ScriptPath = Join-Path ([System.IO.Path]::GetTempPath()) ("takumi-guard-setup-{0}.ps1" -f [guid]::NewGuid())
try {
Invoke-WebRequest -Uri "https://shisho.dev/releases/takumi-guard-setup-$Version.ps1" `
-OutFile $ScriptPath -UseBasicParsing
# Device-level USER_IDENTIFIER: serial number only. Fall back to
# COMPUTERNAME when the BIOS serial is empty or a placeholder value
# (common on VMs).
$Serial = (Get-CimInstance -ClassName Win32_BIOS).SerialNumber
if (-not $Serial -or ($Serial.Trim() -in @('', 'None', '0', 'System Serial Number', 'To Be Filled By O.E.M.', 'Default string', 'Default System Serial Number', 'Not Specified', 'Not Applicable', 'Not Available'))) {
$Serial = $env:COMPUTERNAME
}
# Strip characters the identifier does not allow (spaces, etc.).
$Serial = ($Serial -replace '[^0-9A-Za-z._@+\-]+', '-').Trim('-')
if ($Serial.Length -lt 4) { $Serial = 'unknown-device' }
$UserIdentifier = $Serial
# Win32_UserProfile excludes built-in service profiles (SYSTEM, LocalService,
# NetworkService) via `Special = false`. Profiles whose directories no longer
# exist (deleted users with leftover registry entries) are also filtered out.
$Profiles = Get-CimInstance -ClassName Win32_UserProfile -Filter "Special = false" |
Where-Object { $_.LocalPath -and (Test-Path $_.LocalPath) }
# Phase 1 -- discover an existing active token on this device.
$DeviceToken = $null
:OuterDiscover foreach ($profile in $Profiles) {
$env:USER_HOME = $profile.LocalPath
$candidates = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath discover 2>$null
Remove-Item Env:USER_HOME -ErrorAction SilentlyContinue
if (-not $candidates) { continue }
foreach ($c in @($candidates)) {
if (-not $c) { continue }
$env:TG_BOT_ID = $BotId
$status = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath verify $c 2>$null
Remove-Item Env:TG_BOT_ID -ErrorAction SilentlyContinue
switch ($status) {
'active' {
$DeviceToken = $c
break OuterDiscover
}
'unknown' {
Write-Output "[Error] Could not verify token status against the Shisho Cloud API. Re-run after the API is reachable."
exit 1
}
}
}
}
# Phase 2 -- if no active token exists yet, mint exactly one for the device.
if (-not $DeviceToken) {
# If no profile on this device has anything to configure, skip the
# mint -- otherwise every no-op deploy would still consume a license.
# precheck exit codes: 0 = configurable + writable (mint-able); 1 =
# nothing configurable (benign); 3 = configurable but the config file is
# NOT writable. Mint ONLY when a profile returns 0. If
# no profile is mint-able but some returned 3, report it and exit
# non-zero so the admin fixes permissions and re-runs.
$HasAnyTarget = $false
$unwritable = @()
foreach ($profile in $Profiles) {
$env:USER_HOME = $profile.LocalPath
$pcOut = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath precheck 2>&1
$rc = $LASTEXITCODE
Remove-Item Env:USER_HOME -ErrorAction SilentlyContinue
if ($rc -eq 0) { $HasAnyTarget = $true; break }
elseif ($rc -eq 3) { $unwritable += "$($profile.LocalPath): $(($pcOut | Out-String).Trim())" }
}
if (-not $HasAnyTarget) {
if ($unwritable.Count -gt 0) {
Write-Output "[Failed] Config files are not writable; fix permissions and re-run:"
$unwritable | ForEach-Object { Write-Output " $_" }
exit 1
}
Write-Output "[Done] No configurable tools found on this device. Skipping token mint."
exit 0
}
$env:TG_BOT_ID = $BotId
$DeviceToken = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath issue $UserIdentifier
Remove-Item Env:TG_BOT_ID -ErrorAction SilentlyContinue
if (-not $DeviceToken) {
throw "Failed to obtain device token"
}
}
# Phase 3 -- install the same token for every user profile. Each install is
# its own process; a failure rolls back only that profile. Check its exit
# code, report a per-profile verdict, and exit non-zero at the end if any
# profile failed (e.g. a config file was not writable).
$AnyFailed = $false
foreach ($profile in $Profiles) {
$UserHome = $profile.LocalPath
$UserName = Split-Path $UserHome -Leaf
$env:USER_HOME = $UserHome
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath install $DeviceToken 2>&1 |
ForEach-Object { "$_" }
$rc = $LASTEXITCODE
Remove-Item Env:USER_HOME -ErrorAction SilentlyContinue
if ($rc -eq 0) {
Write-Output "[Done] $UserName configured"
} else {
Write-Output "[Failed] $UserName was not configured -- fix the reported permissions and re-run"
$AnyFailed = $true
}
}
if ($AnyFailed) { exit 1 }
} finally {
Remove-Item Env:TG_BOT_API_KEY -ErrorAction SilentlyContinue
Remove-Item $ScriptPath -Force -ErrorAction SilentlyContinue
}
# Admin deployment wrapper (all-users mode)
# Run as SYSTEM (or any administrator). Requires PowerShell 5.1 or later.
# TODO: Replace with your Bot ID and API key from Shisho Cloud console
$BotId = 'BTXXXXXXXXXXXXXXXXXXXXXXXXXX'
$env:TG_BOT_API_KEY = 'shisho_apikey_XXXXX'
# Set to 'true' to pre-provision package managers that are not installed yet
# (writes config directly and skips the pre-check). The child powershell.exe
# inherits this value.
$env:TG_DIRECT_WRITE = 'false'
# TODO: Update VERSION when a newer setup script becomes available
$Version = '0.10.3'
# Download the setup script
$ScriptPath = Join-Path ([System.IO.Path]::GetTempPath()) ("takumi-guard-setup-{0}.ps1" -f [guid]::NewGuid())
try {
Invoke-WebRequest -Uri "https://shisho.dev/releases/takumi-guard-setup-$Version.ps1" `
-OutFile $ScriptPath -UseBasicParsing
# Get the device serial number for USER_IDENTIFIER generation. Fall
# back to COMPUTERNAME when the BIOS serial is empty or a placeholder
# value (common on VMs).
$Serial = (Get-CimInstance -ClassName Win32_BIOS).SerialNumber
if (-not $Serial -or ($Serial.Trim() -in @('', 'None', '0', 'System Serial Number', 'To Be Filled By O.E.M.', 'Default string', 'Default System Serial Number', 'Not Specified', 'Not Applicable', 'Not Available'))) {
$Serial = $env:COMPUTERNAME
}
# Strip characters the identifier does not allow (spaces, etc.).
$Serial = ($Serial -replace '[^0-9A-Za-z._@+\-]+', '-').Trim('-')
if ($Serial.Length -lt 4) { $Serial = 'unknown-device' }
# Run the setup script for each user profile on this machine.
# Win32_UserProfile excludes built-in service profiles (SYSTEM, LocalService,
# NetworkService) via `Special = false`. Profiles whose directories no longer
# exist (deleted users with leftover registry entries) are also filtered out.
# setup.ps1 exits non-zero -- and never mints -- when a config file is not
# writable. Check the exit code, report a per-user verdict, and exit non-zero
# at the end if any user failed so the permissions are fixed and re-run.
$AnyFailed = $false
foreach ($profile in (Get-CimInstance -ClassName Win32_UserProfile -Filter "Special = false" |
Where-Object { $_.LocalPath -and (Test-Path $_.LocalPath) })) {
$UserHome = $profile.LocalPath
$UserName = Split-Path $UserHome -Leaf
# Build a unique identifier for this device + user combination
$SafeUserName = ($UserName -replace '[^0-9A-Za-z._@+\-]+', '-').Trim('-')
if (-not $SafeUserName) { $SafeUserName = $profile.SID }
$UserIdentifier = "${Serial}_${SafeUserName}"
# Invoke setup.ps1 with USER_HOME pointing at this profile. The script
# resolves the profile owner from USER_HOME so that the resulting config
# files grant access to the target developer, not just SYSTEM.
$env:USER_HOME = $UserHome
& powershell.exe -ExecutionPolicy Bypass -File $ScriptPath $BotId $UserIdentifier
$rc = $LASTEXITCODE
Remove-Item Env:USER_HOME -ErrorAction SilentlyContinue
if ($rc -eq 0) {
Write-Output "[Done] $UserName (USER_IDENTIFIER: $UserIdentifier)"
} else {
Write-Output "[Failed] $UserName was not configured -- fix the reported permissions and re-run"
$AnyFailed = $true
}
}
if ($AnyFailed) { exit 1 }
} finally {
Remove-Item Env:TG_BOT_API_KEY -ErrorAction SilentlyContinue
Remove-Item $ScriptPath -Force -ErrorAction SilentlyContinue
}
Tips
一括セットアップをより柔軟に利用するための設定や、運用に役立つ補足情報を紹介します。
事前検証をせずに設定する
セットアップスクリプトは、Guard の設定を行う前に対象となるパッケージマネージャーを検出し、既定では設定対象が存在する場合にのみ Guard の設定を行います。
これは、Guard の保護対象となるパッケージマネージャーを利用していない端末に対して不要な組織ユーザートークンを発行しないためです。また、利用されていないパッケージマネージャーの設定ファイルを作成・変更しないことで、端末への変更を必要最小限に抑えることも目的としています。
一方で、この挙動には課題があります。セットアップ実行時点でパッケージマネージャーがインストールされていない端末では Guard の設定が行われないため、後から新たにパッケージマネージャーを導入した場合、そのままでは公開レジストリへ直接アクセスし、Guard を経由しない状態となります。
この課題に対応するため、TG_DIRECT_WRITE 環境変数を用意しています。
TG_DIRECT_WRITE=true を設定すると、セットアップスクリプトは事前検証を行わず、対象となるパッケージマネージャーの設定ファイルへ直接 Guard の設定を書き込みます。設定ファイルが存在しない場合は新たに作成します。
# 組織ユーザートークンの発行と Guard の設定をまとめて行う場合
TG_DIRECT_WRITE=true TG_BOT_API_KEY="..." ./setup.sh <BOT_ID> <USER_IDENTIFIER>
# 発行済みの組織ユーザートークンを使用して Guard の設定のみを行う場合
TG_DIRECT_WRITE=true ./setup.sh install <TOKEN>
これにより、セットアップ実行時点ではパッケージマネージャーがインストールされていなくても、後から導入された際に初回利用時から Guard を経由するようになります。
また、このモードではパッケージマネージャーの CLI(npm config set、poetry config、go env -w など)を実行せず、設定ファイルを直接編集して設定を反映します。そのため、対象のパッケージマネージャーがインストールされる前であっても問題なく書き込まれます。
ただし、このモードでは設定対象の有無を確認せずに処理を進めるため、実際には Guard の保護対象となるパッケージマネージャーを利用していないユーザーや端末に対しても、組織ユーザートークンが発行されます。そのため、利用状況によってはコストに影響する場合があります。
不要なトークン発行の抑制と、将来的なパッケージマネージャー利用への備えとのトレードオフを考慮し、状況に応じて使い分けてください。
事前検証の詳細な挙動はパッケージマネージャーの事前検証をご確認ください。
定期実行も検討してください
セットアップスクリプトは、再実行時に Guard が設定済みのツールをスキップし、有効なトークンを再利用するように設計されています。したがって、管理ツール経由で 定期的に実行する運用 にも適しています。
定期実行することで、以下のようなケースに自動で追随できます。
- 新しく組織に参加した開発者の端末に、初回ログイン後に自動でセットアップが行き渡る
- 端末を初期化した・再セットアップした場合に、改めて配信を依頼しなくても再構成される
- ユーザーが誤って Takumi Guard 設定を削除・上書きした場合に、次回実行時に復旧される
実行間隔はおおよそ日次〜週次が現実的です。
Windows Subsystem for Linux(WSL)への設定
WSL への Guard 設定は、ログオン中のユーザー向けラッパースクリプトでのみサポートされています。これは、WSL ディストリビューションがユーザーごとに登録され(HKCU\Lxss)、SYSTEM 権 限のプロセスからは参照できないという Windows の仕様によるものです。Microsoft Intune の「ログオンした資格情報を使用してこのスクリプトを実行する」など、ユーザー権限でスクリプトを実行できる方法を利用してください。
利用している管理ツールでサポートしていない場合は、共有ストレージなどを通じてラッパースクリプトを配布し、各ユーザーに実行してもらう運用を検討してください。
この場合、組織ユーザートークンを発行するためのボット ID と API キーをユーザーへ共有する必要があるため、機密情報をユーザーへ配布することに懸念を抱くかもしれません。
API キーを無効化しても、その API キーを使って発行済みの組織ユーザートークンは引き続き利用できます。そのため、必要なユーザーにのみ一時的に API キーを共有し、セットアップ完了後に速やかに API キーを無効化することで、リスクを最小限に抑えられます。利便性と機密情報の管理を両立する完全な解決策はありませんが、このような運用もあわせて検討してください。
WSL への Guard 設定が不要な場合は、ログオン中のユーザー向けラッパー スクリプトの冒頭で $ConfigureWsl を $false に設定してください。その場合、Guard の設定は Windows ホストにのみ適用されます。
Python パッケージマネージャー Poetry を利用する場合
Poetry は、pip や uv と異なり、パッケージの取得先(source)をユーザー単位で設定する仕組みがなく、取得先はプロジェクトごとに pyproject.toml で管理します。そのため、セットアップスクリプトが Poetry に対して設定するのは認証情報(ユーザー単位の auth.toml)のみです。取得先の設定は行わないため、スクリプトを実行しただけでは Poetry のパッケージ取得は Guard を経由しません。
Guard を利用するには、Poetry を利用するプロジェクトごとに、pyproject.toml へ Guard の取得先を追加する必要があります。次のコマンドを一度実行し、その変更をコミット・プッシュしてください。
poetry source add --priority=primary takumi-guard https://pypi.flatt.tech/simple/
この変更は、そのリポジトリを利用するすべての開発者および CI に適用されます。セットアップスクリプトで設定した認証情報は、この source 名(takumi-guard)に対して自動的に利用されるため、追加の認証設定は不要です。
ヘルスチェック
セットアップスクリプトの配信後、healthcheck サブコマンドを使用して、Guard が正しく設定されているか、および悪性パッケージが実際にブロックされるかを確認できます。このサブコマンドは読み取り専用であり、設定ファイルを変更しません。API クレデンシャル(TG_BOT_API_KEY / TG_BOT_ID)も不要です。
ヘルスチェックではテスト用パッケージのコードがインストール・実行されることはありません。パッケージマネージャーがテスト用パッケージを拒否するかどうかのみを検証します(--dry-run やメタデー タ取得のみを使用)。そのため、仮にテスト用パッケージ自体が侵害されていたとしても、端末上でそのコードが実行されることはありません。
使い方
setup.sh install の完了後にヘルスチェックを実行します。
./setup.sh healthcheck
特定のエコシステムのみを確認する場合は、引数でスコープを指定します。
./setup.sh healthcheck npm
指定可能なスコープ引数は install サブコマンドと同じく、npm、rubygems、golang、pypi です。
検証内容
ヘルスチェックはエコシステムごとに以下の 3 段階で検証を行います。
- 設定確認:パッケージマネージャーが Guard プロキシを参照しているかを検証する
- 接続確認:Guard プロキシへの到達性を確認する
- ブロックテスト:ブロック対象のテスト用パッケージのインストールを試行し、拒否されることを検証する
| エコシステム | テスト用パッケージ |
|---|---|
| npm | @panda-guard/test-malicious |
| RubyGems | hola-takumi v0.1.0 |
| Go | github.com/flatt-security/hola-takumi-go v0.1.0 |
| PyPI | (対応予定) |
終了コード
| 終了コード | 意味 |
|---|---|
0 | すべてのチェックに合格 |
1 | 1 つ以上のチェックに失敗 |
2 | 判定不能(対象のパッケージマネージャーが端末に 1 つも無い場合など) |
出力例
$ ./setup.sh healthcheck
[OK] npm: registry → https://npm.flatt.tech/
[OK] npm: Guard proxy reachable at https://npm.flatt.tech/-/health
[OK] npm: sentinel package @panda-guard/test-malicious correctly blocked (authenticated)
[OK] bundler: mirror → https://rubygems.flatt.tech/
[OK] bundler: Guard proxy reachable
[OK] bundler: sentinel gem hola-takumi@0.1.0 correctly blocked (HTTP 403, authenticated)
[SKIP] go: CLI not found
[OK] go: Guard proxy reachable
[SKIP] go: CLI not found, cannot run block test
[SKIP] pypi healthcheck not yet supported (no sentinel package)
[OK] All healthchecks passed
セキュリティ上の注意事項
- Bot の API キーの取り扱い:漏洩した場合は、Takumi / Shisho Cloud コンソールから直ちに無効化してください。既に発行済みのトー クンには影響しません。
- キーのローテーション:すべての対象端末への配信が完了したら、予防策として Takumi / Shisho Cloud コンソールで Bot の API キーをローテーションしてください。
- トークンの失効:トークンが漏洩した場合は、Takumi / Shisho Cloud コンソールからトークンを失効させてください。失効は最大 60 秒で反映されます。