NixOSにBunのbotをデプロイする

最近XにてNixやNixOSの話題が出ていたので、Bunで実装したbotをNixOSにデプロイしてみました。

サンプルリポジトリはこちら。

mopeneko/bun-nixos-sample

準備するもの

  • /etc/nixos以下をflakeで管理しているNixOS
  • 既存のBunのbotパッケージ

Nixパッケージの作成

まず、npmパッケージのディレクトリ内でnix flake initを実行して、flakeリポジトリを初期化します。

flake.nixの内容を次のようにします。

<code class="language-nix">{
description = &quot;bun-nixos-sample&quot;;

inputs = {
nixpkgs.url = &quot;github:NixOS/nixpkgs/nixpkgs-unstable&quot;;
};

outputs =
{ self, nixpkgs, ... }:
let
supportedSystems = [
&quot;x86_64-linux&quot;
&quot;aarch64-linux&quot;
];
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems f;
in
{
packages = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};

node_modules = pkgs.stdenv.mkDerivation {
name = &quot;bun-nixos-sample-node-modules&quot;;

src = pkgs.lib.cleanSourceWith {
src = ./.;
filter =
path: type:
builtins.elem (baseNameOf path) [
&quot;package.json&quot;
&quot;bun.lock&quot;
];
};

outputHashMode = &quot;recursive&quot;;
outputHashAlgo = &quot;sha256&quot;;
outputHash = &quot;&quot;;

nativeBuildInputs = [ pkgs.bun ];

buildPhase = &apos;&apos;
export HOME=$TMPDIR
bun install --frozen-lockfile
&apos;&apos;;

installPhase = &apos;&apos;
mkdir -p $out
cp -r node_modules $out/
&apos;&apos;;
};
in
{
default = pkgs.stdenv.mkDerivation {
pname = &quot;bun-nixos-sample&quot;;
version = &quot;0.1.0&quot;;

src = pkgs.lib.cleanSource ./.;

nativeBuildInputs = [
pkgs.bun
pkgs.makeWrapper
];

buildPhase = &apos;&apos;
export HOME=$TMPDIR
ln -s ${node_modules}/node_modules ./node_modules
bun build --target=bun ./index.ts --outdir ./dist
&apos;&apos;;

installPhase = &apos;&apos;
mkdir -p $out/lib/bun-nixos-sample
cp dist/index.js $out/lib/bun-nixos-sample/

mkdir -p $out/bin
makeWrapper ${pkgs.bun}/bin/bun $out/bin/bun-nixos-sample \
--add-flags &quot;$out/lib/bun-nixos-sample/index.js&quot;
&apos;&apos;;
};
}
);

nixosModules.default =
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.bun-nixos-sample;
in
{
options.services.bun-nixos-sample = {
enable = lib.mkEnableOption &quot;bun-nixos-sample service&quot;;

package = lib.mkOption {
type = lib.types.package;
default = self.packages.${pkgs.system}.default;
description = &quot;The bun-nixos-sample package to use.&quot;;
};
};

config = lib.mkIf cfg.enable {
users.users.bun-nixos-sample = {
isSystemUser = true;
group = &quot;bun-nixos-sample&quot;;
description = &quot;bun-nixos-sample service user&quot;;
};
users.groups.bun-nixos-sample = { };

systemd.services.bun-nixos-sample = {
description = &quot;bun-nixos-sample&quot;;
wantedBy = [ &quot;multi-user.target&quot; ];
after = [ &quot;network-online.target&quot; ];
wants = [ &quot;network-online.target&quot; ];

serviceConfig = {
Type = &quot;simple&quot;;
ExecStart = &quot;${cfg.package}/bin/bun-nixos-sample&quot;;
Restart = &quot;on-failure&quot;;
RestartSec = 10;
User = &quot;bun-nixos-sample&quot;;
Group = &quot;bun-nixos-sample&quot;;
}
// {
NoNewPrivileges = true;
ProtectSystem = &quot;strict&quot;;
ProtectHome = true;
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictNamespaces = true;
RestrictSUIDSGID = true;
MemoryDenyWriteExecute = false;
};
};
};
};

devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
default = pkgs.mkShell {
packages = with pkgs; [
bun
typescript
];
};
}
);
};
}</code>

この状態で*.nixgit addして、nix buildを実行します。

すると、次のようなエラーが出るはずです。

<code>warning: Git tree '/home/user/projects/bun-nixos-sample' has uncommitted changes
warning: found empty hash, assuming 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
error: hash mismatch in fixed-output derivation '/nix/store/nb17j30gpflnnng66244x0d19frl3ff7-bun-nixos-sample-node-modules.drv':
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
            got:    sha256-9ruhMeqyGUyPT891SVKnwPPtURwgBXGWTmob8mtnJhs=
error: Cannot build '/nix/store/igcrk3d3piqkrxplazl73hcmfrl9b1h5-bun-nixos-sample-0.1.0.drv'.
       Reason: 1 dependency failed.
       Output paths:
         /nix/store/j9xz2qg6qwrlqf3flp9s6mgk08h8b4bh-bun-nixos-sample-0.1.0
❌ git+file:///home/user/projects/bun-nixos-sample#
error: Build failed due to failed dependency</code>

got に表示されている値をflake.nixoutputHashに入力して、再度nix buildすると成功するはずです。

このflake.nixではdevShellを使っているので、nix developを実行すると本番環境と同じバージョンのBunで開発できます。

direnvを導入していればuse flake.envrcに記述すると、シェル起動時に自動的にdevShellに入れます。

(プライベートリポジトリの場合)GitHubにNixOSのSSH公開鍵を登録する

パッケージがプライベートリポジトリの場合、デプロイの前にNixOSのSSH公開鍵をGitHubに登録する必要があります。

<code>ssh-keygen -t ed25519 -f /root/.ssh/github-deploy -N '' -C 'nixos-deploy'
cat /root/.ssh/github-deploy.pub</code>

表示された公開鍵をGitHubに登録したら、NixOSがGitHubへのSSH接続にその鍵を使うように設定します。

<code>cat >> /root/.ssh/config << 'EOF'
Host github.com
  IdentityFile /root/.ssh/github-deploy
EOF

ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null</code>

NixOSにデプロイする

Nixパッケージを作成したので、NixOSでは/etc/nixos以下のファイルを編集してnixos-rebuildするだけでデプロイできます。

まず、flake.nixを編集して、Gitリポジトリをパッケージとして読み込ませます。

<code class="language-nix">{
inputs = {
nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-25.11&quot;;
bun-nixos-sample = {
url = &quot;git+ssh://git@github.com/mopeneko/bun-nixos-sample.git&quot;;
inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
};
};

outputs = { self, nixpkgs, bun-nixos-sample }: {
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = &quot;x86_64-linux&quot;;
modules = [
./configuration.nix
bun-nixos-sample.nixosModules.default
];
};
};
}</code>

次に、configuration.nixにサービスの有効化の設定を追記します。

<code class="language-nix">services.bun-nixos-sample.enable = true;</code>

最後にNixOSに変更を反映させると、botがsystemdサービスとして実行されます。

<code>nixos-rebuild switch --flake .#</code>

感想

Cloud RunやScalewayなどにデプロイするとベンダー固有の設定が生まれたり、VPSではgit cloneして、systemdサービスファイルを作成して実行する必要があったり…

NixOSであれば、どんなクラウドプロバイダーでも設定ファイルを書き換えるだけでパッケージの配置からsystemdサービスの実行までを行えるので便利そうです。

しかし、私は1つのbotだけを動かしているので、正直オーバースペックだと感じています…。

今後botが増えてきたら変わるかもしれないです。

実際のbotではsops-nixを用いてシークレット管理をしているので、これは次回解説します。