From b7dbf45a90657b341be1f4e00b3fd9bb09688b5e Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 24 Aug 2026 00:35:26 +0800 Subject: [PATCH] docs: add field descriptions to the settings schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document all 70 configuration fields with pydantic Field descriptions, ported from the config sample comments and verified against the actual consumers (allow_remote_console and local had no documentation anywhere). The descriptions flow into the OpenAPI schema of GET /v3/settings, giving the Web UI tooltips, defaults and validation bounds from a single source. Response models re-declare six path fields as plain strings, which drops the inherited description — restore them explicitly. Sync the config sample: document local and allow_remote_console, drop hardware_virtualization_check which no longer exists in the schema. --- gns3server/config_samples/gns3_server.conf | 10 +- gns3server/schemas/config.py | 235 +++++++++++++-------- gns3server/schemas/controller/settings.py | 23 +- 3 files changed, 174 insertions(+), 94 deletions(-) diff --git a/gns3server/config_samples/gns3_server.conf b/gns3server/config_samples/gns3_server.conf index 50a6ca01a..86c6007e2 100644 --- a/gns3server/config_samples/gns3_server.conf +++ b/gns3server/config_samples/gns3_server.conf @@ -16,6 +16,9 @@ default_admin_password = admin [Server] +; Local server mode, set by the --local command line argument (not meant to be set by hand) +;local = False + ; Server name, default is what is returned by socket.gethostname() name = GNS3_Server @@ -77,6 +80,10 @@ console_start_port_range = 5000 ; Last console port of the range allocated to devices console_end_port_range = 10000 +; Allow console connections from remote machines +; (console ports only accept local connections by default) +;allow_remote_console = False + ; First VNC console port of the range allocated to devices. ; The value MUST BE >= 5900 and <= 65535 vnc_console_start_port_range = 5900 @@ -135,9 +142,6 @@ install_builtin_appliances = True ; Automatically pull updates from the skills repository when reloading ; skills_auto_update = false -; check if hardware virtualization is used by other emulators (KVM, VMware or VirtualBox) -hardware_virtualization_check = True - [VPCS] ; VPCS executable location, default: search in PATH ;vpcs_path = vpcs diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py index c9c838513..fdcd5dc99 100644 --- a/gns3server/schemas/config.py +++ b/gns3server/schemas/config.py @@ -32,46 +32,68 @@ from typing import List class ControllerSettings(BaseModel): - jwt_secret_key: str = None - jwt_algorithm: str = "HS256" - jwt_access_token_expire_minutes: int = 1440 # 24 hours - jwt_refresh_token_expire_minutes: int = 43200 # 30 days - default_admin_username: str = "admin" - default_admin_password: SecretStr = SecretStr("admin") + jwt_secret_key: str = Field( + None, + description="Secret key used to sign the JWT authentication tokens " + "(normally managed via the secrets directory, not the configuration file)") + jwt_algorithm: str = Field("HS256", description="Algorithm used to sign the JWT tokens") + jwt_access_token_expire_minutes: int = Field( + 1440, description="Lifetime of the JWT access tokens in minutes (24 hours by default)") + jwt_refresh_token_expire_minutes: int = Field( + 43200, description="Lifetime of the JWT refresh tokens in minutes (30 days by default)") + default_admin_username: str = Field( + "admin", + description="Initial default super admin username, cannot be changed once the controller has started once") + default_admin_password: SecretStr = Field( + SecretStr("admin"), + description="Initial default super admin password, cannot be changed once the controller has started once") model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) class VPCSSettings(BaseModel): - vpcs_path: str = "vpcs" + vpcs_path: str = Field("vpcs", description="VPCS executable location, default: search in PATH") model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) class DynamipsSettings(BaseModel): - allocate_aux_console_ports: bool = False - mmap_support: bool = True - dynamips_path: str = "dynamips" - sparse_memory_support: bool = True - ghost_ios_support: bool = True + allocate_aux_console_ports: bool = Field( + False, description="Allocate auxiliary console ports on IOS routers") + mmap_support: bool = Field( + True, description="Use memory-mapped flash files (mmap) to lower the memory usage of routers") + dynamips_path: str = Field("dynamips", description="Dynamips executable location, default: search in PATH") + sparse_memory_support: bool = Field( + True, description="Use sparse memory allocation to lower the memory usage of routers") + ghost_ios_support: bool = Field( + True, description="Enable Ghost IOS support to share memory between identical IOS images") model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) class IOUSettings(BaseModel): - iourc_path: str = None - license_check: bool = True + iourc_path: str = Field( + None, description="Path of your .iourc file, the file is searched in $HOME/.iourc if not provided") + license_check: bool = Field( + True, + description="Validate the iourc license file (if disabled, IOU will not start and no errors " + "will be shown when the license is invalid)") model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) class QemuSettings(BaseModel): - enable_monitor: bool = True - monitor_host: str = "127.0.0.1" - enable_hardware_acceleration: bool = True - require_hardware_acceleration: bool = False - allow_unsafe_options: bool = False - ovmf_firmware_dir: str = "/usr/share/OVMF" + enable_monitor: bool = Field( + True, description="Use the Qemu monitor feature to communicate with Qemu VMs") + monitor_host: str = Field("127.0.0.1", description="IP used to listen for the monitor") + enable_hardware_acceleration: bool = Field( + True, description="Enable hardware acceleration (KVM)") + require_hardware_acceleration: bool = Field( + False, description="Require hardware acceleration in order to start VMs") + allow_unsafe_options: bool = Field( + False, description="Allow unsafe additional command line options") + ovmf_firmware_dir: str = Field( + "/usr/share/OVMF", description="Path to the OVMF firmware directory") model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) @@ -98,12 +120,17 @@ class VMwareSettings(BaseModel): class WebWiresharkSettings(BaseModel): - enabled: bool = True - image: str = "gns3/web-wireshark:latest" - network_subnet: str = "172.31.0.0/22" - memory: str = "2g" - cpus: float = 1.0 - pids_limit: int = 1000 + enabled: bool = Field( + True, description="Enable the Web Wireshark feature (container-based Wireshark in the browser)") + image: str = Field( + "gns3/web-wireshark:latest", description="Docker image for the Web Wireshark containers") + network_subnet: str = Field( + "172.31.0.0/22", + description="Docker network subnet for the Web Wireshark containers (change it if it conflicts " + "with your existing network)") + memory: str = Field("2g", description='Memory limit per container (e.g. "512m", "2g")') + cpus: float = Field(1.0, description="CPU cores per container (e.g. 1.0, 2.0)") + pids_limit: int = Field(1000, description="Process limit per container") model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) @@ -136,64 +163,104 @@ class BuiltinSymbolTheme(str, Enum): class ServerSettings(BaseModel): - local: bool = False - enable_http_auth: bool = True - name: str = f"{socket.gethostname()} (controller)" - protocol: ServerProtocol = ServerProtocol.http - host: str = "0.0.0.0" - port: int = Field(3080, gt=0, le=65535) - secrets_dir: DirectoryPath = None - certfile: FilePath = None - certkey: FilePath = None - enable_ssl: bool = False - images_path: str = "~/GNS3/images" - projects_path: str = "~/GNS3/projects" - appliances_path: str = "~/GNS3/appliances" - symbols_path: str = "~/GNS3/symbols" - configs_path: str = "~/GNS3/configs" - resources_path: str = None - default_symbol_theme: BuiltinSymbolTheme = BuiltinSymbolTheme.affinity_square_blue - allow_raw_images: bool = True - auto_discover_images: bool = True - report_errors: bool = True - additional_images_paths: List[str] = Field(default_factory=list) - console_start_port_range: int = Field(5000, gt=0, le=65535) - console_end_port_range: int = Field(10000, gt=0, le=65535) - vnc_console_start_port_range: int = Field(5900, ge=5900, le=65535) - vnc_console_end_port_range: int = Field(10000, ge=5900, le=65535) - udp_start_port_range: int = Field(10000, gt=0, le=65535) - udp_end_port_range: int = Field(30000, gt=0, le=65535) - ubridge_path: str = "ubridge" - # Transport for the uBridge hypervisor control channel. "unix" (-U, - # AF_UNIX + SO_PEERCRED) is the default — recommended on Linux for - # kernel-level peer authentication. "tcp" (-H) is retained for backward - # compatibility. - ubridge_control_transport: UbridgeControlTransport = UbridgeControlTransport.unix - # Marker (traffic-insight) UDP sink: one listener per compute process that - # receives ubridge MARK signals from every ubridge on this host. The host - # defaults to loopback because ubridge runs on the same host as the compute. - # port=0 lets the OS choose a free port (read back and handed to ubridge). - marker_listen_host: str = "127.0.0.1" - marker_listen_port: int = Field(3070, ge=0, le=65535) - compute_username: str = "gns3" - compute_password: SecretStr = SecretStr("") - allowed_interfaces: List[str] = Field(default_factory=list) - default_nat_interface: str = None - allow_remote_console: bool = False - enable_builtin_templates: bool = True - install_builtin_appliances: bool = True - skills_repo_url: str = "https://github.com/gns3/gns3-skills.git" - skills_repo_branch: str = "main" - skills_auto_update: bool = True - - # MCP (Model Context Protocol) transport security settings - # DNS rebinding protection is disabled by default to allow connections - # from any host (aligns with GNS3 server's 0.0.0.0 binding). - # Users with security requirements can enable protection and specify - # allowed hosts using "host:*" port wildcard patterns. - mcp_enable_dns_rebinding_protection: bool = False - mcp_allowed_hosts: list[str] = Field(default_factory=list) - mcp_allowed_origins: list[str] = Field(default_factory=list) + local: bool = Field( + False, + description="Local server mode, set by the --local command line argument (not meant to be set by hand)") + enable_http_auth: bool = Field(True, description="Enable compute HTTP authentication") + name: str = Field( + f"{socket.gethostname()} (controller)", + description="Server name, default is what is returned by socket.gethostname()") + protocol: ServerProtocol = Field( + ServerProtocol.http, description="Protocol used by the server: http or https") + host: str = Field("0.0.0.0", description="IP address where the server listens for connections") + port: int = Field(3080, gt=0, le=65535, description="HTTP port used to control the server") + secrets_dir: DirectoryPath = Field( + None, description="Directory where secrets are stored (e.g. the JWT secret key)") + certfile: FilePath = Field(None, description="SSL certificate file, requires enable_ssl") + certkey: FilePath = Field(None, description="SSL key file, requires enable_ssl") + enable_ssl: bool = Field(False, description="Enable SSL encryption") + images_path: str = Field("~/GNS3/images", description="Path where binary images are stored") + projects_path: str = Field("~/GNS3/projects", description="Path where user projects are stored") + appliances_path: str = Field("~/GNS3/appliances", description="Path where custom user appliances are stored") + symbols_path: str = Field("~/GNS3/symbols", description="Path where custom user symbols are stored") + configs_path: str = Field("~/GNS3/configs", description="Path where custom user configs are stored") + resources_path: str = Field( + None, + description="Path where files like built-in appliances and Docker resources are stored " + "(defaults to the local user data directory)") + default_symbol_theme: BuiltinSymbolTheme = Field( + BuiltinSymbolTheme.affinity_square_blue, + description='Default symbol theme, e.g. "Classic" or "Affinity-square-blue"') + allow_raw_images: bool = Field( + True, description="Allow raw images to be uploaded to the server") + auto_discover_images: bool = Field( + True, description="Automatically discover images in the images directory") + report_errors: bool = Field( + True, description="Automatically send crash reports to the GNS3 team") + additional_images_paths: List[str] = Field( + default_factory=list, + description="Additional paths to look for images (semicolon-separated in the configuration file)") + console_start_port_range: int = Field( + 5000, gt=0, le=65535, description="First console port of the range allocated to devices") + console_end_port_range: int = Field( + 10000, gt=0, le=65535, description="Last console port of the range allocated to devices") + vnc_console_start_port_range: int = Field( + 5900, ge=5900, le=65535, description="First VNC console port of the range allocated to devices") + vnc_console_end_port_range: int = Field( + 10000, ge=5900, le=65535, description="Last VNC console port of the range allocated to devices") + udp_start_port_range: int = Field( + 10000, gt=0, le=65535, + description="First UDP port of the range allocated for inter-device communication (two ports per link)") + udp_end_port_range: int = Field( + 30000, gt=0, le=65535, + description="Last UDP port of the range allocated for inter-device communication (two ports per link)") + ubridge_path: str = Field("ubridge", description="uBridge executable location, default: search in PATH") + ubridge_control_transport: UbridgeControlTransport = Field( + UbridgeControlTransport.unix, + description='uBridge control channel transport: "unix" (AF_UNIX + SO_PEERCRED, recommended ' + 'on Linux) or "tcp" (loopback, kept for backward compatibility)') + marker_listen_host: str = Field( + "127.0.0.1", + description="Marker (traffic-insight) UDP sink listen host: one listener per compute process " + "receives uBridge MARK signals from every uBridge on this host") + marker_listen_port: int = Field( + 3070, ge=0, le=65535, + description="Marker UDP sink listen port (0 lets the operating system choose a free port)") + compute_username: str = Field( + "gns3", description='Username for compute HTTP authentication, "gns3" is the default') + compute_password: SecretStr = Field( + SecretStr(""), + description="Password for compute HTTP authentication, a randomly generated password is used if not set") + allowed_interfaces: List[str] = Field( + default_factory=list, + description="Only allow these interfaces to be used by GNS3, for the Cloud node for example " + "(comma-separated; do not forget virbr0 for the NAT node to work)") + default_nat_interface: str = Field( + None, description="Interface used by the NAT node, default is virbr0 on Linux (requires libvirt)") + allow_remote_console: bool = Field( + False, + description="Allow console connections from remote machines " + "(console ports only accept local connections by default)") + enable_builtin_templates: bool = Field(True, description="Enable the built-in templates") + install_builtin_appliances: bool = Field(True, description="Install the built-in appliances") + skills_repo_url: str = Field( + "https://github.com/gns3/gns3-skills.git", + description="Git repository URL for the external GNS3 Copilot skills " + "(injection skills, prompts and device skills)") + skills_repo_branch: str = Field("main", description="Git branch of the skills repository") + skills_auto_update: bool = Field( + True, description="Automatically pull updates from the skills repository when reloading") + mcp_enable_dns_rebinding_protection: bool = Field( + False, + description="Enable MCP transport DNS rebinding protection " + "(allowed hosts and origins must be configured)") + mcp_allowed_hosts: list[str] = Field( + default_factory=list, + description='Allowed hosts for MCP connections, only "host:*" port wildcards are supported ' + '(e.g. "127.0.0.1:*")') + mcp_allowed_origins: list[str] = Field( + default_factory=list, + description='Allowed origins for MCP connections (e.g. "http://localhost:*")') model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) diff --git a/gns3server/schemas/controller/settings.py b/gns3server/schemas/controller/settings.py index 9d7f7a233..73bace5bf 100644 --- a/gns3server/schemas/controller/settings.py +++ b/gns3server/schemas/controller/settings.py @@ -50,24 +50,33 @@ class ServerSettingsResponse(ServerSettings): # plain strings instead of FilePath/DirectoryPath: paths are validated when # the settings are loaded or updated, not when echoed back to the client - secrets_dir: Optional[str] = None - certfile: Optional[str] = None - certkey: Optional[str] = None + secrets_dir: Optional[str] = Field( + None, description="Directory where secrets are stored (e.g. the JWT secret key)") + certfile: Optional[str] = Field(None, description="SSL certificate file, requires enable_ssl") + certkey: Optional[str] = Field(None, description="SSL key file, requires enable_ssl") # Optional overrides: typed as plain "str = None" in the config schema, # which fails re-validation when the value actually is None - resources_path: Optional[str] = None - default_nat_interface: Optional[str] = None + resources_path: Optional[str] = Field( + None, + description="Path where files like built-in appliances and Docker resources are stored " + "(defaults to the local user data directory)") + default_nat_interface: Optional[str] = Field( + None, description="Interface used by the NAT node, default is virbr0 on Linux (requires libvirt)") class ControllerSettingsResponse(ControllerSettings): # never serialized: managed via the secrets directory, not the configuration file - jwt_secret_key: Optional[str] = Field(default=None, exclude=True) + jwt_secret_key: Optional[str] = Field( + default=None, exclude=True, + description="Secret key used to sign the JWT authentication tokens " + "(normally managed via the secrets directory, not the configuration file)") class IOUSettingsResponse(IOUSettings): - iourc_path: Optional[str] = None + iourc_path: Optional[str] = Field( + None, description="Path of your .iourc file, the file is searched in $HOME/.iourc if not provided") class SettingsResponse(BaseModel):