nixos/modules: add nominatim module and test
This commit is contained in:
parent
5717441202
commit
5cd09e28ae
@ -1413,6 +1413,7 @@
|
|||||||
./services/search/hound.nix
|
./services/search/hound.nix
|
||||||
./services/search/manticore.nix
|
./services/search/manticore.nix
|
||||||
./services/search/meilisearch.nix
|
./services/search/meilisearch.nix
|
||||||
|
./services/search/nominatim.nix
|
||||||
./services/search/opensearch.nix
|
./services/search/opensearch.nix
|
||||||
./services/search/qdrant.nix
|
./services/search/qdrant.nix
|
||||||
./services/search/quickwit.nix
|
./services/search/quickwit.nix
|
||||||
|
|||||||
324
nixos/modules/services/search/nominatim.nix
Normal file
324
nixos/modules/services/search/nominatim.nix
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.nominatim;
|
||||||
|
|
||||||
|
localDb = cfg.database.host == "localhost";
|
||||||
|
uiPackage = cfg.ui.package.override { customConfig = cfg.ui.config; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.nominatim = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to enable nominatim.
|
||||||
|
|
||||||
|
Also enables nginx virtual host management. Further nginx configuration
|
||||||
|
can be done by adapting `services.nginx.virtualHosts.<name>`.
|
||||||
|
See [](#opt-services.nginx.virtualHosts).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
package = lib.mkPackageOption pkgs.python3Packages "nominatim-api" { };
|
||||||
|
|
||||||
|
hostName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Hostname to use for the nginx vhost.";
|
||||||
|
example = "nominatim.example.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
NOMINATIM_REPLICATION_URL = "https://planet.openstreetmap.org/replication/minute";
|
||||||
|
NOMINATIM_REPLICATION_MAX_DIFF = "100";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Nominatim configuration settings.
|
||||||
|
For the list of available configuration options see
|
||||||
|
<https://nominatim.org/release-docs/latest/customize/Settings>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ui = {
|
||||||
|
package = lib.mkPackageOption pkgs "nominatim-ui" { };
|
||||||
|
|
||||||
|
config = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Nominatim UI configuration placed to theme/config.theme.js file.
|
||||||
|
|
||||||
|
For the list of available configuration options see
|
||||||
|
<https://github.com/osm-search/nominatim-ui/blob/master/dist/config.defaults.js>.
|
||||||
|
'';
|
||||||
|
example = ''
|
||||||
|
Nominatim_Config.Page_Title='My Nominatim instance';
|
||||||
|
Nominatim_Config.Nominatim_API_Endpoint='https://localhost/';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
database = {
|
||||||
|
host = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "localhost";
|
||||||
|
description = ''
|
||||||
|
Host of the postgresql server. If not set to `localhost`, Nominatim
|
||||||
|
database and postgresql superuser with appropriate permissions must
|
||||||
|
exist on target host.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 5432;
|
||||||
|
description = "Port of the postgresql database.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dbname = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "nominatim";
|
||||||
|
description = "Name of the postgresql database.";
|
||||||
|
};
|
||||||
|
|
||||||
|
superUser = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "nominatim";
|
||||||
|
description = ''
|
||||||
|
Postgresql database superuser used to create Nominatim database and
|
||||||
|
import data. If `database.host` is set to `localhost`, a unix user and
|
||||||
|
group of the same name will be automatically created.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
apiUser = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "nominatim-api";
|
||||||
|
description = ''
|
||||||
|
Postgresql database user with read-only permissions used for Nominatim
|
||||||
|
web API service.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Password file used for Nominatim database connection.
|
||||||
|
Must be readable only for the Nominatim web API user.
|
||||||
|
|
||||||
|
The file must be a valid `.pgpass` file as described in:
|
||||||
|
<https://www.postgresql.org/docs/current/libpq-pgpass.html>
|
||||||
|
|
||||||
|
In most cases, the following will be enough:
|
||||||
|
```
|
||||||
|
*:*:*:*:<password>
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConnectionParams = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Extra Nominatim database connection parameters.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
<param1>=<value1>;<param2>=<value2>
|
||||||
|
|
||||||
|
See <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
nominatimSuperUserDsn =
|
||||||
|
"pgsql:dbname=${cfg.database.dbname};"
|
||||||
|
+ "user=${cfg.database.superUser}"
|
||||||
|
+ lib.optionalString (cfg.database.extraConnectionParams != null) (
|
||||||
|
";" + cfg.database.extraConnectionParams
|
||||||
|
);
|
||||||
|
|
||||||
|
nominatimApiDsn =
|
||||||
|
"pgsql:dbname=${cfg.database.dbname}"
|
||||||
|
+ lib.optionalString (!localDb) (
|
||||||
|
";host=${cfg.database.host};"
|
||||||
|
+ "port=${toString cfg.database.port};"
|
||||||
|
+ "user=${cfg.database.apiUser}"
|
||||||
|
)
|
||||||
|
+ lib.optionalString (cfg.database.extraConnectionParams != null) (
|
||||||
|
";" + cfg.database.extraConnectionParams
|
||||||
|
);
|
||||||
|
in
|
||||||
|
lib.mkIf cfg.enable {
|
||||||
|
# CLI package
|
||||||
|
environment.systemPackages = [ pkgs.nominatim ];
|
||||||
|
|
||||||
|
# Database
|
||||||
|
users.users.${cfg.database.superUser} = lib.mkIf localDb {
|
||||||
|
group = cfg.database.superUser;
|
||||||
|
isSystemUser = true;
|
||||||
|
createHome = false;
|
||||||
|
};
|
||||||
|
users.groups.${cfg.database.superUser} = lib.mkIf localDb { };
|
||||||
|
|
||||||
|
services.postgresql = lib.mkIf localDb {
|
||||||
|
enable = true;
|
||||||
|
extensions = ps: with ps; [ postgis ];
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = cfg.database.superUser;
|
||||||
|
ensureClauses.superuser = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = cfg.database.apiUser;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO: add nominatim-update service
|
||||||
|
|
||||||
|
systemd.services.nominatim-init = lib.mkIf localDb {
|
||||||
|
after = [ "postgresql-setup.service" ];
|
||||||
|
requires = [ "postgresql-setup.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = cfg.database.superUser;
|
||||||
|
RemainAfterExit = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
sql="SELECT COUNT(*) FROM pg_database WHERE datname='${cfg.database.dbname}'"
|
||||||
|
db_exists=$(${pkgs.postgresql}/bin/psql --dbname postgres -tAc "$sql")
|
||||||
|
|
||||||
|
if [ "$db_exists" == "0" ]; then
|
||||||
|
${lib.getExe pkgs.nominatim} import --prepare-database
|
||||||
|
else
|
||||||
|
echo "Database ${cfg.database.dbname} already exists. Skipping ..."
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
path = [
|
||||||
|
pkgs.postgresql
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
NOMINATIM_DATABASE_DSN = nominatimSuperUserDsn;
|
||||||
|
NOMINATIM_DATABASE_WEBUSER = cfg.database.apiUser;
|
||||||
|
} // cfg.settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Web API service
|
||||||
|
users.users.${cfg.database.apiUser} = {
|
||||||
|
group = cfg.database.apiUser;
|
||||||
|
isSystemUser = true;
|
||||||
|
createHome = false;
|
||||||
|
};
|
||||||
|
users.groups.${cfg.database.apiUser} = { };
|
||||||
|
|
||||||
|
systemd.services.nominatim = {
|
||||||
|
after = [ "network.target" ] ++ lib.optionals localDb [ "nominatim-init.service" ];
|
||||||
|
requires = lib.optionals localDb [ "nominatim-init.service" ];
|
||||||
|
bindsTo = lib.optionals localDb [ "postgresql.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
wants = [ "network.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = cfg.database.apiUser;
|
||||||
|
ExecStart = ''
|
||||||
|
${pkgs.python3Packages.gunicorn}/bin/gunicorn \
|
||||||
|
--bind unix:/run/nominatim.sock \
|
||||||
|
--workers 4 \
|
||||||
|
--worker-class uvicorn.workers.UvicornWorker "nominatim_api.server.falcon.server:run_wsgi()"
|
||||||
|
'';
|
||||||
|
Environment = lib.optional (
|
||||||
|
cfg.database.passwordFile != null
|
||||||
|
) "PGPASSFILE=${cfg.database.passwordFile}";
|
||||||
|
ExecReload = "${pkgs.procps}/bin/kill -s HUP $MAINPID";
|
||||||
|
KillMode = "mixed";
|
||||||
|
TimeoutStopSec = 5;
|
||||||
|
};
|
||||||
|
environment = {
|
||||||
|
PYTHONPATH =
|
||||||
|
with pkgs.python3Packages;
|
||||||
|
pkgs.python3Packages.makePythonPath [
|
||||||
|
cfg.package
|
||||||
|
falcon
|
||||||
|
uvicorn
|
||||||
|
];
|
||||||
|
NOMINATIM_DATABASE_DSN = nominatimApiDsn;
|
||||||
|
NOMINATIM_DATABASE_WEBUSER = cfg.database.apiUser;
|
||||||
|
} // cfg.settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.sockets.nominatim = {
|
||||||
|
before = [ "nominatim.service" ];
|
||||||
|
wantedBy = [ "sockets.target" ];
|
||||||
|
socketConfig = {
|
||||||
|
ListenStream = "/run/nominatim.sock";
|
||||||
|
SocketUser = cfg.database.apiUser;
|
||||||
|
SocketGroup = config.services.nginx.group;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
appendHttpConfig = ''
|
||||||
|
map $args $format {
|
||||||
|
default default;
|
||||||
|
~(^|&)format=html(&|$) html;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $uri/$format $forward_to_ui {
|
||||||
|
default 0; # No forwarding by default.
|
||||||
|
|
||||||
|
# Redirect to HTML UI if explicitly requested.
|
||||||
|
~/reverse.*/html 1;
|
||||||
|
~/search.*/html 1;
|
||||||
|
~/lookup.*/html 1;
|
||||||
|
~/details.*/html 1;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
upstreams.nominatim = {
|
||||||
|
servers = {
|
||||||
|
"unix:/run/nominatim.sock" = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualHosts = {
|
||||||
|
${cfg.hostName} = {
|
||||||
|
forceSSL = lib.mkDefault true;
|
||||||
|
enableACME = lib.mkDefault true;
|
||||||
|
locations = {
|
||||||
|
"= /" = {
|
||||||
|
extraConfig = ''
|
||||||
|
return 301 $scheme://$http_host/ui/search.html;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/" = {
|
||||||
|
proxyPass = "http://nominatim";
|
||||||
|
extraConfig = ''
|
||||||
|
if ($forward_to_ui) {
|
||||||
|
rewrite ^(/[^/.]*) /ui$1.html redirect;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/ui/" = {
|
||||||
|
alias = "${uiPackage}/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1012,6 +1012,7 @@ in
|
|||||||
nixseparatedebuginfod = runTest ./nixseparatedebuginfod.nix;
|
nixseparatedebuginfod = runTest ./nixseparatedebuginfod.nix;
|
||||||
node-red = runTest ./node-red.nix;
|
node-red = runTest ./node-red.nix;
|
||||||
nomad = runTest ./nomad.nix;
|
nomad = runTest ./nomad.nix;
|
||||||
|
nominatim = runTest ./nominatim.nix;
|
||||||
non-default-filesystems = handleTest ./non-default-filesystems.nix { };
|
non-default-filesystems = handleTest ./non-default-filesystems.nix { };
|
||||||
non-switchable-system = runTest ./non-switchable-system.nix;
|
non-switchable-system = runTest ./non-switchable-system.nix;
|
||||||
noto-fonts = runTest ./noto-fonts.nix;
|
noto-fonts = runTest ./noto-fonts.nix;
|
||||||
|
|||||||
187
nixos/tests/nominatim.nix
Normal file
187
nixos/tests/nominatim.nix
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
{ pkgs, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Andorra - the smallest dataset in Europe (3.1 MB)
|
||||||
|
osmData = pkgs.fetchurl {
|
||||||
|
url = "https://web.archive.org/web/20250430211212/https://download.geofabrik.de/europe/andorra-latest.osm.pbf";
|
||||||
|
hash = "sha256-Ey+ipTOFUm80rxBteirPW5N4KxmUsg/pCE58E/2rcyE=";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "nominatim";
|
||||||
|
meta = {
|
||||||
|
maintainers = with lib.teams; [
|
||||||
|
geospatial
|
||||||
|
ngi
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
# nominatim - self contained host
|
||||||
|
nominatim =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
# Nominatim
|
||||||
|
services.nominatim = {
|
||||||
|
enable = true;
|
||||||
|
hostName = "nominatim";
|
||||||
|
settings = {
|
||||||
|
NOMINATIM_IMPORT_STYLE = "admin";
|
||||||
|
};
|
||||||
|
ui = {
|
||||||
|
config = ''
|
||||||
|
Nominatim_Config.Page_Title='Test Nominatim instance';
|
||||||
|
Nominatim_Config.Nominatim_API_Endpoint='https://localhost/';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Disable SSL
|
||||||
|
services.nginx.virtualHosts.nominatim = {
|
||||||
|
forceSSL = false;
|
||||||
|
enableACME = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Database
|
||||||
|
services.postgresql = {
|
||||||
|
enableTCPIP = true;
|
||||||
|
authentication = lib.mkForce ''
|
||||||
|
local all all trust
|
||||||
|
host all all 0.0.0.0/0 md5
|
||||||
|
host all all ::0/0 md5
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
systemd.services.postgresql-setup.postStart = ''
|
||||||
|
psql --command "ALTER ROLE \"nominatim-api\" WITH PASSWORD 'password';"
|
||||||
|
'';
|
||||||
|
networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# api - web API only
|
||||||
|
api =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
# Database password
|
||||||
|
system.activationScripts = {
|
||||||
|
passwordFile.text = with config.services.nominatim.database; ''
|
||||||
|
mkdir -p /run/secrets
|
||||||
|
echo "${host}:${toString port}:${dbname}:${apiUser}:password" \
|
||||||
|
> /run/secrets/pgpass
|
||||||
|
chown nominatim-api:nominatim-api /run/secrets/pgpass
|
||||||
|
chmod 0600 /run/secrets/pgpass
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Nominatim
|
||||||
|
services.nominatim = {
|
||||||
|
enable = true;
|
||||||
|
hostName = "nominatim";
|
||||||
|
settings = {
|
||||||
|
NOMINATIM_LOG_DB = "yes";
|
||||||
|
};
|
||||||
|
database = {
|
||||||
|
host = "nominatim";
|
||||||
|
passwordFile = "/run/secrets/pgpass";
|
||||||
|
extraConnectionParams = "application_name=nominatim;connect_timeout=2";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Disable SSL
|
||||||
|
services.nginx.virtualHosts.nominatim = {
|
||||||
|
forceSSL = false;
|
||||||
|
enableACME = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
# Test nominatim host
|
||||||
|
nominatim.start()
|
||||||
|
nominatim.wait_for_unit("nominatim.service")
|
||||||
|
|
||||||
|
# Import OSM data
|
||||||
|
nominatim.succeed("""
|
||||||
|
cd /tmp
|
||||||
|
sudo -u nominatim \
|
||||||
|
NOMINATIM_DATABASE_WEBUSER=nominatim-api \
|
||||||
|
NOMINATIM_IMPORT_STYLE=admin \
|
||||||
|
nominatim import --continue import-from-file --osm-file ${osmData}
|
||||||
|
""")
|
||||||
|
nominatim.succeed("systemctl restart nominatim.service")
|
||||||
|
|
||||||
|
# Test CLI
|
||||||
|
nominatim.succeed("sudo -u nominatim-api nominatim search --query Andorra")
|
||||||
|
|
||||||
|
# Test web API
|
||||||
|
nominatim.succeed("curl 'http://localhost/status' | grep OK")
|
||||||
|
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl "http://localhost/search?q=Andorra&format=geojson" | grep "Andorra"
|
||||||
|
curl "http://localhost/reverse?lat=42.5407167&lon=1.5732033&format=geojson"
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Test UI
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl "http://localhost/ui/search.html" \
|
||||||
|
| grep "<title>Nominatim Demo</title>"
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
# Test api host
|
||||||
|
api.start()
|
||||||
|
api.wait_for_unit("nominatim.service")
|
||||||
|
|
||||||
|
# Test web API
|
||||||
|
api.succeed("""
|
||||||
|
curl "http://localhost/search?q=Andorra&format=geojson" | grep "Andorra"
|
||||||
|
curl "http://localhost/reverse?lat=42.5407167&lon=1.5732033&format=geojson"
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
# Test format rewrites
|
||||||
|
# Redirect / to search
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl --verbose "http://localhost" 2>&1 \
|
||||||
|
| grep "Location: http://localhost/ui/search.html"
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Return text by default
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl --verbose "http://localhost/status" 2>&1 \
|
||||||
|
| grep "Content-Type: text/plain"
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Return JSON by default
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl --verbose "http://localhost/search?q=Andorra" 2>&1 \
|
||||||
|
| grep "Content-Type: application/json"
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Return XML by default
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl --verbose "http://localhost/lookup" 2>&1 \
|
||||||
|
| grep "Content-Type: text/xml"
|
||||||
|
|
||||||
|
curl --verbose "http://localhost/reverse?lat=0&lon=0" 2>&1 \
|
||||||
|
| grep "Content-Type: text/xml"
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Redirect explicitly requested HTML format
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl --verbose "http://localhost/search?format=html" 2>&1 \
|
||||||
|
| grep "Location: http://localhost/ui/search.html"
|
||||||
|
|
||||||
|
curl --verbose "http://localhost/reverse?format=html" 2>&1 \
|
||||||
|
| grep "Location: http://localhost/ui/reverse.html"
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Return explicitly requested JSON format
|
||||||
|
nominatim.succeed("""
|
||||||
|
curl --verbose "http://localhost/search?format=json" 2>&1 \
|
||||||
|
| grep "Content-Type: application/json"
|
||||||
|
|
||||||
|
curl --verbose "http://localhost/reverse?format=json" 2>&1 \
|
||||||
|
| grep "Content-Type: application/json"
|
||||||
|
""")
|
||||||
|
'';
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user