# FTP / SFTP Connector — Setup Guide

## Prerequisites

- [ ] FTP or SFTP server hostname and port
- [ ] Service account credentials (username + password)
- [ ] Remote directory path where files are deposited
- [ ] For SFTP: the `[sftp]` extra installed: `uv add entr-adapter-core[sftp]`
- [ ] For FTP: no extra needed (uses Python stdlib `ftplib`)
- [ ] Network access: firewall rules allow outbound connections to the server

## Configuration

Add these settings to your adapter's `config.py` and `.env`:

| Env Var | Type | Required | Default | Example | Description |
|---------|------|----------|---------|---------|-------------|
| `FTP_HOST` | str | Yes | — | `ftp.client.com` | Server hostname or IP |
| `FTP_PORT` | int | No | 21 (FTP) / 22 (SFTP) | `22` | Server port |
| `FTP_USERNAME` | str | No | `anonymous` | `entr-service` | Login username |
| `FTP_PASSWORD` | str | No | `""` | `secret123` | Login password (required for SFTP) |
| `FTP_DIRECTORY` | str | No | `/` | `/invoices/incoming` | Remote directory to monitor |
| `FTP_USE_SFTP` | bool | No | `false` | `true` | Use SFTP instead of FTP |

**In `config.py`:**

```python
from pydantic_settings import BaseSettings
from entr_adapter_core.config import CoreSettings

class Settings(CoreSettings):
    FTP_HOST: str
    FTP_PORT: int = 21
    FTP_USERNAME: str = "anonymous"
    FTP_PASSWORD: str = ""
    FTP_DIRECTORY: str = "/"
    FTP_USE_SFTP: bool = False
```

**In `.env`:**

```env
FTP_HOST=sftp.client.com
FTP_PORT=22
FTP_USERNAME=entr-service
FTP_PASSWORD=secret123
FTP_DIRECTORY=/invoices/incoming
FTP_USE_SFTP=true
```

### Using Config Models (alternative)

```python
from entr_adapter_core.connectors.ftp import FTPConnectorConfig
from entr_adapter_core.connectors.sftp import SFTPConnectorConfig

# For FTP
ftp_config = FTPConnectorConfig(
    host="ftp.client.com",
    port=21,
    username="entr-service",
    password="secret123",
)
ftp = FTPConnector(ftp_config)

# For SFTP
sftp_config = SFTPConnectorConfig(
    host="sftp.client.com",
    port=22,
    username="entr-service",
    password="secret123",
)
sftp = SFTPConnector(sftp_config)
```

## Registration

Wire the FTP/SFTP connector in your adapter's `main.py` or handler:

```python
from entr_adapter_core.connectors.ftp import FTPConnector
# or for SFTP:
from entr_adapter_core.connectors.sftp import SFTPConnector

from app.config import Settings, get_settings

settings = get_settings()

# Create the connector
if settings.FTP_USE_SFTP:
    file_connector = SFTPConnector(
        host=settings.FTP_HOST,
        port=settings.FTP_PORT,
        username=settings.FTP_USERNAME,
        password=settings.FTP_PASSWORD,
    )
else:
    file_connector = FTPConnector(
        host=settings.FTP_HOST,
        port=settings.FTP_PORT,
        username=settings.FTP_USERNAME,
        password=settings.FTP_PASSWORD,
    )

# Use in your handler
class MyHandler(BaseHandler):
    def __init__(self, settings, **kwargs):
        super().__init__(settings, **kwargs)
        self.file_connector = file_connector

    async def process(self, payload):
        files = await self.file_connector.list_files(
            settings.FTP_DIRECTORY,
            extension_filter=".csv",
        )
        # ... process files
```

## Verification

**1. Test FTP connection (list files):**

```python
# In a Python shell or test script:
import asyncio
from entr_adapter_core.connectors.ftp import FTPConnector

connector = FTPConnector(host="ftp.client.com", username="user", password="pass")
files = asyncio.run(connector.list_files("/invoices"))
print(f"Found {len(files)} files")
```

**2. Expected success log output:**

```
INFO  Listing files in /invoices on FTP server ftp.client.com
INFO  Found 3 files in /invoices
```

**3. For SFTP, expected connection log:**

```
INFO  Attempting SFTP connection to sftp.client.com:22 (attempt 1/3)
INFO  SFTP connection established successfully
```

**4. Common failure modes:**

| Error | Cause | Fix |
|-------|-------|-----|
| `Connection refused` | Server not reachable or wrong port | Check hostname, port, and firewall rules |
| `Authentication failed` | Wrong username/password | Verify credentials with the client |
| `SFTP host is required` | Missing host configuration | Set `FTP_HOST` in `.env` |
| `SFTP password is required` | Missing password for SFTP | Set `FTP_PASSWORD` in `.env` |
| `ImportError: ...paramiko...` | Missing SFTP extra | Run `uv add entr-adapter-core[sftp]` |
| `FileNotFoundError` | Remote directory doesn't exist | Verify `FTP_DIRECTORY` path with client |

## Common Pitfalls

1. **SFTP vs FTP**: These are entirely different protocols. SFTP uses SSH (port 22), FTP uses its own protocol (port 21). Don't confuse them — the wrong protocol will fail to connect.
2. **Passive vs active mode**: FTP passive mode (default in ftplib) requires the server to open data ports. Some firewalls block these. If connections hang during file listing, ask the client's IT team about passive mode support.
3. **File locking**: Some FTP servers lock files during upload. If you download a partially uploaded file, you'll get corrupted data. Coordinate with the client on pickup timing or use a staging directory pattern.
4. **Character encoding in filenames**: FTP servers on Windows may use Latin-1 for filenames. SFTP typically uses UTF-8. If filenames appear garbled, check the server's encoding.
5. **Connection persistence**: `SFTPConnector` maintains a persistent connection with auto-reconnect. `FTPConnector` opens a new connection per operation. For high-frequency operations, prefer SFTP.
6. **Timeouts**: SFTP reconnection uses exponential backoff (2s, 4s, 8s). If the server is slow, increase `max_retries` in your connection logic.

## Client Onboarding Template

Send this checklist to the client's IT team before integration:

- [ ] **Protocol**: FTP or SFTP? (determines which connector and dependencies to use)
- [ ] **Server details**: hostname, port (default 21 for FTP, 22 for SFTP)
- [ ] **Service account**: username and password for ENTR to connect
- [ ] **Directory path**: where should ENTR look for files? (e.g., `/invoices/incoming`)
- [ ] **IP whitelist**: does the server require source IP whitelisting? If so, provide ENTR's outbound IP
- [ ] **File naming convention**: what pattern do filenames follow? (e.g., `INV-*.csv`, `order_*.pdf`)
- [ ] **File extension filter**: which file types should ENTR process? (e.g., `.csv`, `.pdf`, `.xml`)
- [ ] **Pickup behavior**: should ENTR delete files after processing, or move to an archive directory?
- [ ] **Transfer window**: is there a time window when files are guaranteed to be complete (not mid-upload)?
- [ ] **Firewall rules**: ensure outbound access from ENTR's IP to the server's port is allowed
