# How Modly Installs Extensions from GitHub URLs: Cloning, Validation, and Atomic Deployment Workflow

> Learn how Modly installs extensions from GitHub URLs using a 12-step workflow that clones, validates manifests, and deploys atomically for reliable extension management.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: how-to-guide
- Published: 2026-08-15

---

**Modly's extension installation from GitHub URLs uses a 12-step Electron IPC workflow that downloads tarballs, validates manifests, stages extensions atomically, and runs language-specific setup routines.**

The [lightningpixel/modly](https://github.com/lightningpixel/modly) repository implements a complete extension installation pipeline that allows users to install models, Python processes, and Node.js processes directly from public GitHub repositories. This workflow is orchestrated by the **Electron main process** through the `extensions:installFromGitHub` IPC handler defined in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts).

## URL Parsing and Initial Validation

The installation workflow begins with strict URL validation to prevent malicious source domains. The handler receives the raw GitHub URL and performs two critical checks:

1. **Hostname verification** — the URL must resolve to `github.com` (line 35)
2. **Path structure verification** — the URL must contain at least `owner/repo` format (line 37)

```typescript
// From electron/main/ipc-handlers.ts (lines 35-37)
const parsed = new URL(rawUrl);
if (parsed.hostname !== 'github.com') throw new Error('Invalid hostname');
const parts = parsed.pathname.split('/').filter(Boolean);
if (parts.length < 2) throw new Error('Invalid repository path');

```

Any URL failing these checks is immediately rejected before network requests are made.

## Downloading and Extracting the Repository Tarball

Once validated, Modly fetches the repository's `HEAD` tarball using the GitHub REST API. This approach avoids git dependencies and enables streaming download progress.

### Tarball Download with Progress Streaming

```typescript
// From electron/main/ipc-handlers.ts (lines 43-56)
const tarballUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/HEAD`;
const response = await axios.get(tarballUrl, {
  responseType: 'stream',
  onDownloadProgress: (progressEvent) => {
    const percent = Math.round((progressEvent.loaded / progressEvent.total) * 80);
    emitProgress({ step: 'download', percent });
  }
});

```

The download streams to a temporary file while emitting progress events from **0% → 80%**. The 80% cap reserves headroom for extraction and setup phases.

### Tarball Extraction with Path Stripping

GitHub tarballs wrap contents in a top-level folder named `{owner}-{repo}-{sha}`. Modly strips this automatically:

```typescript
// From electron/main/ipc-handlers.ts (line 64)
await tar.x({
  file: tarPath,
  cwd: extractDir,
  strip: 1  // Removes the top-level GitHub wrapper folder
});

```

## Manifest.json Validation and Security Hardening

Every extension must declare a [`manifest.json`](https://github.com/lightningpixel/modly/blob/main/manifest.json). Modly validates this file through multiple security layers before accepting the installation.

### Required Manifest Validation

The `validateInstallManifest` function in [`electron/main/extension-install-utils.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-install-utils.ts) enforces:

- **Required fields**: `id`, `type` (process|model), valid entry files
- **Non-empty `nodes` array**: Ensures the extension declares at least one processing node
- **Entry file existence**: Verifies [`processor.js`](https://github.com/lightningpixel/modly/blob/main/processor.js) (default) or [`generator.py`](https://github.com/lightningpixel/modly/blob/main/generator.py) is present

```typescript
// From electron/main/ipc-handlers.ts (lines 70-81)
if (!fs.existsSync(path.join(extractDir, 'manifest.json'))) {
  throw new Error('manifest.json not found');
}
const manifestRaw = await fs.readJson(manifestPath);
const manifest = validateInstallManifest(manifestRaw, extractDir);

```

### Safe Extension ID Sanitization

The raw `id` from the manifest is passed through `assertSafeExtensionId` to prevent **directory traversal attacks**:

```typescript
// From electron/main/ipc-handlers.ts (lines 84-86)
const safeId = assertSafeExtensionId(manifest.id);
manifest.id = safeId;
activeExtensionInstalls.add(safeId);

```

### Source Trust Override

The `source` field is overwritten with the canonical GitHub URL, ensuring trust derives from the repository origin rather than any arbitrary upstream value:

```typescript
// From electron/main/ipc-handlers.ts (lines 89-91)
manifest.source = `https://github.com/${owner}/${repo}`;

```

## Atomic Staging and Deployment

Modly uses a **staging → atomic swap** pattern to prevent partial installations and enable crash recovery.

### Staging with Incomplete-Install Markers

```typescript
// From electron/main/ipc-handlers.ts (lines 106-108)
await fs.ensureDir(stagingDir);
await fs.copy(extractDir, stagingDir);
await fs.writeFile(path.join(stagingDir, '.modly-incomplete'), '');

```

The **incomplete-install marker** protects against crashes during setup. Modly's startup reconciler detects these markers and triggers cleanup or recovery.

### Compile TypeScript Entries (Process Extensions)

For `.ts` entry points, Modly compiles to JavaScript using `esbuild`:

```typescript
// From electron/main/ipc-handlers.ts (lines 110-122)
if (manifest.entry.endsWith('.ts')) {
  const compiledPath = path.join(stagingDir, 'main.js');
  esbuild.buildSync({
    entryPoints: [path.join(stagingDir, manifest.entry)],
    outfile: compiledPath,
    bundle: true,
    platform: 'node',
    format: 'cjs'
  });
  manifest.entry = 'main.js';
}

```

### Atomic Swap with Backup

The final deployment uses atomic rename operations. Existing extensions are backed up before replacement:

```typescript
// From electron/main/ipc-handlers.ts (lines 128-144)
const backupDir = path.join(extensionsDir, `.modly-backup-${safeId}-${Date.now()}`);
if (await fs.pathExists(targetDir)) {
  await fs.move(targetDir, backupDir);
}
await fs.move(stagingDir, targetDir);  // Atomic operation

```

If rename fails (e.g., file lock), the staging folder is removed and the install aborts.

## Language-Specific Setup Phase

After atomic deployment, Modly runs type-specific setup routines:

### Python Process Setup

```typescript
// From electron/main/ipc-handlers.ts (lines 148-158)
const venvPath = path.join(extensionDir, '.venv');
await runCommand('python', ['-m', 'venv', venvPath], { cwd: extensionDir });
await runCommand(path.join(venvPath, 'bin/pip'), ['install', '.'], {
  cwd: extensionDir,
  onLine: (line) => emitProgress({ step: 'setup', message: line })
});

```

### Node.js Process Setup

```typescript
// From electron/main/ipc-handlers.ts (lines 160-183)
if (await fs.pathExists(path.join(extensionDir, 'package.json'))) {
  await runCommand('npm', ['install'], {
    cwd: extensionDir,
    onLine: (line) => emitProgress({ step: 'setup', message: line })
  });
}

```

Each line of setup output is streamed as a progress event to the renderer.

## Failure Recovery and Cleanup

If any setup phase fails, Modly performs **automatic rollback**:

```typescript
// From electron/main/ipc-handlers.ts (lines 194-200)
if (backupDir && await fs.pathExists(backupDir)) {
  await fs.remove(targetDir);
  await fs.move(backupDir, targetDir);
} else {
  await fs.remove(targetDir);
}
// Incomplete marker preserved for startup reconciler

```

The incomplete-install marker remains, allowing **crash-consistent recovery** on next startup.

## Frontend Integration: Triggering and Monitoring Installs

### Trigger Installation from Renderer

```typescript
// From src/shared/stores/extensionsStore.ts
import { useExtensionsStore } from '@/stores/extensionsStore'

function installFromGitHub(url: string) {
  extensionsStore.installFromGitHub(url).then(result => {
    if (result.success) {
      console.log('Extension installed:', result.extensionId);
    } else {
      console.error('Install failed:', result.error);
    }
  });
}

```

### Listen for Progress Events

```typescript
// Using the IPC channel defined in electron/preload/electron-api.ts (lines 244-251)
useEffect(() => {
  const handler = (data: InstallProgress) => {
    // { step: 'download'|'extract'|'validate'|'setup'|'done', percent?: number, message?: string }
    console.log('Progress:', data.percent, '% -', data.step);
  };
  window.electron.extensions.onInstallProgress(handler);
  return () => window.electron.extensions.offInstallProgress(handler);
}, []);

```

### Example Valid manifest.json

```json
{
  "id": "my-awesome-extension",
  "type": "process",
  "entry": "main.ts",
  "nodes": [{ "id": "node1", "name": "Primary Node" }]
}

```

## Security Architecture Summary

| Layer | Implementation | File Reference |
|-------|---------------|--------------|
| URL validation | Hostname and path parsing | `ipc-handlers.ts:35-37` |
| Manifest schema | `validateInstallManifest` function | [`extension-install-utils.ts`](https://github.com/lightningpixel/modly/blob/main/extension-install-utils.ts) |
| Path traversal prevention | `assertSafeExtensionId` sanitization | `ipc-handlers.ts:84-86` |
| Source trust | Canonical URL overwrite | `ipc-handlers.ts:89-91` |
| Atomic deployment | Staging + rename with backup | `ipc-handlers.ts:106-144` |
| Crash recovery | Incomplete-install markers | [`extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/extension-path-guard.ts) |

## Summary

- **Modly's GitHub URL installation** is implemented in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts) as a 12-step IPC workflow triggered by `extensions:installFromGitHub`
- **Security layers** include URL hostname validation, manifest schema enforcement, extension ID sanitization, and source trust override
- **Atomic deployment** uses staging directories with incomplete-install markers, enabling crash-consistent recovery
- **TypeScript compilation** happens automatically for `.ts` entry points using `esbuild` before final deployment
- **Language-specific setup** runs `pip install` for Python extensions and `npm install` for Node.js extensions with streamed progress
- **Automatic rollback** restores previous versions or removes partial installs when setup fails

## Frequently Asked Questions

### What happens if the GitHub repository is private or doesn't exist?

The workflow fails during the tarball download phase. The GitHub API returns 404 or 403, which `axios` propagates as an error. The IPC handler catches this, emits a failure progress event, and cleans up temporary files without modifying the extension directory.

### Can extensions specify their own source URL in manifest.json?

No. Modly explicitly overwrites the `source` field with the canonical `https://github.com/<owner>/<repo>` URL. This prevents extensions from claiming trust relationships with arbitrary upstream sources they don't control.

### How does Modly handle installation interruptions like app crashes?

The **incomplete-install marker** file (`.modly-incomplete`) remains in the staging or target directory. On next startup, Modly's reconciler scans for these markers and either completes interrupted atomic swaps or removes corrupted installations, restoring from `.modly-backup-*` folders if available.

### Why does Modly use tarballs instead of git clone?

**Tarball downloads** eliminate git binary dependencies, reduce download size (no `.git` history), enable precise `HEAD` snapshots, and support streaming progress reporting through `axios`. The GitHub tarball API also handles authentication and rate limiting consistently.