gns3 deploy initial repo
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# gns3-remote-install-redux
|
||||
|
||||
Revised version of the OG [upstream GNS3 remote installer](https://github.com/GNS3/gns3-server/blob/master/scripts/remote-install.sh).
|
||||
|
||||
## What's different
|
||||
|
||||
- **Single primary apt pass**: packages, repos, and groups rolled up from flags before install runs (as much as possible)
|
||||
- **Preflight**: checks root, OS, needed commands, ports, modules, CPU virt extensions. Reports every error condition at once
|
||||
- **Auto-loads KVM**: `modprobe` + persist to `/etc/modules-load.d/`, unless `--without-kvm`
|
||||
- **Retry + verify**: apt retries 3x with backoff; services verified with `systemctl is-active`; GNS3 API curled post-install
|
||||
- **WireGuard**: adds modern VPN option
|
||||
- **EC crypto**: P-384 by default for OpenVPN (faster keygen, no DH params). `--legacy-rsa` if needed
|
||||
- **Encrypted configs**: VPN configs are encrypted with one-time passphrase. A copy+paste one-liner is printed to console to curl and decrypt the file at once. `--unsafe-configs` to skip
|
||||
- **Scoped firewall**: UFW rules allow VPN from 0.0.0.0/0, others scoped to SSH source + local subnets
|
||||
- **Sysctl hardening**: rp_filter, syncookies, ICMP redirect rejection, source route disable
|
||||
- **Landing page**: ephemeral web page gives server info, version-matched client downloads, VPN configs, warnings, and resources. Auto-expires via systemd timer. Uses Python httpd.server module, instead of installing nginx package.
|
||||
- **Reconfigure**: enhanced idempotence. detects prior install, stops services, re-runs cleanly
|
||||
- **Testable**: `main()` guarded so functions can be sourced for BATS or plain bash tests
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
sudo ./gns3-remote-install-redux.sh [OPTIONS]
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|---|---|---|
|
||||
| `--with-openvpn` | off | Install and configure OpenVPN |
|
||||
| `--with-wireguard` | off | Install and configure WireGuard |
|
||||
| `--with-welcome` | off | Install GNS3-VM welcome console UI |
|
||||
| `--without-kvm` | KVM on | Disable KVM hardware acceleration |
|
||||
| `--without-docker` | Docker on | Skip Docker CE installation |
|
||||
| `--without-firewall` | UFW on | Skip automatic UFW rule configuration |
|
||||
| `--without-system-upgrade` | upgrade on | Skip `apt upgrade` |
|
||||
| `--unsafe-configs` | encrypted | Serve VPN configs unencrypted |
|
||||
| `--legacy-rsa` | EC P-384 | Use RSA-2048 + DH for OpenVPN |
|
||||
| `--unstable` | stable PPA | Use the GNS3 unstable PPA |
|
||||
| `--custom-repository REPO` | `ppa` | Use a custom GNS3 PPA name |
|
||||
| `-h`, `--help` | | Show help |
|
||||
|
||||
## Requirements
|
||||
|
||||
Ubuntu Server 24.04 LTS (amd64). Run as root or with `sudo`.
|
||||
|
||||
## License
|
||||
|
||||
Based on the original GNS3 remote installer (GPLv3).
|
||||
@@ -0,0 +1,142 @@
|
||||
# TODO.md Convention Adoption
|
||||
|
||||
`[x]` done, `[-]` partial, `[ ]` not started
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Low-Hanging Fruit (done or nearly done)
|
||||
|
||||
- [x] `set -euo pipefail` + `IFS=$'\n\t'`
|
||||
- [x] `readonly PATH` (Linux-only, homebrew paths removed)
|
||||
- [x] Structured header block with metadata
|
||||
- [x] `_READONLY_GLOBAL_` naming for constants
|
||||
- [x] `_local_var` naming for function locals and iterators
|
||||
- [x] `"${VAR}"` expansion everywhere (quoted + braced)
|
||||
- [x] `log_*` hierarchy (`info`, `ok`, `warn`, `error`, `fatal`)
|
||||
- [x] `log_warn_sticky`->`WARN_MESSAGES` array
|
||||
- [x] `ensure_directory` / `ensure_config` / `require_file` helpers
|
||||
- [x] `fetch_file` (stdout-based, composable)
|
||||
- [x] `render_template` (stdin-based, `{{KEY}}` substitution)
|
||||
- [x] `enable_and_start` with retry loop
|
||||
- [x] Predicates: `file_exists`, `dir_exists`, `var_exists`, `port_open`
|
||||
- [x] `if/then/fi` instead of `[[ ]] &&` shorthand (most converted)
|
||||
- [x] `encrypted_copy` uses `openssl enc` with `-pbkdf2`
|
||||
- [x] Decrypt commands match encryption params
|
||||
- [x] `gns3_repo_present` handles deb822 format
|
||||
- [x] Sudoers lines have no leading whitespace
|
||||
- [x] Stray box-drawing chars removed from section headers
|
||||
- [x] `fetch_resource` removed `fetch_file` used everywhere
|
||||
- [x] IP lookups cached as `SERVER_PUBLIC_IP` / `SERVER_PRIVATE_IP` / `SERVER_HOSTNAME`
|
||||
- [x] No redundant IP re-checks mid-script
|
||||
- [x] `.shellcheckrc` with justified exclusions
|
||||
- [-] Remaining `[[ ]] && / ||` patterns most converted, grep to find stragglers
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Structural Alignment
|
||||
|
||||
- [ ] Replace `on_error()` + separate `INT/TERM` trap with single `cleanup()` on `EXIT`
|
||||
- Convention: `trap cleanup EXIT` as first line of `main()`
|
||||
- `cleanup()` preserves exit code, disarms traps, `set +e`
|
||||
- Restart GNS3 on failure (current `on_error` behavior) moves into cleanup
|
||||
- INT/TERM: set context flag and `exit` - cleanup runs via EXIT trap
|
||||
- [ ] Add `setup_tempdir()` - centralized `TEMP_DIR` used by `apt_retry` and `ensure_config`
|
||||
- Currently both use ad-hoc `mktemp`
|
||||
- `ensure_config` should use `mktemp "${TEMP_DIR}/..."` instead of `mktemp "${_path}.XXXXXX"`
|
||||
- [ ] Add tracking arrays wired to helpers:
|
||||
- `DIRECTORIES_CREATED` ← `ensure_directory` appends
|
||||
- `SERVICES_ENABLED` ← `enable_and_start` appends
|
||||
- `PACKAGES_INSTALLED` ← `install_packages` populates (optional)
|
||||
- Summary reads from these arrays
|
||||
- [ ] Add `ROLLBACK_SERVICES` / `ROLLBACK_DIRS` typed registries
|
||||
- `cleanup()` iterates in reverse on failure
|
||||
- [ ] Replace `flock` lockfile with `mkdir`-based lock + stale PID detection (skeleton pattern)
|
||||
- [ ] Remove `WARNINGS_OCCURRED` or use it - currently set but never read
|
||||
- [ ] Add `require_args` to helper functions (from skeleton)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Naming & Organization
|
||||
|
||||
- [ ] Standardize template placeholder names
|
||||
- Current: mixed `_DEPLOY_MARKER_` / `_listen_host` / `GNS3_PORT` / `_hw_accel`
|
||||
- Target: consistent `SCREAMING_SNAKE` for all placeholders (they're config values, not locals)
|
||||
- [ ] Rename phase functions to convention verbs:
|
||||
- `preflight_checks`->`preflight`
|
||||
- `setup_config_server`->`config_server`
|
||||
- `configure_firewall`->`firewall`
|
||||
- `apply_sysctl_hardening`->`hardening`
|
||||
- `start_services`->(merge into per-service phases or remove)
|
||||
- `print_summary`->`summary`
|
||||
- [ ] Rename sub-functions with phase prefix:
|
||||
- `setup_groups`->`acct_create_groups`
|
||||
- `setup_gns3_user`->`acct_create_gns3_user`
|
||||
- `propagate_groups_to_invoker`->`acct_add_invoker`
|
||||
- `add_gns3_repository`->`repo_add_gns3`
|
||||
- `add_docker_repository`->`repo_add_docker`
|
||||
- VPN functions: `ovpn_*` and `wg_*` prefixes
|
||||
- [ ] Reorder function definitions to match execution order
|
||||
- [ ] Add INDEX section to header mapping functions to sections
|
||||
- [ ] Add `VERSION` constant, print in `--help` and summary
|
||||
- [ ] Cache `_SCRIPT_DIR_` as readonly global (currently computed multiple times in `fetch_file`)
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Macro Refactor
|
||||
|
||||
- [ ] Two-tier `main()` structure:
|
||||
```
|
||||
main->bootstrap->preflight->repositories->packages →
|
||||
accounts->gns3->docker->openvpn->wireguard →
|
||||
welcome->firewall->hardening->config_server →
|
||||
validate->summary
|
||||
```
|
||||
- [ ] Each phase is a noun function containing only sub-function calls
|
||||
- [ ] Sub-functions use `phase_verb_noun` naming
|
||||
- [ ] Phase banner comments with purpose description
|
||||
- [ ] Flag gating in `main()` with `if/then`:
|
||||
```bash
|
||||
if [[ "${WITH_DOCKER}" -eq 1 ]]; then docker; fi
|
||||
if [[ "${WITH_OPENVPN}" -eq 1 ]]; then openvpn; fi
|
||||
```
|
||||
- [ ] Add predicate functions for recurring checks:
|
||||
- `service_active` / `user_exists` / `interface_up` / `repo_present` / `packages_installed`
|
||||
- [ ] Move `apply_option_flags` (rollup) into its own named phase or call from `main()` before bootstrap
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Multi-File Split (future, when script exceeds ~1000 LOC of logic)
|
||||
|
||||
- [ ] `conf/` - readonly vars/arrays per feature
|
||||
- [ ] `lib/` source-only helpers, no `main()`
|
||||
- [ ] `mod/` executable modules with numeric prefix ordering
|
||||
- [ ] `bootstrap.sh` top-level orchestrator, executes `mod/*.sh`
|
||||
- [ ] Source guards: `[[ -n "${_LIB_X_LOADED:-}" ]] && return 0`
|
||||
- [ ] Direct-execution guards on sourced files
|
||||
|
||||
---
|
||||
|
||||
## Ongoing / Per-Commit
|
||||
|
||||
- [ ] `shfmt -i 2 -ci` before commit
|
||||
- [ ] `shellcheck` zero warnings (with `.shellcheckrc` exclusions)
|
||||
- [ ] Update `Changed:` date in header on substantive edits
|
||||
- [ ] Update INDEX when function structure changes
|
||||
- [ ] Convention: Check / Do / Verify for every action
|
||||
- [ ] Convention: secrets never as function arguments; use env/files/stdin
|
||||
- [ ] Convention: `backup_file` before modifying existing configs (not yet implemented)
|
||||
|
||||
---
|
||||
|
||||
## Known Deferred Items
|
||||
|
||||
- [ ] `check_integrity()` SHA256 validation for downloaded resources
|
||||
- [ ] `--with-optimization` phase TCP/KVM tuning (BBR, swappiness, etc.)
|
||||
- [ ] 26.04 codename fallback check GNS3 PPA Packages.xz for actual content
|
||||
- [ ] OpenVPN client.ovpn still uses heredoc (PEM blocks don't work with `render_template`)
|
||||
- [ ] `docker.list` still uses heredoc (inline `$(dpkg --print-architecture)` call)
|
||||
- [ ] Validate function could check `ufw status numbered` rule count matches expectations
|
||||
- [ ] Landing page: version-matched GNS3 client download URLs via JS
|
||||
- [ ] GPG-signed bootstrap at `lrk.cx/gns3`
|
||||
|
||||
---
|
||||
File diff suppressed because it is too large
Load Diff
+1724
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
|
||||
=0YYh
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,577 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import configparser
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
from json import loads as convert
|
||||
|
||||
from dialog import Dialog, PythonDialogBug
|
||||
|
||||
|
||||
class Welcome_dialog:
|
||||
def __init__(self):
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
except locale.Error:
|
||||
# Not supported via SSH
|
||||
pass
|
||||
self.display = Dialog(dialog="dialog", autowidgetsize=True)
|
||||
if self.gns3_version() is None:
|
||||
self.display.set_background_title("GNS3")
|
||||
else:
|
||||
self.display.set_background_title("GNS3 {}".format(self.gns3_version()))
|
||||
|
||||
def get_ip(self):
|
||||
"""
|
||||
Return the active IP
|
||||
"""
|
||||
# request 'ip addr' data in JSON format from shell
|
||||
ip_addr_response = subprocess.run(["ip", "--json", "addr"], capture_output=True)
|
||||
|
||||
# process response, decode and use json.loads to convert the string to a dict
|
||||
ip_addr_data = convert(ip_addr_response.stdout.decode("utf-8"))
|
||||
|
||||
# search ip_addr_data for the first ip adress that is not under a virtual bridge or loopback interface
|
||||
for i in ip_addr_data:
|
||||
if ("virbr" in i["ifname"]) or ("lo" in i["ifname"]):
|
||||
continue
|
||||
try:
|
||||
if "UP" in i["flags"]:
|
||||
ip_addr = i["addr_info"][0]["local"]
|
||||
break
|
||||
except:
|
||||
continue
|
||||
ip_addr = None
|
||||
|
||||
return ip_addr
|
||||
|
||||
def repair_remote_install(self):
|
||||
"""
|
||||
This method is only called by remote-install.sh during setup to ensure it is setting the same IP as shown by Dialog
|
||||
"""
|
||||
ip_addr = self.get_ip()
|
||||
subprocess.run(
|
||||
[
|
||||
"sed",
|
||||
"-i",
|
||||
f"s/host = 0.0.0.0/host = {ip_addr}/",
|
||||
"/etc/gns3/gns3_server.conf",
|
||||
],
|
||||
capture_output=False,
|
||||
)
|
||||
subprocess.run(["service", "gns3", "stop"], capture_output=False)
|
||||
subprocess.run(["service", "gns3", "start"], capture_output=False)
|
||||
|
||||
def get_config(self):
|
||||
"""
|
||||
Read the config
|
||||
"""
|
||||
config = configparser.RawConfigParser()
|
||||
path = os.path.expanduser("~/.config/GNS3/gns3_server.conf")
|
||||
config.read([path], encoding="utf-8")
|
||||
return config
|
||||
|
||||
def write_config(self, config):
|
||||
"""
|
||||
Write the config file
|
||||
"""
|
||||
|
||||
with open(os.path.expanduser("~/.config/GNS3/gns3_server.conf"), "w") as f:
|
||||
config.write(f)
|
||||
|
||||
def gns3_major_version(self):
|
||||
"""
|
||||
Returns the GNS3 major server version
|
||||
"""
|
||||
|
||||
version = self.gns3_version()
|
||||
if version:
|
||||
match = re.search(r"\d+.\d+", version)
|
||||
return match.group(0)
|
||||
return ""
|
||||
|
||||
def gns3_version(self):
|
||||
"""
|
||||
Return the GNS3 server version
|
||||
"""
|
||||
try:
|
||||
return subprocess.check_output(["gns3server", "--version"]).strip().decode()
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return None
|
||||
|
||||
def gns3vm_version(self):
|
||||
"""
|
||||
Return the GNS3 VM version
|
||||
"""
|
||||
try:
|
||||
with open("/home/gns3/.config/GNS3/gns3vm_version") as f:
|
||||
return f.read().strip()
|
||||
except FileNotFoundError:
|
||||
return "Remote Install"
|
||||
|
||||
def mode(self):
|
||||
if (
|
||||
self.display.yesno(
|
||||
"This feature is for testers only. You may break your GNS3 installation. Are you REALLY sure you want to continue?",
|
||||
yes_label="Exit (Safe option)",
|
||||
no_label="Continue",
|
||||
)
|
||||
== self.display.OK
|
||||
):
|
||||
return
|
||||
code, tag = self.display.menu(
|
||||
"Select the GNS3 version",
|
||||
choices=[
|
||||
("2.1", "Stable release for this GNS3 VM (RECOMMENDED)"),
|
||||
("2.1dev", "Development version for stable release"),
|
||||
("2.2", "Latest stable release"),
|
||||
],
|
||||
)
|
||||
self.display.clear()
|
||||
if code == Dialog.OK:
|
||||
os.makedirs(os.path.expanduser("~/.config/GNS3"), exist_ok=True)
|
||||
with open(os.path.expanduser("~/.config/GNS3/gns3_release"), "w+") as f:
|
||||
f.write(tag)
|
||||
|
||||
self.update(force=True)
|
||||
|
||||
def get_release(self):
|
||||
try:
|
||||
with open(os.path.expanduser("~/.config/GNS3/gns3_release")) as f:
|
||||
content = f.read()
|
||||
|
||||
# Support old VM versions
|
||||
if content == "stable":
|
||||
content = "1.5"
|
||||
elif content == "testing":
|
||||
content = "1.5"
|
||||
elif content == "unstable":
|
||||
content = "1.5dev"
|
||||
|
||||
return content
|
||||
except OSError:
|
||||
return "1.5"
|
||||
|
||||
def update(self, force=False):
|
||||
if not force:
|
||||
if (
|
||||
self.display.yesno(
|
||||
"It is recommended to ensure all Nodes are shutdown before upgrading. Continue?"
|
||||
)
|
||||
!= self.display.OK
|
||||
):
|
||||
return
|
||||
code, option = self.display.menu(
|
||||
"Select an option",
|
||||
choices=[
|
||||
("Upgrade GNS3", "Upgrades only the GNS3 pakage and dependences."),
|
||||
("Upgrade All", "Upgrades all avaiable packages"),
|
||||
(
|
||||
"Dist Upgrade",
|
||||
"Upgrades all avaiable packages and the Linux Kernel. Requires a reboot.",
|
||||
),
|
||||
],
|
||||
)
|
||||
if code == Dialog.OK:
|
||||
if option == "Upgrade GNS3":
|
||||
ret = os.system(
|
||||
"sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A2E3EF7B \
|
||||
&& sudo apt-get update \
|
||||
&& sudo apt-get install -y --only-upgrade gns3-server"
|
||||
)
|
||||
elif option == "Upgrade All":
|
||||
ret = os.system(
|
||||
'sudo apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com \
|
||||
&& sudo apt-get update \
|
||||
&& sudo apt-get upgrade --yes --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"'
|
||||
)
|
||||
elif option == "Dist Upgrade":
|
||||
ret = os.system(
|
||||
'sudo apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com \
|
||||
&& sudo apt-get update \
|
||||
&& sudo apt-get dist-upgrade --yes --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"'
|
||||
)
|
||||
if ret != 0:
|
||||
print(
|
||||
"ERROR DURING UPGRADE PROCESS PLEASE TAKE A SCREENSHOT IF YOU NEED SUPPORT"
|
||||
)
|
||||
time.sleep(15)
|
||||
return
|
||||
if option == "Dist Upgrade":
|
||||
if self.display.yesno("Reboot now?") == self.display.OK:
|
||||
os.system("sudo reboot now")
|
||||
|
||||
def migrate(self):
|
||||
"""
|
||||
Migrate GNS3 VM data.
|
||||
"""
|
||||
|
||||
code, option = self.display.menu(
|
||||
"Select an option",
|
||||
choices=[
|
||||
("Setup", "Configure this VM to send data to another GNS3 VM"),
|
||||
("Send", "Send images and projects to another GNS3 VM"),
|
||||
],
|
||||
)
|
||||
self.display.clear()
|
||||
if code == Dialog.OK:
|
||||
(answer, destination) = self.display.inputbox(
|
||||
"What is IP address or hostname of the other GNS3 VM?",
|
||||
init="172.16.1.128",
|
||||
)
|
||||
if answer != self.display.OK:
|
||||
return
|
||||
if destination == self.get_ip():
|
||||
self.display.msgbox(
|
||||
"The destination cannot be the same as this VM IP address ({})".format(
|
||||
destination
|
||||
)
|
||||
)
|
||||
return
|
||||
if option == "Send":
|
||||
# first make sure they are no files belonging to root
|
||||
os.system("sudo chown -R gns3:gns3 /opt/gns3")
|
||||
# then rsync the data
|
||||
command = r"rsync -az --progress -e 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /home/gns3/.ssh/gns3-vm-key' /opt/gns3 gns3@{}:/opt".format(
|
||||
destination
|
||||
)
|
||||
ret = os.system('bash -c "{}"'.format(command))
|
||||
time.sleep(10)
|
||||
if ret != 0:
|
||||
self.display.msgbox(
|
||||
"Could not send data to the other GNS3 VM located at {}".format(
|
||||
destination
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.display.msgbox(
|
||||
"Images and projects have been successfully sent to the other GNS3 VM located at {}".format(
|
||||
destination
|
||||
)
|
||||
)
|
||||
elif option == "Setup":
|
||||
script = """
|
||||
if [ ! -f ~/.ssh/gns3-vm-key ]
|
||||
then
|
||||
ssh-keygen -f ~/.ssh/gns3-vm-key -N '' -C gns3@{}
|
||||
fi
|
||||
ssh-copy-id -i ~/.ssh/gns3-vm-key gns3@{}
|
||||
""".format(self.get_ip(), destination)
|
||||
ret = os.system('bash -c "{}"'.format(script))
|
||||
time.sleep(10)
|
||||
if ret != 0:
|
||||
self.display.msgbox("Error while setting up the migrate feature")
|
||||
else:
|
||||
self.display.msgbox(
|
||||
"Configuration successful, you can now send data to the GNS3 VM located at {} without password".format(
|
||||
destination
|
||||
)
|
||||
)
|
||||
|
||||
def shrink_disk(self):
|
||||
|
||||
ret = os.system("lspci | grep -i vmware")
|
||||
if ret != 0:
|
||||
self.display.msgbox(
|
||||
"Shrinking the disk is only supported when running inside VMware"
|
||||
)
|
||||
return
|
||||
|
||||
if (
|
||||
self.display.yesno(
|
||||
"Would you like to shrink the VM disk? The VM will reboot at the end of the process. Continue?"
|
||||
)
|
||||
!= self.display.OK
|
||||
):
|
||||
return
|
||||
|
||||
os.system("sudo service gns3 stop")
|
||||
os.system("sudo service docker stop")
|
||||
os.system("sudo vmware-toolbox-cmd disk shrink /opt")
|
||||
os.system("sudo vmware-toolbox-cmd disk shrink /")
|
||||
|
||||
self.display.msgbox("The GNS3 VM will reboot")
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "reboot"])
|
||||
|
||||
def vm_information(self):
|
||||
"""
|
||||
Show IP, SSH settings....
|
||||
"""
|
||||
|
||||
content = "Welcome to GNS3 appliance\n\n"
|
||||
|
||||
version = self.gns3_version()
|
||||
if version is None:
|
||||
content += "GNS3 is not installed please install it with sudo pip3 install gns3-server. Or download a preinstalled VM.\n\n"
|
||||
else:
|
||||
content = "GNS3 version: {gns3_version}\nVM version: {gns3vm_version}\nKVM support available: {kvm}\n\n".format(
|
||||
gns3vm_version=self.gns3vm_version(),
|
||||
gns3_version=version,
|
||||
kvm=self.kvm_support(),
|
||||
)
|
||||
|
||||
ip = self.get_ip()
|
||||
|
||||
if ip:
|
||||
content += f"""
|
||||
IP: {ip}
|
||||
Web UI: http://{ip}:3080
|
||||
|
||||
To log in using SSH:
|
||||
ssh gns3@{ip}
|
||||
Password: gns3
|
||||
|
||||
Images and projects are located in /opt/gns3
|
||||
""".strip()
|
||||
|
||||
else:
|
||||
content += "eth0 is not configured. Please manually configure it via the Networking menu."
|
||||
|
||||
content += "\n\nRelease channel: " + self.get_release()
|
||||
|
||||
try:
|
||||
self.display.msgbox(content)
|
||||
# If it's an scp command or any bugs
|
||||
except:
|
||||
os.execvp("bash", ["/bin/bash"])
|
||||
|
||||
def check_internet_connectivity(self):
|
||||
self.display.pause("Please wait...\n\n")
|
||||
try:
|
||||
response = urllib.request.urlopen("http://pypi.python.org/", timeout=5)
|
||||
except urllib.request.URLError as err:
|
||||
self.display.infobox(
|
||||
"Can't connect to Internet (pypi.python.org): {}".format(str(err))
|
||||
)
|
||||
time.sleep(15)
|
||||
return
|
||||
self.display.infobox("Connection to Internet: OK")
|
||||
time.sleep(2)
|
||||
|
||||
def keyboard_configuration():
|
||||
"""
|
||||
Allow user to change the keyboard layout
|
||||
"""
|
||||
os.system("/usr/bin/sudo dpkg-reconfigure keyboard-configuration")
|
||||
|
||||
def set_security(self):
|
||||
config = self.get_config()
|
||||
if self.display.yesno("Enable server authentication?") == self.display.OK:
|
||||
if not config.has_section("Server"):
|
||||
config.add_section("Server")
|
||||
config.set("Server", "auth", True)
|
||||
(answer, text) = self.display.inputbox("Login?")
|
||||
if answer != self.display.OK:
|
||||
return
|
||||
config.set("Server", "user", text)
|
||||
(answer, text) = self.display.passwordbox("Password?")
|
||||
if answer != self.display.OK:
|
||||
return
|
||||
config.set("Server", "password", text)
|
||||
else:
|
||||
config.set("Server", "auth", False)
|
||||
|
||||
self.write_config(config)
|
||||
|
||||
def log(self):
|
||||
os.system("/usr/bin/sudo chmod 755 /var/log/upstart/gns3.log")
|
||||
with open("/var/log/upstart/gns3.log") as f:
|
||||
try:
|
||||
while True:
|
||||
line = f.readline()
|
||||
sys.stdout.write(line)
|
||||
except (KeyboardInterrupt, MemoryError):
|
||||
return
|
||||
|
||||
def edit_config(self):
|
||||
"""
|
||||
Edit GNS3 configuration file
|
||||
"""
|
||||
|
||||
major_version = self.gns3_major_version()
|
||||
if major_version == "2.2":
|
||||
os.system("nano ~/.config/GNS3/{}/gns3_server.conf".format(major_version))
|
||||
else:
|
||||
os.system("nano ~/.config/GNS3/gns3_server.conf")
|
||||
|
||||
def edit_network(self):
|
||||
"""
|
||||
Edit network configuration file
|
||||
"""
|
||||
if (
|
||||
self.display.yesno(
|
||||
"The server will reboot at the end of the process. Continue?"
|
||||
)
|
||||
!= self.display.OK
|
||||
):
|
||||
return
|
||||
os.system("sudo nano /etc/network/interfaces")
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "reboot"])
|
||||
|
||||
def edit_proxy(self):
|
||||
"""
|
||||
Configure proxy settings
|
||||
"""
|
||||
res, http_proxy = self.display.inputbox(
|
||||
text="HTTP proxy string, for example http://<user>:<password>@<proxy>:<port>. Leave empty for no proxy."
|
||||
)
|
||||
if res != self.display.OK:
|
||||
return
|
||||
res, https_proxy = self.display.inputbox(
|
||||
text="HTTPS proxy string, for example http://<user>:<password>@<proxy>:<port>. Leave empty for no proxy."
|
||||
)
|
||||
if res != self.display.OK:
|
||||
return
|
||||
|
||||
with open("/tmp/00proxy", "w+") as f:
|
||||
f.write('Acquire::http::Proxy "' + http_proxy + '";')
|
||||
os.system("sudo mv /tmp/00proxy /etc/apt/apt.conf.d/00proxy")
|
||||
os.system("sudo chown root /etc/apt/apt.conf.d/00proxy")
|
||||
os.system("sudo chmod 744 /etc/apt/apt.conf.d/00proxy")
|
||||
|
||||
with open("/tmp/proxy.sh", "w+") as f:
|
||||
f.write('export http_proxy="' + http_proxy + '"\n')
|
||||
f.write('export https_proxy="' + https_proxy + '"\n')
|
||||
f.write('export HTTP_PROXY="' + http_proxy + '"\n')
|
||||
f.write('export HTTPS_PROXY="' + https_proxy + '"\n')
|
||||
os.system("sudo mv /tmp/proxy.sh /etc/profile.d/proxy.sh")
|
||||
os.system("sudo chown root /etc/profile.d/proxy.sh")
|
||||
os.system("sudo chmod 744 /etc/profile.d/proxy.sh")
|
||||
os.system("sudo cp /etc/profile.d/proxy.sh /etc/default/docker")
|
||||
|
||||
self.display.msgbox("The GNS3 VM will reboot")
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "reboot"])
|
||||
|
||||
def kvm_support(self):
|
||||
"""
|
||||
Returns true if KVM is available
|
||||
"""
|
||||
return subprocess.call("kvm-ok") == 0
|
||||
|
||||
def kvm_control(self):
|
||||
"""
|
||||
Check if KVM is correctly configured
|
||||
"""
|
||||
|
||||
kvm_ok = self.kvm_support()
|
||||
config = self.get_config()
|
||||
try:
|
||||
if config.getboolean("Qemu", "enable_kvm") is True:
|
||||
if kvm_ok is False:
|
||||
if (
|
||||
self.display.yesno(
|
||||
"KVM is not available!\n\nQemu VM will crash!!\n\nThe reason could be unsupported hardware or another virtualization solution is already running.\n\nDisable KVM and get lower performances?"
|
||||
)
|
||||
== self.display.OK
|
||||
):
|
||||
config.set("Qemu", "enable_kvm", False)
|
||||
self.write_config(config)
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "reboot"])
|
||||
else:
|
||||
if kvm_ok is True:
|
||||
if (
|
||||
self.display.yesno(
|
||||
"KVM is available on your computer.\n\nEnable KVM and get better performances?"
|
||||
)
|
||||
== self.display.OK
|
||||
):
|
||||
config.set("Qemu", "enable_kvm", True)
|
||||
self.write_config(config)
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "reboot"])
|
||||
except configparser.NoSectionError:
|
||||
return
|
||||
|
||||
def display_loop(self):
|
||||
try:
|
||||
while True:
|
||||
code, tag = self.display.menu(
|
||||
"GNS3 {}".format(self.gns3_version()),
|
||||
choices=[
|
||||
("Information", "Display VM information"),
|
||||
("Upgrade", "Upgrade GNS3"),
|
||||
("Migrate", "Migrate data to another GNS3 VM"),
|
||||
("Shell", "Open a console"),
|
||||
("Security", "Configure authentication"),
|
||||
("Keyboard", "Change keyboard layout"),
|
||||
(
|
||||
"Configure",
|
||||
"Edit server configuration (advanced users ONLY)",
|
||||
),
|
||||
("Proxy", "Configure proxy settings"),
|
||||
("Networking", "Configure networking settings"),
|
||||
("Log", "Show server log"),
|
||||
("Test", "Check internet connection"),
|
||||
("Shrink", "Shrink the VM disk"),
|
||||
("Version", "Select the GNS3 version"),
|
||||
("Restore", "Restore the VM (if you have trouble for upgrade)"),
|
||||
("Reboot", "Reboot the VM"),
|
||||
("Shutdown", "Shutdown the VM"),
|
||||
],
|
||||
)
|
||||
self.display.clear()
|
||||
if code == Dialog.OK:
|
||||
if tag == "Shell":
|
||||
print("Type: 'welcome.py' to get back to the dialog menu.")
|
||||
sys.exit(0)
|
||||
elif tag == "Version":
|
||||
self.mode()
|
||||
elif tag == "Restore":
|
||||
os.execvp(
|
||||
"sudo", ["/usr/bin/sudo", "/usr/local/bin/gns3restore"]
|
||||
)
|
||||
elif tag == "Reboot":
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "reboot"])
|
||||
elif tag == "Shutdown":
|
||||
os.execvp("sudo", ["/usr/bin/sudo", "poweroff"])
|
||||
elif tag == "Upgrade":
|
||||
self.update()
|
||||
elif tag == "Information":
|
||||
self.vm_information()
|
||||
elif tag == "Log":
|
||||
self.log()
|
||||
elif tag == "Migrate":
|
||||
self.migrate()
|
||||
elif tag == "Configure":
|
||||
self.edit_config()
|
||||
elif tag == "Networking":
|
||||
self.edit_network()
|
||||
elif tag == "Security":
|
||||
self.set_security()
|
||||
elif tag == "Keyboard":
|
||||
self.keyboard_configuration()
|
||||
elif tag == "Test":
|
||||
self.check_internet_connectivity()
|
||||
elif tag == "Proxy":
|
||||
self.edit_proxy()
|
||||
elif tag == "Shrink":
|
||||
self.shrink_disk()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ws = Welcome_dialog()
|
||||
ws.vm_information()
|
||||
ws.kvm_control()
|
||||
ws.display_loop()
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GNS3 Server</title>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Fira+Sans+Extra+Condensed:wght@200&family=Fira+Mono&display=swap");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
background: #070600;
|
||||
color: #e0e0e0;
|
||||
font-family: "Fira Sans Extra Condensed", sans-serif;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
padding: 2rem;
|
||||
max-width: 950px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
color: #ff1053;
|
||||
font-size: 4rem;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h2 {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
color: #ff1053;
|
||||
font-size: 2rem;
|
||||
letter-spacing: 1px;
|
||||
margin: 2rem 0 0.5rem;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
a {
|
||||
color: #ff1053;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
code,
|
||||
.mono {
|
||||
font-family: "Fira Mono", monospace;
|
||||
font-size: 1.4rem;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
background: #1a1a1a;
|
||||
padding: 1.2rem;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-family: "Fira Mono", monospace;
|
||||
font-size: 0.85em;
|
||||
margin: 0.5rem 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.card {
|
||||
background: #111;
|
||||
border: 1px solid #222;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.warn {
|
||||
border-left: 3px solid #ff1053;
|
||||
}
|
||||
.dl {
|
||||
display: inline-block;
|
||||
background: #ff1053;
|
||||
color: #070600;
|
||||
padding: 0.4rem 1rem;
|
||||
font-weight: 700;
|
||||
margin: 0.3rem 0.3rem 0.3rem 0;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.dl:hover {
|
||||
background: #cc0d42;
|
||||
text-decoration: none;
|
||||
}
|
||||
.muted {
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>GNS3 SERVER</h1>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script>
|
||||
// Data injected by bash script via sed
|
||||
const D = {
|
||||
lan_ip: "{{LAN_IP}}",
|
||||
public_ip: "{{PUBLIC_IP}}",
|
||||
hostname: "{{HOSTNAME}}",
|
||||
gns3_version: "{{GNS3_VERSION}}",
|
||||
with_openvpn: {{WITH_OPENVPN}},
|
||||
with_wireguard: {{WITH_WIREGUARD}},
|
||||
with_docker: {{WITH_DOCKER}},
|
||||
disable_kvm: {{DISABLE_KVM}},
|
||||
unsafe_configs: {{UNSAFE_CONFIGS}},
|
||||
serve_slug: "{{SERVE_SLUG}}",
|
||||
serve_hours: {{SERVE_HOURS}},
|
||||
serve_port: {{SERVE_PORT}},
|
||||
warnings: {{WARNINGS_JSON}}
|
||||
};
|
||||
|
||||
const $ = document.getElementById('app');
|
||||
let h = '';
|
||||
|
||||
h += '<h2>YOUR SERVER</h2>';
|
||||
h += '<div class="card">';
|
||||
h += `<p><strong>GNS3:</strong> <a href="http://${D.lan_ip}:3080">http://${D.lan_ip}:3080</a></p>`;
|
||||
h += `<p><strong>Version:</strong> <code>${D.gns3_version}</code></p>`;
|
||||
h += `<p><strong>Host:</strong> ${D.hostname}</p>`;
|
||||
h += `<p><strong>Docker:</strong> ${D.with_docker ? 'Yes' : 'No'}</p>`;
|
||||
h += `<p><strong>KVM:</strong> ${D.disable_kvm ? '<span style="color:#FF1053">Disabled</span>' : 'Enabled'}</p>`;
|
||||
h += '</div>';
|
||||
|
||||
if (D.with_wireguard || D.with_openvpn) {
|
||||
h += '<h2>VPN</h2>';
|
||||
h += '<div class="card">';
|
||||
const ext = D.unsafe_configs ? '' : '.enc';
|
||||
|
||||
if (D.with_wireguard) {
|
||||
h += `<p><strong>WireGuard:</strong> <code>${D.public_ip}:51820</code></p>`;
|
||||
h += `<p><strong>Tunnel:</strong> <code>172.16.254.1:3080</code></p>`;
|
||||
h += `<a class="dl" href="${D.serve_slug}/w">Download WireGuard Config${ext ? ' (encrypted)' : ''}</a> `;
|
||||
}
|
||||
|
||||
if (D.with_openvpn) {
|
||||
h += `<p><strong>OpenVPN:</strong> <code>${D.public_ip}:1194/udp</code></p>`;
|
||||
h += `<p><strong>Tunnel:</strong> <code>172.16.253.1:3080</code></p>`;
|
||||
h += `<a class="dl" href="${D.serve_slug}/o">Download OpenVPN Config${ext ? ' (encrypted)' : ''}</a> `;
|
||||
}
|
||||
|
||||
if (!D.unsafe_configs) {
|
||||
h += '<p class="muted" style="margin-top:0.5rem">Configs are encrypted. Passphrase is on your server terminal.</p>';
|
||||
}
|
||||
h += '<p class="muted">Port forwarding required on your router.</p>';
|
||||
h += '</div>';
|
||||
}
|
||||
|
||||
// Dynamic Client Download Links based on Semantic Version
|
||||
const dl_win = D.gns3_version !== "unknown"
|
||||
? `https://github.com/GNS3/gns3-gui/releases/download/v${D.gns3_version}/GNS3-${D.gns3_version}-all-in-one.exe`
|
||||
: `https://github.com/GNS3/gns3-gui/releases/latest`;
|
||||
|
||||
const dl_mac = D.gns3_version !== "unknown"
|
||||
? `https://github.com/GNS3/gns3-gui/releases/download/v${D.gns3_version}/GNS3-${D.gns3_version}.dmg`
|
||||
: `https://github.com/GNS3/gns3-gui/releases/latest`;
|
||||
|
||||
h += '<h2>GNS3 CLIENT</h2>';
|
||||
h += '<div class="grid">';
|
||||
h += '<div class="card"><strong>Windows</strong><br>';
|
||||
h += `<a class="dl" href="${dl_win}">GNS3 Client ${D.gns3_version !== "unknown" ? '(v' + D.gns3_version + ')' : ''}</a>`;
|
||||
h += '<br><a class="dl" href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY</a>';
|
||||
h += '</div>';
|
||||
h += '<div class="card"><strong>macOS</strong><br>';
|
||||
h += `<a class="dl" href="${dl_mac}">GNS3 Client ${D.gns3_version !== "unknown" ? '(v' + D.gns3_version + ')' : ''}</a>`;
|
||||
h += '<br><a class="dl" href="https://iterm2.com">iTerm2</a>';
|
||||
h += '</div>';
|
||||
h += '<div class="card"><strong>Linux</strong><br>';
|
||||
h += '<a class="dl" href="https://github.com/GNS3/gns3-gui/releases/latest">GNS3 Client</a>';
|
||||
h += '<br><code>sudo apt install gns3-gui</code>';
|
||||
h += '</div>';
|
||||
h += '</div>';
|
||||
|
||||
h += '<h2>RESOURCES</h2>';
|
||||
h += '<div class="card">';
|
||||
h += '<p><a href="https://gns3.com/marketplace/appliances">GNS3 Appliance Marketplace</a></p>';
|
||||
h += '<p><a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso">VirtIO Windows Drivers</a></p>';
|
||||
h += '<p><a href="https://docs.gns3.com">GNS3 Documentation</a></p>';
|
||||
h += '<p><a href="https://lark.cx">lark.cx tutorials</a></p>';
|
||||
h += '</div>';
|
||||
|
||||
if (D.warnings && D.warnings.length > 0) {
|
||||
h += '<h2>WARNINGS</h2>';
|
||||
D.warnings.forEach(w => {
|
||||
h += `<div class="card warn"><code>${w}</code></div>`;
|
||||
});
|
||||
}
|
||||
|
||||
h += '<h2>THIS PAGE</h2>';
|
||||
h += '<div class="card">';
|
||||
h += `<p>Auto-expires in <span style="color:#FF1053;font-family:'Fira Mono',monospace">${D.serve_hours}h</span></p>`;
|
||||
h += '<p class="muted">Download configs before this page disappears.</p>';
|
||||
h += '</div>';
|
||||
|
||||
$.innerHTML = h;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
if systemctl is-active --quiet gns3-config-serve.service 2>/dev/null; then
|
||||
echo ""
|
||||
echo " GNS3 config: http://${_lan_ip}:${_CONFIG_SERVE_PORT_}/"
|
||||
echo " (auto-expires — download configs now)"
|
||||
fi
|
||||
@@ -0,0 +1,10 @@
|
||||
[Interface]
|
||||
Address = 172.16.254.2/24
|
||||
PrivateKey = ${_client_privkey}
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${_server_pubkey}
|
||||
Endpoint = ${_public_ip}:${_WG_PORT_}
|
||||
AllowedIPs = 172.16.253.0/24, 172.16.254.0/24
|
||||
PersistentKeepalive = 25
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Stop and clean up GNS3 config server
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/systemctl stop gns3-config-serve.service
|
||||
ExecStart=/bin/systemctl disable gns3-config-serve.service
|
||||
ExecStart=/bin/rm -rf ${_CONFIG_SERVE_DIR_}
|
||||
ExecStart=/bin/systemctl disable gns3-config-serve-stop.timer
|
||||
ExecStart=-/usr/sbin/ufw delete allow ${_CONFIG_SERVE_PORT_}/tcp
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=GNS3 ephemeral config server
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=${_CONFIG_SERVE_DIR_}
|
||||
ExecStart=/usr/bin/python3 -m http.server ${_CONFIG_SERVE_PORT_} --bind 0.0.0.0
|
||||
Restart=no
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Stop GNS3 config server after ${_CONFIG_SERVE_HOURS_} hours
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=${_CONFIG_SERVE_HOURS_}h
|
||||
AccuracySec=1min
|
||||
Unit=gns3-config-serve-stop.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=GNS3 server
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Conflicts=shutdown.target
|
||||
|
||||
[Service]
|
||||
User=${_GNS3_USER_}
|
||||
Group=${_GNS3_USER_}
|
||||
PermissionsStartOnly=true
|
||||
EnvironmentFile=/etc/environment
|
||||
ExecStartPre=/bin/mkdir -p /var/log/gns3 /var/run/gns3
|
||||
ExecStartPre=/bin/chown -R ${_GNS3_USER_}:${_GNS3_USER_} /var/log/gns3 /var/run/gns3
|
||||
ExecStart=${_gns3_bin} --log /var/log/gns3/gns3.log
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
LimitNOFILE=16384
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,13 @@
|
||||
${_DEPLOY_MARKER_}
|
||||
[Server]
|
||||
host = ${_listen_host}
|
||||
port = ${_GNS3_PORT_}
|
||||
images_path = ${_GNS3_HOME_}/images
|
||||
projects_path = ${_GNS3_HOME_}/projects
|
||||
appliances_path = ${_GNS3_HOME_}/appliances
|
||||
configs_path = ${_GNS3_HOME_}/configs
|
||||
report_errors = True
|
||||
|
||||
[Qemu]
|
||||
enable_hardware_acceleration = ${_hw_accel}
|
||||
require_hardware_acceleration = ${_hw_accel}
|
||||
@@ -0,0 +1,114 @@
|
||||
<!doctype html>
|
||||
<!-- Note: cozy by design. Keeping "small but decipherable". -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GNS3 Server</title>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Fira+Sans+Extra+Condensed:wght@200&family=Fira+Mono&display=swap");
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #070600; color: #e0e0e0; font-family: "Fira Sans Extra Condensed", sans-serif; font-size: 24px;
|
||||
line-height: 1.8; padding: 2rem; max-width: 950px; margin: 0 auto; }
|
||||
h1 { font-family: "Bebas Neue", sans-serif; color: #ff1053; font-size: 4rem; letter-spacing: 2px; margin-bottom: 0.5rem; }
|
||||
h2 { font-family: "Bebas Neue", sans-serif; color: #ff1053; font-size: 2rem; letter-spacing: 1px; margin: 2rem 0 0.5rem; border-bottom: 1px solid #333; padding-bottom: 0.3rem; }
|
||||
a { color: #ff1053; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
code, .mono { font-family: "Fira Mono", monospace; font-size: 1.4rem; padding: 2px 6px; border-radius: 3px; }
|
||||
pre { background: #1a1a1a; padding: 1.2rem; border-radius: 4px; overflow-x: auto; font-family: "Fira Mono", monospace; font-size: 0.85em; margin: 0.5rem 0; white-space: pre-wrap; word-break: break-all; }
|
||||
.card { background: #111; border: 1px solid #222; padding: 1rem; margin: 0.5rem 0; border-radius: 4px; }
|
||||
.warn { border-left: 3px solid #ff1053; }
|
||||
.dl { display: inline-block; background: #ff1053; color: #070600; padding: 0.4rem 1rem; font-weight: 700; margin: 0.3rem 0.3rem 0.3rem 0; border-radius: 3px; font-size: 0.9rem; }
|
||||
.dl:hover { background: #cc0d42; text-decoration: none; }
|
||||
.muted { color: #666; font-size: 1.1rem; }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
|
||||
@media (max-width: 720px) { .grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>GNS3 SERVER</h1>
|
||||
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
// Data injected by gns3-remote.sh
|
||||
const D = {
|
||||
lan_ip: "{{LAN_IP}}", public_ip: "{{PUBLIC_IP}}", hostname: "{{HOSTNAME}}", gns3_version: "{{GNS3_VERSION}}",
|
||||
with_openvpn: {{WITH_OPENVPN}}, with_wireguard: {{WITH_WIREGUARD}}, with_docker: {{WITH_DOCKER}},
|
||||
disable_kvm: {{DISABLE_KVM}}, unsafe_configs: {{UNSAFE_CONFIGS}},
|
||||
serve_slug: "{{SERVE_SLUG}}", serve_hours: {{SERVE_HOURS}}, serve_port: {{SERVE_PORT}},
|
||||
gns3_port: {{_GNS3_PORT_}}, ovpn_port: {{_OVPN_PORT_}}, wg_port: {{_WG_PORT_}},
|
||||
warnings: {{WARNINGS_JSON}}
|
||||
};
|
||||
|
||||
const $ = document.getElementById('app');
|
||||
let h = '';
|
||||
|
||||
h += '<h2>YOUR SERVER</h2>';h += '<div class="card">';
|
||||
h += `<p><strong>GNS3:</strong> <a href="http://${D.lan_ip}:${D.gns3_port}">http://${D.lan_ip}:${D.gns3_port}</a></p>`;
|
||||
h += `<p><strong>Version:</strong> <code>${D.gns3_version}</code></p>`;
|
||||
h += `<p><strong>Host:</strong> ${D.hostname}</p>`;
|
||||
h += `<p><strong>Docker:</strong> ${D.with_docker ? 'Yes' : 'No'}</p>`;
|
||||
h += `<p><strong>KVM:</strong> ${D.disable_kvm ? '<span style="color:#FF1053">Disabled</span>' : 'Enabled'}</p>`;
|
||||
h += '</div>';
|
||||
|
||||
if (D.with_wireguard || D.with_openvpn) {
|
||||
h += '<h2>VPN</h2><div class="card">';
|
||||
const ext = D.unsafe_configs ? '' : '.enc';
|
||||
|
||||
if (D.with_wireguard) {
|
||||
h += `<p><strong>WireGuard:</strong> <code>${D.public_ip}:${D.wg_port}</code></p>`;
|
||||
h += `<p><strong>Tunnel:</strong> <code>172.16.254.1:${D.gns3_port}</code></p>`;
|
||||
h += `<a class="dl" href="${D.serve_slug}/w">Download WireGuard Config${ext ? ' (encrypted)' : ''}</a> `;
|
||||
}
|
||||
|
||||
if (D.with_openvpn) {
|
||||
h += `<p><strong>OpenVPN:</strong> <code>${D.public_ip}:${D.ovpn_port}/udp</code></p>`;
|
||||
h += `<p><strong>Tunnel:</strong> <code>172.16.253.1:${D.gns3_port}</code></p>`;
|
||||
h += `<a class="dl" href="${D.serve_slug}/o">Download OpenVPN Config${ext ? ' (encrypted)' : ''}</a> `;
|
||||
}
|
||||
|
||||
if (!D.unsafe_configs) {
|
||||
h += '<p class="muted" style="margin-top:0.5rem">Configs encrypted. Passphrase on server terminal.</p>';
|
||||
}
|
||||
h += '<p class="muted">Port forwarding required on your router.</p></div>';
|
||||
}
|
||||
|
||||
// Dynamic Download Links based on Semantic Version
|
||||
const dl_win = D.gns3_version !== "unknown"
|
||||
? `https://github.com/GNS3/gns3-gui/releases/download/v${D.gns3_version}/GNS3-${D.gns3_version}-all-in-one.exe`
|
||||
: `https://github.com/GNS3/gns3-gui/releases/latest`;
|
||||
const dl_mac = D.gns3_version !== "unknown"
|
||||
? `https://github.com/GNS3/gns3-gui/releases/download/v${D.gns3_version}/GNS3-${D.gns3_version}.dmg`
|
||||
: `https://github.com/GNS3/gns3-gui/releases/latest`;
|
||||
|
||||
h += '<h2>GNS3 CLIENT</h2>';
|
||||
h += '<div class="grid">';
|
||||
h += '<div class="card"><strong>Windows</strong><br>';
|
||||
h += `<a class="dl" href="${dl_win}">GNS3 Client ${D.gns3_version !== "unknown" ? '(v' + D.gns3_version + ')' : ''}</a>`;
|
||||
h += '<br><a class="dl" href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY</a></div>';
|
||||
h += '<div class="card"><strong>macOS</strong><br>';
|
||||
h += `<a class="dl" href="${dl_mac}">GNS3 Client ${D.gns3_version !== "unknown" ? '(v' + D.gns3_version + ')' : ''}</a>`;
|
||||
h += '<br><a class="dl" href="https://iterm2.com">iTerm2</a></div>';
|
||||
h += '<div class="card"><strong>Linux</strong><br>';
|
||||
h += '<a class="dl" href="https://github.com/GNS3/gns3-gui/releases/latest">GNS3 Client</a>';
|
||||
h += '<br><code>sudo apt install gns3-gui</code></div></div>';
|
||||
|
||||
h += '<h2>RESOURCES</h2>'; h += '<div class="card">';
|
||||
h += '<p><a href="https://gns3.com/marketplace/appliances">GNS3 Appliance Marketplace</a></p>';
|
||||
h += '<p><a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso">VirtIO Windows Drivers</a></p>';
|
||||
h += '<p><a href="https://docs.gns3.com">GNS3 Documentation</a></p>';
|
||||
h += '<p><a href="https://lark.cx">lark.cx tutorials</a></p></div>';
|
||||
|
||||
if (D.warnings && D.warnings.length > 0) {
|
||||
h += '<h2>WARNINGS</h2>';
|
||||
D.warnings.forEach(w => { h += `<div class="card warn"><code>${w}</code></div>`; });
|
||||
}
|
||||
|
||||
h += '<h2>THIS PAGE</h2>';
|
||||
h += '<div class="card">';
|
||||
h += `<p>Auto-expires in <span style="color:#FF1053;font-family:'Fira Mono',monospace">${D.serve_hours}h</span></p>`;
|
||||
h += '<p class="muted">Download configs before this page disappears.</p></div>';
|
||||
$.innerHTML = h;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
[Interface]
|
||||
Address = 172.16.254.1/24
|
||||
ListenPort = ${_WG_PORT_}
|
||||
PrivateKey = ${_server_privkey}
|
||||
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ${_default_iface} -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ${_default_iface} -j MASQUERADE
|
||||
|
||||
[Peer]
|
||||
# client1
|
||||
PublicKey = ${_client_pubkey}
|
||||
AllowedIPs = 172.16.254.2/32
|
||||
Reference in New Issue
Block a user