【完全再現】Intel N95ミニPCで作る多層防御型プライベートクラウドストレージ構築手順書

📋 完全構築インデックス

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) 設定

  1. ミニPCの電源を入れ、ロゴが表示されたら Delete キー(または F2)を連打します。
  2. Advanced または Chipset タブに移動します。
  3. Restore on AC Power Loss(または State After G3)を探し、Power On に変更します。
  4. 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%転送可能です。