Preserve peers on config upload

- Preserve uploaded configs with peer blocks by saving raw content

- Populate peers from file via existing getPeers() logic

- Add path validation and basic content checks for safety

- Inform users in UI when peers are preserved from upload
pull/1111/head
Super User 2026-01-30 00:32:53 -05:00
parent 0f98bb2537
commit 6f71fc201f
3 changed files with 103 additions and 59 deletions

View File

@ -392,32 +392,35 @@ def API_addWireguardConfiguration():
f"Already have a configuration with the address \"{data['Address']}\"",
"Address")
if "Backup" in data.keys():
path = {
"wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
}
if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "wg"
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "awg"
try:
if "Backup" in data.keys():
path = {
"wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
}
if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "wg"
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "awg"
else:
return ResponseObject(False, "Backup does not exist")
shutil.copy(
os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]),
os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf')
)
WireguardConfigurations[data['ConfigurationName']] = (
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName']))
else:
return ResponseObject(False, "Backup does not exist")
shutil.copy(
os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]),
os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf')
)
WireguardConfigurations[data['ConfigurationName']] = (
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName']))
else:
WireguardConfigurations[data['ConfigurationName']] = (
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data))
WireguardConfigurations[data['ConfigurationName']] = (
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data))
except WireguardConfiguration.InvalidConfigurationFileException as exc:
return ResponseObject(False, str(exc))
return ResponseObject()
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
@ -1703,4 +1706,4 @@ def index():
if __name__ == "__main__":
startThreads()
DashboardPlugins.startThreads()
app.run(host=app_ip, debug=False, port=app_port)
app.run(host=app_ip, debug=False, port=app_port)

View File

@ -91,35 +91,58 @@ class WireguardConfiguration:
else:
setattr(self, i, str(data[i]))
self.__parser["Interface"] = {
"PrivateKey": self.PrivateKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
"PreUp": f"{self.PreUp}",
"PreDown": f"{self.PreDown}",
"PostUp": f"{self.PostUp}",
"PostDown": f"{self.PostDown}",
"SaveConfig": "true"
}
raw_configuration = data.get("RawConfiguration") if data else None
if raw_configuration:
try:
if not self.__isPathWithinBase(self.configPath, self.__getProtocolPath()):
raise self.InvalidConfigurationFileException("Configuration path is invalid")
if not raw_configuration.lstrip().startswith("[Interface]"):
raise self.InvalidConfigurationFileException("Invalid configuration file: missing [Interface]")
if any(ord(ch) == 0 for ch in raw_configuration):
raise self.InvalidConfigurationFileException("Invalid configuration file: binary content detected")
self.createDatabase()
with open(self.configPath, "w+") as configFile:
configFile.write(raw_configuration)
current_app.logger.info(f"Configuration file {self.configPath} created from upload")
self.__parseConfigurationFile()
self.__initPeersList()
except Exception:
try:
os.remove(self.configPath)
except OSError:
pass
self.__dropDatabase()
raise
else:
self.__parser["Interface"] = {
"PrivateKey": self.PrivateKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
"PreUp": f"{self.PreUp}",
"PreDown": f"{self.PreDown}",
"PostUp": f"{self.PostUp}",
"PostDown": f"{self.PostDown}",
"SaveConfig": "true"
}
if self.Protocol == 'awg':
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jmin"] = self.Jmin
self.__parser["Interface"]["Jmax"] = self.Jmax
self.__parser["Interface"]["S1"] = self.S1
self.__parser["Interface"]["S2"] = self.S2
self.__parser["Interface"]["H1"] = self.H1
self.__parser["Interface"]["H2"] = self.H2
self.__parser["Interface"]["H3"] = self.H3
self.__parser["Interface"]["H4"] = self.H4
if self.Protocol == 'awg':
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jmin"] = self.Jmin
self.__parser["Interface"]["Jmax"] = self.Jmax
self.__parser["Interface"]["S1"] = self.S1
self.__parser["Interface"]["S2"] = self.S2
self.__parser["Interface"]["H1"] = self.H1
self.__parser["Interface"]["H2"] = self.H2
self.__parser["Interface"]["H3"] = self.H3
self.__parser["Interface"]["H4"] = self.H4
if "Backup" not in data.keys():
self.createDatabase()
with open(self.configPath, "w+") as configFile:
self.__parser.write(configFile)
current_app.logger.info(f"Configuration file {self.configPath} created")
self.__initPeersList()
if "Backup" not in data.keys():
self.createDatabase()
with open(self.configPath, "w+") as configFile:
self.__parser.write(configFile)
current_app.logger.info(f"Configuration file {self.configPath} created")
self.__initPeersList()
if not os.path.exists(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')):
os.mkdir(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup'))
@ -147,6 +170,12 @@ class WireguardConfiguration:
else self.DashboardConfig.GetConfig("Server", "awg_conf_path")
return path
def __isPathWithinBase(self, path: str, base: str) -> bool:
try:
return os.path.commonpath([os.path.realpath(path), os.path.realpath(base)]) == os.path.realpath(base)
except ValueError:
return False
def __initPeersList(self):
self.Peers: list[Peer] = []
self.getPeers()
@ -1291,4 +1320,4 @@ class WireguardConfiguration:
conn.execute(sqlalchemy.text('VACUUM;'))
except Exception as e:
return False
return True
return True

View File

@ -54,7 +54,9 @@ export default {
success: false,
loading: false,
parseInterfaceResult: undefined,
parsePeersResult: undefined
parsePeersResult: undefined,
rawConfiguration: "",
rawConfigHasPeers: false
}
},
created() {
@ -81,7 +83,11 @@ export default {
async saveNewConfiguration(){
if (this.goodToSubmit){
this.loading = true;
await fetchPost("/api/addWireguardConfiguration", this.newConfiguration, async (res) => {
const payload = {...this.newConfiguration};
if (this.rawConfiguration && this.rawConfigHasPeers){
payload.RawConfiguration = this.rawConfiguration;
}
await fetchPost("/api/addWireguardConfiguration", payload, async (res) => {
if (res.status){
this.success = true
await this.store.getConfigurations()
@ -104,8 +110,11 @@ export default {
if (!file) return false;
const reader = new FileReader();
reader.onload = (evt) => {
this.parseInterfaceResult = parseInterface(evt.target.result);
this.parsePeersResult = parsePeers(evt.target.result);
const raw = evt.target.result;
this.rawConfiguration = raw;
this.parseInterfaceResult = parseInterface(raw);
this.parsePeersResult = parsePeers(raw);
this.rawConfigHasPeers = Array.isArray(this.parsePeersResult) && this.parsePeersResult.length > 0;
let appliedFields = 0;
if (this.parseInterfaceResult){
this.newConfiguration.ConfigurationName = file.name.replace('.conf', '')
@ -118,6 +127,9 @@ export default {
}
if (appliedFields > 0){
this.dashboardStore.newMessage("WGDashboard", `Parse successful! Updated ${appliedFields} field(s)`, "success")
if (this.rawConfigHasPeers){
this.dashboardStore.newMessage("WGDashboard", "Peers detected. The uploaded file will be imported as-is to preserve peer definitions.", "info")
}
}else {
this.dashboardStore.newMessage("WGDashboard", `Parse failed`, "danger")
}
@ -415,4 +427,4 @@ export default {
.protocolBtnGroup a{
transition: all 0.2s ease-in-out;
}
</style>
</style>