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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,8 @@ rate_limit:
# Configuration
FLAPI_CONFIG=path/to/flapi.yaml # Config file path
FLAPI_LOG_LEVEL=debug|info|warn|error
FLAPI_PORT=8080 # HTTP port (fallback for --port)
FLAPI_HOST=0.0.0.0 # Bind address (fallback for --host)

# Authentication
JWT_SECRET=your-secret-key # JWT signing key
Expand Down
59 changes: 58 additions & 1 deletion docs/CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This document provides a complete reference for the `flapi` server executable's
2. [Command-Line Options](#2-command-line-options)
- [Configuration File](#configuration-file---c---config)
- [Server Port](#server-port---p---port)
- [Bind Host](#bind-host---host)
- [Log Level](#log-level---log-level)
- [Validate Configuration](#validate-configuration---validate-config)
- [Configuration Service](#configuration-service---config-service)
Expand Down Expand Up @@ -134,11 +135,22 @@ Overrides the HTTP server port defined in the configuration file.
| Type | integer |
| Default | From config file (typically `8080`) |
| Required | No |
| Environment variable | `FLAPI_PORT` |

**Description:**

When specified, this option overrides the `http-port` value in the configuration file. Useful for running multiple instances or when port configuration needs to be dynamic.

**Precedence (highest wins):**
1. `-p` / `--port` CLI flag
2. `FLAPI_PORT` environment variable
3. `http-port` from `flapi.yaml`
4. Built-in default (`8080`)

Invalid `FLAPI_PORT` values (non-integer, `<1`, `>65535`) cause flapi to
exit with a single-line error -- a typo like `FLAPI_PORT=abc` surfaces
immediately rather than silently falling through to the config-file value.

**Example:**

```bash
Expand All @@ -147,9 +159,52 @@ When specified, this option overrides the `http-port` value in the configuration

# Override config file port
./flapi -c production.yaml --port 80

# 12-factor: pick up the port from the environment
export FLAPI_PORT=9000
./flapi
```

> **Implementation:** `src/main.cpp`, `src/api_server.cpp` | **Tests:** `test/integration/test_env_overrides.py`, `test/integration/conftest.py`

---

### Bind Host (`--host`)

Overrides the bind address (`http-host`) defined in the configuration file.

| Property | Value |
|----------|-------|
| Long form | `--host` |
| Type | string |
| Default | From config file (`0.0.0.0` if unset) |
| Required | No |
| Environment variable | `FLAPI_HOST` |

**Description:**

Controls which network interface the HTTP server binds on. Use
`127.0.0.1` to restrict access to the loopback interface only, or
`0.0.0.0` to accept connections on all interfaces.

**Precedence (highest wins):**
1. `--host` CLI flag
2. `FLAPI_HOST` environment variable
3. `http-host` from `flapi.yaml`
4. Built-in default (`0.0.0.0`)

**Example:**

```bash
# Loopback only (useful behind a reverse proxy)
./flapi --host 127.0.0.1

# 12-factor: pick up the host from the environment
export FLAPI_HOST=127.0.0.1
./flapi
```

> **Implementation:** `src/main.cpp`, `src/api_server.cpp` | **Tests:** `test/integration/conftest.py`
> **Implementation:** `src/main.cpp`, `src/api_server.cpp`, `src/config_manager.cpp` | **Tests:** `test/integration/test_env_overrides.py`

---

Expand Down Expand Up @@ -520,6 +575,8 @@ notarisation.
| Variable | Description | Used By |
|----------|-------------|---------|
| `FLAPI_CONFIG` | Path to `flapi.yaml` (fallback for `-c`) | `--config` fallback |
| `FLAPI_PORT` | HTTP server port (fallback for `-p` / `--port`); invalid values exit 1 | `--port` fallback |
| `FLAPI_HOST` | Bind address (fallback for `--host`) | `--host` fallback |
| `FLAPI_LOG_LEVEL` | Log verbosity (fallback for `--log-level`); invalid values exit 1 | `--log-level` fallback |
| `FLAPI_CONFIG_SERVICE_TOKEN` | Authentication token for configuration service API | `--config-service-token` fallback |
| `FLAPI_NO_TELEMETRY` | Disable telemetry when set to `1`, `true`, or `yes` | `--no-telemetry` fallback |
Expand Down
7 changes: 6 additions & 1 deletion docs/CONFIG_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ should not bake in.
| Env var | Read at | Effect | Precedence |
|---------|---------|--------|------------|
| `FLAPI_CONFIG` | startup | Path to `flapi.yaml` (fallback for `-c`) | CLI > env > `flapi.yaml` default |
| `FLAPI_PORT` | startup | HTTP server port (fallback for `-p` / `--port`) | CLI > env > `http-port` config > `8080`; invalid values exit non-zero |
| `FLAPI_HOST` | startup | Bind address (fallback for `--host`) | CLI > env > `http-host` config > `0.0.0.0` |
| `FLAPI_LOG_LEVEL` | startup | Log verbosity (fallback for `--log-level`) | CLI > env > `info` default; invalid values exit non-zero |
| `FLAPI_CONFIG_SERVICE_TOKEN` | startup | Bearer token for the management API (fallback for `--config-service-token`) | CLI > env > auto-generate |
| `FLAPI_NO_TELEMETRY` | startup | Disable PostHog telemetry (fallback for `--no-telemetry`) | CLI > env > config-file > enabled |
Expand Down Expand Up @@ -178,14 +180,16 @@ The main configuration file defines global settings, connections, and server beh
| `project-name` | string | - | Human-readable project name |
| `project-description` | string | - | Project description |
| `server-name` | string | `"localhost"` | Server hostname for generated URLs |
| `http-port` | integer | `8080` | HTTP server port |
| `http-port` | integer | `8080` | HTTP server port (overridable via `--port` / `FLAPI_PORT`) |
| `http-host` | string | `"0.0.0.0"` | Bind address (overridable via `--host` / `FLAPI_HOST`); use `127.0.0.1` to restrict to loopback |

**Example:**

```yaml
project-name: Customer API
project-description: REST API for customer data access
server-name: api.example.com
http-host: 0.0.0.0
http-port: 8080
```

Expand Down Expand Up @@ -1808,6 +1812,7 @@ flAPI supports both hyphenated and camelCase naming for backward compatibility:
| Configuration | Default Value |
|---------------|---------------|
| `http-port` | `8080` |
| `http-host` | `"0.0.0.0"` |
| `server-name` | `"localhost"` |
| `duckdb.db_path` | `:memory:` |
| `duckdb.access_mode` | `READ_WRITE` |
Expand Down
11 changes: 7 additions & 4 deletions src/api_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,19 +283,22 @@ void APIServer::run(int port) {
}

const auto& https = configManager->getHttpsConfig();
const std::string bind_host = configManager->getHttpHost();
if (https.enabled) {
CROW_LOG_INFO << "HTTPS enabled: serving TLS on port " << configManager->getHttpPort();
CROW_LOG_INFO << "HTTPS enabled: serving TLS on " << bind_host << ":" << configManager->getHttpPort();
CROW_LOG_DEBUG << " cert: " << https.ssl_cert_file;
CROW_LOG_DEBUG << " key: " << https.ssl_key_file;
app.port(configManager->getHttpPort())
app.bindaddr(bind_host)
.port(configManager->getHttpPort())
.server_name("flAPI")
.multithreaded()
.use_compression(crow::compression::GZIP)
.ssl_file(https.ssl_cert_file, https.ssl_key_file)
.run();
} else {
CROW_LOG_INFO << "Server starting on port " << configManager->getHttpPort() << "...";
app.port(configManager->getHttpPort())
CROW_LOG_INFO << "Server starting on " << bind_host << ":" << configManager->getHttpPort() << "...";
app.bindaddr(bind_host)
.port(configManager->getHttpPort())
.server_name("flAPI")
.multithreaded()
.use_compression(crow::compression::GZIP)
Expand Down
4 changes: 4 additions & 0 deletions src/config_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,11 @@ void ConfigManager::parseMainConfig() {
project_description = safeGet<std::string>(config, "project-description", "project-description");
server_name = safeGet<std::string>(config, "server-name", "server-name", "localhost");
http_port = safeGet<int>(config, "http-port", "http-port", 8080);
http_host = safeGet<std::string>(config, "http-host", "http-host", "0.0.0.0");

CROW_LOG_DEBUG << "Project Name: " << project_name;
CROW_LOG_DEBUG << "Server Name: " << server_name;
CROW_LOG_DEBUG << "HTTP Host: " << http_host;
CROW_LOG_DEBUG << "HTTP Port: " << http_port;

parseHttpsConfig();
Expand Down Expand Up @@ -1184,6 +1186,8 @@ std::string ConfigManager::getProjectDescription() const { return project_descri
std::string ConfigManager::getServerName() const { return server_name; }
int ConfigManager::getHttpPort() const { return http_port; }
void ConfigManager::setHttpPort(int port) { http_port = port; }
std::string ConfigManager::getHttpHost() const { return http_host; }
void ConfigManager::setHttpHost(const std::string& host) { http_host = host; }
std::string ConfigManager::getTemplatePath() const { return template_config.path; }
std::filesystem::path ConfigManager::getFullTemplatePath() const { return std::filesystem::path(base_path) / template_config.path; }
std::shared_ptr<IFileProvider> ConfigManager::getFileProvider() const {
Expand Down
3 changes: 3 additions & 0 deletions src/include/config_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,8 @@ class ConfigManager {
std::string getServerName() const;
int getHttpPort() const;
void setHttpPort(int port);
std::string getHttpHost() const;
void setHttpHost(const std::string& host);
virtual std::string getTemplatePath() const;
std::string getCacheSchema() const;
const std::unordered_map<std::string, ConnectionConfig>& getConnections() const;
Expand Down Expand Up @@ -606,6 +608,7 @@ class ConfigManager {
std::string cache_schema = "flapi";
std::string server_name;
int http_port = 8080;
std::string http_host = "0.0.0.0";
std::unordered_map<std::string, ConnectionConfig> connections;
RateLimitConfig rate_limit_config;
bool auth_enabled;
Expand Down
40 changes: 37 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <csignal>
#include <atomic>
#include <thread>
Expand Down Expand Up @@ -322,6 +323,10 @@ int main(int argc, char* argv[])
.default_value(-1)
.scan<'i', int>();

program.add_argument("--host")
.help("Bind address for the web server (e.g. 0.0.0.0, 127.0.0.1)")
.default_value(std::string(""));

program.add_argument("--log-level")
.help("Set the log level (debug, info, warning, error)")
.default_value(std::string("info"));
Expand Down Expand Up @@ -439,13 +444,16 @@ int main(int argc, char* argv[])

std::string config_file = program.get<std::string>("--config");
int cmd_port = program.get<int>("--port");
std::string cmd_host = program.get<std::string>("--host");
std::string log_level = program.get<std::string>("--log-level");
bool validate_config = program.get<bool>("--validate-config");

// 12-factor env-var fallback (#47). Precedence:
// CLI flag > env var > built-in default.
// 12-factor env-var fallback (#47, #63). Precedence:
// CLI flag > env var > config file > built-in default.
// CLI wins because we only consult the env when the user didn't
// pass the flag.
// pass the flag; config-file values are applied later in
// initializeConfig() and only kick in when neither CLI nor env
// provided a value.
if (!program.is_used("--config")) {
if (const char* env = std::getenv("FLAPI_CONFIG"); env != nullptr && *env != '\0') {
config_file = env;
Expand All @@ -456,6 +464,29 @@ int main(int argc, char* argv[])
log_level = env;
}
}
if (!program.is_used("--port")) {
if (const char* env = std::getenv("FLAPI_PORT"); env != nullptr && *env != '\0') {
// Reject non-int / out-of-range early so a typo doesn't
// silently fall through to the config-file value.
try {
size_t consumed = 0;
const int parsed = std::stoi(env, &consumed);
if (consumed != std::strlen(env) || parsed < 1 || parsed > 65535) {
throw std::invalid_argument("out of range");
}
cmd_port = parsed;
} catch (const std::exception&) {
std::cerr << "flapi: invalid FLAPI_PORT '" << env
<< "'; must be an integer in 1..65535\n";
return 1;
}
}
}
if (!program.is_used("--host")) {
if (const char* env = std::getenv("FLAPI_HOST"); env != nullptr && *env != '\0') {
cmd_host = env;
}
}
// Validate log_level. Invalid values are an error, not a silent
// fallback -- typos like FLAPI_LOG_LEVEL=DEBUG should surface
// immediately, not run the server at the wrong verbosity.
Expand Down Expand Up @@ -519,6 +550,9 @@ int main(int argc, char* argv[])
if (cmd_port != -1) {
config_manager->setHttpPort(cmd_port);
}
if (!cmd_host.empty()) {
config_manager->setHttpHost(cmd_host);
}

initializeDatabase(config_manager);

Expand Down
Loading
Loading