Metadata-Version: 2.4
Name: aird
Version: 0.5.8
Summary: Self-hosted file browser, editor, and LAN file transfer
Home-page: https://github.com/blinkerbit/aird
Author: Viswantha Srinivas P
Author-email: psviswanatha@gmail.com
License: Custom
Project-URL: Documentation, https://github.com/blinkerbit/aird/tree/main/docs
Project-URL: Issues, https://github.com/blinkerbit/aird/issues
Project-URL: PyPI, https://pypi.org/project/aird/
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications :: File Sharing
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: tornado>=6.5.9
Requires-Dist: uvloop>=0.19.0; sys_platform == "linux"
Requires-Dist: ldap3>=2.9.1
Requires-Dist: aiofiles>=25.1.0
Requires-Dist: argon2-cffi>=23.1.0
Requires-Dist: requests>=2.34.2
Requires-Dist: urllib3>=2.8.0
Requires-Dist: cryptography>=50.0.2
Requires-Dist: chardet<8.0.0,>=7.6.0
Requires-Dist: pyasn1>=0.6.4
Requires-Dist: webauthn>=3.0.1
Requires-Dist: watchdog>=4.0.0
Requires-Dist: pywebpush>=2.5.0
Provides-Extra: compress
Requires-Dist: zstandard>=0.22.0; extra == "compress"
Provides-Extra: native
Requires-Dist: maturin<2,>=1.4; extra == "native"
Provides-Extra: chat
Requires-Dist: bleach>=6.0; extra == "chat"
Provides-Extra: all
Requires-Dist: zstandard>=0.22.0; extra == "all"
Requires-Dist: maturin<2,>=1.4; extra == "all"
Requires-Dist: bleach>=6.0; extra == "all"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Aird

<p align="center">
  <img src="aird/static/img/logo-full.svg" alt="Aird" width="280">
</p>

<p align="center">
  <a href="https://github.com/blinkerbit/aird/actions/workflows/ci.yml"><img src="https://github.com/blinkerbit/aird/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
  <a href="https://pypi.org/project/aird/"><img src="https://img.shields.io/pypi/v/aird" alt="PyPI"></a>
  <a href="https://pypi.org/project/aird/"><img src="https://img.shields.io/pypi/pyversions/aird" alt="Python versions"></a>
  <a href="https://sonarcloud.io/summary/new_code?id=blinkerbit_aird"><img src="https://sonarcloud.io/api/project_badges/measure?project=blinkerbit_aird&metric=alert_status" alt="Quality gate"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-BSL%201.1-blue" alt="License: BSL 1.1"></a>
</p>

![Aird demo](./demo.webp)

**Aird** is a self-hosted file browser, editor, and sharing platform built on **Python** and **Tornado**. It targets fast local and LAN use: parallel HTTP transfers for large files, real-time log streaming, content search, secure shares, optional multi-user isolation, direct messages between users, and an admin console that applies settings without restarts.

---

## Highlights

| Area | What you get |
|------|----------------|
| **File manager** | Browse, upload, download, rename, move, copy, bulk ops, in-browser text edit, ZIP download |
| **Large transfers** | Parallel **HTTP `Content-Range`** uploads/downloads (primary path for big files) |
| **Search** | **Super Search** — glob + regex content search with live WebSocket progress |
| **Direct messages** | Optional chat plugin — DMs, file/GIF attachments, realtime WebSocket, “From chat” on Shares |
| **Streaming** | Tail log files over WebSocket with filters |
| **Sharing** | Token-based public/private shares, static or live folder views |
| **Security** | CSRF, CSP, path traversal checks, optional **ABAC** policies, WebAuthn, LDAP |
| **Integrations** | Google Drive / OneDrive browse, P2P WebRTC rooms, optional embedded SMB & WebDAV |
| **Ops** | SQLite-backed settings, audit log, feature flags, transfer rate limits, health endpoint |

---

## File transfers

Large uploads are designed for **high-throughput links** (e.g. gigabit LAN or VPN):

1. **Small files** — single `POST /upload`.
2. **Large files** — client opens a range session (`POST /api/upload/range/session`), then sends many parallel **`PUT`** requests with `Content-Range`. Each chunk is written **in place** at the correct byte offset on disk (no separate part files, no concat step at the end).
3. **Downloads** — `GET /files/...?download=1` with optional `Range` for parallel fetch.

Defaults (all adjustable in **Admin → Upload settings**):

| Setting | Default | Notes |
|---------|---------|--------|
| Max file size | 10 240 MB (10 GiB) | Hard cap per file |
| Single-request max | 100 MB | Files **≥** this use parallel HTTP ranges |
| HTTP chunk size | 90 MB | Per range `PUT` body |
| HTTP parallelism | 16 | Concurrent upload streams |

**Behind nginx/Caddy:** set `client_max_body_size` (or equivalent) to at least your HTTP chunk size. If **Single-request max** is `0`, Aird uses a **100 MB** parallel threshold (proxy-safe), not a single POST for the entire max file size.

Optional WebSocket upload (`/ws/file-transfer`) remains for specialized paths; the browser UI uses **HTTP parallel** by default.

Transfer progress, cancel, and resume metadata are handled in the browser (`transfer-tracker.js`, `transfer-engine/`, service worker `sw-transfer.js`).

---

## Quick start

### Install

```bash
pip install aird

# Optional extras
pip install "aird[compress]"   # zstandard HTTP compression
pip install "aird[chat]"     # direct messages (bleach)
pip install "aird[all]"      # compress + native build tools + chat

# From source
git clone https://github.com/blinkerbit/aird.git
cd aird
pip install -e .
# With chat:
pip install -e ".[chat]"
```

**Python:** 3.10+ required. On Linux, **free-threaded** builds (`3.13t` / `3.14t`) are supported and recommended for parallel disk I/O; the server detects nogil at startup and sizes the I/O thread pool accordingly.

### Run

```bash
# Default: port 8000, current directory as root
python -m aird

# Custom root and port
python -m aird --root /data --port 8080

# Multi-user (per-user home directories under root)
python -m aird --root /data --multi-user

# TLS
python -m aird --ssl-cert /path/cert.pem --ssl-key /path/key.pem --port 443

# Worker processes (Linux; default is auto from CPU topology)
python -m aird --workers 4
```

On first start, random **access** and **admin** tokens are generated unless you set them via `--token`, `--admin-token`, `config.json`, or `AIRD_ACCESS_TOKEN`. Users log in with username/password; the access token is for API/CLI automation.

A port that is already taken stops the process. Set `AIRD_PORT_RETRY=1` to try the next two ports.

Open `http://localhost:8000/` → redirects to `/files/`.

### CLI client

```bash
pip install aird   # includes aird-cli
aird-cli config set server https://your-host
aird-cli login
aird-cli ls /
```

---

## Direct messages (optional)

Requires `pip install "aird[chat]"` and **Admin → Direct messages** enabled (feature flag `direct_messages`).

| Feature | Details |
|---------|---------|
| **UI** | `/chat` — conversation list, rich-text composer, browse/upload/GIF attachments |
| **Realtime** | WebSocket `/ws/chat` (messages, read receipts, notifications) |
| **File shares** | Copies land in recipient `{home}/.aird-shares/{sender}/` (hidden from browse root) |
| **Shares page** | “From chat” section — grouped by sender, recipient can delete received files |
| **Notifications** | Nav badge, in-app toasts, optional browser notifications (profile toggle) |

Logged-in DB/LDAP users only (not token-only sessions).

---

## GitLab (experimental)

Enable under **Admin → Plugins (experimental)**. Flag `gitlab_integration`.

- Bind a folder to a **code** project (CI, MRs) and a possibly different **issues** project (board, tickets).
- Folder owner: save a PAT on the machine (`{home}/.aird/secrets/gitlab_token`) or use `GITLAB_TOKEN` / `glab`. Aird calls GitLab on the server.
- Share viewers: their PAT stays in the **browser** (`localStorage`). Aird never uses the owner token for them.
- File comments are stored in Aird and visible to anyone who can see the file. Promote a comment onto a GitLab issue.
- Copilot MCP: `aird-gitlab-mcp` (stdio). Set `AIRD_URL` and `AIRD_TOKEN`. Optional CORS helper: `aird-gitlab-mcp --bridge` on `127.0.0.1:8765`.

```json
{
  "mcpServers": {
    "aird-gitlab": {
      "command": "aird-gitlab-mcp",
      "env": { "AIRD_URL": "http://127.0.0.1:8000", "AIRD_TOKEN": "…" }
    }
  }
}
```

---

## Configuration

### `config.json`

```json
{
  "port": 8000,
  "root": "/srv/files",
  "hostname": "files.example.com",
  "token": "your-access-token",
  "admin_token": "your-admin-token",
  "multi_user": false,
  "workers": 2,
  "ldap": false,
  "ldap_server": "ldaps://directory.example.com:636",
  "ldap_base_dn": "dc=example,dc=com",
  "feature_flags": {
    "file_upload": true,
    "super_search": true,
    "direct_messages": false,
    "gitlab_integration": false,
    "abac_engine": false
  }
}
```

```bash
python -m aird --config /etc/aird/config.json
```

### Environment variables (common)

| Variable | Purpose |
|----------|---------|
| `AIRD_ACCESS_TOKEN` | Login / API token (required in multi-user mode) |
| `AIRD_COOKIE_SECRET` | Persistent session signing (set in production) |
| `AIRD_LDAP_CA_FILE` | PEM CA for LDAPS when the directory certificate is private |
| `AIRD_CORPORATE_IP_CIDRS` | Comma-separated CIDRs for ABAC / WAN compression rules |
| `AIRD_GDRIVE_ACCESS_TOKEN` / `AIRD_ONEDRIVE_ACCESS_TOKEN` | Cloud providers |

### Admin console

`GET /admin` — feature flags, upload limits, extension allow-list, WebSocket pool limits, LDAP, network shares, ABAC policies/tags, users, audit. Changes persist to SQLite and apply without restart (feature-flag subscribers refresh over `/features` WebSocket).

---

## Production deployment

### Reverse proxy

Aird listens on one port (default **8000**). Terminate TLS at **Caddy**, **nginx**, or similar.

**nginx example** (adjust chunk size to match Admin → HTTP chunk):

```nginx
client_max_body_size 128m;

location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
```

WebSocket routes (`/stream/`, `/search/ws`, `/features`, `/ws/chat`, `/ws/…`) need `Upgrade` and `Connection` headers if you proxy them.

### Ubuntu deploy script

`deploy_local.ps1` (run from Windows) syncs source or a wheel to a remote host, creates a **uv** venv with **Python 3.14t**, and installs the package. See `docs/wireguard-deploy.md` for VPN-only TLS layouts.

### LDAP

LDAP login is optional (`--ldap` or `"ldap": true` in `config.json`). The directory URL is `ldap_server` / `--ldap-server`.

Production binds should use **LDAPS** so the password is not on the wire:

```bash
python -m aird --ldap \
  --ldap-server ldaps://directory.example.com:636 \
  --ldap-base-dn "dc=example,dc=com" \
  --ldap-user-template "uid={username},ou=users,dc=example,dc=com"
```

`ldaps://` turns on TLS and certificate checks (`ssl.CERT_REQUIRED`, including the hostname). Trust comes from the host store, or from `AIRD_LDAP_CA_FILE` when the directory uses a private CA. `ldap://` is cleartext and is only appropriate on a network you already trust. Proxy TLS in front of Aird does not cover the LDAP connection.

Config keys: `ldap`, `ldap_server`, `ldap_base_dn`, `ldap_user_template`, `ldap_filter_template`, `ldap_attributes`, `ldap_attribute_map`. Admin group sync uses the same `ldaps://` URL. Details: [docs/admin-ldap.md](docs/admin-ldap.md).

### Systemd

Run `python -m aird` (or the venv equivalent) as a service user with `--config` pointing at your JSON file. Logs go to the data directory (`aird.log` under the platform app data path).

---

## API

Swagger UI is served by the running server at [`/api/docs`](http://localhost:8000/api/docs). The OpenAPI document is [`/api/openapi.json`](http://localhost:8000/api/openapi.json). That document is the contract a native client (including a future Android app) uses against this same process. Browser pages call those routes today.

`POST /api/auth/login` with `{"username","password"}` returns a `token`. Send it as `Authorization: Bearer`. That call, and later bearer calls, do not use the `_xsrf` cookie. Cookie sessions still do. Send `Accept: application/json` so file-change errors are JSON.

## API overview

Authentication: session cookie after `/login`, bearer token, or `Authorization` header where supported. Mutating requests require the `_xsrf` cookie + `X-XSRFToken` header.

| Operation | Method / path |
|-----------|----------------|
| List directory | `GET /api/files/{path}` |
| Upload (small) | `POST /upload` |
| Upload (large) | `POST /api/upload/range/session` then `PUT /api/upload/range/{id}` |
| Upload status | `GET /api/upload/range/{id}/status` |
| Download | `GET /files/{path}?download=1` |
| Edit | `GET /edit/{path}`, `POST /edit` |
| Delete / rename / mkdir | `POST /delete`, `POST /rename`, `POST /mkdir` |
| Search UI | `GET /search` |
| Search (live) | WebSocket `/search/ws` |
| Log stream | WebSocket `/stream/{path}` |
| Shares | `POST /share/create`, `GET /share/list`, … |
| Chat (optional) | `GET /chat`, `GET/POST /api/chat/conversations`, WebSocket `/ws/chat` |
| GitLab (optional) | `/api/gitlab/*`, WebSocket `/ws/file-comments` |
| Health | `GET /health` |

Page-level UI contracts and routes are documented under [`docs/`](docs/README.md).

---

## Architecture notes

- **Async I/O:** Tornado event loop; on Linux optionally **uvloop**.
- **Free-threaded Python:** SQLite access is wrapped (`aird/db/sync.py`); upload chunk writes use `asyncio.to_thread` / `os.pwrite` so parallel range PUTs do not block the loop.
- **HTTP compression:** `gzip` by default; optional `zstandard` via `pip install aird[compress]` (loaded only on builds where the extension is nogil-safe).
- **Security headers:** COOP/COEP/CORP for transfer workers; strict CSP on HTML pages.
- **ABAC:** Optional policy engine (`abac_engine` flag) with admin-defined policies, tags, and user attributes.
- **Plugins:** Optional features (e.g. chat) live under `aird/plugins/` and register routes from `main.py`. An alternate **microservices** layout exists under `aird/ms/` for split deployment behind Caddy (not used by default `python -m aird`).

---

## Development

### Frontend assets

```bash
npm install
npm run css:build          # Tailwind → aird/static/css/app.css
npm run js:share           # Bundle share UI → aird/static/js/share/app.js
npm run vendor:fflate      # Compression worker dependency
```

### Tests

```bash
python -m pytest tests/
```

### Project layout

```
aird/
  handlers/       # HTTP & WebSocket handlers (monolith)
  plugins/        # Optional features (e.g. chat)
  ms/             # Starlette microservices (split deploy)
  services/       # Config, quota, share, audit, …
  static/js/      # Browser UI & transfer engine
  templates/      # Jinja2 pages
  core/           # Compression, mmap, rate limits, …
docs/             # Per-page UI & admin documentation
```

---

## Security

- Path traversal and symlink checks on file access
- Argon2 password hashing for local users
- CSRF on state-changing requests
- Upload extension allow-list (or allow-all) with max size enforced server-side
- Optional storage quotas (multi-user)
- Audit trail and live policy-decision stream for ABAC

For production, set `AIRD_COOKIE_SECRET`, use TLS, and restrict admin routes. Report vulnerabilities through [SECURITY.md](SECURITY.md).

## Contributing

Local setup, tests, and the `dev` → `pre-release` → `main` flow are in [CONTRIBUTING.md](CONTRIBUTING.md). Issues labeled [`good first issue`](https://github.com/blinkerbit/aird/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) are a starting point.

---

## License

**Business Source License 1.1 (BSL)** — see [LICENSE](LICENSE). Converts to **Apache 2.0** after the change date in the license file.

Commercial **File-Management-as-a-Service** use requires a separate license. Contact **Viswanatha Srinivas P**.

---

## Links

- **Repository:** https://github.com/blinkerbit/aird  
- **PyPI:** https://pypi.org/project/aird/  
- **UI / admin docs:** [docs/README.md](docs/README.md) · [docs/transfers.md](docs/transfers.md)  
- **Issues:** https://github.com/blinkerbit/aird/issues  

---

Made by **Viswanatha Srinivas P**
