Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Internxt Python CLI

A Python implementation of the Internxt CLI for encrypted cloud storage with path-based operations, timestamp preservation, and a built-in WebDAV server.

⚠️ Disclaimer

This is an unofficial, open-source project and is not affiliated with, endorsed by, or supported by Internxt, Inc. It is a personal project built for learning and to provide an alternative interface. Use it at your own risk.

✨ Features

🌐 WebDAV Server

  • Mount as a local drive: Access your Internxt Drive directly from Finder, File Explorer, or any WebDAV client.
  • Cross-platform support: Works on Windows, macOS, and Linux.
  • Stable and Compatible: Uses waitress (recommended) or cheroot for the best client compatibility.
  • Server Choice: Force waitress or cheroot with the --server flag for debugging.

🛣️ Path-Based Operations

  • Human-readable paths: Use /Documents/report.pdf instead of UUIDs.
  • Fuzzy Search: Instantly find files and folders with a fast, server-side search command.
  • Wildcard Find: Use find with patterns like *.pdf, report*, etc.
  • Tree visualization: See your folder structure at a glance.
  • Path navigation: Browse folders like your local filesystem.

🔐 Core Functionality

  • Timestamp Preservation: Preserves original file modification/creation dates on upload and download-path.
  • Secure authentication: Login/logout with 2FA support.
  • File operations: Upload, download with progress indicators.
  • Stream from stdin (rcat): Pipe data straight to a Drive file (rclone-rcat style) for unattended backups — mariadb-dump | xz | cli.py rcat /backups/db.xz.
  • Large files: Streaming encrypt/upload and decrypt/download keep memory bounded (a few MB) regardless of file size; files ≥ 100 MiB automatically use true multipart upload (30 MB parts, uploaded in parallel and each retried independently) for resilient transfers on slow/flaky connections. Parallel ranged downloads are available opt-in via --ranged.
  • Resumable uploads: Interrupted multipart uploads (≥ 100 MiB) are checkpointed as parts complete — a failed part is first repaired in-session (the AES-CTR keystream is seekable, so only that part is re-encrypted and re-PUT), and if the process dies, re-running the same upload resumes it: already-uploaded parts are skipped (no official Internxt client can do this). Resume works until the presigned URLs expire, then falls back cleanly to a fresh upload. Opt out with --no-resume.
  • Folder management: Create and organize folders.
  • Zero-knowledge encryption: AES-256-CTR client-side encryption.

🚀 Quick Start

# Install dependencies
pip install -r requirements.txt
# (Recommended for WebDAV)
pip install waitress
# Login to your account
python cli.py login
# Mount your drive locally! (EASIEST WAY TO USE)
python cli.py webdav-start
# Or, use path-based commands
python cli.py list-path
python cli.py search "report"
python cli.py find /Documents "*.pdf"
python cli.py upload ./my-docs --target /Backups

📖 Usage Guide

🔐 Authentication

# Login with interactive prompts
python cli.py login
# Login non-interactively
python cli.py login --email user@example.com --password mypass --2fa 123456
# Check current user
python cli.py whoami
# Logout and clear credentials
python cli.py logout

Supplying credentials securely

Ways to provide the password, most to least secure:

# 1. Pipe via stdin — never appears in shell history, argv, or the process listprintf'%s'"$MY_PASSWORD"| python cli.py login -e you@example.com --password-stdin
# 2. Environment variables (good for CI; avoid `export`-ing into your shell rc)
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$MY_PASSWORD" python cli.py login
# 3. The -e/-p flags ⚠️ leak into shell history and `ps` — avoid on shared hosts

For 2FA in automation, pass the TOTP secret instead of a one-off code: --tfa-secret (or INTERNXT_TFA_SECRET) auto-generates the 6-digit code.

Implicit auto-login. You don't strictly need to run login first: when no valid session is stored, every command (e.g. rcat) auto-logs-in from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET if 2FA is on). This also kicks in transparently if a stored token has expired. Set INTERNXT_DEBUG=1 to surface the internal auth trace (off by default; it goes to stderr so it never pollutes piped output).

How the session is stored

After login the session (which includes your mnemonic — the key to all your files) is encrypted at rest in ~/.internxt-cli/.inxtcli (file 0600, dir 0700). The encryption key is sourced in this order:

  1. OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager) via keyring — a random per-install key; recommended. Install with pip install keyring if not already present.
  2. INTERNXT_CREDENTIALS_KEY env var — a key you supply (handy for CI where there's no desktop keychain).
  3. Legacy static key — obfuscation only; used as a last resort if neither of the above is available. The 0600 permission is your real protection here.

Set INTERNXT_NO_KEYRING=1 to skip the OS keychain (forces option 2/3). Legacy credential files from older versions are read and upgraded automatically.

🌐 WebDAV Server

Mount your Internxt Drive as a local disk.

# Start the WebDAV server (it will print the URL and credentials)
python cli.py webdav-start
# Start in the background
python cli.py webdav-start --background
# Force a specific server (e.g., cheroot for SSL)
python cli.py webdav-start --server cheroot
# Check if the server is running
python cli.py webdav-status
# Stop the server
python cli.py webdav-stop
# Show mount instructions for your OS
python cli.py webdav-mount
# Test if the server is responding correctly
python cli.py webdav-test
# Show full WebDAV configuration and paths
python cli.py webdav-config
# Show advanced debugging info
python cli.py webdav-debug
# Regenerate SSL certs
python cli.py webdav-regenerate-ssl

After starting, open your file manager (Finder/File Explorer) or Client (like CyberDuck) and connect to the server (e.g., http://localhost:8080) with username internxt and password internxt-webdav.

🛣️ Path-Based Operations

List & Navigate

# List root folder with readable paths
python cli.py list-path
# List specific folders
python cli.py list-path "/Documents"
python cli.py list-path "/Photos/2023/Summer"# Show detailed information (size, date)
python cli.py list-path "/Documents" --detailed
# Show folder structure as tree
python cli.py tree
python cli.py tree "/Projects" --depth 2

Search & Find

# Fast, global, server-side fuzzy search
python cli.py search "report"# Show full details (size, date, full path) (slow!)
python cli.py search "report" --detailed
# Slow, client-side wildcard find (POSIX-like syntax, slow!)
python cli.py find / "*.pdf"# All PDF files in entire drive
python cli.py find /Photos "*.jpg"# All JPGs in /Photos
python cli.py find ."report*"# Files starting with "report" in current path

⬆️⬇️ Upload & Download

Upload

# Upload a single file, preserving its timestamp
python cli.py upload ./local-report.pdf --target /Documents --preserve-timestamps
# Upload a whole folder recursively, preserving all timestamps
python cli.py upload ./my-project --target /Backups --preserve-timestamps
# Upload with filters and overwrite conflicts
python cli.py upload ./photos --target /Photos --include "*.jpg" --on-conflict overwrite
# Legacy shorthand is also accepted: final remote-looking argument becomes the target
python cli.py upload ./photos /Photos
# Windows: use forward slashes for Internxt paths; quote local paths with spaces
python cli.py upload "D:/alle fotos" --target "/Katharina" --on-conflict skip

Stream from stdin (rcat)

# Pipe a stream straight to a Drive file (rclone-rcat style) — great for# unattended backups without a named local file. REMOTE_PATH includes the# filename; the parent folder is created if missing.
mariadb-dump mydb | xz -6 | python cli.py rcat /backups/mydb.xz
tar czf - /etc | python cli.py rcat /backups/etc.tar.gz
# Auto-login: if you're not logged in, rcat (and the other commands) log in# from INTERNXT_EMAIL / INTERNXT_PASSWORD (+ INTERNXT_TFA_SECRET for 2FA), so a# pipeline needs no separate `login` step:
INTERNXT_EMAIL=you@example.com INTERNXT_PASSWORD="$PW" \
pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp
# ...or keep them separate and trap each phase:
python cli.py login -e you@example.com --password-stdin <<<"$PW" \
&& PGPASSWORD="$DBPW" pg_dump -Fc mydb | python cli.py rcat /backups/mydb.dmp \
&& python cli.py logout# Upstream passwords: while piping into rcat the dump tool has no terminal, so# it can't prompt for ITS OWN db password — supply it non-interactively, e.g.# PGPASSWORD / ~/.pgpass (Postgres) or MYSQL_PWD / ~/.my.cnf (MySQL/MariaDB).# Note: Internxt needs the exact size up front, so rcat first spools stdin to a# temp file to measure it, then encrypts+uploads in one pass. Ensure free temp# space for the whole stream (use --temp-dir to relocate the spool).

Download

# Download a file by path, preserving its timestamp
python cli.py download-path -p "/Documents/report.pdf"# Download a folder recursively to a local directory
python cli.py download-path -r "/Photos/2023" --destination ./My-Photos
# Download a folder with filters
python cli.py download-path -r "/Music" --include "*.mp3" --exclude "demo_*"# Opt in to parallel ranged downloads for large files (≥ 100 MiB). Off by# default; falls back to a single stream if the server ignores HTTP Range.
python cli.py download-path -p "/Backups/bigdump.xz" --ranged --chunk-workers 4

🗑️ Delete & Trash Operations

Move to Trash (Recoverable)

# Move to trash by path
python cli.py trash-path "/OldDocuments/outdated.pdf"
python cli.py trash-path "/TempFolder"

Permanent Delete (⚠️ Cannot Be Undone)

# Permanently delete by path (with warnings)
python cli.py delete-path "/TempFile.txt"

📁 Traditional Operations (UUID-based)

# List folders (old way with UUIDs)
python cli.py list
python cli.py list --folder-id <folder-uuid># Create folders
python cli.py mkdir "My New Folder"# Upload/Download by UUID (see path-based commands for more features)
python cli.py upload ./document.pdf
python cli.py download <file-uuid>

🔧 Utility Commands

# Show current configuration
python cli.py config
# Test CLI components
python cli.py test# Extended help with examples
python cli.py help-extended
# Debug path resolution
python cli.py resolve "/Documents/report.pdf"

📦 Installation

# Clone the repository
git clone https://github.com/CrispStrobe/internxt-python
cd internxt-python
# Install dependencies
pip install -r requirements.txt
# For the best WebDAV experience, install 'waitress'
pip install waitress # (Highly recommended for WebDAV)# Start using immediately
python cli.py login
python cli.py webdav-start

Requirements

  • Python 3.8 – 3.14
  • Dependencies: cryptography, mnemonic, tqdm, requests, click, WsgiDAV
  • WebDAV Server: waitress (recommended) or cheroot
  • OpenPGP login keys are generated by a built-in backend (uses cryptography; no extra dependency, all Python versions). Optional alternatives are auto-detected if installed: PGPy (Python < 3.13 only) or python-gnupg (needs a system gpg binary) — pip install '.[pgpy]' / '.[gnupg]'.

🔒 Security & Privacy

This CLI implements the same security model as official Internxt clients:

  • Client-side encryption: All files encrypted on your device before upload (AES-256-CTR).
  • Zero-knowledge: Internxt servers never see your unencrypted data or keys.
  • Secure Credentials: Encrypted and stored locally in ~/.internxt-cli/.

🏗️ Development

Project Structure

internxt-python/
├── cli.py # Main CLI interface with all commands
├── config/
│ └── config.py # Configuration management
├── services/
│ ├── auth.py # Authentication & login
│ ├── crypto.py # Encryption/decryption (AES-256-CTR)
│ ├── drive.py # Drive operations & path resolution
│ ├── network_utils.py # SSL cert lifecycle, range parsing
│ ├── webdav_provider.py # WsgiDAV provider for Internxt
│ └── webdav_server.py # WebDAV server management
├── utils/
│ └── api.py # HTTP API client
├── tests/ # Pytest suite (622 tests, 90% coverage)
├── pyproject.toml # Pytest, coverage, ruff config
├── requirements-dev.txt # Dev/test dependencies
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12

Development Setup

# Clone and setup development environment
git clone https://github.com/CrispStrobe/internxt-python.git
cd internxt-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# Install in development mode
pip install -e .
pip install -r requirements-dev.txt

Running Tests

The project ships with a 591-test unit suite at 90% line coverage (plus 31 optional live tests — 622 total). It covers crypto round-trips, path resolution, upload/download conflict handling, the WebDAV provider, all major CLI commands, and a real encrypt → upload → "wire" → download → decrypt round-trip cycle.

# Run the full test suite (~3 seconds)
pytest
# With coverage report
pytest --cov=services --cov=utils --cov=config
# Run a specific test file
pytest tests/test_crypto.py -v
# In-CLI smoke check (no test framework needed)
python cli.py test

Per-module coverage:

ModuleCoverage
services/auth.py100%
services/crypto.py100%
utils/api.py98%
services/webdav_provider.py91%
services/network_utils.py90%
services/drive.py89%
config/config.py85%
services/webdav_server.py83%
Total90%

Project documentation

FilePurpose
HISTORY.mdWhat's been done — full audit summary + every bug found and fixed during the test build-out.
PLAN.mdWhat's left — roadmap of unimplemented work: confirmed gaps vs. upstream internxt/cli, potential differentiators (folder copy, quota), WebDAV-providers reliability test, known maintenance debt.
LEARNINGS.mdLessons carried forward — what each audit tool actually catches, why unit tests miss certain bugs, safety patterns for live tests against real accounts.

Live integration smoke (optional)

tests/test_live_smoke.py is a 31-test suite that runs end-to-end against the real Internxt backend, covering: login, list, upload (small

  • unicode + extensionless + 2 MB), download with byte-for-byte verification, recursive folder creation, file rename/move/copy/update, folder rename/move, trash, server-side fuzzy search, and client-side wildcard find. Auto-skipped unless credentials are present.
# Put creds in a .env file (gitignored — never committed)echo'IXT_ACCOUNT=you@example.com'> .env
echo'IXT_PWD=your-password'>> .env
# Runs in ~60-90 seconds, always cleans up
pytest tests/test_live_smoke.py -v

All operations happen inside a unique sentinel folder (/__pytest_internxt_cli_smoke__/<run-uuid>/) which is always trashed at teardown — your real files are never touched. Every file/folder name within a test includes a UUID suffix so transient retries never collide. Transient API failures (rate-limit, eventual-consistency) are auto-retried via pytest-rerunfailures.

Quality Gates

The CI runs four gates on every push/PR:

ruff check .# lint
mypy --no-incremental cli.py services # types
bandit -r . -x ./.mypy_cache,./__pycache__,./.git,./tests -ll # security (medium+)
pytest --cov=services --cov=utils --cov=config # tests

All four must pass.

Getting Help

python cli.py --help
python cli.py help-extended
python cli.py <command> --help

📄 License

AGPL-3.0 license


Made with ❤️ for the Internxt community

About

(unofficial) python command line client for internxt, includes webdav server

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages