diff --git a/scripts/g.extension/g.extension.html b/scripts/g.extension/g.extension.html
index cdbadb07bf6..df26a14bb5b 100644
--- a/scripts/g.extension/g.extension.html
+++ b/scripts/g.extension/g.extension.html
@@ -126,6 +126,49 @@
Compilation and installation
as manual page are compiled, not only the source code (which is really
necessary to compile just in case of C).
+Using an alternative addon server (MS-Windows)
+
+On MS-Windows, g.extension downloads precompiled addon ZIPs from the
+official WinGRASS server (http://wingrass.fsv.cvut.cz) by default.
+The base URL of the server can be redirected to a mirror or a private addon
+server by setting ADDONS_BASE_URL. The setting affects both the
+addon listing (g.extension -l, -c, -g)
+and the install download URL, so listing and install always come from the same
+place.
+
+
+The setting can be applied in two ways:
+
+
+- From inside GRASS (persists across sessions, settable from the GUI's
+ variables panel as well):
+
+g.gisenv set="ADDONS_BASE_URL=https://your.server.example"
+
+
+- As a shell environment variable named
GRASS_ADDONS_BASE_URL (useful
+ for one-off shell sessions or CI jobs). The gisenv setting takes
+ precedence over the environment variable; if neither is set, the
+ default WinGRASS server is used.
+
+
+
+To revert to the default server, unset the value:
+
+
+g.gisenv unset=ADDONS_BASE_URL
+
+
+
+Alternative servers are:
+
+
+
+Note, settings explained in this section are is honoured on MS-Windows only; on
+Linux/macOS it has no effect.
+
EXAMPLES
Download and install of an extension
@@ -143,6 +186,35 @@ Download and install of an extension
g.extension r.stream.distance
+Installing from an alternative addon server (MS-Windows)
+
+On MS-Windows, redirect the addon server to a mirror or private host
+once per session, then use g.extension normally — listing and install
+both go through the configured server.
+
+
+# Point at the alternative server (persists across GRASS sessions)
+g.gisenv set="ADDONS_BASE_URL=https://ecodiv.earth/share"
+
+# List what the alternative server provides
+g.extension -l
+
+# Install an extension from it
+g.extension extension=r.tpi
+
+# Revert to the default WinGRASS server
+g.gisenv unset=ADDONS_BASE_URL
+
+
+
+For a one-off shell session, set the environment variable instead:
+
+
+# cmd.exe
+set GRASS_ADDONS_BASE_URL=https://ecodiv.earth/share
+g.extension extension=r.tpi
+
+
Download and install of an extension when behind a proxy
Example for an open http proxy:
@@ -204,8 +276,10 @@ Installing from various online repositories: GitHub, GitLab, Bitbucket
g.extension r.example url=http://example.com/.../r.example?format=zip
-Note that because of MS-Windows operating system architecture,
-only official repository is supported on this platform.
+On Windows, this will work also, but only when pointing at a server with
+pre-compiled addons and set up using the same directory layout as the
+official WinGRASS server, namely {base}/grass{MM}/addons/grass-{M.M.P}/
+containing one ZIP per extension and a modules.xml listing.
Install a specific version from Addons
@@ -248,6 +322,12 @@ KNOWN ISSUES
(a Python replacement for Python scripts should be implemented).
+
+On Windows, it is now possible to point at an alternative server. However, note
+that this server should be set up using the same directory layout as the
+official WinGRASS server, namely {base}/grass{MM}/addons/grass-{M.M.P}/
+containing pre-compiled extensions as ZIP file, and a modules.xml listing.
+
TROUBLESHOOTING
Since extensions have to be compiled on Unix based systems (Linux, Mac OSX etc.)
@@ -276,4 +356,5 @@ AUTHORS
Markus Neteler (original shell script)
Martin Landa, Czech Technical University in Prague, Czech Republic (Python rewrite)
-Vaclav Petras, NCSU GeoForAll Lab (support for general sources, partial refactoring)
+Vaclav Petras, NCSU GeoForAll Lab (support for general sources, partial refactoring)
+Paulo van Breugel, HAS green academy (add option for Windows users to point at alternative repositories)
diff --git a/scripts/g.extension/g.extension.md b/scripts/g.extension/g.extension.md
index 78767c5059f..893177fb2a6 100644
--- a/scripts/g.extension/g.extension.md
+++ b/scripts/g.extension/g.extension.md
@@ -115,6 +115,42 @@ extensions. The reason is that more things such as manual page are
compiled, not only the source code (which is really necessary to compile
just in case of C).
+### Using an alternative addon server (MS-Windows)
+
+On MS-Windows, *g.extension* downloads precompiled addon ZIPs from the
+official WinGRASS server (`http://wingrass.fsv.cvut.cz`) by default.
+The base URL of the server can be redirected to a mirror or a private
+addon server by setting `ADDONS_BASE_URL`. The setting affects both the
+addon listing (`g.extension -l`, `-c`, `-g`) and the install download
+URL, so listing and install always come from the same place.
+
+The setting can be applied in two ways:
+
+- From inside GRASS (persists across sessions, settable from the GUI's
+ variables panel as well):
+
+ ```sh
+ g.gisenv set="ADDONS_BASE_URL=https://your.server.example"
+ ```
+
+- As a shell environment variable named `GRASS_ADDONS_BASE_URL` (useful
+ for one-off shell sessions or CI jobs). The gisenv setting takes
+ precedence over the environment variable; if neither is set, the
+ default WinGRASS server is used.
+
+To revert to the default server, unset the value:
+
+```sh
+g.gisenv unset=ADDONS_BASE_URL
+```
+
+Alternative servers are:
+
+- (Python extensions only)
+
+Note, settings explained in this section are is honoured on MS-Windows only; on
+Linux/macOS it has no effect.
+
## EXAMPLES
### Download and install of an extension
@@ -132,6 +168,34 @@ convenience, a shorter syntax can be used:
g.extension r.stream.distance
```
+### Installing from an alternative addon server (MS-Windows)
+
+On MS-Windows, redirect the addon server to a mirror or private host
+once per session, then use *g.extension* normally — listing and install
+both go through the configured server.
+
+```sh
+# Point at the alternative server (persists across GRASS sessions)
+g.gisenv set="ADDONS_BASE_URL=https://ecodiv.earth/share"
+
+# List what the alternative server provides
+g.extension -l
+
+# Install an extension from it
+g.extension extension=r.tpi
+
+# Revert to the default WinGRASS server
+g.gisenv unset=ADDONS_BASE_URL
+```
+
+For a one-off shell session, set the environment variable instead:
+
+```sh
+# cmd.exe
+set GRASS_ADDONS_BASE_URL=https://ecodiv.earth/share
+g.extension extension=r.tpi
+```
+
### Download and install of an extension when behind a proxy
Example for an open http proxy:
@@ -198,8 +262,10 @@ can be used:
g.extension r.example url=http://example.com/.../r.example?format=zip
```
-Note that because of MS-Windows operating system architecture, only
-official repository is supported on this platform.
+On Windows, this will work also, but only when pointing at a server with
+pre-compiled addons and set up using the same directory layout as the
+official WinGRASS server, namely `{base}/grass{MM}/addons/grass-{M.M.P}/`
+containing one ZIP per extension and a `modules.xml` listing.
### Install a specific version from Addons
@@ -239,6 +305,11 @@ On MS-Windows, only the official repository is working because there is
no way of compiling the modules (a Python replacement for Python scripts
should be implemented).
+On Windows, it is now possible to point at an alternative server. However, note
+that this server should be set up using the same directory layout as the
+official WinGRASS server, namely `{base}/grass{MM}/addons/grass-{M.M.P}/`
+containing pre-compiled extensions as ZIP file, and a `modules.xml` listing.
+
## TROUBLESHOOTING
Since extensions have to be compiled on Unix based systems (Linux, Mac
@@ -269,3 +340,5 @@ Martin Landa, Czech Technical University in Prague, Czech Republic
Vaclav Petras, [NCSU GeoForAll
Lab](https://geospatial.ncsu.edu/geoforall/) (support for general
sources, partial refactoring)
+Paulo van Breugel, HAS green academy (add option for Windows users to
+point at alternative repositories)
diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py
old mode 100755
new mode 100644
index ab1428e5934..7db2c306391
--- a/scripts/g.extension/g.extension.py
+++ b/scripts/g.extension/g.extension.py
@@ -8,7 +8,7 @@
# Vaclav Petras (support for general sources)
# PURPOSE: Tool to download and install extensions into local installation
#
-# COPYRIGHT: (C) 2009-2025 by Markus Neteler, and the GRASS Development Team
+# COPYRIGHT: (C) 2009-2026 by Markus Neteler, and the GRASS Development Team
#
# This program is free software under the GNU General
# Public License (>=v2). Read the file COPYING that
@@ -1188,7 +1188,9 @@ def install_extension(source=None, url=None, xmlurl=None, branch=None):
ret1 = 0
new_modules_ext = None
if sys.platform == "win32":
- ret1, new_modules_ext, new_files_ext = install_extension_win(extension)
+ ret1, new_modules_ext, new_files_ext = install_extension_win(
+ extension, url=url
+ )
else:
(
ret1,
@@ -1590,21 +1592,59 @@ def install_module_xml(mlist):
return mlist
-def install_extension_win(name):
- """Install extension on MS Windows"""
+def get_addons_base_url():
+ """Resolve the base URL of the addon server (Windows binary repository).
+
+ Precedence (highest first):
+ 1. ``ADDONS_BASE_URL`` set via ``g.gisenv set="ADDONS_BASE_URL=..."``.
+ Persists across GRASS sessions and is settable from inside GRASS.
+ 2. ``GRASS_ADDONS_BASE_URL`` shell environment variable. Useful for
+ CI / system administrators / one-off shell sessions.
+ 3. The built-in default ``http://wingrass.fsv.cvut.cz`` (the official
+ WinGRASS server).
+ """
+ try:
+ gisenv_value = gs.gisenv().get("ADDONS_BASE_URL")
+ except Exception: # noqa: BLE001
+ # gisenv() can fail outside a GRASS session; fall through.
+ gisenv_value = None
+ if gisenv_value:
+ return gisenv_value.rstrip("/")
+ env_value = os.environ.get("GRASS_ADDONS_BASE_URL")
+ if env_value:
+ return env_value.rstrip("/")
+ return "http://wingrass.fsv.cvut.cz"
+
+
+def install_extension_win(name, url=None):
+ """Install extension on MS Windows."""
gs.message(
_("Downloading precompiled GRASS Addons <{}>...").format(options["extension"])
)
- # build base URL
- base_url = (
- "http://wingrass.fsv.cvut.cz/"
- f"grass{VERSION[0]}{VERSION[1]}/addons/"
- f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}"
- )
+ # Resolve the final ZIP URL.
+ if url:
+ if not url.lower().endswith(".zip"):
+ gs.fatal(
+ _(
+ "On MS-Windows, the 'url' option must point to a ZIP file "
+ "(URL ending in '.zip'). To use a different addon server, "
+ "set the ADDONS_BASE_URL gisenv variable or the "
+ "GRASS_ADDONS_BASE_URL environment variable instead."
+ )
+ )
+ zip_url = url
+ else:
+ base = get_addons_base_url()
+ base_url = (
+ f"{base}/"
+ f"grass{VERSION[0]}{VERSION[1]}/addons/"
+ f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}"
+ )
+ zip_url = f"{base_url}/{name}.zip"
# resolve ZIP URL
- source, url = resolve_source_code(url="{0}/{1}.zip".format(base_url, name))
+ source, url = resolve_source_code(url=zip_url)
# to hide non-error messages from subprocesses
outdev = open(os.devnull, "w") if gs.verbosity() <= 2 else sys.stdout
@@ -2597,11 +2637,29 @@ def resolve_xmlurl_prefix(url, source=None):
"""
gs.debug("resolve_xmlurl_prefix(url={0}, source={1})".format(url, source))
if source in {"official", "official_fork"}:
- # use pregenerated modules XML file
- # Define branch to fetch from (latest or current version)
- version_branch = get_version_branch(VERSION[0])
-
- url = "https://grass.osgeo.org/addons/{}/".format(version_branch)
+ # On MS-Windows only, the user can redirect both listing and install
+ # to a custom addon server via the ADDONS_BASE_URL gisenv setting or
+ # the GRASS_ADDONS_BASE_URL env var.
+ # On non-Windows, this override is intentionally not honoured
+ override = None
+ if sys.platform == "win32":
+ try:
+ override = gs.gisenv().get("ADDONS_BASE_URL") or os.environ.get(
+ "GRASS_ADDONS_BASE_URL"
+ )
+ except Exception: # noqa: BLE001
+ override = os.environ.get("GRASS_ADDONS_BASE_URL")
+ if override:
+ override = override.rstrip("/")
+ url = (
+ f"{override}/grass{VERSION[0]}{VERSION[1]}/addons/"
+ f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}/"
+ )
+ else:
+ # use pregenerated modules XML file
+ # Define branch to fetch from (latest or current version)
+ version_branch = get_version_branch(VERSION[0])
+ url = "https://grass.osgeo.org/addons/{}/".format(version_branch)
# else try to get extensions XMl from SVN repository (provided URL)
# the exact action depends on subsequent code (somewhere)
@@ -2930,7 +2988,7 @@ def main():
if options["operation"] == "add":
check_dirs()
if sys.platform == "win32":
- install_extension()
+ install_extension(url=original_url)
else:
if original_url == "" or flags["o"]:
# Query GitHub API only if extension will be downloaded