# 機能設計書 50-ローカルサーバー

## 概要

本ドキュメントは、Actualの「ローカルサーバー」機能の設計内容を記載する。デスクトップアプリに内蔵された同期サーバー機能について、アーキテクチャと処理内容を定義する。

### 本機能の処理概要

ローカルサーバー機能は、Electronデスクトップアプリ内でActual同期サーバーを起動し、外部サーバーなしでデータ同期機能を提供する。ユーザーは自己完結型の予算管理環境を構築できる。

**業務上の目的・背景**：外部サーバーを必要とせずに複数デバイス間でのデータ同期を実現する。プライバシーを重視するユーザーや、ネットワーク環境に制限があるユーザーに対して、完全にローカルで動作する同期ソリューションを提供する。

**機能の利用シーン**：外部サーバーを使用せずにデータ同期を行いたい場合、自宅内LANで複数デバイスを接続したい場合、完全オフライン環境でバックアップ用サーバーを運用したい場合に利用される。

**主要な処理内容**：
1. 同期サーバープロセスの起動（startSyncServer）
2. 同期サーバープロセスの停止（stopSyncServer）
3. サーバー自動起動設定
4. サーバー設定（ポート、ホスト名、データディレクトリ）
5. Webルートの提供（Actualウェブインターフェース）

**関連システム・外部連携**：@actual-app/sync-server、@actual-app/web

**権限による制御**：OSのネットワーク/ファイルシステム権限に依存

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | サーバー設定画面 | 主機能 | サーバーURL設定・接続設定の変更処理 |
| 1 | ブートストラップ画面 | 補助機能 | 初回起動時のサーバーURL設定・接続確認処理 |

## 機能種別

インフラ・サーバー基盤機能

## 入力仕様

### 入力パラメータ

#### グローバル設定（syncServerConfig）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| port | number | No | サーバーポート | デフォルト: 5007 |
| hostname | string | No | ホスト名 | デフォルト: 'localhost' |
| autoStart | boolean | No | 自動起動フラグ | true/false |

### 入力データソース

- グローバル設定ファイル（global-store.json）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| syncServerProcess | UtilityProcess | 同期サーバープロセス |
| server-started | メッセージ | サーバー起動完了通知 |

### 出力先

- プロセス管理（Electron UtilityProcess）
- ログ出力（メインプロセスコンソール）

## 処理フロー

### 処理シーケンス

```
1. サーバー起動（startSyncServer）
   ├─ 既存プロセス確認
   ├─ グローバル設定読み込み
   ├─ 同期サーバー設定構築
   │    ├─ ポート（デフォルト: 5007）
   │    ├─ ホスト名（デフォルト: localhost）
   │    ├─ ACTUAL_SERVER_DATA_DIR
   │    ├─ ACTUAL_SERVER_FILES
   │    └─ ACTUAL_USER_FILES
   ├─ サーバーディレクトリ作成（存在しない場合）
   ├─ utilityProcess.fork()でサーバー起動
   ├─ stdout/stderrハンドリング
   └─ 'server-started'メッセージ待機（タイムアウト20秒）

2. サーバー停止（stopSyncServer）
   ├─ syncServerProcess.kill()
   └─ syncServerProcess = null

3. 自動起動（app.on('ready')内）
   └─ globalPrefs.syncServerConfig?.autoStartがtrueの場合、
      createWindow前にstartSyncServerを呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[startSyncServer呼び出し] --> B{既存プロセス存在?}

    B -->|Yes| C[ログ出力して終了]
    B -->|No| D[loadGlobalPrefs]

    D --> E[syncServerConfig構築]
    E --> F[サーバーディレクトリ確認]
    F --> G{ディレクトリ存在?}

    G -->|No| H[mkdir recursive]
    G -->|Yes| I[continue]

    H --> I
    I --> J[utilityProcess.fork]
    J --> K[stdout/stderrハンドリング]
    K --> L[server-startedメッセージ待機]

    L --> M{タイムアウト?}
    M -->|Yes| N[エラーログ出力]
    M -->|No| O{server-started受信?}

    O -->|Yes| P[起動完了]
    O -->|No| L

    N --> Q[起動失敗]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | デフォルトポート | サーバーポートのデフォルトは5007 | 未設定時 |
| BR-50-02 | デフォルトホスト | ホスト名のデフォルトはlocalhost | 未設定時 |
| BR-50-03 | 起動タイムアウト | サーバー起動は20秒以内に完了する必要がある | startSyncServer |
| BR-50-04 | 自動起動タイミング | autoStart時はcreateWindow前にサーバーを起動 | syncServerConfig.autoStart |
| BR-50-05 | 二重起動防止 | 既存プロセスが存在する場合は起動をスキップ | startSyncServer |

### ディレクトリ構成

```
{ACTUAL_DATA_DIR}/
└── actual-server/              (ACTUAL_SERVER_DATA_DIR)
    ├── server-files/           (ACTUAL_SERVER_FILES)
    └── user-files/             (ACTUAL_USER_FILES)
```

## データベース操作仕様

本機能はデータベースを直接操作しない。同期サーバープロセスが内部でデータベース操作を行う。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 起動タイムアウト | 20秒以内にserver-started受信なし | エラーログ出力、GitHub Issue報告を推奨 |
| - | プロセスエラー | utilityProcess起動失敗 | エラーログ出力 |

### リトライ仕様

自動リトライなし。ユーザーによる再起動が必要。

## トランザクション仕様

なし（プロセス管理のみ）

## パフォーマンス要件

- サーバー起動: 20秒以内
- サーバー停止: 即座

## セキュリティ考慮事項

1. **ローカルバインド**: デフォルトでlocalhostにバインド（外部からのアクセス制限）
2. **データディレクトリ**: ユーザーデータディレクトリ内にサーバーデータを格納
3. **ファイル権限**: OSのファイルシステム権限に依存

## 備考

- ローカルサーバーはElectronデスクトップアプリ専用機能
- @actual-app/sync-serverパッケージのapp.jsをforkして起動
- @actual-app/webパッケージのビルド済みファイルをWebルートとして提供
- 同期サーバーのすべての機能（認証、同期、銀行連携等）が利用可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ローカルサーバーで使用される設定構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/desktop-electron/index.ts` | syncServerConfig構造（217-236行目） |

**読解のコツ**: syncServerConfigはport, hostname, ACTUAL_SERVER_DATA_DIR, ACTUAL_SERVER_FILES, ACTUAL_USER_FILESを含む。すべてのパスはACTUAL_DATA_DIR/actual-server/配下に構築される。

#### Step 2: エントリーポイントを理解する

ローカルサーバーの起動処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/desktop-electron/index.ts` | startSyncServer関数（207-323行目） |

**主要処理フロー**:
1. **209-215行目**: 既存プロセスチェック
2. **217-236行目**: syncServerConfig構築
3. **238-249行目**: サーバーパス取得（require.resolve）
4. **251-259行目**: 環境変数設定
5. **263-265行目**: ディレクトリ作成
6. **267-274行目**: forkOptions設定
7. **279行目**: utilityProcess.fork()でサーバー起動
8. **281-304行目**: stdout/stderr/messageハンドリング
9. **306-319行目**: タイムアウト処理（20秒）

#### Step 3: サーバー停止処理を理解する

サーバー停止処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/desktop-electron/index.ts` | stopSyncServer関数（325-329行目） |

**主要処理フロー**:
- **326行目**: syncServerProcess?.kill()
- **327行目**: syncServerProcess = null
- **328行目**: ログ出力

#### Step 4: 自動起動処理を理解する

アプリ起動時の自動起動処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/desktop-electron/index.ts` | app.on('ready')内の自動起動処理（436-439行目） |

**主要処理フロー**:
- **436行目**: syncServerConfig?.autoStart条件チェック
- **438行目**: await startSyncServer()（createWindow前）

### プログラム呼び出し階層図

```
index.ts (メインプロセス)
    │
    ├─ app.on('ready')
    │      └─ [autoStart時] await startSyncServer()
    │
    ├─ startSyncServer()
    │      ├─ 既存プロセスチェック (syncServerProcess存在確認)
    │      ├─ loadGlobalPrefs() → syncServerConfig取得
    │      │
    │      ├─ syncServerConfig構築
    │      │      ├─ port: globalPrefs.syncServerConfig?.port || 5007
    │      │      ├─ hostname: 'localhost'
    │      │      ├─ ACTUAL_SERVER_DATA_DIR: ACTUAL_DATA_DIR/actual-server
    │      │      ├─ ACTUAL_SERVER_FILES: .../actual-server/server-files
    │      │      └─ ACTUAL_USER_FILES: .../actual-server/user-files
    │      │
    │      ├─ サーバーパス取得
    │      │      ├─ require.resolve('@actual-app/sync-server/package.json')
    │      │      └─ require.resolve('@actual-app/web/package.json')
    │      │
    │      ├─ 環境変数設定
    │      │      ├─ ACTUAL_PORT
    │      │      ├─ ACTUAL_HOSTNAME
    │      │      ├─ ACTUAL_SERVER_FILES
    │      │      ├─ ACTUAL_USER_FILES
    │      │      ├─ ACTUAL_DATA_DIR
    │      │      └─ ACTUAL_WEB_ROOT
    │      │
    │      ├─ mkdir(ACTUAL_SERVER_DATA_DIR, {recursive: true})
    │      │
    │      ├─ utilityProcess.fork(serverPath)
    │      │
    │      └─ Promise.race([syncServerPromise, syncServerTimeout])
    │             ├─ 'server-started'メッセージ待機
    │             └─ 20秒タイムアウト
    │
    ├─ stopSyncServer()
    │      ├─ syncServerProcess?.kill()
    │      └─ syncServerProcess = null
    │
    ├─ ipcMain.handle('start-sync-server')
    │      └─ startSyncServer()
    │
    ├─ ipcMain.handle('stop-sync-server')
    │      └─ stopSyncServer()
    │
    └─ ipcMain.handle('is-sync-server-running')
           └─ return syncServerProcess ? true : false
```

### データフロー図

```
[入力]                    [処理]                         [出力]

グローバル設定 ─────────▶ loadGlobalPrefs()
(global-store.json)            │
                               ├─ syncServerConfig構築 ───▶ 設定オブジェクト
                               │
                               ├─ mkdir() ───────────────▶ ディレクトリ作成
                               │
                               ├─ utilityProcess.fork() ─▶ UtilityProcess
                               │       │                    (syncServerProcess)
                               │       │
                               │       ├─ stdout ─────────▶ ログ出力
                               │       │                    (Sync-Server:)
                               │       ├─ stderr ─────────▶ エラーログ
                               │       │
                               │       └─ message ────────▶ 'server-started'
                               │
                               └─ Promise.race ───────────▶ 起動完了 or タイムアウト

IPC呼び出し              ipcMain.handle
start-sync-server ─────▶ startSyncServer() ─────────────▶ サーバー起動

stop-sync-server ──────▶ stopSyncServer() ──────────────▶ サーバー停止

is-sync-server-running ▶ ────────────────────────────────▶ true/false
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/desktop-electron/index.ts` | ソース | Electronメインプロセス、ローカルサーバー管理 |
| app.ts | `packages/sync-server/src/app.ts` | ソース | 同期サーバーメインエントリー |
| app.js | `packages/sync-server/build/app.js` | ビルド | 同期サーバービルド済みファイル |
| package.json | `packages/sync-server/package.json` | 設定 | 同期サーバーパッケージ設定 |
| package.json | `packages/web/package.json` | 設定 | Webインターフェースパッケージ設定 |
