Make FTL read and parse FTLCONF_* environmental variables. If they exist, they take precedence over config file values. The config file is updated from environmental variables so any changes can be followed therein.

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2023-10-17 16:53:12 +02:00
parent 529d7f7a30
commit f80044c3e2
5 changed files with 294 additions and 27 deletions
+267 -18
View File
@@ -444,7 +444,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok)
conf_item->v.b = val.u.b;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type bool", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid bool", conf_item->k);
break;
}
case CONF_ALL_DEBUG_BOOL:
@@ -453,7 +453,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok)
set_all_debug(newconf, val.u.b);
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type bool", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid bool", conf_item->k);
break;
}
case CONF_INT:
@@ -462,7 +462,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok)
conf_item->v.i = val.u.i;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type integer", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid integer", conf_item->k);
break;
}
case CONF_UINT:
@@ -471,7 +471,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok && val.u.i >= 0)
conf_item->v.ui = val.u.i;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type unsigned integer", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid unsigned integer", conf_item->k);
break;
}
case CONF_UINT16:
@@ -480,7 +480,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok && val.u.i >= 0 && val.u.i <= UINT16_MAX)
conf_item->v.ui = val.u.i;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type unsigned integer (16 bit)", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid unsigned integer (16 bit)", conf_item->k);
break;
}
case CONF_LONG:
@@ -489,7 +489,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok)
conf_item->v.l = val.u.i;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type long", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid long integer", conf_item->k);
break;
}
case CONF_ULONG:
@@ -498,7 +498,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok && val.u.i >= 0)
conf_item->v.ul = val.u.i;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type unsigned long", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid unsigned long integer", conf_item->k);
break;
}
case CONF_DOUBLE:
@@ -507,7 +507,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok)
conf_item->v.d = val.u.d;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type double", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid double", conf_item->k);
break;
}
case CONF_STRING:
@@ -522,7 +522,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
conf_item->t = CONF_STRING_ALLOCATED;
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_PTR_TYPE:
@@ -538,7 +538,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_BUSY_TYPE:
@@ -554,7 +554,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_BLOCKING_MODE:
@@ -570,7 +570,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a validstring", conf_item->k);
break;
}
case CONF_ENUM_REFRESH_HOSTNAMES:
@@ -586,7 +586,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_LISTENING_MODE:
@@ -602,7 +602,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_WEB_THEME:
@@ -618,7 +618,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_TEMP_UNIT:
@@ -634,7 +634,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
break;
}
case CONF_ENUM_PRIVACY_LEVEL:
@@ -643,7 +643,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
if(val.ok && val.u.i >= PRIVACY_SHOW_ALL && val.u.i <= PRIVACY_MAXIMUM)
conf_item->v.i = val.u.i;
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is invalid (not of type integer or outside allowed bounds)", conf_item->k);
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is invalid (not an integer or outside allowed bounds)", conf_item->k);
break;
}
case CONF_STRUCT_IN_ADDR:
@@ -654,8 +654,12 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
{
if(inet_pton(AF_INET, val.u.s, &addr4))
memcpy(&conf_item->v.in_addr, &addr4, sizeof(addr4));
else
log_warn("Config %s is invalid (not of type IPv4 address)", conf_item->k);
free(val.u.s);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is invalid (not a valid string of type IPv4 address)", conf_item->k);
break;
}
case CONF_STRUCT_IN6_ADDR:
@@ -666,8 +670,12 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
{
if(inet_pton(AF_INET6, val.u.s, &addr6))
memcpy(&conf_item->v.in6_addr, &addr6, sizeof(addr6));
else
log_warn("Config %s is invalid (not of type IPv6 address)", conf_item->k);
free(val.u.s);
}
else
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is invalid (not a valid string of type IPv6 address)", conf_item->k);
break;
}
case CONF_JSON_STRING_ARRAY:
@@ -686,7 +694,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
const toml_datum_t d = toml_string_at(array, i);
if(!d.ok)
{
log_debug(DEBUG_CONFIG, "%s is an invalid array (found at index %u)", conf_item->k, i);
log_warn("Config %s is an invalid array (found at index %u)", conf_item->k, i);
break;
}
// Only import non-empty entries
@@ -709,3 +717,244 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
}
}
}
#define FTLCONF_PREFIX "FTLCONF_"
bool readEnvValue(struct conf_item *conf_item, struct config *newconf)
{
// Allocate memory for config key + prefix (sizeof includes the trailing '\0')
const size_t envkey_size = strlen(conf_item->k) + sizeof(FTLCONF_PREFIX);
char *envkey = calloc(envkey_size, sizeof(char));
// Build env key to look for
strcpy(envkey, FTLCONF_PREFIX);
strcat(envkey, conf_item->k);
// Replace all "." by "_" as this is the convention used in v5.x and earlier
for(unsigned int i = 0; i < envkey_size - 1; i++)
if(envkey[i] == '.')
envkey[i] = '_';
// First check if a environmental variable with the given key exists
const char *envvar = getenv(envkey);
// Return early if this environment variable does not exist
if(envvar == NULL)
{
log_debug(DEBUG_CONFIG, "ENV %s is not set", envkey);
free(envkey);
return false;
}
log_debug(DEBUG_CONFIG, "ENV %s = \"%s\"", envkey, envvar);
switch(conf_item->t)
{
case CONF_BOOL:
{
if(strcasecmp(envkey, "true") == 0 || strcasecmp(envkey, "yes") == 0)
conf_item->v.b = true;
else if(strcasecmp(envkey, "false") == 0 || strcasecmp(envkey, "no") == 0)
conf_item->v.b = false;
else
log_warn("ENV %s is not of type bool", envkey);
break;
}
case CONF_ALL_DEBUG_BOOL:
{
if(strcasecmp(envkey, "true") == 0 || strcasecmp(envkey, "yes") == 0)
set_all_debug(newconf, true);
else if(strcasecmp(envkey, "false") == 0 || strcasecmp(envkey, "no") == 0)
set_all_debug(newconf, false);
else
log_warn("ENV %s is not of type bool", envkey);
break;
}
case CONF_INT:
{
int val = 0;
if(sscanf(envvar, "%i", &val) == 1)
conf_item->v.i = val;
else
log_warn("ENV %s is not of type integer", envkey);
break;
}
case CONF_UINT:
{
unsigned int val = 0;
if(sscanf(envvar, "%u", &val) == 1)
conf_item->v.ui = val;
else
log_warn("ENV %s is not of type unsigned integer", envkey);
break;
}
case CONF_UINT16:
{
unsigned int val = 0;
if(sscanf(envvar, "%u", &val) == 1 && val <= UINT16_MAX)
conf_item->v.ui = val;
else
log_warn("ENV %s is not of type unsigned integer (16 bit)", envkey);
break;
}
case CONF_LONG:
{
long val = 0;
if(sscanf(envvar, "%li", &val) == 1)
conf_item->v.l = val;
else
log_warn("ENV %s is not of type long", envkey);
break;
}
case CONF_ULONG:
{
unsigned long val = 0;
if(sscanf(envvar, "%lu", &val) == 1)
conf_item->v.ul = val;
else
log_warn("ENV %s is not of type unsigned long", envkey);
break;
}
case CONF_DOUBLE:
{
double val = 0;
if(sscanf(envvar, "%lf", &val) == 1)
conf_item->v.d = val;
else
log_warn("ENV %s is not of type double", envkey);
break;
}
case CONF_STRING:
case CONF_STRING_ALLOCATED:
{
if(conf_item->t == CONF_STRING_ALLOCATED)
free(conf_item->v.s);
conf_item->v.s = strdup(envvar);
conf_item->t = CONF_STRING_ALLOCATED;
break;
}
case CONF_ENUM_PTR_TYPE:
{
const int ptr_type = get_ptr_type_val(envvar);
if(ptr_type != -1)
conf_item->v.ptr_type = ptr_type;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_BUSY_TYPE:
{
const int busy_reply = get_busy_reply_val(envvar);
if(busy_reply != -1)
conf_item->v.busy_reply = busy_reply;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_BLOCKING_MODE:
{
const int blocking_mode = get_blocking_mode_val(envvar);
if(blocking_mode != -1)
conf_item->v.blocking_mode = blocking_mode;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_REFRESH_HOSTNAMES:
{
const int refresh_hostnames = get_refresh_hostnames_val(envvar);
if(refresh_hostnames != -1)
conf_item->v.refresh_hostnames = refresh_hostnames;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_LISTENING_MODE:
{
const int listeningMode = get_listeningMode_val(envvar);
if(listeningMode != -1)
conf_item->v.listeningMode = listeningMode;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_WEB_THEME:
{
const int web_theme = get_web_theme_val(envvar);
if(web_theme != -1)
conf_item->v.web_theme = web_theme;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_TEMP_UNIT:
{
const int temp_unit = get_temp_unit_val(envvar);
if(temp_unit != -1)
conf_item->v.temp_unit = temp_unit;
else
log_warn("ENV %s is invalid, allowed options are: %s", envkey, conf_item->h);
break;
}
case CONF_ENUM_PRIVACY_LEVEL:
{
int val = 0;
if(sscanf(envvar, "%i", &val) == 1 && val >= PRIVACY_SHOW_ALL && val <= PRIVACY_MAXIMUM)
conf_item->v.i = val;
else
log_warn("ENV %s is invalid (not of type integer or outside allowed bounds)", envkey);
break;
}
case CONF_STRUCT_IN_ADDR:
{
struct in_addr addr4 = { 0 };
if(inet_pton(AF_INET, envvar, &addr4))
memcpy(&conf_item->v.in_addr, &addr4, sizeof(addr4));
else
log_warn("ENV %s is invalid (not of type IPv4 address)", envkey);
break;
}
case CONF_STRUCT_IN6_ADDR:
{
struct in6_addr addr6 = { 0 };
if(inet_pton(AF_INET6, envvar, &addr6))
memcpy(&conf_item->v.in6_addr, &addr6, sizeof(addr6));
else
log_warn("ENV %s is invalid (not of type IPv6 address)", envkey);
break;
}
case CONF_JSON_STRING_ARRAY:
{
// Make a copy of envvar as strtok modified the input string
char *envvar_copy = strdup(envvar);
// Free previously allocated JSON array
cJSON_Delete(conf_item->v.json);
conf_item->v.json = cJSON_CreateArray();
// Parse envvar array and generate a JSON array
const char delim[] =",";
const char *elem = strtok(envvar_copy, delim);
while(elem != NULL)
{
// Only import non-empty entries
if(strlen(elem) > 0)
{
// Add string to our JSON array
cJSON *item = cJSON_CreateString(elem);
cJSON_AddItemToArray(conf_item->v.json, item);
}
// Search for the next element
elem = strtok(NULL, delim);
}
free(envvar_copy);
break;
}
case CONF_PASSWORD:
{
// This is ignored, it is only a pseudo-element with no real content
}
}
// Free allocated env var name
free(envkey);
return true;
}
+1
View File
@@ -23,5 +23,6 @@ void print_comment(FILE *fp, const char *str, const char *intro, const unsigned
void print_toml_allowed_values(cJSON *allowed_values, FILE *fp, const unsigned int width, const unsigned int indent);
void writeTOMLvalue(FILE * fp, const int indent, const enum conf_type t, union conf_value *v);
void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *toml, struct config *newconf);
bool readEnvValue(struct conf_item *conf_item, struct config *newconf);
#endif //CONFIG_WRITER_H
+17 -8
View File
@@ -30,11 +30,10 @@ static void reportDebugFlags(void);
bool readFTLtoml(struct config *conf, toml_table_t *toml, const bool verbose)
{
// Parse lines in the config file if we did not receive a pointer to a TOML
// table (e.g. from an imported Teleporter file)
bool external = true;
if(toml == NULL)
// table from an imported Teleporter file
bool teleporter = (toml != NULL);
if(!teleporter)
{
external = false;
toml = parseTOML();
if(!toml)
return false;
@@ -42,13 +41,17 @@ bool readFTLtoml(struct config *conf, toml_table_t *toml, const bool verbose)
// Try to read debug config. This is done before the full config
// parsing to allow for debug output further down
toml_table_t *conf_debug = toml_table_in(toml, "debug");
if(conf_debug)
readTOMLvalue(&conf->debug.config, "config", conf_debug, conf);
// First try to read env variable, if this fails, read TOML
if(teleporter || !readEnvValue(&conf->debug.config, conf))
{
toml_table_t *conf_debug = toml_table_in(toml, "debug");
if(conf_debug)
readTOMLvalue(&conf->debug.config, "config", conf_debug, conf);
}
set_debug_flags(conf);
log_debug(DEBUG_CONFIG, "Reading %s TOML config file: full config",
external ? "external" : "default");
teleporter ? "teleporter" : "default");
// Read all known config items
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
@@ -56,6 +59,12 @@ bool readFTLtoml(struct config *conf, toml_table_t *toml, const bool verbose)
// Get pointer to memory location of this conf_item
struct conf_item *conf_item = get_conf_item(conf, i);
// First try to read this config option from an environment variable
// Skip reading environment variables when importing from Teleporter
// If this succeeds, skip searching the TOML file for this config item
if(!teleporter && readEnvValue(conf_item, conf))
continue;
// Get config path depth
unsigned int level = config_path_depth(conf_item->p);
+1
View File
@@ -69,6 +69,7 @@ OLDUMASK=$(umask)
umask 0022
# Start FTL
export FTLCONF_misc_nice="-11"
if ! su pihole -s /bin/sh -c /home/pihole/pihole-FTL; then
echo "pihole-FTL failed to start"
exit 1
+8 -1
View File
@@ -1185,7 +1185,7 @@
[[ ${lines[0]} == "0" ]]
}
@test "No config errors in pihole.toml" {
@test "No missing config items in pihole.toml" {
run bash -c 'grep "DEBUG_CONFIG: " /var/log/pihole/FTL.log'
printf "%s\n" "${lines[@]}"
run bash -c 'grep "DEBUG_CONFIG: " /var/log/pihole/FTL.log | grep -c "DOES NOT EXIST"'
@@ -1251,6 +1251,13 @@
[[ "${lines[0]}" == "192.168.1.7" ]]
}
@test "Environmental variable is favored over config file" {
# The config file has -10 but we set FTLCONF_misc_nice="-11"
run bash -c 'grep -c "nice = -11" /etc/pihole/pihole.toml'
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "1" ]]
}
@test "API domain search: Non-existing domain returns expected JSON" {
run bash -c 'curl -s 127.0.0.1/api/search/non.existent'
printf "%s\n" "${lines[@]}"