背景・目的
本ページは、ElectronでNFSマウント操作UIを作成したときのメモになります。
ローカルの仮想環境(UTM + Ubuntu Desktop)を使って、仕組みや動作の検証を進めています。
実践
開発環境を用意する
-
Ubuntuのページを開きます
-
前回、インストールしたUTMを起動します
-
「選択」をクリックし、ダウンロードした「ubuntu-24.04.2-live-server-amd64.iso」を選択します
-
「続ける」をクリックします
-
下記を指定して「続ける」をクリックします
- メモリ:4096MB
- CPUコア数:2
-
ストレージは、「64GiB」を指定して、「続ける」をクリックします
-
共有ディレクトリを指定して、「続ける」をクリックします
-
問題なければ、「保存」をクリックします
-
「Try or Install Ubuntu Server」 を選びます
-
SSHサーバをインストールします
-
セットアップが進んでいき、「Reboot」します
-
ターミナルでEnterをクリックします
NFSサーバのインストール
-
IPアドレスを確認します
ip a
-
ローカルPCからSSHします
ssh <Username>@<UbuntuサーバのIP> -i ~/.ssh/秘密鍵
-
NFSサーバのインストール
sudo apt update sudo apt install nfs-kernel-server
-
共有ディレクトリを作成します
~$ ls -l /mnt total 0 ~$ sudo mkdir -p /mnt/share ~$ sudo chown nobody:nogroup /mnt/share/ ~$ ls -l /mnt total 4 drwxr-xr-x 2 nobody nogroup 4096 Apr 20 10:33 share ~$
-
ファイルを置きます
~$ echo "hello_nfs" | sudo tee /mnt/share/hello.txt hello_nfs ~$
-
/etc/exports にエクスポート設定を追記します
~$ echo "/mnt/share 127.0.0.1(rw,sync,no_subtree_check)" | sudo tee -a /etc/exports /mnt/share 127.0.0.1(rw,sync,no_subtree_check) ~$ cat /etc/exports # /etc/exports: the access control list for filesystems which may be exported # to NFS clients. See exports(5). # # Example for NFSv2 and NFSv3: # /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check) # # Example for NFSv4: # /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check) # /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check) # /mnt/share 127.0.0.1(rw,sync,no_subtree_check) ~$
-
サーバ再起動します
~$ sudo exportfs -a ~$ sudo systemctl restart nfs-kernel-server $
-
マウントをします
~$ sudo mkdir -p /mnt/test_mount ~$ sudo mount -t nfs 127.0.0.1:/mnt/share /mnt/test_mount ~$
-
見えました
~$ ls -l /mnt/test_mount/ total 4 -rw-r--r-- 1 root root 10 Apr 20 10:46 hello.txt ~$ cat /mnt/test_mount/hello.txt hello_nfs ~$
Nodeのインストール
-
nvmをインストールします
~$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
-
シェルに反映します
source ~/.bashrc
-
最新のLTSバージョンのNode.jsをインストールします
~$ nvm install --lts Installing latest LTS version. Downloading and installing node v22.14.0... Downloading https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.xz... ###################################################################################################################################################################################################### 100.0% Computing checksum with sha256sum Checksums matched! Now using node v22.14.0 (npm v10.9.2) Creating default alias: default -> lts/* (-> v22.14.0) ~$
-
インストールを確認します
~$ node -v v22.14.0 ~$ npm -v 10.9.2 ~$
Ubuntu Desktopのインストール
- Ubuntu desktopをインストールします
sudo apt install -y ubuntu-desktop
GUIライブラリの一括インストール
-
apt update
を実行します$ sudo apt update [sudo] password for XXX: Hit:1 http://security.ubuntu.com/ubuntu noble-security InRelease Hit:2 http://jp.archive.ubuntu.com/ubuntu noble InRelease Get:3 http://jp.archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB] Hit:4 http://jp.archive.ubuntu.com/ubuntu noble-backports InRelease Fetched 126 kB in 3s (47.9 kB/s) Reading package lists... Done Building dependency tree... Done Reading state information... Done 55 packages can be upgraded. Run 'apt list --upgradable' to see them. $
- ライブラリをインストールします
sudo apt install -y \ libatk1.0-0t64 \ libgtk-3-0t64 \ libxss1 \ libasound2t64 \ libnss3 \ libx11-xcb1 \ libxcomposite1 \ libxdamage1 \ libxrandr2 \ libgbm1 \ libpango-1.0-0 \ libcairo2
Electron UIの構築
プロジェクトの作成
-
専用ディレクトリを作成します
~$ mkdir nfs-ui && cd nfs-ui
-
初期化します
~/nfs-ui$ npm init -y Wrote to /home/XXXX/nfs-ui/package.json: { "name": "nfs-ui", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "" } ~/nfs-ui$
-
electronをインストールします
~/nfs-ui$ npm install electron --save-dev npm warn deprecated boolean@3.2.0: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. added 70 packages, and audited 71 packages in 26s 17 packages are looking for funding run `npm fund` for details found 0 vulnerabilities ~/nfs-ui$
main.jsの作成
- main.jsを作成します
const { app, BrowserWindow, ipcMain } = require('electron'); const { exec } = require('child_process'); function createWindow () { const win = new BrowserWindow({ width: 400, height: 300, webPreferences: { nodeIntegration: true, contextIsolation: false } }); win.loadFile('index.html'); } app.whenReady().then(createWindow); // mount処理 ipcMain.handle('mount-nfs', async () => { return new Promise((resolve, reject) => { exec('sudo mount -t nfs 127.0.0.1:/mnt/share /mnt/test_mount', (error, stdout, stderr) => { if (error) return reject(stderr); resolve('mounted'); }); }); }); // umount処理 ipcMain.handle('umount-nfs', async () => { return new Promise((resolve, reject) => { exec('sudo umount /mnt/test_mount', (error, stdout, stderr) => { if (error) return reject(stderr); resolve('unmounted'); }); }); });
index.htmlの作成
- index.htmlを作成します
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>NFS Control</title> </head> <body> <h1>NFS操作</h1> <button onclick="mount()">マウント</button> <button onclick="umount()">アンマウント</button> <p id="status"></p> <script src="renderer.js"></script> </body> </html>
renderer.jsの作成
- renderer.js を作成します
const { ipcRenderer } = require('electron'); async function mount() { try { await ipcRenderer.invoke('mount-nfs'); document.getElementById('status').innerText = 'マウント成功'; } catch (e) { document.getElementById('status').innerText = 'マウント失敗: ' + e; } } async function umount() { try { await ipcRenderer.invoke('umount-nfs'); document.getElementById('status').innerText = 'アンマウント成功'; } catch (e) { document.getElementById('status').innerText = 'アンマウント失敗: ' + e; } }
package.jsonの修正
- package.jsonに、
"start": "electron ."
、main:main.js
を追加します~/nfs-ui$ cat package.json { "name": "nfs-ui", "version": "1.0.0", "main": "main.js", "scripts": { "start": "electron .", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "devDependencies": { "electron": "^35.2.0" } } ~/nfs-ui$
chrome-sandbox に権限を設定
Electronは内部的にChrome(Chromium)を使っており、Linuxで特権昇格(setuid)機能によるサンドボックス保護を追加います。
- 権限を設定します
~/nfs-ui$ sudo chown root:root ./node_modules/electron/dist/chrome-sandbox ~/nfs-ui$ sudo chmod 4755 ./node_modules/electron/dist/chrome-sandbox
起動
ファイルの一覧を確認するように変更する
main.jsを修正
- 下記のように修正する
const { app, BrowserWindow, ipcMain } = require('electron'); const { exec } = require('child_process'); const fs = require('fs'); const path = require('path'); function createWindow () { const win = new BrowserWindow({ width: 600, height: 400, webPreferences: { nodeIntegration: true, contextIsolation: false, // 今回は簡略化のためfalse } }); win.loadFile('index.html'); } app.whenReady().then(createWindow); const mountPoint = '/mnt/test_mount'; const nfsTarget = '127.0.0.1:/mnt/share'; // ✅ mountしてファイル一覧を返す ipcMain.handle('mount-nfs', async () => { return new Promise((resolve, reject) => { exec(`sudo mount -t nfs ${nfsTarget} ${mountPoint}`, (error, stdout, stderr) => { if (error) return reject(stderr); try { const files = fs.readdirSync(mountPoint); resolve(files); } catch (err) { reject('マウントは成功しましたが、ファイル一覧取得に失敗しました'); } }); }); }); // ✅ umountのみ ipcMain.handle('umount-nfs', async () => { return new Promise((resolve, reject) => { exec(`sudo umount ${mountPoint}`, (error, stdout, stderr) => { if (error) return reject(stderr); resolve('アンマウント完了'); }); }); });
index.html
- 下記のように修正します
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>NFS UI</title> </head> <body> <h2>NFS マウントテスト</h2> <button id="mount">📂 Mount</button> <button id="umount">📤 Unmount</button> <ul id="file-list"></ul> <script src="renderer.js"></script> </body> </html>
render.js
- 下記のように修正します
const { ipcRenderer } = require('electron'); document.getElementById('mount').onclick = async () => { try { const files = await ipcRenderer.invoke('mount-nfs'); const ul = document.getElementById('file-list'); ul.innerHTML = ''; // 初期化 files.forEach(f => { const li = document.createElement('li'); li.textContent = f; ul.appendChild(li); }); } catch (err) { alert('マウントに失敗: ' + err); } }; document.getElementById('umount').onclick = async () => { try { const result = await ipcRenderer.invoke('umount-nfs'); alert(result); document.getElementById('file-list').innerHTML = ''; } catch (err) { alert('アンマウントに失敗: ' + err); } };
起動
ファイルを追加
- ファイルを追加します
$ sudo touch /mnt/share/hello2.txt [sudo] password for XXXX: $ ls -l /mnt/share/ total 4 -rw-r--r-- 1 root root 0 Apr 20 12:40 hello2.txt -rw-r--r-- 1 root root 10 Apr 20 10:46 hello.txt $
- Mountをクリックすると、追加し他ファイルが見えました
考察
今回、ElectronでNFSマウントを行うUIを作成してみました。次回以降はコピーや削除なども試してみます。
参考