ハンズオン / 自宅サーバー完全構築
【完全再現】Intel N95ミニPCで作る多層防御型プライベートクラウドストレージ構築手順書
投稿日: 2026年8月5日 • 著者: Enliru • 所要時間: 約45分
1. 必要なハードウェアと動作環境
本チュートリアルは、1.5万〜2万円程度で購入できる Intel N95プロセッサ搭載ミニPC 上の Ubuntu 22.04 LTS / 24.04 LTS を前提に記述されています。他メーカーのミニPCやRaspberry Pi 4/5、Debian/Fedora環境でも同様の手順で100%構築可能です。
🔧 必須環境・ライブラリリスト:
- OS: Linux (Ubuntu 22.04 / 24.04 推奨)
- 必須パッケージ:
python3, python3-pip, nginx, ffmpeg, poppler-utils, curl
- Pythonライブラリ:
Pillow, pillow-heif, gdown
2. 全体アーキテクチャとディレクトリ設計
システム全体のフォルダ構造をあらかじめ定義します。すべてのファイル・設定・ログを ~/shared_storage 配下に集約することで、バックアップや移行が極めて容易になります。
~/shared_storage/
├── bin/
│ ├── filebrowser # File Browser本体バイナリ
│ └── heic_autoconvert.py # HEIC自動変換Pythonスクリプト
├── config/
│ ├── filebrowser.json # File Browser設定ファイル
│ └── filebrowser.db # ユーザー・権限データベース
├── data/ # 実際のストレージ(共有フォルダ)
│ └── shared/ # 外部アクセス・共有可能ディレクトリ
└── web_ui/
├── pdfjs_hook.js # フロントエンドUIカスタムスクリプト
└── gdrive_api.py # Google Driveダイレクト転送Python API
3. STEP 1: File Browserのインストールとシステム常駐化
まずはストレージのコアとなる軽量Webブラウザ型ファイルマネージャー File Browser を配置します。
ターミナルで実行するコマンド
# 1. ディレクトリ構造の作成
mkdir -p ~/shared_storage/bin ~/shared_storage/config ~/shared_storage/data/shared ~/shared_storage/web_ui
# 2. File Browser バイナリのインストール
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
mv /usr/local/bin/filebrowser ~/shared_storage/bin/
# 3. 設定ファイル (filebrowser.json) の作成
cat << "EOF" > ~/shared_storage/config/filebrowser.json
{
"port": 8085,
"address": "127.0.0.1",
"baseurl": "/storage",
"database": "/home/your_username/shared_storage/config/filebrowser.db",
"root": "/home/your_username/shared_storage/data"
}
EOF
# ※注意: "your_username" 部分をご自身のLinuxユーザー名に変更してください!
sed -i "s/your_username/$(whoami)/g" ~/shared_storage/config/filebrowser.json
初期データベースの作成と管理者パスワードの設定
ターミナルで実行するコマンド
# データベースの初期化
~/shared_storage/bin/filebrowser config init -c ~/shared_storage/config/filebrowser.json
# 管理者アカウントの作成 (初期パスワードを設定)
~/shared_storage/bin/filebrowser users add admin "YourSecurePassword123" --perm.admin -c ~/shared_storage/config/filebrowser.json
4. STEP 2: iPhone HEIC全自動JPEG変換デーモンの作成
iPhoneで撮影した .HEIC / .HEIF 写真がアップロードされた際、バックグラウンドで即座に同名の高画質 .jpg プレビュー画像を自動生成する常駐デーモンを作成します。
必要なPythonライブラリのインストール
pip3 install Pillow pillow-heif
~/shared_storage/bin/heic_autoconvert.py
#!/usr/bin/env python3
import os
import time
from PIL import Image
import pillow_heif
# HEIFオープナーをPillowに登録
pillow_heif.register_heif_opener()
WATCH_DIR = os.path.expanduser("~/shared_storage/data")
def process_heic_files():
for root, dirs, files in os.walk(WATCH_DIR):
for file in files:
if file.lower().endswith(('.heic', '.heif')):
heic_path = os.path.join(root, file)
base_name = os.path.splitext(heic_path)[0]
jpg_path = base_name + ".jpg"
# まだJPGが生成されていない場合のみ変換を実行
if not os.path.exists(jpg_path):
try:
print(f"[HEIC AutoConvert] Converting: {heic_path}")
img = Image.open(heic_path)
img.convert('RGB').save(jpg_path, 'JPEG', quality=92, optimize=True)
print(f"[HEIC AutoConvert] Saved: {jpg_path}")
except Exception as e:
print(f"[HEIC Error] Failed to convert {heic_path}: {e}")
if __name__ == '__main__':
print("[HEIC AutoConvert Daemon Started]")
while True:
try:
process_heic_files()
except Exception as e:
print(f"[Daemon Loop Error] {e}")
time.sleep(5)
実行権限の付与
chmod +x ~/shared_storage/bin/heic_autoconvert.py
5. STEP 3: Mozilla PDF.jsによるスマホ対応PDFプレビューの構築
スマホブラウザ(Safari/Chrome)でPDFを開いた際、「プラグイン未対応」エラーを出さず、アプリ保存なしで全ページを直接画面内で拡大表示できる環境を構築します。
3-1. PDF.js ライブラリの配置
ターミナルで実行するコマンド
# PDF.js のダウンロードと配置
mkdir -p ~/browser_game/pdfjs
cd ~/browser_game/pdfjs
curl -fsSL https://github.com/mozilla/pdf.js/releases/download/v3.11.174/pdfjs-3.11.174-dist.zip -o pdfjs.zip
unzip -o pdfjs.zip
rm pdfjs.zip
3-2. スクリプトフック (pdfjs_hook.js) の作成
~/browser_game/pdfjs_hook.js
// File Browser カスタマイズ: PDF.js 統合 & Google Drive ダイレクト転送ボタン追加
(function() {
console.log("[Custom File Browser Hook] Active.");
// Google Drive 転送ボタンのUI注入
function injectGDriveButton() {
if (document.getElementById('gdrive-transfer-btn')) return;
let uploadBtn = document.getElementById('upload-button') || document.querySelector('button[title*="Upload"], button[title*="アップロード"], button.action');
if (!uploadBtn) return;
let btn = document.createElement('button');
btn.id = 'gdrive-transfer-btn';
btn.className = 'action';
btn.innerHTML = '☁️ Drive追加';
btn.title = 'Google Driveの共有リンクから直接転送保存';
btn.style.cssText = 'background:#2563eb; color:#ffffff; border:none; padding:4px 10px; margin-right:8px; border-radius:6px; font-size:0.82rem; font-weight:bold; cursor:pointer; display:inline-flex; align-items:center; gap:4px; box-shadow:0 2px 4px rgba(0,0,0,0.2); vertical-align:middle;';
btn.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
let url = prompt('Google Drive の共有リンク(またはファイルURL)を入力してください:\n\n例: https://drive.google.com/file/d/XXXX/view?usp=sharing');
if (!url) return;
let currentPath = window.location.pathname.replace('/storage/files', '');
btn.innerHTML = '⏳ 転送中...';
btn.disabled = true;
fetch('/api/gdrive_import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url, folder: currentPath })
})
.then(res => res.json())
.then(data => {
alert(data.message || '転送リクエストを送信しました!');
window.location.reload();
})
.catch(err => alert('転送エラー: ' + err))
.finally(() => {
btn.innerHTML = '☁️ Drive追加';
btn.disabled = false;
});
};
uploadBtn.parentNode.insertBefore(btn, uploadBtn);
}
// PDF Object タグの PDF.js iframe 置換
function processPdfObjects() {
let objects = document.querySelectorAll('object[type="application/pdf"], object[data*=".pdf"]');
objects.forEach(function(obj) {
if (obj.dataset.pdfjsHandled) return;
obj.dataset.pdfjsHandled = "true";
let pdfUrl = obj.getAttribute('data') || '';
if (!pdfUrl) return;
let iframe = document.createElement('iframe');
iframe.src = '/pdfjs/web/viewer.html?file=' + encodeURIComponent(pdfUrl);
iframe.style.cssText = 'width:100% !important; height:100% !important; min-height:80vh !important; border:none;';
if (obj.parentNode) {
obj.parentNode.replaceChild(iframe, obj);
}
});
}
setInterval(function() {
injectGDriveButton();
processPdfObjects();
}, 400);
})();
6. STEP 4: Google Driveダイレクト転送API(gdown)の構築
スマホのデータ通信(ギガ)を消費せず、Google Driveのパブリック共有リンクからサーバー同士でダイレクト転送するバックグラウンドAPIを作成します。
gdown のインストール
pip3 install gdown
~/browser_game/gdrive_api.py
#!/usr/bin/env python3
import os
import re
import sys
import threading
import subprocess
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
PORT = 3005
SAVE_DIR = os.path.expanduser("~/shared_storage/data")
def extract_gdrive_id(url):
file_match = re.search(r'/file/d/([a-zA-Z0-9_-]+)', url)
if file_match:
return file_match.group(1), 'file'
id_match = re.search(r'id=([a-zA-Z0-9_-]+)', url)
if id_match:
return id_match.group(1), 'file'
folder_match = re.search(r'/drive/folders/([a-zA-Z0-9_-]+)', url)
if folder_match:
return folder_match.group(1), 'folder'
return None, None
def run_download_background(cmd, target_dir):
try:
print(f"[Async Download Started] Executing: {' '.join(cmd)}")
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(f"[Async Download Finished] stdout: {res.stdout}")
except Exception as e:
print(f"[Async Download Error] {e}")
class GDriveHandler(BaseHTTPRequestHandler):
def _set_headers(self, status=200):
self.send_response(status)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
try:
data = json.loads(post_data.decode('utf-8'))
url = data.get('url', '').strip()
dest_folder = data.get('folder', '').strip()
target_dir = SAVE_DIR
if dest_folder and dest_folder != '/':
target_dir = os.path.join(SAVE_DIR, dest_folder.lstrip('/'))
os.makedirs(target_dir, exist_ok=True)
gdrive_id, gdrive_type = extract_gdrive_id(url)
if gdrive_id:
if gdrive_type == 'folder':
cmd = ["gdown", "--folder", f"https://drive.google.com/drive/folders/{gdrive_id}", "-O", target_dir]
else:
cmd = ["gdown", f"https://drive.google.com/uc?id={gdrive_id}", "-O", target_dir + "/"]
else:
cmd = ["wget", "-nc", "-P", target_dir, url]
# スレッドで非同期バックグラウンド実行(Webレスポンスをブロックしない)
thread = threading.Thread(target=run_download_background, args=(cmd, target_dir))
thread.daemon = True
thread.start()
self._set_headers(200)
self.wfile.write(json.dumps({'success': True, 'message': 'Google Driveダイレクト転送を開始しました!'}).encode('utf-8'))
except Exception as e:
self._set_headers(500)
self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
if __name__ == '__main__':
server = HTTPServer(('127.0.0.1', PORT), GDriveHandler)
print(f"[GDrive Transfer Server] Running on http://127.0.0.1:{PORT}")
server.serve_forever()
8. STEP 5: NginxリバースプロキシとUIスクリプトインジェクション
Nginx を用いて File Browser、PDF.js、Google Drive API を単一のWebポート(80/443)に統合し、HTMLに pdfjs_hook.js を自動挿入します。
/etc/nginx/sites-available/default 設定例
server {
listen 80;
server_name your-domain.com; # またはご自身のIPアドレス
# 検索エンジンの自動インデックス拒否 (robots.txt 代替ヘッダー)
add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always;
# PDF.js ビューア
location /pdfjs/ {
alias /home/your_username/browser_game/pdfjs/;
try_files $uri $uri/ =404;
}
# Google Drive 転送 API
location /api/gdrive_import {
proxy_pass http://127.0.0.1:3005;
proxy_set_header Host $host;
}
# File Browser 本体(UIフックJavaScriptの自動挿入)
location /storage/ {
proxy_pass http://127.0.0.1:8085/storage/;
proxy_set_header Host $host;
proxy_set_header Accept-Encoding "";
# HTMLタグの直前に pdfjs_hook.js を動的に埋め込む
sub_filter '</body>' '<script src="/pdfjs_hook.js"></script></body>';
sub_filter_once on;
}
# カスタムフックJavaScript
location /pdfjs_hook.js {
alias /home/your_username/browser_game/pdfjs_hook.js;
}
}
Nginxのテストと再起動
sudo nginx -t && sudo systemctl restart nginx
9. STEP 6: 停電復旧・BIOS自動パワーオンと定期バックアップ設定
自宅サーバーを24時間365日無人で完全運用するための自動化設定を行います。
6-1. 全プロセスの自動再起動登録(Crontab)
ターミナルで実行するコマンド
crontab -e
# 以下の4行を末尾に追加して保存します
@reboot /home/your_username/shared_storage/bin/filebrowser -c /home/your_username/shared_storage/config/filebrowser.json > /home/your_username/shared_storage/config/filebrowser.log 2>&1 &
@reboot python3 /home/your_username/shared_storage/bin/heic_autoconvert.py > /home/your_username/shared_storage/config/heic.log 2>&1 &
@reboot python3 /home/your_username/browser_game/gdrive_api.py > /home/your_username/shared_storage/config/gdrive_api.log 2>&1 &
6-2. BIOSの AC Power Loss (自動電源ON) 設定
- ミニPCの電源を入れ、ロゴが表示されたら
Delete キー(または F2)を連打します。
Advanced または Chipset タブに移動します。
Restore on AC Power Loss(または State After G3)を探し、Power On に変更します。
F10 キーを押して設定を保存し、再起動します。
これで落雷やブレーカー落下で電源が切れても、コンセントの電気が復旧した瞬間にPCが全自動起動し、すべてのストレージサービスが100%自動で復旧します。
10. 動作検証とトラブルシューティング
Q1. HEIC写真がJPGに変換されません
pip3 install pillow-heif が正しく完了しているか確認してください。また、python3 ~/shared_storage/bin/heic_autoconvert.py を手動実行してエラーログを確認してください。
Q2. Google Driveからの転送でエラーになります
Google Drive上の対象フォルダまたはファイルの共有権限が「リンクを知っている全員」になっているかご確認ください。個別ファイルに制限がかかっている場合は、Google Drive上でフォルダをZIP圧縮して共有リンクを発行することで100%転送可能です。