Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:24-alpine

WORKDIR /app
COPY . .

COPY .env.template .env
COPY .npmrc.template .npmrc

RUN yarn install
RUN yarn build

RUN chmod +x /app/docker/entrypoint.sh
RUN ln -s '/app/bin/run.js' /usr/local/bin/internxt

ENTRYPOINT ["/app/docker/entrypoint.sh"]
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ EXAMPLES
$ internxt autocomplete --refresh-cache
```

_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.2.34/src/commands/autocomplete/index.ts)_
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.2.35/src/commands/autocomplete/index.ts)_

## `internxt config`

Expand Down Expand Up @@ -416,12 +416,14 @@ Logs into an Internxt account. If the account is two-factor protected, then an e

```
USAGE
$ internxt login [--json] [-x] [-e <value>] [-p <value>] [-w 123456]
$ internxt login [--json] [-x] [-e <value>] [-p <value>] [-w 123456] [-t token]

FLAGS
-e, --email=<value> The email to log in
-p, --password=<value> The plain password to log in
-w, --twofactor=123456 The two factor auth code (only needed if the account is two-factor protected)
-e, --email=<value> The email to log in
-p, --password=<value> The plain password to log in
-t, --twofactortoken=token The TOTP secret token. It is used to generate a TOTP code if needed. It has prority over
the two factor code flag.
-w, --twofactor=123456 The two factor auth code (TOTP).

HELPER FLAGS
-x, --non-interactive Prevents the CLI from being interactive. When enabled, the CLI will not request input through
Expand Down
115 changes: 115 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Internxt WebDAV (Docker Integration)

This repository provides a **Dockerized version of Internxt WebDAV**, making it easy to run on **NAS devices**, **servers**, or any environment where Docker is supported.
With this image, you can quickly deploy and access your Internxt files over the WebDAV protocol.

---

## 🚀 Quick Start

### Using Docker Compose

```yaml
services:
internxt-webdav:
image: internxt/webdav:latest
container_name: internxt-webdav
restart: unless-stopped
environment:
INXT_USER: "" # Your Internxt account email
INXT_PASSWORD: "" # Your Internxt account password
INXT_TWOFACTORCODE: "" # (Optional) Current 2FA one-time code
INXT_OTPTOKEN: "" # (Optional) OTP secret for auto-generating 2FA codes
WEBDAV_PORT: "" # (Optional) WebDAV port. Defaults to 3005 if empty
WEBDAV_PROTOCOL: "" # (Optional) WebDAV protocol. Accepts 'http' or 'https'. Defaults to 'https' if empty
ports:
- "3005:3005" # Map container port to host. Change if WEBDAV_PORT is customized
```

Start it detached with:

```bash
docker compose up -d
```

### Using Docker CLI:

```bash
docker run -d \
--name internxt-webdav \
--restart unless-stopped \
-e INXT_USER="your@email.com" \
-e INXT_PASSWORD="your_password" \
-e INXT_TWOFACTORCODE="" \
-e INXT_OTPTOKEN="" \
-e WEBDAV_PORT="" \
-e WEBDAV_PROTOCOL="" \
-p 3005:3005 \
internxt/webdav:latest
```

### Using Docker on NAS Devices

You can also run the `internxt/webdav` image directly on popular NAS devices like **Synology** or **QNAP**.

**Synology DSM (Docker Package):**

1. Open the Docker app.
2. Go to **Registry**, search for `internxt/webdav`, and download the latest image.
3. Go to **Image**, select `internxt/webdav`, and click **Launch**.
4. Configure environment variables (`INXT_USER`, `INXT_PASSWORD`, etc.) and port mappings (e.g., `3005:3005`).
5. Start the container.

**QNAP Container Station:**

1. Open Container Station.
2. Click **Create Container** and search for `internxt/webdav`.
3. Select the latest image and click **Next**.
4. Set environment variables (`INXT_USER`, `INXT_PASSWORD`, etc.) and port mappings.
5. Apply settings and start the container.


## 🔑 Authentication & Environment Variables

| Variable | Required | Description |
|----------------------|----------|------------------------------------------------------------------------------------------------|
| `INXT_USER` | ✅ Yes | Your Internxt account email. |
| `INXT_PASSWORD` | ✅ Yes | Your Internxt account password. |
| `INXT_TWOFACTORCODE` | ❌ No | Temporary one-time code from your 2FA app. Must be refreshed every startup. |
| `INXT_OTPTOKEN` | ❌ No | OTP secret key (base32). Used to auto-generate fresh codes at runtime. |
| `WEBDAV_PORT` | ❌ No | Port for the WebDAV server. Defaults to `3005` if left empty. |
| `WEBDAV_PROTOCOL` | ❌ No | Protocol for the WebDAV server. Accepts `http` or `https`. Defaults to `https` if left empty. |


---

### 🔄 2FA Options Explained

If your Internxt account has **two-factor authentication enabled**, you can choose one of the following:

| Option | What it is | When to use it | Limitation |
|-------------------------|--------------------------------------------------------------|------------------------------------------|------------|
| **`INXT_TWOFACTORCODE`** | A **temporary code** generated by your authenticator app (e.g., Google Authenticator, Authy). | Use this if you want to provide the code manually. | Must be updated each time the container restarts. |
| **`INXT_OTPTOKEN`** | The **private OTP secret** (the key you scan when setting up 2FA). | Use this for unattended containers. The server will generate fresh codes automatically. | If set, `INXT_TWOFACTORCODE` will be ignored. |

💡 **Recommended:** Use `INXT_OTPTOKEN` if you want your container to run unattended without re-entering codes on each restart.


## 🌐 Accessing WebDAV

Once running, your Internxt WebDAV server will be available at:

```
https://127.0.0.1:3005
```

## 📘 WebDAV Reference

For more details about WebDAV usage, configuration, and troubleshooting, please refer to the official WebDAV readme:

[WebDAV Readme](https://github.com/internxt/cli/blob/main/WEBDAV.md)

## 🤝 Contributing

Contributions, issues, and feature requests are welcome!
Feel free to open an issue or submit a pull request.
45 changes: 45 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/sh

set -e

if [ -z "$INXT_USER" ] || [ -z "$INXT_PASSWORD" ]; then
echo "Error: INXT_USER and INXT_PASSWORD environment variables must be set."
exit 1
fi


echo "Logging into your account [$INXT_USER]"

LOGIN_CMD="internxt login -x -e=\"$INXT_USER\" -p=\"$INXT_PASSWORD\""

if [ -n "$INXT_OTPTOKEN" ]; then
echo "Using 2FA secret token"
LOGIN_CMD="$LOGIN_CMD -t=\"$INXT_OTPTOKEN\""
elif [ -n "$INXT_TWOFACTORCODE" ]; then
echo "Using 2FA code"
LOGIN_CMD="$LOGIN_CMD -w=\"$INXT_TWOFACTORCODE\""
fi

eval $LOGIN_CMD


WEBDAV_CMD="internxt webdav-config"

if [ -n "$WEBDAV_PORT" ]; then
WEBDAV_CMD="$WEBDAV_CMD -p=$WEBDAV_PORT"
fi

proto=$(echo "$WEBDAV_PROTOCOL" | tr '[:upper:]' '[:lower:]')
if [ "$proto" = "http" ]; then
WEBDAV_CMD="$WEBDAV_CMD -h"
elif [ "$proto" = "https" ]; then
WEBDAV_CMD="$WEBDAV_CMD -s"
fi

eval $WEBDAV_CMD

internxt webdav enable

mkdir -p /root/.internxt-cli/logs
touch /root/.internxt-cli/logs/internxt-webdav-combined.log
tail -f /root/.internxt-cli/logs/internxt-webdav-combined.log
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
"@internxt/inxt-js": "2.2.9",
"@internxt/lib": "1.3.1",
"@internxt/sdk": "1.11.11",
"@oclif/core": "4.5.3",
"@oclif/plugin-autocomplete": "3.2.34",
"@oclif/core": "4.5.4",
"@oclif/plugin-autocomplete": "3.2.35",
"axios": "1.12.2",
"bip39": "3.1.0",
"body-parser": "2.2.0",
Expand All @@ -54,7 +54,7 @@
"mime-types": "3.0.1",
"openpgp": "6.2.2",
"otpauth": "9.4.1",
"pm2": "6.0.11",
"pm2": "6.0.13",
"range-parser": "1.2.1",
"selfsigned": "3.0.1",
"tty-table": "4.2.3",
Expand All @@ -73,9 +73,9 @@
"@vitest/spy": "3.2.4",
"eslint": "9.36.0",
"husky": "9.1.7",
"lint-staged": "16.1.6",
"lint-staged": "16.2.3",
"nodemon": "3.1.10",
"oclif": "4.22.22",
"oclif": "4.22.27",
"prettier": "3.6.2",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
Expand Down
28 changes: 24 additions & 4 deletions src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfigService } from '../services/config.service';
import { ValidationService } from '../services/validation.service';
import { CLIUtils } from '../utils/cli.utils';
import { SdkManager } from '../services/sdk-manager.service';
import * as OTPAuth from 'otpauth';

export default class Login extends Command {
static readonly args = {};
Expand All @@ -17,7 +18,7 @@ export default class Login extends Command {
email: Flags.string({
char: 'e',
aliases: ['mail'],
env: 'INXT_EMAIL',
env: 'INXT_USER',
description: 'The email to log in',
required: false,
}),
Expand All @@ -32,10 +33,20 @@ export default class Login extends Command {
char: 'w',
aliases: ['two', 'two-factor'],
env: 'INXT_TWOFACTORCODE',
description: 'The two factor auth code (only needed if the account is two-factor protected)',
description: 'The two factor auth code (TOTP). ',
required: false,
helpValue: '123456',
}),
twofactortoken: Flags.string({
char: 't',
aliases: ['otp', 'otp-token'],
env: 'INXT_OTPTOKEN',
description:
'The TOTP secret token. It is used to generate a TOTP code if needed.' +
' It has prority over the two factor code flag.',
required: false,
helpValue: 'token',
}),
};
static readonly enableJsonFlag = true;

Expand All @@ -49,7 +60,16 @@ export default class Login extends Command {
const is2FANeeded = await AuthService.instance.is2FANeeded(email);
let twoFactorCode: string | undefined;
if (is2FANeeded) {
twoFactorCode = await this.getTwoFactorCode(flags['twofactor'], nonInteractive);
const twoFactorToken = flags['twofactortoken'];
if (twoFactorToken && twoFactorToken.trim().length > 0) {
const totp = new OTPAuth.TOTP({
secret: twoFactorToken,
digits: 6,
});
twoFactorCode = totp.generate();
} else {
twoFactorCode = await this.getTwoFactorCode(flags['twofactor'], nonInteractive);
}
}

const loginCredentials = await AuthService.instance.doLogin(email, password, twoFactorCode);
Expand Down Expand Up @@ -131,7 +151,7 @@ export default class Login extends Command {
{
nonInteractive,
prompt: {
message: 'What is your two-factor token?',
message: 'What is your two-factor code?',
options: { type: 'mask' },
},
},
Expand Down
Loading